詳解Javascript的繼承實現

来源:http://www.cnblogs.com/lyzg/archive/2016/03/25/5313752.html
-Advertisement-
Play Games

我最早掌握的在js中實現繼承的方法是在w3school學到的混合原型鏈和對象冒充的方法,在工作中,只要用到繼承的時候,我都是用這個方法實現。它的實現簡單,思路清晰:用對象冒充繼承父類構造函數的屬性,用原型鏈繼承父類prototype 對象的方法,滿足我遇到過的所有繼承的場景。正因如此,我從沒想過下次... ...


我最早掌握的在js中實現繼承的方法是在w3school學到的混合原型鏈和對象冒充的方法,在工作中,只要用到繼承的時候,我都是用這個方法實現。它的實現簡單,思路清晰:用對象冒充繼承父類構造函數的屬性,用原型鏈繼承父類prototype 對象的方法,滿足我遇到過的所有繼承的場景。正因如此,我從沒想過下次寫繼承的時候,我要換一種方式來寫,直到今天晚上看了三生石上關於javascript繼承系列的博客(出的很早,現在才看,真有點可惜),才發現在js裡面,繼承機制也可以寫的如此貼近java這種後端語言的實現,確實很妙!所以我想在充分理解他博客的思路下,實現一個自己今後用得到的一個繼承庫。希望本文可以幫助你寫出更流弊的javascript:)

1. 混合方式實現及問題

瞭解問題之前,先看看它的具體實現:

//父類構造函數
function Employee(name, salary) {
    //實例屬性:姓名
    this.name = name;
    //實例屬性:薪資
    this.salary = salary;
}

//通過字面量對象設置父類的原型,給父類添加實例方法
Employee.prototype = {
    //由於此處添加實例方法時也是通過修改父類原型處理的,
    //所以必須修改父類原型的constructor指向,避免父類實例的constructor屬性指向Object函數
    constructor: Employee,
    getName: function () {
        return this.name;
    },
    getSalary: function () {
        return this.salary;
    },
    toString: function () {
        return this.name + '\'s salary is ' + this.getSalary() + '.';
    }
}

//子類構造函數
function Manager(name, salary, percentage) {
    //對象冒充,實現屬性繼承(name, salary)
    Employee.apply(this, [name, salary]);
    //實例屬性:提成
    this.percentage = percentage;
}

//將父類的一個實例設置為子類的原型,實現方法繼承
Manager.prototype = new Employee();
//修改子類原型的constructor指向,避免子類實例的constructor屬性指向父類的構造函數
Manager.prototype.constructor = Manager;
//給子類添加新的實例方法
Manager.prototype.getSalary = function () {
    return this.salary + this.salary * this.percentage;
}

var e = new Employee('jason', 5000);
var m = new Manager('tom', 8000, 0.15);

console.log(e.toString()); //jason's salary is 5000.
console.log(m.toString()); //tom's salary is 9200.

console.log(m instanceof Manager); //true
console.log(m instanceof Employee); //true
console.log(e instanceof Employee); //true
console.log(e instanceof Manager); //false

從結果上來說,這種繼承實現方式沒有問題,Manager的實例同時繼承到了Employee類的實例屬性和實例方法,並且通過instanceOf運算的結果也都正確。但是從代碼組織和實現細節層面,這種方法還有以下幾個問題:
1)代碼組織不夠優雅,繼承實現的關鍵部分的邏輯是通用的,都是如下結構:

//將父類的一個實例設置為子類的原型,實現方法繼承
SubClass.prototype = new SuperClass();
//修改子類原型的constructor指向,避免子類實例的constructor屬性指向父類的構造函數
SubClass.prototype.constructor = SubClass;
//給子類添加新的實例方法
SubClass.prototype.method1 = function() {
}
SubClass.prototype.method2 = function() {
}
SubClass.prototype.method3 = function() {
}

這段代碼缺乏封裝。另外在添加子類的實例方法時,不能通過SubClass.prototype = { method1: function() {} }這種方式去設置,否則就把子類的原型整個又修改了,繼承就無法實現了,這樣每次都得按SubClass.prototype.method1 = function() {} 的結構去寫,代碼看起來很不連續。

