【深入JavaScript】一種JS的繼承方法


這些天讀了John Resig的《Secrets of JavaScript Ninja》,其中討論到JS中實現繼承的方案,非常有趣,自己探索了一下,形成了筆記,放到這里。

這個方案在Resig的博客上也有,雖然代碼略微有點不一致,但核心思想是一樣的,請戳 這里 。 

 

<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提出的繼承方案,文章見 這里


免責聲明!

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



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