關於JS中OOP的具體實現,許多大神級的JS專家都給出了本身的方案。javascript
一:Douglas Crockfordhtml
1.1 Douglas Crockford實現的類繼承java
/** * 原文地址:http://javascript.crockford.com/inheritance.html */ Function.prototype.method = function (name, func) { this.prototype[name] = func; return this; }; Function.method('inherits', function (parent) { var d = {}, p = (this.prototype = new parent()); this.prototype.constructor = parent; this.method('uber', function uber(name) { if (!(name in d)) { d[name] = 0; } var f, r, t = d[name], v = parent.prototype; if (t) { while (t) { v = v.constructor.prototype; t -= 1; } f = v[name]; } else { f = p[name]; if (f == this[name]) { f = v[name]; } } d[name] += 1; r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); d[name] -= 1; return r; }); return this; });
DC實現的這個類繼承的思路是:正則表達式
1. this.prototype = new parent()經過重寫構造函數的原型對象來實現繼承瀏覽器
2. 增長實例方法uber(),經過instance.uber()能夠訪問父類中的某個方法。固然uber方法有點複雜,分析以前咱們要先明白如下幾點:閉包
a. 閉包變量不會被銷燬,同時函數的做用域是在函數定義時肯定的。app
b. 每一個函數都有prototype屬性,指向一個對象,這個對象會在函數做爲構造函數建立對象時,做爲建立對象的模板,這個對象咱們稱之爲原型對象。函數
c. 每一個對象都有一個__proto__指針,指向此對象在建立時所依賴的原型對象。Object.prototype.__proto__ === null;測試
d. 在對一個對象求某個鍵值時,若是對象自己不存在這個鍵,則會在對象的原型(鏈)上面尋找this
Function.method('inherits', function (parent) { //d, p, parent都是一直存在的閉包變量,可供uber()方法使用 var d = {}, //經過 this.prototype = new parent() 實現繼承 p = (this.prototype = new parent()); this.prototype.constructor = parent; //經過增長原型方法uber()實現對象父類方法的調用(特別是子父類中的同名方法) //相似Java中的super,調用其直接父類中的方法 this.method('uber', function uber(name) { //d[name]是標識某方法在原型鏈中的層級,以便在原型鏈中正確獲得相應的父類方法 //使用d[name]這樣的map是由於在一個方法中可能調用多個父類的同名方法 if (!(name in d)) { d[name] = 0; } var f, r, t = d[name], v = parent.prototype; if (t) { //若是層級標識不爲0,則要循環在原型鏈上找到對象的原型對象,再肯定對應的父方法name while (t) { v = v.constructor.prototype; t -= 1; } f = v[name]; } else { f = p[name]; // p === this.prototype === new parent;當前類的原型對象 //若是當前類的原型對象上的方法name與對象實例的name方法相等。 //這裏要注意,對於引用類型的相等,比較的是指針。引用類型值a == 引用類型值b 只能說明a,b都指向同一塊內存 //原型對象上的方法name與對象實例的方法name相等,只能說明對象自己沒有這個方法,這個方法是存放在對象原型對象中的 if (f == this[name]) { f = v[name]; // v = parent.prototype 是父類的原型對象。上面的屬性會被父類的實例與子類的實例共享 } } //uber()方法層級+1 d[name] += 1; //使用apply調用父類的方法f,並設置其上下文爲this,即子類實例 r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); //層級還原 d[name] -= 1; return r; }); return this; });
//測試 function Animal() {} Animal.prototype.getInfo = function() { return 'Animal'; } Animal.prototype.getAge = function() { return '10'; } function Dog() { } //注意Dog.prototype上新增的方法必定要寫在調用了inherits()後面 Dog.inherits(Animal); Dog.prototype.getInfo = function() { var a = 'dogName:' + this.uber('getInfo'); var b = 'dogAge:' + this.getAge(); return a +'|'+ b; } function Collie() {} Collie.inherits(Dog); Collie.prototype.getInfo = function() { return this.uber('getInfo') + '|Collie'; } var c = new Collie(); console.log(c.getInfo()); //dogName:Animal|dogAge:10|Collie console.log(c instanceof Collie);// true console.log(c instanceof Dog); // true console.log(c instanceof Animal); //true console.log(c.constructor === Animal); //false console.log(c.constructor === Dog); //true console.log(c.constructor === Collie); //false //c.constructor 應該等於Collie纔對,如今卻指向了Dog.錯誤緣由:this.prototype.constructor = parent;
在玉伯早期的一篇文章中(http://www.iteye.com/topic/248933),有下面一個例子。至於結果,找出完整的調整棧,看一下就完成明白了。結果與原文有點不同,可能DC修改過他的代碼吧。
// 例2 function D1() {} D1.prototype.getName = function() { return 'D1' }; // @4 function D2() {} D2.inherits(D1); D2.prototype.getName = function () { return this.uber('getName') + ',D2'; }; // @5 function D3() {} D3.inherits(D2); function D4() {} D4.inherits(D3); function D5() {} D5.inherits(D4); D5.prototype.getName = function () { return this.uber('getName') + ',D5'; }; // @6 function D6() {} D6.inherits(D5); var d6 = new D6(); println(d6.getName()); // => D1,D2,D2,D2,D5,D5 println(d6.uber('getName')); // => D1,D2,D2,D2,D5
發現結果是D1,D2,D2…這樣。上面已經說過:在對一個對象求某個鍵值時,若是對象自己不存在這個鍵,則會在對象的原型(鏈)上面尋找。這就是產生多個D2的緣由。但這結果與咱們指望的super效果不同。下面是玉伯加了patch的代碼:
// patched by lifesinger@gmail.com 2008/10/4 Function.method('inherits', function (parent) { var d = { }, p = (this.prototype = new parent()); // 還原constructor p.constructor = this; // 添加superclass屬性 p.superclass = parent; this.method('uber', function uber(name) { if (!(name in d)) { d[name] = 0; } var f, r, t = d[name], v = parent.prototype; if (t) { while (t) { // 利用superclass來上溯,避免contructor陷阱。要注意parent沒有被修改過,因此v在每次進入uber時的值是同樣的。始終指向父類原型對象 v = v.superclass.prototype; // 跳過「斷層」的繼承點。不會由於「對象自己沒有向原型(鏈)拿」形成重複執行 if(v.hasOwnProperty(name)) { t -= 1; } } f = v[name]; } else { f = p[name]; if (f == this[name]) { f = v[name]; } } d[name] += 1; if(f == this[name]) { // this[name]在父類中的情景 r = this.uber.apply(this, Array.prototype.slice.apply(arguments)); } else { r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); } d[name] -= 1; return r; }); return this; });
// 例3 function F1() { } F1.prototype.getName = function() { return 'F1'; }; function F2() { } F2.inherits(F1); F2.prototype.getName = function() { return this.uber('getName') + ',F2'; }; function F3() { } F3.inherits(F2); F3.prototype.getName = function() { return this.uber('getName') + ',F3'; }; function F4() { } F4.inherits(F3); F4.prototype.getName = function() { return this.uber('getName') + ',F4'; }; document.write('<hr />') var f3 = new F3(); document.write(f3.getName()); // => F1,F2,F3 document.write('<hr />') var f4 = new F4(); document.write(f4.getName()); // => F1,F2,F3,F4 console.log(f3 instanceof F3);//true console.log(f3 instanceof F2);//true console.log(f3 instanceof F1);//true console.log(f3.constructor === F3);//true console.log(f3.constructor === F2);//false console.log(f3.constructor === F1);//false console.log(f4.constructor === F4);//true console.log(f4.constructor === F3);//false console.log(f4.constructor === F2);//false console.log(f4.constructor === F1);//false
至此,能夠發現已經實現:
》實現了繼承
》修正了實例的constructor屬性指向錯誤;
》instanceof能正常運行
》能使用uber調用父類的方法
固然也會有缺點:
》每一次繼承都會建立多個閉包變量,內存佔用多
》每一次繼承都要先建立一個父類的實例,(new parent())
》子類與父類必須先定義好,爲子類增長實例方法也必須放到inherits()方法調用以後。
參考文章:
http://javascript.crockford.com/inheritance.html
http://www.iteye.com/topic/248933
1.2 Douglas Crockford實現的基於原型的繼承
if (typeof Object.create !== 'function') { Object.create = function (o) { //定義類 function F() {} //重寫原型,實現繼承 F.prototype = o; //返回類的實例 return new F(); }; }
這樣也能夠實現繼承。不過沒有類,實例相關的概念。固然也不具備什麼uber()方法能力。不過DC說, 這纔是JavaScript的「本性」。JavaScript自己就是無類的,基於原型的。ES5已經實現這個方法。
二:John Resig
jQuery的做者John Resig的繼承實現思路是:實現一個全局對象Class(實際上是一個函數,但在JS中函數也是對象),這個對象具備靜態方法extends()。extends()須要傳入一個對象做爲返回(構造)函數的原型對象依據。同時也實現了與uber()相似的_super()方法。
/* Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype (function(){ var initializing = false, // 摘自http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html // fnTest是一個正則表達式,可能的取值爲(/\b_super\b/ 或 /.*/) // - 對 /xyz/.test(function() { xyz; }) 的測試是爲了檢測瀏覽器是否支持test參數爲函數的狀況 // - 不過我對IE7.0,Chrome2.0,FF3.5進行了測試,此測試都返回true。 // - 因此我想這樣對fnTest賦值大部分狀況下也是對的:fnTest = /\b_super\b/; fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; //這裏的this指向window, 至關於window.Class = function(){} this.Class = function(){}; //@1 /** * 向全局對象Class(實際上是一個函數,但在JS中函數也是特殊的對象,能夠在上面掛屬性或方法)上面增長靜態方法extend * extend方法須要一個對象做爲返回類的原型對象的模板 * @param {*} prop * @returns {Function} */ Class.extend = function(prop) { //上面說過,Class是一個(函數)對象,函數做爲對象的方法調用時,this指向函數所屬的對象 //因此this指向Class對象:@1。其實就是一個匿名函數 var _super = this.prototype; initializing = true; //new this()其實就是包含父類原型的一個空對象。這個爲繼承打了基礎。保證了instanceof能獲得正確的結果 var prototype = new this(); //@2 initializing = false; /** * 把傳進來的prop對象上面的屬性複製到@2對象上 * 這時候有三個對象: * prop:用戶做爲extend方法的參數傳進來的對象 * _super:父類的原型對象 * prototype: 要返回的類的原型對象 * 它們之間的關係是:prop用來擴展prototype; prototype用於實現繼承; * _super用於實現調用父類的方法 */ for (var name in prop) { /** * 注意:true && true && 1 > 0 ? 1: -1; === (true && true && 1 > 0 ) ? 1: -1; * 因此下面的賦值過程爲: * 1. 若是prop[name]不是一個函數: prototype[name] = prop[name] * 2. 若是prop[name]是一個函數,但_super[name]不是一個函數:prototype[name] = prop[name] * 3. 若是prop[name], _super[name]都是一個函數,且fn.Test.test(prop[name])爲假:prototype[name] = prop[name] * 4. 其它狀況:prototype[name] = 匿名函數自執行的結果 * * 備註:/\b_super\b/.test(function () {}) === > /\b_super\b/.test((function() {}).toString()); * 即要測試的函數代碼中包含_super這個字符串都會返回true; * /\b_super\b/.test(function() {var a = '_super'}) === true * /\b_super\b/.test(function() {var a = '_supe'}) === false */ //在prototype上面加入本身的原型屬性 prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && //若是傳入對象的某個屬性中包含'_super',則要作特殊處理 fnTest.test(prop[name]) ? (function(name, fn){ //這個name, fn會成爲閉包變量 return function() { var tmp = this._super; //把全部的父類中有'_super'字符串的方法都用閉包變量name保存起來 //同時_super也是一個閉包變量,這樣就能夠找到在調用this._super()時要調用父類的那個方法 //uber()方法要手動傳入一個方法名,但_super()方法卻能自動找到父類的同名方法 this._super = _super[name]; //在前面已經準備好this._super的指向,而後調用包含this._super的實例方法, //就會直接轉到父類方法 var ret = fn.apply(this, arguments); //將this._super還原。this._super也可能包含其它值 this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } //定義類Klass。Jhon Resig原代碼是用的Class,增長了理解難度 function Klass() { //全部的初始化都是在構建函數的init方法裏面進行的。 if ( !initializing && this.init ) this.init.apply(this, arguments); } //重寫類的原型,實現繼承 Klass.prototype = prototype; //修正類原型對象的constructor指向 Klass.prototype.constructor = Klass; //爲類增長靜態方法extend Klass.extend = arguments.callee; //返回類,實際上是構造方法 return Klass; }; })();
分析:
1. 在Object的上增長了一層:實現了一個Class類,這個類會做爲全部使用extend產生類的第二基類,上面有一個extend方法。這個方法會返回一個類(構造函數),返回時已經設置好原型對象。這個原型對象由 extend傳入的參數對象與此類父類的原型共同生成
2. 當調用new Constructor()時,會檢測對象是否有init方法,若是有,會調用這個init方法對實例進行初始化,不然返回一個空對象
3. 可使用返回的類再產生一個類
var Person = Class.extend({ init: function(isDancing){ this.dancing = isDancing; }, flag:'PersonProp' }); var Ninja = Person.extend({ init: function(){ //能自動找到父類中的init方法 this._super( false ); }, flag:'NinjaProp' }); var p = new Person(true); p.dancing; // => true var n = new Ninja(); n.dancing; // => false p instanceof Object; //=> true p instanceof Class;//=> true p instanceof Person; //=> true p.constructor === Person;//=>true
http://ejohn.org/blog/simple-javascript-inheritance/
http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html