解決方式:利用模塊化的方式,將通用的邏輯封裝起來,對外提供簡單的介面,只要按照約定的介面調用,就能夠簡化類的構建與類的繼承。具體實現請看後面的內容介紹,暫時只能提供理論的說明。

2)在給子類的原型設置成父類的實例時,調用的是new SuperClass(),這是對父類構造函數的無參調用,那麼就要求父類必須有無參的構造函數。可是在javascript中,函數無法重載,所以父類不可能提供多個構造函數,在實際業務中,大部分場景下父類構造函數又不可能沒有參數,為了在唯一的一個構造函數中模擬函數重載,只能藉助判斷arguments.length來處理。問題就是,有時候很難保證每次寫父類構造函數的時候都會添加arguments.length的判斷邏輯。這樣的話,這個處理方式就是有風險的。要是能把構造函數里的邏輯抽離出來,讓類的構造函數全部是無參函數的話,這個問題就很好解決了。

解決方式:把父類跟子類的構造函數全部無參化,並且在構造函數內不寫任何邏輯,把構造函數的邏輯都遷移到init這個實例方法,比如前面給出的Employee和Manager的例子就能改造成下麵這個樣子:

//無參無邏輯的父類構造函數
function Employee() {}

Employee.prototype = {
    constructor: Employee,
    //把構造邏輯搬到init方法中來
    init: function (name, salary) {
            this.name = name;
            this.salary = salary;
        },
        getName: function () {
            return this.name;
        },
        getSalary: function () {
            return this.salary;
        },
        toString: function () {
            return this.name + '\'s salary is ' + this.getSalary() + '.';
        }
};

//無參無邏輯的子類構造函數
function Manager() {}

Manager.prototype = new Employee();
Manager.prototype.constructor = Manager;
//把構造邏輯搬到init方法中來
Manager.prototype.init = function (name, salary, percentage) {
    //借用父類的init方法,實現屬性繼承(name, salary)
    Employee.prototype.init.apply(this, [name, salary]);
    this.percentage = percentage;
};
Manager.prototype.getSalary = function () {
    return this.salary + this.salary * this.percentage;
};

用init方法來完成構造功能,就可以保證在設置子類原型時(Manager.prototype = new Employee()),父類的實例化操作一定不會出錯,唯一不好的是在調用類的構造函數來初始化實例的時候,必須在調用構造函數後手動調用init方法來完成實際的構造邏輯:

var e = new Employee();
e.init('jason', 5000);
var m = new Manager();
m.init('tom', 8000, 0.15);

console.log(e.toString()); //jason's salary is 5000.
console.log(m.toString()); //tom's salary is 9200.

console.log(m instanceof Manager); //true
console.log(m instanceof Employee); //true
console.log(e instanceof Employee); //true
console.log(e instanceof Manager); //false

要是能把這個init的邏輯放在構造函數內部就好了,可是這樣的話就會違背前面說的構造函數無參無邏輯的原則。換一種方式來考慮,這個原則的目的是為了保證在實例化父類作為子類原型的時候,調用父類的構造函數不會出錯,那麼就可以稍微打破一下這個原則,在類的構造函數里添加少量的並且一定不會有問題的邏輯來解決:

//添加一個全局標識initializing,表示是否正在進行子類的構建和類的繼承
var initializing = false;
//可自動調用init方法的父類構造函數
function Employee() {
    if (!initializing) {
        this.init.apply(this, arguments);
    }
}

Employee.prototype = {
    constructor: Employee,
    //把構造邏輯搬到init方法中來
    init: function (name, salary) {
            this.name = name;
            this.salary = salary;
        },
        getName: function () {
            return this.name;
        },
        getSalary: function () {
            return this.salary;
        },
        toString: function () {
            return this.name + '\'s salary is ' + this.getSalary() + '.';
        }
};

//可自動調用init方法的子類構造函數
function Manager() {
    if (!initializing) {
        this.init.apply(this, arguments);
    }
}

