程式碼分享:體現js靈活性的def.js

邊浩發表於2019-02-27

def.js是之前在研究js繼承的時候發現的一個很有趣的js,恰巧現在前端團隊中每週進行一次分享,就翻出來整理成一篇小文章

先看一段def.js實現繼承的方式

def ("Person") ({
    init: function(name){
        this.name = name;
    },
	
    speak: function(text){
        alert(text || "Hi, my name is " + this.name);
    }
});

def ("Ninja") < Person ({
    init: function(name){
        this._super();
    },
	
    kick: function(){
        this.speak("I kick u!");
    }
});

var ninjy = new Ninja("JDD");

ninjy.speak();
ninjy.kick();
複製程式碼

可以看到是以 < 實現的Ninja繼承於Person

js中,當兩個物件進行算術運算或大小比較時,如果運算子兩邊不是數值,則呼叫valueOf方法,所以def.js一定是重寫了valueOf,在裡面實現了繼承

下面看def.js的原始碼(為了閱讀上下文方便,刪除了中間空行)

(function(global){
// def方法返回就是deferred方法,所以在繼承時執行的valueOf為下文中看到的deferred.valueOf
    var deferred;
// extend方法是通過混入的方式把deferred接收到的引數作為物件屬性
    function extend(source){
        var prop, target = this.prototype;
        for(var key in source) if(source.hasOwnProperty(key)){
            prop = target[key] = source[key];
            if(`function` == typeof prop){
// 這兩個屬性是為了下文中實現._super呼叫超類的同名方法,起到標記作用
                prop._name = key;
                prop._class = this;
            }
        }
        return this;
    }
// 此方法用來呼叫超類的同名方法
    function base(){
// callee已經在es5的嚴格模式中被棄用
        var caller = arguments.callee.caller;
        return caller._class._super.prototype[caller._name]
            .apply(this, arguments.length ? arguments : caller.arguments);
    }
    function def(context, klassName){
        klassName || (klassName = context, context = global);
// 在此處把通過def方法建立的類掛載到了global上
        var Klass = context[klassName] = function Klass(){
            if(context != this){
                return this.init && this.init.apply(this, arguments);
            }
// 把當前物件記錄到deferred中,方便<左側繼承
            deferred._super = Klass;
// 把超類後面的引數暫存在繼承過後再把這些屬性進行混入,類似一開始的例子中Person後面跟的引數
            deferred._props = arguments[0] || { };
        }
        Klass.extend = extend;
        deferred = function(props){
            return Klass.extend(props);
        };
        function Subclass(){ }
        deferred.valueOf = function(){
            var Superclass = deferred._super;
            if(!Superclass){
                return Klass;
            }
            Subclass.prototype = Superclass.prototype;
            var proto = Klass.prototype = new Subclass;
            Klass._class = Klass;
            Klass.toString = function(){
              return klassName;  
            };
            proto.constructor = Klass;
// 這個地方我把這兩句放到一起的原因,一個_super是在Klass物件上的,一個是在Klass.prototype上的
// 不明白的請複習原型鏈跟new的原理
            Klass._super = Superclass;
            proto._super = base;
            deferred(deferred._props);
        };
        return deferred;
    }
    global.def = def;
}(this));
複製程式碼

最後還有一點就是def ("Ninja") < Person()這句話的執行順序

  1. def(“Ninja”)返回一個deferred
  2. Person()設定deferred._super跟deferred._proto
  3. 左側執行valueOf即deferred.valueOf實現繼承

當然如今es8都要來了,def.js也沒什麼使用場景了

相關文章