發(fā)布我的Javascript OOP框架YOOP
大家好!今天我正式發(fā)布我的OOP框架YOOP!該框架將幫助開發(fā)者更好地進(jìn)行面向?qū)ο缶幊獭?/p>
當(dāng)前版本號(hào):v1.1
介紹
該框架包含接口、抽象類、類。
接口Interface可以繼承多個(gè)接口,可以定義方法、屬性。
抽象類AClass可以繼承多個(gè)接口、一個(gè)抽象類,可以定義構(gòu)造函數(shù)、公有成員、私有成員、保護(hù)成員、靜態(tài)成員、虛成員、抽象成員。
類Class可以繼承多個(gè)接口、一個(gè)抽象類或類,可以定義構(gòu)造函數(shù)、公有成員、私有成員、保護(hù)成員、靜態(tài)成員、虛成員。
子類調(diào)用父類成員
在子類中,可以使用this.base()來(lái)調(diào)用父類同名方法。也可以使用this.baseClass來(lái)訪問(wèn)父類的原型。
主要的語(yǔ)法規(guī)則
類Class:
- 創(chuàng)建實(shí)例時(shí)調(diào)用構(gòu)造函數(shù)。
- 驗(yàn)證是否實(shí)現(xiàn)了接口的方法、屬性,如果沒有實(shí)現(xiàn)會(huì)拋出異常。
- 驗(yàn)證是否實(shí)現(xiàn)了父類的抽象成員,如果沒有實(shí)現(xiàn)會(huì)拋出異常。
- 只能繼承一個(gè)類(AClass或Class),否則拋出異常.
- 不能定義抽象成員,否則拋出異常。
抽象類AClass:
- 可以聲明構(gòu)造函數(shù),供子類Class調(diào)用。
- 抽象類如果繼承類Class,會(huì)拋出異常。
- 不用實(shí)現(xiàn)接口,可以交給子類Class實(shí)現(xiàn)。
- 不用實(shí)現(xiàn)父類抽象成員,可以交給子類Class實(shí)現(xiàn)。
- 只能繼承一個(gè)抽象類AClass,否則拋出異常。
接口Interface:
- 接口只能繼承接口,否則拋出異常。
使用YOOP
YOOP支持AMD、CMD、CommonJS規(guī)范,可在Sea.js、node.js中使用:
var yoop = require("./YOOP.js"); yoop.Class({});
也可以通過(guò)script標(biāo)簽在頁(yè)面上直接引用
頁(yè)面上引用script:
<script src="./YOOP.js"></script>
然后通過(guò)命名空間YYC來(lái)使用:
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 () { //構(gòu)造函數(shù) }, Protected: { //保護(hù)成員 Abstract: { //保護(hù)抽象成員 }, Virtual: { //保護(hù)虛方法 }, P_proA: true, //保護(hù)屬性 P_proM: function () { } //保護(hù)方法 }, Public: { //公有成員 Abstract: { //公有抽象成員 }, Virtual: { //公有虛方法 }, pubM: function () { }, //公有方法 pubA: 0 //公有屬性 }, Private: { //私有成員 _priA: "", //私有屬性 _priM: function () { } //私有方法 }, Abstract: { //公有抽象成員 }, Virtual: { //公有虛方法 } });
繼承抽象類
var A = YYC.AClass({}); var B = YYC.AClass(A, {}); var C = YYC.AClass({Class: A}, {});
繼承接口
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 () { } } });
繼承接口和抽象類
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 } });
類
定義類
var A = YYC.Class({ Init: function () { //構(gòu)造函數(shù) }, Protected: { //保護(hù)成員 Virtual: { //保護(hù)虛方法 }, P_proA: true, //保護(hù)屬性 P_proM: function () { } //保護(hù)方法 }, Public: { //公有成員 Virtual: { //公有虛方法 }, pubM: function () { }, //公有方法 pubA: 0 //公有屬性 }, Private: { //私有成員 _priA: "", //私有屬性 _priM: function () { } //私有方法 }, Virtual: { //公有虛方法 } });
繼承抽象類
var A = YYC.AClass({}); var B = YYC.AClass(A, {}); var C = YYC.AClass({ Class: A }, {});
繼承類
var A = YYC.Class({}); var B = YYC.Class(A, {}); var C = YYC.Class({ Class: A }, {});
繼承接口
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 () { } } });
繼承接口和抽象類/類
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 } });
構(gòu)造函數(shù)
var A = YYC.Class({ Init: function(t){ this.value = t; } }); var a = new A(100); console.log(a.value); //100
靜態(tài)成員
使用“類.靜態(tài)成員”的形式來(lái)調(diào)用靜態(tài)成員。這里靜態(tài)成員實(shí)質(zhì)是類(function,function也是對(duì)象)的成員。
注意!靜態(tài)方法中的this指向類,不是指向類的實(shí)例!
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
類的成員互相調(diào)用
使用this來(lái)調(diào)用。
var A = YYC.Class({ Private: { _a: 100 }, Public: { method: function (t) { return this._a; } } }); var a = new A(); console.log(a.method); //100
子類調(diào)用父類
使用this.base()可調(diào)用父類同名函數(shù)。
使用this.baseClass.xx.call(this, xx)可調(diào)用父類的成員。
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
父類調(diào)用子類
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
覆寫父類方法,實(shí)現(xiàn)接口成員、抽象成員
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("實(shí)現(xiàn)抽象方法"); } }, Public: { method: function () { console.log("覆蓋父類同名方法"); }, m1: function () { console.log("實(shí)現(xiàn)接口"); } } });
其它API
stubParentMethod、stubParentMethodByAClass
讓父類(Class/AClass)指定方法不執(zhí)行。
測(cè)試用例如下:
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("讓父類指定方法不執(zhí)行,用于Class的測(cè)試方法中調(diào)用了父類方法的情況", 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測(cè)試父類指定方法的調(diào)用情況", 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();
}
}
});
//想要測(cè)試B的done方法,必須先建一個(gè)空子類繼承B,然后測(cè)試空子類的done方法
function getInstance() {
var T = YYC.Class(B, {
});
return new T();
}
t = getInstance();
sandbox = sinon.sandbox.create();
});
afterEach(function () {
sandbox.restore();
});
it("讓父類指定方法不執(zhí)行,用于AClass的測(cè)試方法中調(diào)用了父類方法的情況", 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測(cè)試父類指定方法的調(diào)用情況", function () {
t.stubParentMethodByAClass(sandbox, "done");
t.done();
expect(t.lastBaseClassForTest.done.calledOnce).toBeTruthy();
});
});
isInstanceOf
判斷是否為類的實(shí)例。
測(cè)試用例如下:
describe("isInstanceOf", function () {
it("直接判斷是否為Class的實(shí)例", function () {
var A = YYC.Class({});
expect(new A().isInstanceOf(A)).toBeTruthy();
});
describe("測(cè)試?yán)^承抽象類時(shí)的情況", function () {
it("測(cè)試1", function () {
var A = YYC.AClass({});
var B = YYC.Class(A, {});
expect(new B().isInstanceOf(B)).toBeTruthy();
expect(new B().isInstanceOf(A)).toBeTruthy();
});
it("測(cè)試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("測(cè)試?yán)^承接口時(shí)的情況", function () {
it("測(cè)試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("測(cè)試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("綜合測(cè)試", 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();
});
});
YOOP.version
返回當(dāng)前版本號(hào)。
測(cè)試用例如下:
it("獲得當(dāng)前版本號(hào)", function () {
expect(YYC.YOOP.version).toBeString();
});
約定
在該框架的實(shí)現(xiàn)中,類的實(shí)例可以訪問(wèn)類的公有成員、保護(hù)成員、私有成員,所有成員都是添加到類的原型中(如果是繼承,則將父類的成員添和子類的成員都添加到子類的原型中),框架只是從語(yǔ)義上區(qū)分了成員的訪問(wèn)權(quán)限,在機(jī)制上沒有對(duì)成員的訪問(wèn)權(quán)限設(shè)任何限制!
因此,用戶需要采用命名約定的方式來(lái)區(qū)分不同的成員,需要自覺遵守訪問(wèn)權(quán)限規(guī)則(如類的實(shí)例只能訪問(wèn)公有成員;不能訪問(wèn)其它類的私有成員;子類可以訪問(wèn)父類的保護(hù)成員等等)。
私有成員和保護(hù)成員的建議命名約定
基類的私有成員以“_”開頭,保護(hù)成員以“P_”開頭。
在繼承樹中,第一層類私有成員以“_”開頭,第二層類私有成員以“__”開頭,以此類推,從而區(qū)分不同層級(jí)中同名的私有成員。
所有層級(jí)中的保護(hù)成員前綴都為“P_”(原因見后面“為什么每層子類的保護(hù)成員前綴都一樣”的討論)。
用戶也可以將第一層子類的私有成員前綴設(shè)為“_1_”,第二層子類的私有成員設(shè)為“_2_”。。。。。。
前綴設(shè)置規(guī)則用戶可自訂,只要在繼承中使不同層級(jí)的類的私有成員不重名即可。
見下面的實(shí)例代碼:
不繼承接口
var A = YYC.AClass({ //私有成員以“_”開頭,保護(hù)成員以“P_”開頭 Private: { _value: 0, _method: function () { } }, Protected: { P_value: 0, Virtual: { P_method: function () { } } } }); var B = YYC.Class(A, { //私有成員以“__”開頭,保護(hù)成員以“P_”開頭 Private: { __value: 0, __method: function () { } }, Protected: { P_method: function () { } } });
繼承接口
var I = YYC.Interface("method"); var A = YYC.AClass({ Interface: I }, { //私有成員以“_”開頭,保護(hù)成員以“P_” Private: { _value: 0, _method: function () { } }, Protected: { P_value: 0, Virtual: { P_method: function () { } } } }); var B = YYC.Class(A, { //私有成員以“__”開頭,保護(hù)成員以“P_” Private: { __value: 0, __method: function () { } }, Protected: { P_method: function () { } }, Public: { method: function () { } } });
為什么每層子類的私有前綴最好不一樣?
如果子類與父類有同名的私有成員時(shí),當(dāng)子類調(diào)用父類成員時(shí),可能會(huì)出現(xiàn)父類成員調(diào)用子類的私有成員。
見下面的示例代碼:
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); //失?。∑谕祷谹->_val(100),實(shí)際返回的是B->_val(200)
為什么每層子類的保護(hù)成員前綴都一樣?
如果父類與子類的保護(hù)成員同名,則父類的該保護(hù)成員一般都是設(shè)計(jì)為虛成員,專門供子類覆寫的。因此當(dāng)子類調(diào)用父類成員時(shí),本來(lái)就期望父類成員調(diào)用子類覆寫的保護(hù)成員。
見下面的示例代碼:
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應(yīng)該返回B覆寫后的P_val(200)
baseClass
為了防止子類的prototype.baseClass覆蓋父類prototype.baseClass,在子類繼承父類時(shí),用戶需要先判斷父類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); //調(diào)用A1.a } } }); var B = YYC.Class(A2, { Public: { a: function () { this.arr.push(3); this._baseClass.a.call(this, null); //調(diào)用A2.a this.baseClass.a.call(this, null); //調(diào)用A1.a return this.arr; } } }); var b = new B(); expect(b.a()).toEqual([3, 2, 1, 1]);
注意事項(xiàng)
子類使用this.baseClass調(diào)用父類成員時(shí),要將父類成員的this指向子類。
錯(cuò)誤的寫法:
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
已解決的問(wèn)題
YOOP框架目前已解決了下面的問(wèn)題:
1、同一個(gè)類的實(shí)例之間不應(yīng)該共享屬性。
問(wèn)題描述
參考下面的代碼:
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([]); //失?。?shí)際為["a"]!
原因分析
因?yàn)閅OOP將類的成員都加入到類的原型對(duì)象中,而類實(shí)例的成員都是鏈接自類的原型對(duì)象,所以同一個(gè)類的實(shí)例之間成員共享。
解決方案
在Class的構(gòu)造函數(shù)中深拷貝原型的屬性到實(shí)例中,不拷貝原型的方法,從而同一個(gè)類的實(shí)例之間共享同一原型對(duì)象的方法,但它們的屬性相互獨(dú)立。
2、繼承于同一父類的子類實(shí)例之間不應(yīng)該共享屬性。
問(wèn)題描述
參考下面的代碼
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([]); //失敗!實(shí)際為["a"]!
原因分析
目前是通過(guò)原型繼承的方式來(lái)實(shí)現(xiàn)繼承的。這樣子類之間的成員都鏈接自父類的原型對(duì)象,從而會(huì)造成同一父類的子類實(shí)例之間成員共享。
解決方案
修改類繼承方式,通過(guò)“深拷貝父類原型所有成員到子類中”的方式實(shí)現(xiàn)繼承,從而同一父類的子類實(shí)例之間的成員相互獨(dú)立。
缺點(diǎn)
只是從語(yǔ)義上約定了訪問(wèn)權(quán)限,而沒有從機(jī)制上限制訪問(wèn)權(quán)限。
如可以根據(jù)命名約定區(qū)分類的公有成員、保護(hù)成員、私有成員,但是類的實(shí)例卻可以訪問(wèn)類的所有成員。
版本歷史
2013-06-07 發(fā)布YOOP v1.0
2014-08-26 發(fā)布YOOP v1.1
1、類實(shí)例增加isInstanceOf方法,用于判斷是否為類的實(shí)例,適用于接口繼承、類繼承等情況
2、protected方法也可以使用this.base來(lái)訪問(wèn)父類同名方法了
3、解決了“若一個(gè)方法中調(diào)用其它方法,則它們的this.base會(huì)互相干擾”的問(wèn)題
4、增加stubParentMethod和stubParentMethodByAClass方法,該方法讓父類(Class/AClass)指定方法不執(zhí)行,用于Class的測(cè)試方法中調(diào)用了父類方法的情況(如調(diào)用了this.base()或this.baseClass.xxx)
5、現(xiàn)在支持AMD、CMD、CommonJS規(guī)范了
6、增加YYC.YOOP.version屬性,用于獲得當(dāng)前版本號(hào)
7、Class的構(gòu)造函數(shù)F中現(xiàn)在只拷貝原型的屬性到實(shí)例中,從而同一個(gè)類的實(shí)例之間共享同一原型對(duì)象的方法,但屬性相互獨(dú)立。
浙公網(wǎng)安備 33010602011771號(hào)