//表示開始子類的構建和類的繼承
initializing = true;
//此時調用new Emplyee(),並不會調用Employee.prototype.init方法
Manager.prototype = new Employee();
Manager.prototype.constructor = Manager;
//表示結束子類的構建和類的繼承,之後調用new Employee或new Manager都會自動調用init實例方法
initializing = false;

//把構造邏輯搬到init方法中來
Manager.prototype.init = function (name, salary, percentage) {
    //借用父類的init方法,實現屬性繼承(name, salary)
    Employee.prototype.init.apply(this, [name, salary]);
    this.percentage = percentage;
};
Manager.prototype.getSalary = function () {
    return this.salary + this.salary * this.percentage;
};

調用結果仍然和前面的例子一樣。但是這個實現還有一個小問題,它引入了一個全局變數initializing,要是能把引入這個全局變數就好了,這個其實很好解決,只要我們把關於類的構建跟繼承,封裝成一個模塊,然後把這個變數放在模塊的內部,就沒有問題了。

3)在構造子類的時候,是把子類的原型設置成了父類的一個實例,這個是不符合語義的,繼承應該發生在類與類之間,而不是類與實例之間。之所以要用父類的一個實例來作為子類的原型:

SubClass.prototype = new SuperClass();

完全是因為父類的這個實例,指向父類的原型,而子類的實例又會指向子類的原型,所以最終子類的實例就能通過原型鏈訪問到父類原型上的方法。這個做法雖然能實現實例方法的繼承,但是它不符合語義,而且它還有一個很大的問題就是會增加原型鏈的長度,導致子類在調用父類方法時,必須通過原型鏈的查找到父類的方法才行。要是繼承層次較深,會對js的執行性能有些影響。

解決方式:在解決這個問題之前,先想想繼承能幫我們解決什麼問題:從父類復用已有的實例屬性和實例方法。在javascript面向對象編程中,一直有一個原則就是,實例屬性都寫在構造函數或者實例方法裡面,實例方法寫在原型上面,也就是說類的原型,按照這個原則來說,就是用來寫實例方法的,而且是只用來寫實例方法,那麼我們完全可以在構建子類時,通過複製的方式將父類原型的所有方法全部添加到子類的原型上,不一定要把父類的一個實例設置成子類的原型,這樣就能將原型鏈的長度大大地縮短,藉助一個簡短的copy函數,我們就能輕鬆對前面的代碼進行改造:

//用來複制父類原型,由於父類原型上約定只寫實例方法,所以複製的時候不必擔心引用的問題
var copy = function (source) {
    var target = {};
    for (var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }
    return target;
}

function Employee() {
    this.init.apply(this, arguments);
}

Employee.prototype = {
    constructor: Employee,
    init: function (name, salary) {
            this.name = name;
            this.salary = salary;
        },
        getName: function () {
            return this.name;
        },
        getSalary: function () {
            return this.salary;
        },
        toString: function () {
            return this.name + '\'s salary is ' + this.getSalary() + '.';
        }
};

function Manager() {
    this.init.apply(this, arguments);
}
//將父類的原型方法複製到子類的原型上
Manager.prototype = copy(Employee.prototype);
//子類還是需要修改constructor指向,因為從父類原型複製出來的對象的constructor還是指向父類的構造函數
Manager.prototype.constructor = Manager;

Manager.prototype.init = function (name, salary, percentage) {
    Employee.prototype.init.apply(this, [name, salary]);
    this.percentage = percentage;
};
Manager.prototype.getSalary = function () {
    return this.salary + this.salary * this.percentage;
};

var e = new Employee('jason', 5000);
var m = new Manager('tom', 8000, 0.15);

console.log(e.toString()); //jason's salary is 5000.
console.log(m.toString()); //tom's salary is 9200.

console.log(m instanceof Manager); //true
console.log(m instanceof Employee); //false
console.log(e instanceof Employee); //true
console.log(e instanceof Manager); //false

