發布我的Javascript OOP框架YOOP


大家好!今天我正式發布我的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中現在只拷貝原型的屬性到實例中,從而同一個類的實例之間共享同一原型對象的方法,但屬性相互獨立。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM