• 发布我的Javascript OOP框架YOOP Wonder


    大家好!今天我正式发布我的OOP框架YOOP!该框架将帮助开发者更好地进行面向对象编程。

    当前版本号:v1.1

    GitHub下载地址

    介绍

    该框架包含接口、抽象类、类。

    接口Interface可以继承多个接口,可以定义方法、属性。

    抽象类AClass可以继承多个接口、一个抽象类,可以定义构造函数、公有成员、私有成员、保护成员、静态成员、虚成员、抽象成员。

    类Class可以继承多个接口、一个抽象类或类,可以定义构造函数、公有成员、私有成员、保护成员、静态成员、虚成员。 

    子类调用父类成员

    在子类中,可以使用this.base()来调用父类同名方法。也可以使用this.baseClass来访问父类的原型。

    主要的语法规则

    类Class:

    1. 创建实例时调用构造函数。
    2. 验证是否实现了接口的方法、属性,如果没有实现会抛出异常。
    3. 验证是否实现了父类的抽象成员,如果没有实现会抛出异常。
    4. 只能继承一个类(AClass或Class),否则抛出异常.
    5. 不能定义抽象成员,否则抛出异常。

    抽象类AClass:

    1. 可以声明构造函数,供子类Class调用。
    2. 抽象类如果继承类Class,会抛出异常。
    3. 不用实现接口,可以交给子类Class实现。
    4. 不用实现父类抽象成员,可以交给子类Class实现。
    5. 只能继承一个抽象类AClass,否则抛出异常。

    接口Interface:

    1. 接口只能继承接口,否则抛出异常。 

    使用YOOP

    YOOP支持AMD、CMD、CommonJS规范,可在Sea.js、node.js中使用:

    var yoop = require("./YOOP.js");
    
    yoop.Class({});

    也可以通过script标签在页面上直接引用

    页面上引用script:

    <script src="./YOOP.js"></script>

    然后通过命名空间YYC来使用:

    YYC.Class({});

    用法

    接口

    定义接口

    只有方法:

    var A = YYC.Interface("method1", "method2");

    只有属性:

    var A = YYC.Interface([], ["attribute1", "attribute2"]);

    既有方法又有属性:

    var A = YYC.Interface(["method1", "method2"], ["attribute1", "attribute2"]);

    继承接口

    var A = YYC.Interface(["method1", "method2"],["attribute1", "attribute2"]);
    var B = YYC.Interface(A, "m1", "m2");
    var C = YYC.Interface([A], ["m1", "m2"], ["a1", "a2"]);
    var D = YYC.Interface([A, B], ["m1", "m2"], ["a1", "a2"]);

    抽象类

    定义抽象类

    var A = YYC.AClass({
        Init: function () { //构造函数
        },
        Protected: {    //保护成员
            Abstract: { //保护抽象成员
            },
            Virtual: {  //保护虚方法
            },
            P_proA: true,   //保护属性
           P_proM: function () { }    //保护方法
        },
        Public: {   //公有成员
            Abstract: { //公有抽象成员
            },
            Virtual: {  //公有虚方法
            },
            pubM: function () { },  //公有方法
          pubA: 0    //公有属性
        },
        Private: {  //私有成员
            _priA: "",   //私有属性
            _priM: function () { } //私有方法
        },
        Abstract: { //公有抽象成员
        },
        Virtual: {  //公有虚方法
        }
    });
    View Code

    继承抽象类

    var A = YYC.AClass({});
    var B = YYC.AClass(A, {});
    var C = YYC.AClass({Class: A}, {}); 
    View Code

    继承接口

    var A = YYC.Interface("m1");
    var B = YYC.Interface("m2");
    var C = YYC.AClass({ Interface: A }, {
        Public: {
            m1: function () { }
        }
    });
    var D = YYC.AClass({ Interface: [A, B] }, {
        Public: {
            m1: function () { },
            m2: function () { }
        }
    });
    View Code

    继承接口和抽象类

    var A = YYC.Interface("m1");
    var B = YYC.Interface(["m2"], ["a"]);
    var C = YYC.AClass({});
    var D = YYC.AClass({ Interface: [A, B], Class: C }, {
        Public: {
            m1: function () { },
            a: 0
        }
    });
    View Code

    定义类

    var A = YYC.Class({
                    Init: function () { //构造函数
                    },
                    Protected: {    //保护成员
                        Virtual: {  //保护虚方法
                        },
                        P_proA: true,   //保护属性
                        P_proM: function () { }    //保护方法
                    },
                    Public: {   //公有成员
                        Virtual: {  //公有虚方法
                        },
                        pubM: function () { },  //公有方法
                        pubA: 0    //公有属性
                    },
                    Private: {  //私有成员
                        _priA: "",   //私有属性
                        _priM: function () { } //私有方法
                    },
                    Virtual: {  //公有虚方法
                    }
                });
    View Code

    继承抽象类

    var A = YYC.AClass({});
    var B = YYC.AClass(A, {});
    var C = YYC.AClass({ Class: A }, {});
    View Code

    继承类

    var A = YYC.Class({});
    var B = YYC.Class(A, {});
    var C = YYC.Class({ Class: A }, {});
    View Code

    继承接口

    var A = YYC.Interface("m1");
    var B = YYC.Interface("m2");
    var C = YYC.Class({ Interface: A }, {
        Public: {
            m1: function () { }
        }
    });
    var D = YYC.Class({ Interface: [A, B] }, {
        Public: {
            m1: function () { },
            m2: function () { }
        }
    });
    View Code

    继承接口和抽象类/类

    var A = YYC.Interface("m1");
    var B = YYC.Interface(["m2"], ["a"]);
    var C = YYC.AClass({});
    var D = YYC.Class({});
    var E = YYC.AClass({ Interface: [A, B], Class: C }, {
        Public: {
            m1: function () { },
            a: 0
        }
    });
    var F = YYC.AClass({ Interface: [A, B], Class: D }, {
        Public: {
            m1: function () { },
            a: 0
        }
    });
    View Code

    构造函数

    var A = YYC.Class({
      Init: function(t){
        this.value = t;
      }
    });
    var a = new A(100);
    console.log(a.value);  //100
    View Code

    静态成员

    使用“类.静态成员”的形式来调用静态成员。这里静态成员实质是类(function,function也是对象)的成员。

    注意!静态方法中的this指向类,不是指向类的实例!

    var A = YYC.Class({
        Static: {
            a: 100,
            method1: function () {
                return 200;
            },
            method2: function () {
                 this.k = 300;
            }
        }
    });
    
    A.method2();
    
    console.log(A.a);   //100
    console.log(A.method1());    //200
    console.log(A.k);   //300
    View Code

    类的成员互相调用

    使用this来调用。

    var A = YYC.Class({
        Private: {
            _a: 100
        },
        Public: {
            method: function (t) {
                return this._a;
            }
        }
    });
    var a = new A();
    console.log(a.method);  //100
    View Code

    子类调用父类

    使用this.base()可调用父类同名函数。

    使用this.baseClass.xx.call(this, xx)可调用父类的成员。

    var A = YYC.AClass({
                        Init: function () {
                            this.p = 100;
                        },
                        Public: {
                            method1: function () {
                                this.m = 300;
                            },
                            method2: function () {
                                return 100;
                            }
                        }
                    });
                    var B = YYC.Class(A, {
                        Init: function () {
                            this.base();
                        },
                        Private: {
                            _a: 100
                        },
                        Public: {
                            method1: function (t) {
                                this.base();
                                return this.baseClass.method2.call(this, null) + this._a;
                            }
                        }
                    });
                    var b = new B();
                    console.log(b.method1());  //200
                    console.log(b.p);  //100
                    console.log(b.m);  //300
    View Code

    父类调用子类

    var A = YYC.AClass({
        Public: {
            method: function () {
                console.log(this.value);
            }
        }
    });
    var B = YYC.Class(A, {
        Public: {
            value: 100
        }
    });
    var b = new B();
    b.method(); //100
    View Code

    覆写父类方法,实现接口成员、抽象成员

    var A = YYC.Interface("m1");
    var B = YYC.AClass({ Interface: A }, {
        Protected: {
            Abstract: {
                P_method: function () { }
            }
        },
        Public: {
            method: function () { }
        }
    });
    var C = YYC.Class(B, {
        Protected: {
            P_method: function () {
                console.log("实现抽象方法");
            }
        },
        Public: {
            method: function () {
                console.log("覆盖父类同名方法");
            },
            m1: function () {
                console.log("实现接口");
            }
        }
    });
    View Code

    其它API

    stubParentMethod、stubParentMethodByAClass

    让父类(Class/AClass)指定方法不执行。

    测试用例如下:

    describe("stubParentMethod", function () {
            var sandbox = null;
            var A = null,
                B = null,
                C = null,
                a = null,
                b = null,
                c = null;
    
            beforeEach(function () {
                A = YYC.Class({
                    Public: {
                        done: function () {
                            throw new Error("");
                        }
                    }
                });
                B = YYC.Class(A, {
                    Public: {
                        done: function () {
                            this.baseClass.done.call(this, null);
                        }
                    }
                });
                C = YYC.Class(B, {
                    Public: {
                        a: 0,
    
                        done: function () {
                            this.base();
    
                            this.a = 100;
                        }
                    }
                });
                a = new A();
                b = new B();
                c = new C();
    
                sandbox = sinon.sandbox.create();
            });
            afterEach(function () {
                sandbox.restore();
            });
    
            it("让父类指定方法不执行,用于Class的测试方法中调用了父类方法的情况", function () {
                expect(function () {
                    b.done();
                }).toThrow();
                expect(function () {
                    c.done();
                }).toThrow();
    
                b.stubParentMethod(sandbox, "done");
                c.stubParentMethod(sandbox, "done");
    
                expect(function () {
                    b.done();
                }).not.toThrow();
                expect(function () {
                    c.done();
                }).not.toThrow();
            });
            it("可将父类指定方法替换为假方法", function () {
                c.stubParentMethod(sandbox, "done", function () {
                    this.val = 1;
                });
    
                c.done();
    
                expect(c.val).toEqual(1);
            });
            it("可按照sinon->stub API测试父类指定方法的调用情况", function () {
                c.stubParentMethod(sandbox, "done");
    
                c.done();
    
                expect(c.lastBaseClassForTest.done.calledOnce).toBeTruthy();
            });
        });
    
        describe("stubParentMethodByAClass", function () {
            var sandbox = null;
            var A = null,
                B = null,
                t = null;
    
            beforeEach(function () {
                A = YYC.AClass({
                    Public: {
                        a: 0,
                        done: function () {
                            throw new Error("");
                        }
                    }
                });
                B = YYC.AClass(A, {
                    Public: {
                        done: function () {
                            this.base();
                        }
                    }
                });
    
                //想要测试B的done方法,必须先建一个空子类继承B,然后测试空子类的done方法
                function getInstance() {
                    var T = YYC.Class(B, {
                    });
    
                    return new T();
                }
    
                t = getInstance();
    
                sandbox = sinon.sandbox.create();
            });
            afterEach(function () {
                sandbox.restore();
            });
    
            it("让父类指定方法不执行,用于AClass的测试方法中调用了父类方法的情况", function () {
                expect(t.done).toThrow();
    
                t.stubParentMethodByAClass(sandbox, "done");
    
                expect(t.done).not.toThrow();
            });
            it("可将父类指定方法替换为假方法", function () {
                t.stubParentMethodByAClass(sandbox, "done", function () {
                    this.val = 1;
                });
    
                t.done();
    
                expect(t.val).toEqual(1);
            });
            it("可按照sinon->stub API测试父类指定方法的调用情况", function () {
                t.stubParentMethodByAClass(sandbox, "done");
    
                t.done();
    
                expect(t.lastBaseClassForTest.done.calledOnce).toBeTruthy();
            });
        });
    View Code

    isInstanceOf

    判断是否为类的实例。

    测试用例如下:

         describe("isInstanceOf", function () {
            it("直接判断是否为Class的实例", function () {
                var A = YYC.Class({});
    
                expect(new A().isInstanceOf(A)).toBeTruthy();
            });
            describe("测试继承抽象类时的情况", function () {
                it("测试1", function () {
                    var A = YYC.AClass({});
                    var B = YYC.Class(A, {});
    
                    expect(new B().isInstanceOf(B)).toBeTruthy();
                    expect(new B().isInstanceOf(A)).toBeTruthy();
                });
                it("测试2", function () {
                    var A = YYC.AClass({});
                    var B = YYC.AClass(A, {});
                    var C = YYC.Class(B, {});
                    var D = YYC.Class(A, {});
    
                    expect(new C().isInstanceOf(B)).toBeTruthy();
                    expect(new C().isInstanceOf(A)).toBeTruthy();
                    expect(new D().isInstanceOf(A)).toBeTruthy();
                    expect(new D().isInstanceOf(B)).toBeFalsy();
                });
            });
    
            describe("测试继承接口时的情况", function () {
                it("测试1", function () {
                    var A = YYC.Interface("a");
                    var B = YYC.Class({Interface: A}, {
                        Public: {
                            a: function () {
                            }
                        }
                    });
    
                    expect(new B().isInstanceOf(B)).toBeTruthy();
                    expect(new B().isInstanceOf(A)).toBeTruthy();
                });
                it("测试2", function () {
                    var A = YYC.Interface("a");
                    var B = YYC.Interface("b");
                    var C = YYC.Interface([A, B], "c");
                    var D = YYC.Class({Interface: C}, {
                        Public: {
                            a: function () {
                            },
                            b: function () {
                            },
                            c: function () {
                            }
                        }
                    });
    
                    expect(new D().isInstanceOf(C)).toBeTruthy();
                    expect(new D().isInstanceOf(B)).toBeTruthy();
                    expect(new D().isInstanceOf(A)).toBeTruthy();
                });
            });
    
            it("综合测试", function () {
                var A = YYC.Interface("a1");
                var B = YYC.Interface(A, "a2");
                var C = YYC.AClass({Interface: B}, {
                    Public: {
                        a1: function () {
                        },
                        a2: function () {
                        }
                    }
                });
                var D = YYC.AClass(C, {
                    Public: {
                        a1: function () {
                        },
                        a2: function () {
                        }
                    }
                });
                var E = YYC.Class(C, {
                });
                var F = YYC.Class(E, {
                });
                var G = YYC.Class({Interface: B, Class: D}, {
                });
    
                expect(new E().isInstanceOf(C)).toBeTruthy();
                expect(new E().isInstanceOf(B)).toBeTruthy();
                expect(new E().isInstanceOf(A)).toBeTruthy();
    
                expect(new F().isInstanceOf(E)).toBeTruthy();
                expect(new F().isInstanceOf(C)).toBeTruthy();
                expect(new F().isInstanceOf(B)).toBeTruthy();
                expect(new F().isInstanceOf(A)).toBeTruthy();
    
                expect(new G().isInstanceOf(B)).toBeTruthy();
                expect(new G().isInstanceOf(D)).toBeTruthy();
    
                expect(new G().isInstanceOf(E)).toBeFalsy();
            });
        }); 
    View Code

    YOOP.version

    返回当前版本号。

    测试用例如下:

        it("获得当前版本号", function () {
           expect(YYC.YOOP.version).toBeString();
        });

    约定

    在该框架的实现中,类的实例可以访问类的公有成员、保护成员、私有成员,所有成员都是添加到类的原型中(如果是继承,则将父类的成员添和子类的成员都添加到子类的原型中),框架只是从语义上区分了成员的访问权限,在机制上没有对成员的访问权限设任何限制!

    因此,用户需要采用命名约定的方式来区分不同的成员,需要自觉遵守访问权限规则(如类的实例只能访问公有成员;不能访问其它类的私有成员;子类可以访问父类的保护成员等等)。

    私有成员和保护成员的建议命名约定

    基类的私有成员以“_”开头,保护成员以“P_”开头。

    在继承树中,第一层类私有成员以“_”开头,第二层类私有成员以“__”开头,以此类推,从而区分不同层级中同名的私有成员。

    所有层级中的保护成员前缀都为“P_”(原因见后面“为什么每层子类的保护成员前缀都一样”的讨论)。

    用户也可以将第一层子类的私有成员前缀设为“_1_”,第二层子类的私有成员设为“_2_”。。。。。。

    前缀设置规则用户可自订,只要在继承中使不同层级的类的私有成员不重名即可。

    见下面的实例代码:

    不继承接口

    var A = YYC.AClass({    //私有成员以“_”开头,保护成员以“P_”开头
        Private: {
            _value: 0,
            _method: function () {
            }
        },
        Protected: {
            P_value: 0,
    
            Virtual: {
                P_method: function () {
                }
            }
        }
    });
    var B = YYC.Class(A, {  //私有成员以“__”开头,保护成员以“P_”开头
        Private: {
            __value: 0,
            __method: function () {
            }
        },
        Protected: {
            P_method: function () {
            }
        }
    });

    继承接口

    var I = YYC.Interface("method");
    var A = YYC.AClass({ Interface: I }, {   //私有成员以“_”开头,保护成员以“P_”
        Private: {
            _value: 0,
            _method: function () {
            }
        },
        Protected: {
            P_value: 0,
    
            Virtual: {
                P_method: function () {
                }
            }
        }
    });
    var B = YYC.Class(A, {  //私有成员以“__”开头,保护成员以“P_”
        Private: {
            __value: 0,
            __method: function () {
            }
        },
        Protected: {
            P_method: function () {
            }
        },
        Public: {
            method: function () {
            }
        }
    });

    为什么每层子类的私有前缀最好不一样?

    如果子类与父类有同名的私有成员时,当子类调用父类成员时,可能会出现父类成员调用子类的私有成员。

    见下面的示例代码:

    var A = YYC.AClass({
        Private: {
            _val: 100
        },
        Public: {
            getVal: function () {
                return this._val;
            }
        }
    });
    
    var B = YYC.Class(A, {
        Private: {
            _val: 200
        },
        Public: {
            getVal: function () {
                return this.base();
            }
        }
    });
    
    expect(new B().getVal()).toEqual(100);  //失败!期望返回A->_val(100),实际返回的是B->_val(200)

    为什么每层子类的保护成员前缀都一样?

    如果父类与子类的保护成员同名,则父类的该保护成员一般都是设计为虚成员,专门供子类覆写的。因此当子类调用父类成员时,本来就期望父类成员调用子类覆写的保护成员。

    见下面的示例代码:

    var A = YYC.AClass({
        Protected: {
            Virtual: {
                P_val: 100
            }
        },
        Public: {
            getVal: function () {
                return this.P_val;
            }
        }
    });
    
    var B = YYC.Class(A, {
        Protected: {
            P_val: 200
        },
        Public: {
            getVal: function () {
                return this.base();
            }
        }
    });
    
    expect(new B().getVal()).toEqual(200);  //由于B覆写了A的虚属性P_val,因此B->getVal应该返回B覆写后的P_val(200)

    baseClass

    为了防止子类的prototype.baseClass覆盖父类prototype.baseClass,在子类继承父类时,用户需要先判断父类prototype.baseClass是否存在。如果存在,则加上前缀“_”,如“_baseClass”。如果加上前缀后依然存在,则再加上前缀“_”,如“__baseClass”。以此类推。

    如:

                    var A1 = YYC.AClass({
                        Public: {
                            arr: [],
                            a: function () {
                                this.arr.push(1);
                            }
                        }
                    });
                    var A2 = YYC.AClass(A1, {
                        Public: {
                            a: function () {
                                this.arr.push(2);
                                this.baseClass.a.call(this, null);  //调用A1.a
                            }
                        }
                    });
                    var B = YYC.Class(A2, {
                        Public: {
                            a: function () {
                                this.arr.push(3);
                                this._baseClass.a.call(this, null); //调用A2.a
                                this.baseClass.a.call(this, null);  //调用A1.a
    
                                return this.arr;
                            }
                        }
                    });
                    var b = new B();
    
                    expect(b.a()).toEqual([3, 2, 1, 1]);
    View Code

    注意事项

    子类使用this.baseClass调用父类成员时,要将父类成员的this指向子类。

    错误的写法:

    var A = YYC.AClass({
        Public: {
            method: function () {
                this.p = 100;
            }
        }
    });
    var B = YYC.Class(A, {
        Public: {
            method: function () {
                this.baseClass.method();
            }
        }
    });
    var b = new B();
    b.method();
    console.log(b.p);   //此处为undefined,而不是100!

    正确的写法:

    var A = YYC.AClass({
        Public: {
            method: function () {
                this.p = 100;
            }
        }
    });
    var B = YYC.Class(A, {
        Public: {
            method: function () {
                this.baseClass.method.call(this, null);
            }
        }
    });
    var b = new B();
    b.method();
    console.log(b.p);   //100

    已解决的问题

    YOOP框架目前已解决了下面的问题:

    1、同一个类的实例之间不应该共享属性。

    问题描述

    参考下面的代码:

                var A = YYC.Class({
                    Init: function () {
                    },
                    Public: {
                        a:[]
                    }
                });
    
                var t = new A();
                t.a.push("a");
                var m = new A();
    
                expect(t.a).toEqual(["a"]);
                expect(m.a).toEqual([]);    //失败!实际为["a"]!

    原因分析

    因为YOOP将类的成员都加入到类的原型对象中,而类实例的成员都是链接自类的原型对象,所以同一个类的实例之间成员共享。

    解决方案

    在Class的构造函数中深拷贝原型的属性到实例中,不拷贝原型的方法,从而同一个类的实例之间共享同一原型对象的方法,但它们的属性相互独立。

    2、继承于同一父类的子类实例之间不应该共享属性。

    问题描述

    参考下面的代码

                var Parent = YYC.AClass({
                    Init: function () {
                        console.log("Parent Init!");
                    },
                    Public: {
                        a: []
                    }
                });
                var Sub1 = YYC.Class(Parent, {
                    Init: function () {
                    },
                    Public: {
                    }
                });
                var Sub2 = YYC.Class(Parent, {
                    Init: function () {
                    }
                });
    
                var t = new Sub1();
                t.a.push("a");
                var k = new Sub2();
    
                expect(t.a).toEqual(["a"]);
                expect(k.a).toEqual([]);    //失败!实际为["a"]!

    原因分析

    目前是通过原型继承的方式来实现继承的。这样子类之间的成员都链接自父类的原型对象,从而会造成同一父类的子类实例之间成员共享。

    解决方案

    修改类继承方式,通过“深拷贝父类原型所有成员到子类中”的方式实现继承,从而同一父类的子类实例之间的成员相互独立。

     缺点

    只是从语义上约定了访问权限,而没有从机制上限制访问权限。

    如可以根据命名约定区分类的公有成员、保护成员、私有成员,但是类的实例却可以访问类的所有成员。

    版本历史

    2013-06-07 发布YOOP v1.0 

    2014-08-26 发布YOOP v1.1 

    1、类实例增加isInstanceOf方法,用于判断是否为类的实例,适用于接口继承、类继承等情况

    2、protected方法也可以使用this.base来访问父类同名方法了

    3、解决了“若一个方法中调用其它方法,则它们的this.base会互相干扰”的问题

    4、增加stubParentMethod和stubParentMethodByAClass方法,该方法让父类(Class/AClass)指定方法不执行,用于Class的测试方法中调用了父类方法的情况(如调用了this.base()或this.baseClass.xxx)

    5、现在支持AMD、CMD、CommonJS规范了

    6、增加YYC.YOOP.version属性,用于获得当前版本号 

    7、Class的构造函数F中现在只拷贝原型的属性到实例中,从而同一个类的实例之间共享同一原型对象的方法,但属性相互独立。

  • 相关阅读:
    PHP:_SERVER变量和_ENV变量列表
    一些使用jQuery開發的在線Web富文本編輯器
    xhtml+css排版,清理浮动整理汇总
    PHP 6 的新特性
    用PHP的CURL写的一个采集Discuz的例子
    SA、SD、SE 这三者的含义及区分
    PHP 之父 Rasmus Lerdorf 谈PHP开发
    http头的组成
    Jquery插件 可以随着滚动条的滚动而即时加载图片
    在你的网页中嵌入PDF等文件,实现pdf在线阅读
  • 原文地址:https://www.cnblogs.com/chaogex/p/3123999.html
Copyright © 2020-2023  润新知