這麼做了以後,當調用m.toString的時候其實調用的是Manager類自身原型上的方法,而不是Employee類的實例方法,縮短了在原型鏈上查找方法的距離。這個做法在性能上有很大的優點,但不好的是通過原型鏈維持的繼承關係其實已經斷了,子類的原型和子類的實例都無法再通過js原生的屬性訪問到父類的原型,所以這個調用console.log(m instanceof Employee)輸出的是false。不過跟性能比起來,這個都可以不算問題:一是instanceOf的運算,幾乎在javascript的開發裡面用不到,至少我是沒碰到過;二是通過複製方式完全能夠把父類的實例方法繼承下來,這就已經達到了繼承的最大目的。

這個方法還有一個額外的好處是,解決了第2個問題最後提到的引入initializing全局變數的問題,如果是複製的話,就不需要在構建繼承關係時,去調用父類的構造函數,那麼也就沒有必要在構造函數內先判斷initializing才能去調用init方法,上面的代碼中就已經去掉了initializing這個變數的處理。

4)在子類的構造函數和實例方法內如果想要調用父類的構造函數或者方法,顯得比較繁瑣:

function SuperClass() {}

SuperClass.prototype = {
    constructor: SuperClass,
    method1: function () {}
}

function SubClass() {
    //調用父類構造函數
    SuperClass.apply(this);
}

SubClass.prototype = new SuperClass();
SubClass.prototype.constructor = SubClass;
SubClass.prototype.method1 = function () {
    //調用父類的實例方法
    SuperClass.prototype.method1.apply(this, arguments);
}
SubClass.prototype.method2 = function () {}
SubClass.prototype.method3 = function () {}

每次都得靠apply借用方法來處理。要是能改成如下的調用就好用多了:

function SubClass() {
//調用父類構造函數
        this.base();
}

SubClass.prototype = new SuperClass();
SubClass.prototype.constructor = SubClass;
SubClass.prototype.method1 = function() {
//調用父類的實例方法
        this.base();
}

解決方式:如果要在每個實例方法里,都能通過this.base()調用父類原型上相應的方法,那麼this.base就一定不是一個固定的方法,需要在每個實例方法執行期間動態地將this.base指定為父類原型的同名方法,能夠做到這個實現的方式,就只有通過方法代理了,前面的Employee和Manager的例子可以改造如下:

//用來複制父類原型,由於父類原型上約定只寫實例方法,所以複製的時候不必擔心引用的問題
var copy = function (source) {
    var target = {};
    for (var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }
    return target;
};

function Employee() {
    this.init.apply(this, arguments);
}

Employee.prototype = {
    constructor: Employee,
    init: function (name, salary) {
            this.name = name;
            this.salary = salary;
        },
        getName: function () {
            return this.name;
        },
        getSalary: function () {
            return this.salary;
        },
        toString: function () {
            return this.name + '\'s salary is ' + this.getSalary() + '.';
        }
};

function Manager() {
    //必須在每個實例中添加baseProto屬性,以便實例內部可以通過這個屬性訪問到父類的原型
    //因為copy函數導致原型鏈斷裂,無法通過原型鏈訪問到父類的原型
    this.baseProto = Employee.prototype;
    this.init.apply(this, arguments);
}

Manager.prototype = copy(Employee.prototype);
//子類還是需要修改constructor指向,因為從父類原型複製出來的對象的constructor還是指向父類的構造函數
Manager.prototype.constructor = Manager;

Manager.prototype.init = (function (name, func) {
    return function () {
        //記錄實例原有的this.base的值
        var old = this.base;
        //將實例的this.base指向父類的原型的同名方法
        this.base = this.baseProto[name];
        //調用子類自身定義的init方法,也就是func參數傳遞進來的函數
        var ret = func.apply(this, arguments);
        //還原實例原有的this.base的值
        this.base = old;
        return ret;
    }
})('init', function (name, salary, percentage) {
    //通過this.base調用父類的init方法
    //這個函數真實的調用位置是var ret = func.apply(this, arguments);
    //當調用Manager實例的init方法時,其實不是調用的這個函數
    //而是調用上面那個匿名函數裡面return的匿名函數
    //在return的匿名函數里,先把this.base指向為了父類原型的同名函數,然後在調用func
    //func內部再通過調用this.base時,就能調用父類的原型方法。
    this.base(name, salary);
    this.percentage = percentage;
});

