這些天讀了John Resig的《Secrets of JavaScript Ninja》,其中討論到JS中實現繼承的方案,很是有趣,本身探索了一下,造成了筆記,放到這裏。javascript
這個方案在Resig的博客上也有,雖然代碼略微有點不一致,但核心思想是同樣的,請戳 這裏 。 html
<html> <head> <title></title> </head> <body> <script type="text/javascript"> // call a immediate funciton,prevent global namespace from being polluted. (function(){ // 這個initializing變量用於標識當前是否處於類的初始建立階段,下面會繼續詳述 var initializing = false, // 這是一個技巧性的寫法,用於檢測當前環境下函數是否可以序列化 // 附一篇討論函數序列化的文章:http://www.cnblogs.com/ziyunfei/archive/2012/12/04/2799603.html // superPattern引用一個正則對象,該對象用於驗證被驗證函數中是否有使用_super方法 superPattern = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; Object.subClass = function(properties){ // 當前對象(父類)的原型對象 var _super = this.prototype; // initializing = true表示當前處於類的初始建立階段。 // this構造函數裏會判斷initializing的狀態,若是爲false則不執行Init方法。 // 事實上這也是很是須要的,由於在這個時候,咱們須要的只是一個乾淨的虛構的構造函數,徹底不須要其執行init函數,以免污染。init方法只有在當前類被實例化的時候才須要被執行,而當前正執行繼承行爲,不該該執行Init方法。 initializing = true; // 當前對象(父類)的一個實例對象 var proto = new this(); // 初始建立階段完成,置initializing爲false initializing = false; // 在properties裏提供的屬性,做爲當前對象(父類)實例的公共屬性,供其子類實例共享; // 在properties裏提供的方法,做爲當前對象(父類)實例的公共方法,供其子類實例共享。 for(var name in properties){ proto[name] = typeof properties[name] == 'function' && //檢測當前提供的是否爲函數 typeof _super[name] == 'function' && //檢測當前提供的函數名是否已經存在於父類的原型對象中,若是是,則須要下面的操做,以保證父類中的方法不會被覆蓋且能夠以某種方式被調用,若是否,則直接將該函數賦值爲父類實例的方法 superPattern.test(properties[name]) ? f//檢測當前提供的函數內是否使用了_super方法,若是有使用_super方法,則須要下面的操做,以保證父類中的方法不會被覆蓋且能夠以某種方式被調用,若是沒有用到_super方法,則直接將該函數賦值爲父類實例的方法,即便父類原型中已經擁有同名方法(覆蓋) // 使用一個立刻執行的函數,返回一個閉包,這樣每一個閉包引用的都是各自的name和fn。 (function(name, fn){ return function() { // 首先將執行方法的當前對象(子類的實例化對象)的_super屬性保存到tmp變量裏。 // 這是很是必要的, 由於this永遠指向當前正在被調用的對象。 // 當C繼承B,B繼承A,而A\B\C均有一個dance方法且B\C的dance方法均使用了this._super來引用各自父類的方法時,下面這句操做就顯得很是重要了。它使得在方法調用時,this._super永遠指向「當前類」的父類的原型中的同名方法,從而避免this._super被隨便改寫。 var tmp = this._super; // 而後將父類的原型中的同名方法賦值給this._super,以便子類的實例化對象能夠在其執行name方法時經過this._super使用對應的父類原型中已經存在的方法 this._super = _super[name]; // 執行建立子類時提供的函數,並經過arguments傳入參數 var ret = fn.apply(this, arguments); // 將tmp裏保存的_super屬性從新賦值回this._super中 this._super = tmp; // 返回函數的執行結果 return ret; }; })(name, properties[name]) : properties[name]; } // 內部定義個名叫Class的類,構造函數內部只有一個操做:執行當前對象中可能存在的init方法 // 這樣作的緣由:新建一個類(閉包),能夠防止不少干擾(詳細可對比JS高級設計第三版) function Class(){ // 若是不是正在實現繼承,而且當前類的init方法存在,則執行init方法 // 每當subClass方法執行完畢後,都會返回這個Class構造函數,當用戶使用new 方法時,就會執行這裏面的操做 // 本質:每次調用subClass都新建一個類(閉包) if(!initializing && this.init){ // 這是子類的初始化方法,裏面能夠定義子類的私有屬性,公共屬性請在上方所述處添加 this.init.apply(this, arguments); } } // 重寫Class構造函數的prototype,使其再也不指向了Class原生的原型對象,而是指向了proto,即當前對象(類)的一個實例 // 本質:一個類的原型是另外一個類的實例(繼承) Class.prototype = proto; // 爲何要重寫Class的構造函數?由於這個Class函數,它原來的constructor指向的是Function對象,這裏修正它的指向,使其指向本身。 Class.constructor = Class; // 就是這個操做,使得每次調用subClass都會新生命的Class對象,也擁有subClass方法,能夠繼續被繼承下去 // 本質:使得每次繼承的子類都擁有被繼承的能力 Class.subClass = arguments.callee; // 返回這個內部新定義的構造函數(閉包) return Class; }; })(); var Person = Object.subClass({ init: function(isDancing) { this.dancing = isDancing; }, dance: function(){ console.log('i am a person,i dance.'); return this.dancing; } }); var Ninja = Person.subClass({ init:function(){ }, dance: function() { console.log('i am an Ninja,i dance.'); this._super(); return; }, swingSword:function(){ return true; } }); var Chileung = Ninja.subClass({ dance: function(){ console.log('i am Chileung.i dance.'); this._super(); return; } }); var p = new Person(); p.dance(); var n = new Ninja(); n.dance(); var c = new Chileung(); c.dance(); </script> </body> </html>
在博客園裏也找到了一篇不錯的有關這種繼承方式的討論,參見 這裏
另外本身之前曾經也思考過Zakas提出的繼承方案,文章見 這裏java