Manager.prototype.getSalary = function () {
    return this.salary + this.salary * this.percentage;
};

var e = new Employee('jason', 5000);
var m = new Manager('tom', 8000, 0.15);

console.log(e.toString()); //jason's salary is 5000.
console.log(m.toString()); //tom's salary is 9200.

console.log(m instanceof Manager); //true
console.log(m instanceof Employee); //false
console.log(e instanceof Employee); //true
console.log(e instanceof Manager); //false

通過代理的方式,就解決了在在實例方法內部通過this.base調用父類原型同名方法的問題。可是在實際情況中,每個實例方法都有可能需要調用父類的實例,那麼每個實例方法都要添加同樣的代碼,顯然這會增加很多麻煩,好在這部分的邏輯也是同樣的,我們可以把它抽象一下,最後都放到模塊化的內部去,這樣就能簡化代理的工作。

5)未考慮靜態屬性和靜態方法。儘管靜態成員是不需要繼承的,但在有些場景下,我們還是需要靜態成員,所以得考慮靜態成員應該添加在哪裡。

解決方式:由於js原生並不支持靜態成員,所以只能藉助一些公共的位置來處理。最佳的位置是添加到構造函數上:

var copy = function (source) {
    var target = {};
    for (var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }
    return target;
};

function Employee() {
    this.init.apply(this, arguments);
}

//添加一個靜態屬性
Employee.idCounter = 1;
//添加一個靜態方法
Employee.getId = function () {
    return Employee.idCounter++;
};

Employee.prototype = {
    constructor: Employee,
    init: function (name, salary) {
            this.name = name;
            this.salary = salary;
            //調用靜態方法
            this.id = Employee.getId();
        },
        getName: function () {
            return this.name;
        },
        getSalary: function () {
            return this.salary;
        },
        toString: function () {
            return this.name + '\'s salary is ' + this.getSalary() + '.';
        }
};

function Manager() {
    this.baseProto = Employee.prototype;
    this.init.apply(this, arguments);
}

Manager.prototype = copy(Employee.prototype);
Manager.prototype.constructor = Manager;

Manager.prototype.init = (function (name, func) {
    return function () {
        var old = this.base;
        this.base = this.baseProto[name];
        var ret = func.apply(this, arguments);
        this.base = old;
        return ret;
    }
})('init', function (name, salary, percentage) {
    this.base(name, salary);
    this.percentage = percentage;
});

Manager.prototype.getSalary = function () {
    return this.salary + this.salary * this.percentage;
};

var e = new Employee('jason', 5000);
var m = new Manager('tom', 8000, 0.15);

console.log(e.toString()); //jason's salary is 5000.
console.log(m.toString()); //tom's salary is 9200.

console.log(m instanceof Manager); //true
console.log(m instanceof Employee); //false
console.log(e instanceof Employee); //true
console.log(e instanceof Manager); //false
console.log(m.id); //2
console.log(e.id); //1

最後的兩行輸出了正確的實例id,而這個id是通過Employee類的靜態方法生成的。在java的面向對象編程中,子類跟父類都可以定義靜態成員,在調用的時候還存在覆蓋的問題,在js裡面,因為受語言的限制,自定義的靜態成員不可能實現全面的面向對象功能,就像上面這種,能夠給類提供一些公共的屬性和公共方法,就已經足夠了。

2. 期望的調用方式

從第1部分的分析可以看出,在js裡面,類的構建與繼承,有很多通用的邏輯,完全可以把這些邏輯封裝成一個單獨的模塊,形成一個通用的類庫,以便在工作中有需要的時候,都可以直接拿來使用。這個類庫要求能完成我們需要的功能(類的構建與繼承和靜態成員的添加),同時在使用時要足夠簡潔方便。在利用bootstrap的modal組件自定義alert,confirm和modal對話框這篇文章里,我曾說過一些從組件期望的調用方式,去反推組件實現的一些觀點,當你明確你需要什麼東西時,你才知道這個東西你該怎麼去創造。本文要編寫的這個繼承組件也會採取這個方法來實現,我先用前面Employee和Manager的例子來模擬調用這個繼承庫的場景,通過預設的一些組件名稱或者介面名稱以及調用方式,來嘗試走通真實使用這個繼承庫的流程,有了這個東西,下一步我只需要根據這個要求去實現即可,模擬如下:

//通過調用Class函數構造一個類
var Employee = Class({
    //通過instanceMembers指定這個類的實例成員
    instanceMembers: {
        init: function (name, salary) {
                this.name = name;
                this.salary = salary;
                //調用靜態方法
                this.id = Employee.getId();
            },
            getName: function () {
                return this.name;
            },
            getSalary: function () {
                return this.salary;
            },
            toString: function () {
                return this.name + '\'s salary is ' + this.getSalary() + '.';
            }
    },
    //通過staticMembers指定這個類的靜態成員
    //靜態方法內部可通過this訪問其它靜態成員
    //在外部可通過Employee.getId這種方式訪問到靜態成員
    staticMembers: {
        idCounter: 1,
        getId: function () {
            return this.idCounter++;
        }
    }
});

var Manager = Class({
    instanceMembers: {
        init: function (name, salary, percentage) {
                this.base(name, salary);
                this.percentage = percentage;
                Manager.count++;
            },
            getSalary: function () {
                return this.salary + this.salary * this.percentage;
            }
    },
    //通過extend指定要繼承的類
    extend: Employee
});

從模擬的結果來看,我想要的繼承庫對外提供的名稱只有Class, instanceMembers, staticMembers和extend而已,調用方式也很簡單,只要傳遞參數給Class函數即可。接下來就按照這個目標,看看如何一步步根據第一部分羅列的那些問題和解決方式,把這個庫給寫出來。

3. 繼承庫的詳細實現

根據API名稱和介面以及前面第1部分提出的問題,這個繼承庫要完成的功能有:
1)類的構建(關鍵:init方法)和靜態成員處理;
2)繼承關係的構建(關鍵:父類原型的複製);
3)父類方法的簡化調用(關鍵:父類原型上同名方法的代理)。
所以這個庫的實現,可以按照這三點分成三版來開發。

1)第一版

在第一版裡面,僅需要實現類的構架和靜態成員添加的功能即可,細節如下:

var Class = (function () {
    var hasOwn = Object.prototype.hasOwnProperty;

    //用來判斷是否為Object的實例
    function isObject(o) {
        return typeof (o) === 'object';
    }

    //用來判斷是否為Function的實例
    function isFunction(f) {
        return typeof (f) === 'function';
    }

    function ClassBuilder(options) {
        if (!isObject(options)) {
            throw new Error('Class options must be an valid object instance!');
        }

        var instanceMembers = isObject(options) && options.instanceMembers || {},
            staticMembers = isObject(options) && options.staticMembers || {},
            extend = isObject(options) && isFunction(options.extend) && options.extend,
            prop;

        //表示要構建的類的構造函數
        function TargetClass() {
            if (isFunction(this.init)) {
                this.init.apply(this, arguments);
            }
        }

        //添加靜態成員,這段代碼需在原型設置的前面執行,避免staticMembers中包含prototype屬性,覆蓋類的原型
        for (prop in staticMembers) {
            if (hasOwn.call(staticMembers, prop)) {
                TargetClass[prop] = staticMembers[prop];
            }
        }

        TargetClass.prototype = instanceMembers;
        TargetClass.prototype.constructor = TargetClass;

        return TargetClass;
    }

    return ClassBuilder
})();

這一版核心代碼在於類的構建和靜態成員添加的部分,其它代碼僅僅提供一些提前可以想到的賦值函數和變數(isObject, isFunction),並做一些參數合法性校驗的處理。添加靜態成員的代碼一定要在設置原型的代碼之前,否則就有原型被覆蓋的風險。有了這個版本,就可以直接構建帶靜態成員的Employee類了:

var Employee = Class({
    instanceMembers: {
        init: function (name, salary) {
                this.name = name;
                this.salary = salary;
                //調用靜態方法
                this.id = Employee.getId();
            },
            getName: function () {
                return this.name;
            },
            getSalary: function () {
                return this.salary;
            },
            toString: function () {
                return this.name + '\'s salary is ' + this.getSalary() + '.';
            }
    },
    staticMembers: {
        idCounter: 1,
        getId: function () {
            return this.idCounter++;
        }
    }
});

var e = new Employee('jason', 5000);
console.log(e.toString()); //jason's salary is 5000.
console.log(e.id); //1
console.log(e.constructor === Employee); //true

在getId方法中之所以直接使用this就能訪問到構造函數Employee,是因為getId這個方法是添加到構造函數上的,所以當調用Employee.getId()時,getId方法裡面的this指向的就是Employee這個函數對象。

2)第二版

第二版在第一版的基礎上,實現繼承關係的構建部分:

var Class = (function () {
    var hasOwn = Object.prototype.hasOwnProperty;

    //用來判斷是否為Object的實例
    function isObject(o) {
        return typeof (o) === 'object';
    }

    //用來判斷是否為Function的實例
    function isFunction(f) {
        return typeof (f) === 'function';
    }

    //簡單複製
    function copy(source) {
        var target = {};
        for (var i in source) {
            if (hasOwn.call(source, i)) {
                target[i] = source[i];
            }
        }
        return target;
    }

    function ClassBuilder(options) {
        if (!isObject(options)) {
            throw new Error('Class options must be an valid object instance!');
        }

        var instanceMembers = isObject(options) && options.instanceMembers || {},
            staticMembers = isObject(options) && options.staticMembers || {},
            extend = isObject(options) && isFunction(options.extend) && options.extend,
            prop;

        //表示要構建的類的構造函數
        function TargetClass() {
            if (extend) {
                //如果有要繼承的父類
                //就在每個實例中添加baseProto屬性,以便實例內部可以通過這個屬性訪問到父類的原型
                //因為copy函數導致原型鏈斷裂,無法通

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 做WEB項目的過程中難免涉及到表單的處理,包括:數據校驗、數據提交、返回處理、信息提示等。 下麵的代碼就是從前不久一個項目中提煉出來的,希望對大家有些幫助。 下麵是主要的代碼片段: 說明: - form必須定義一個id,在後面會用到 - submit按鈕的data-url屬性指定了後端處理程式 - ...
  • 接著上回新聞搜索的例子。現在我們要通過路由進入一個新的頁面來查看新聞詳細內容。 react和路由並沒有什麼直接關係,用什麼路由都可以。不過使用react-router可以讓我們的代碼風格統一, 並且有些工具使用起來很方便。 先來安裝react-router庫(我目前安裝的版本是2.0.1,跟1.x版 ...
  • 出處:http://www.zhangxinxu.com/study/201603/icon-text-vertical-align.html css html 效果圖1 小字型大小 2 大字型大小 ...
  • html5繪製折線圖詳細代碼 運行結果如下: ...
  • 觸摸事件是移動瀏覽器特有的HTML5事件,雖然click事件在pc和移動端更通用,但是在移動端會出現300ms延遲,較為影響用戶體驗,300ms延遲來自判斷雙擊和長按,因為只有預設等待時間結束以確定沒有後續動作發生時,才會觸發click事件。所以觸摸事件反應更快,體驗更好。 觸摸事件的類型: 為了區 ...
  • 現在若是我們要在動畫結束時候加一個事件該怎麼辦呢? 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <style> 5 div { width: 100px; height: 100px; background: pink; transition: width 1s; - ...
  • 看到自定義標簽的文檔時,文檔作者解釋的能力實在太弱,也可能是本人太笨,一下繞不過來。 看了一個stackoverflow答案,才算明白,在此貼出翻譯,以供大家參考。 1: app.directive('mytag',function() { 2: return { 3: restrict: 'E',... ...
  • 例子如下: 1.父頁index.jsp <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"><html xmlns="http ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...