国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

詳解Javascript的繼承實(shí)現(xiàn)

go4it / 3626人閱讀

摘要:它的實(shí)現(xiàn)簡(jiǎn)單,思路清晰用對(duì)象冒充繼承父類構(gòu)造函數(shù)的屬性,用原型鏈繼承父類對(duì)象的方法,滿足我遇到過(guò)的所有繼承的場(chǎng)景。解決方式在解決這個(gè)問(wèn)題之前,先想想繼承能幫我們解決什么問(wèn)題從父類復(fù)用已有的實(shí)例屬性和實(shí)例方法。

我最早掌握的在js中實(shí)現(xiàn)繼承的方法是在w3school學(xué)到的混合原型鏈和對(duì)象冒充的方法,在工作中,只要用到繼承的時(shí)候,我都是用這個(gè)方法實(shí)現(xiàn)。它的實(shí)現(xiàn)簡(jiǎn)單,思路清晰:用對(duì)象冒充繼承父類構(gòu)造函數(shù)的屬性,用原型鏈繼承父類prototype 對(duì)象的方法,滿足我遇到過(guò)的所有繼承的場(chǎng)景。正因如此,我從沒(méi)想過(guò)下次寫繼承的時(shí)候,我要換一種方式來(lái)寫,直到今天晚上看了三生石上關(guān)于javascript繼承系列的博客(出的很早,現(xiàn)在才看,真有點(diǎn)可惜),才發(fā)現(xiàn)在js里面,繼承機(jī)制也可以寫的如此貼近java這種后端語(yǔ)言的實(shí)現(xiàn),確實(shí)很妙!所以我想在充分理解他博客的思路下,實(shí)現(xiàn)一個(gè)自己今后用得到的一個(gè)繼承庫(kù)。

1. 混合方式實(shí)現(xiàn)及問(wèn)題

了解問(wèn)題之前,先看看它的具體實(shí)現(xiàn):

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

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

//子類構(gòu)造函數(shù)
function Manager(name, salary, percentage) {
    //對(duì)象冒充,實(shí)現(xiàn)屬性繼承(name, salary)
    Employee.apply(this, [name, salary]);
    //實(shí)例屬性:提成
    this.percentage = percentage;
}

//將父類的一個(gè)實(shí)例設(shè)置為子類的原型,實(shí)現(xiàn)方法繼承
Manager.prototype = new Employee();
//修改子類原型的constructor指向,避免子類實(shí)例的constructor屬性指向父類的構(gòu)造函數(shù)
Manager.prototype.constructor = Manager;
//給子類添加新的實(shí)例方法
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

從結(jié)果上來(lái)說(shuō),這種繼承實(shí)現(xiàn)方式?jīng)]有問(wèn)題,Manager的實(shí)例同時(shí)繼承到了Employee類的實(shí)例屬性和實(shí)例方法,并且通過(guò)instanceOf運(yùn)算的結(jié)果也都正確。但是從代碼組織和實(shí)現(xiàn)細(xì)節(jié)層面,這種方法還有以下幾個(gè)問(wèn)題:

1)代碼組織不夠優(yōu)雅,繼承實(shí)現(xiàn)的關(guān)鍵部分的邏輯是通用的,都是如下結(jié)構(gòu):

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

這段代碼缺乏封裝。另外在添加子類的實(shí)例方法時(shí),不能通過(guò)SubClass.prototype = { method1: function() {} }這種方式去設(shè)置,否則就把子類的原型整個(gè)又修改了,繼承就無(wú)法實(shí)現(xiàn)了,這樣每次都得按SubClass.prototype.method1 = function() {} 的結(jié)構(gòu)去寫,代碼看起來(lái)很不連續(xù)。

解決方式:利用模塊化的方式,將通用的邏輯封裝起來(lái),對(duì)外提供簡(jiǎn)單的接口,只要按照約定的接口調(diào)用,就能夠簡(jiǎn)化類的構(gòu)建與類的繼承。具體實(shí)現(xiàn)請(qǐng)看后面的內(nèi)容介紹,暫時(shí)只能提供理論的說(shuō)明。

2)在給子類的原型設(shè)置成父類的實(shí)例時(shí),調(diào)用的是new SuperClass(),這是對(duì)父類構(gòu)造函數(shù)的無(wú)參調(diào)用,那么就要求父類必須有無(wú)參的構(gòu)造函數(shù)??墒窃趈avascript中,函數(shù)無(wú)法重載,所以父類不可能提供多個(gè)構(gòu)造函數(shù),在實(shí)際業(yè)務(wù)中,大部分場(chǎng)景下父類構(gòu)造函數(shù)又不可能沒(méi)有參數(shù),為了在唯一的一個(gè)構(gòu)造函數(shù)中模擬函數(shù)重載,只能借助判斷arguments.length來(lái)處理。問(wèn)題就是,有時(shí)候很難保證每次寫父類構(gòu)造函數(shù)的時(shí)候都會(huì)添加arguments.length的判斷邏輯。這樣的話,這個(gè)處理方式就是有風(fēng)險(xiǎn)的。要是能把構(gòu)造函數(shù)里的邏輯抽離出來(lái),讓類的構(gòu)造函數(shù)全部是無(wú)參函數(shù)的話,這個(gè)問(wèn)題就很好解決了。

解決方式:把父類跟子類的構(gòu)造函數(shù)全部無(wú)參化,并且在構(gòu)造函數(shù)內(nèi)不寫任何邏輯,把構(gòu)造函數(shù)的邏輯都遷移到init這個(gè)實(shí)例方法,比如前面給出的Employee和Manager的例子就能改造成下面這個(gè)樣子:

//無(wú)參無(wú)邏輯的父類構(gòu)造函數(shù)
function Employee() {}

Employee.prototype = {
    constructor: Employee,
    //把構(gòu)造邏輯搬到init方法中來(lái)
    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() + ".";
        }
};

//無(wú)參無(wú)邏輯的子類構(gòu)造函數(shù)
function Manager() {}

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

用init方法來(lái)完成構(gòu)造功能,就可以保證在設(shè)置子類原型時(shí)(Manager.prototype = new Employee()),父類的實(shí)例化操作一定不會(huì)出錯(cuò),唯一不好的是在調(diào)用類的構(gòu)造函數(shù)來(lái)初始化實(shí)例的時(shí)候,必須在調(diào)用構(gòu)造函數(shù)后手動(dòng)調(diào)用init方法來(lái)完成實(shí)際的構(gòu)造邏輯:

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

要是能把這個(gè)init的邏輯放在構(gòu)造函數(shù)內(nèi)部就好了,可是這樣的話就會(huì)違背前面說(shuō)的構(gòu)造函數(shù)無(wú)參無(wú)邏輯的原則。換一種方式來(lái)考慮,這個(gè)原則的目的是為了保證在實(shí)例化父類作為子類原型的時(shí)候,調(diào)用父類的構(gòu)造函數(shù)不會(huì)出錯(cuò),那么就可以稍微打破一下這個(gè)原則,在類的構(gòu)造函數(shù)里添加少量的并且一定不會(huì)有問(wèn)題的邏輯來(lái)解決:

//添加一個(gè)全局標(biāo)識(shí)initializing,表示是否正在進(jìn)行子類的構(gòu)建和類的繼承
var initializing = false;
//可自動(dòng)調(diào)用init方法的父類構(gòu)造函數(shù)
function Employee() {
    if (!initializing) {
        this.init.apply(this, arguments);
    }
}

Employee.prototype = {
    constructor: Employee,
    //把構(gòu)造邏輯搬到init方法中來(lái)
    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() + ".";
        }
};

//可自動(dòng)調(diào)用init方法的子類構(gòu)造函數(shù)
function Manager() {
    if (!initializing) {
        this.init.apply(this, arguments);
    }
}

//表示開(kāi)始子類的構(gòu)建和類的繼承
initializing = true;
//此時(shí)調(diào)用new Emplyee(),并不會(huì)調(diào)用Employee.prototype.init方法
Manager.prototype = new Employee();
Manager.prototype.constructor = Manager;
//表示結(jié)束子類的構(gòu)建和類的繼承,之后調(diào)用new Employee或new Manager都會(huì)自動(dòng)調(diào)用init實(shí)例方法
initializing = false;

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

調(diào)用結(jié)果仍然和前面的例子一樣。但是這個(gè)實(shí)現(xiàn)還有一個(gè)小問(wèn)題,它引入了一個(gè)全局變量initializing,要是能把引入這個(gè)全局變量就好了,這個(gè)其實(shí)很好解決,只要我們把關(guān)于類的構(gòu)建跟繼承,封裝成一個(gè)模塊,然后把這個(gè)變量放在模塊的內(nèi)部,就沒(méi)有問(wèn)題了。

3)在構(gòu)造子類的時(shí)候,是把子類的原型設(shè)置成了父類的一個(gè)實(shí)例,這個(gè)是不符合語(yǔ)義的,繼承應(yīng)該發(fā)生在類與類之間,而不是類與實(shí)例之間。之所以要用父類的一個(gè)實(shí)例來(lái)作為子類的原型:

SubClass.prototype = new SuperClass();

完全是因?yàn)楦割惖倪@個(gè)實(shí)例,指向父類的原型,而子類的實(shí)例又會(huì)指向子類的原型,所以最終子類的實(shí)例就能通過(guò)原型鏈訪問(wèn)到父類原型上的方法。這個(gè)做法雖然能實(shí)現(xiàn)實(shí)例方法的繼承,但是它不符合語(yǔ)義,而且它還有一個(gè)很大的問(wèn)題就是會(huì)增加原型鏈的長(zhǎng)度,導(dǎo)致子類在調(diào)用父類方法時(shí),必須通過(guò)原型鏈的查找到父類的方法才行。要是繼承層次較深,會(huì)對(duì)js的執(zhí)行性能有些影響。

解決方式:在解決這個(gè)問(wèn)題之前,先想想繼承能幫我們解決什么問(wèn)題:從父類復(fù)用已有的實(shí)例屬性和實(shí)例方法。在javascript面向?qū)ο缶幊讨校恢庇幸粋€(gè)原則就是,實(shí)例屬性都寫在構(gòu)造函數(shù)或者實(shí)例方法里面,實(shí)例方法寫在原型上面,也就是說(shuō)類的原型,按照這個(gè)原則來(lái)說(shuō),就是用來(lái)寫實(shí)例方法的,而且是只用來(lái)寫實(shí)例方法,那么我們完全可以在構(gòu)建子類時(shí),通過(guò)復(fù)制的方式將父類原型的所有方法全部添加到子類的原型上,不一定要把父類的一個(gè)實(shí)例設(shè)置成子類的原型,這樣就能將原型鏈的長(zhǎng)度大大地縮短,借助一個(gè)簡(jiǎn)短的copy函數(shù),我們就能輕松對(duì)前面的代碼進(jìn)行改造:

//用來(lái)復(fù)制父類原型,由于父類原型上約定只寫實(shí)例方法,所以復(fù)制的時(shí)候不必?fù)?dān)心引用的問(wèn)題
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);
}
//將父類的原型方法復(fù)制到子類的原型上
Manager.prototype = copy(Employee.prototype);
//子類還是需要修改constructor指向,因?yàn)閺母割愒蛷?fù)制出來(lái)的對(duì)象的constructor還是指向父類的構(gòu)造函數(shù)
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

這么做了以后,當(dāng)調(diào)用m.toString的時(shí)候其實(shí)調(diào)用的是Manager類自身原型上的方法,而不是Employee類的實(shí)例方法,縮短了在原型鏈上查找方法的距離。這個(gè)做法在性能上有很大的優(yōu)點(diǎn),但不好的是通過(guò)原型鏈維持的繼承關(guān)系其實(shí)已經(jīng)斷了,子類的原型和子類的實(shí)例都無(wú)法再通過(guò)js原生的屬性訪問(wèn)到父類的原型,所以這個(gè)調(diào)用console.log(m instanceof Employee)輸出的是false。不過(guò)跟性能比起來(lái),這個(gè)都可以不算問(wèn)題:一是instanceOf的運(yùn)算,幾乎在javascript的開(kāi)發(fā)里面用不到,至少我是沒(méi)碰到過(guò);二是通過(guò)復(fù)制方式完全能夠把父類的實(shí)例方法繼承下來(lái),這就已經(jīng)達(dá)到了繼承的最大目的。

這個(gè)方法還有一個(gè)額外的好處是,解決了第2個(gè)問(wèn)題最后提到的引入initializing全局變量的問(wèn)題,如果是復(fù)制的話,就不需要在構(gòu)建繼承關(guān)系時(shí),去調(diào)用父類的構(gòu)造函數(shù),那么也就沒(méi)有必要在構(gòu)造函數(shù)內(nèi)先判斷initializing才能去調(diào)用init方法,上面的代碼中就已經(jīng)去掉了initializing這個(gè)變量的處理。

4)在子類的構(gòu)造函數(shù)和實(shí)例方法內(nèi)如果想要調(diào)用父類的構(gòu)造函數(shù)或者方法,顯得比較繁瑣:

function SuperClass() {}

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

function SubClass() {
    //調(diào)用父類構(gòu)造函數(shù)
    SuperClass.apply(this);
}

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

每次都得靠apply借用方法來(lái)處理。要是能改成如下的調(diào)用就好用多了:

function SubClass() {
//調(diào)用父類構(gòu)造函數(shù)
        this.base();
}

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

解決方式:如果要在每個(gè)實(shí)例方法里,都能通過(guò)this.base()調(diào)用父類原型上相應(yīng)的方法,那么this.base就一定不是一個(gè)固定的方法,需要在每個(gè)實(shí)例方法執(zhí)行期間動(dòng)態(tài)地將this.base指定為父類原型的同名方法,能夠做到這個(gè)實(shí)現(xiàn)的方式,就只有通過(guò)方法代理了,前面的Employee和Manager的例子可以改造如下:

//用來(lái)復(fù)制父類原型,由于父類原型上約定只寫實(shí)例方法,所以復(fù)制的時(shí)候不必?fù)?dān)心引用的問(wèn)題
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() {
    //必須在每個(gè)實(shí)例中添加baseProto屬性,以便實(shí)例內(nèi)部可以通過(guò)這個(gè)屬性訪問(wèn)到父類的原型
    //因?yàn)閏opy函數(shù)導(dǎo)致原型鏈斷裂,無(wú)法通過(guò)原型鏈訪問(wèn)到父類的原型
    this.baseProto = Employee.prototype;
    this.init.apply(this, arguments);
}

Manager.prototype = copy(Employee.prototype);
//子類還是需要修改constructor指向,因?yàn)閺母割愒蛷?fù)制出來(lái)的對(duì)象的constructor還是指向父類的構(gòu)造函數(shù)
Manager.prototype.constructor = Manager;

Manager.prototype.init = (function (name, func) {
    return function () {
        //記錄實(shí)例原有的this.base的值
        var old = this.base;
        //將實(shí)例的this.base指向父類的原型的同名方法
        this.base = this.baseProto[name];
        //調(diào)用子類自身定義的init方法,也就是func參數(shù)傳遞進(jìn)來(lái)的函數(shù)
        var ret = func.apply(this, arguments);
        //還原實(shí)例原有的this.base的值
        this.base = old;
        return ret;
    }
})("init", function (name, salary, percentage) {
    //通過(guò)this.base調(diào)用父類的init方法
    //這個(gè)函數(shù)真實(shí)的調(diào)用位置是var ret = func.apply(this, arguments);
    //當(dāng)調(diào)用Manager實(shí)例的init方法時(shí),其實(shí)不是調(diào)用的這個(gè)函數(shù)
    //而是調(diào)用上面那個(gè)匿名函數(shù)里面return的匿名函數(shù)
    //在return的匿名函數(shù)里,先把this.base指向?yàn)榱烁割愒偷耐瘮?shù),然后在調(diào)用func
    //func內(nèi)部再通過(guò)調(diào)用this.base時(shí),就能調(diào)用父類的原型方法。
    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

通過(guò)代理的方式,就解決了在在實(shí)例方法內(nèi)部通過(guò)this.base調(diào)用父類原型同名方法的問(wèn)題。可是在實(shí)際情況中,每個(gè)實(shí)例方法都有可能需要調(diào)用父類的實(shí)例,那么每個(gè)實(shí)例方法都要添加同樣的代碼,顯然這會(huì)增加很多麻煩,好在這部分的邏輯也是同樣的,我們可以把它抽象一下,最后都放到模塊化的內(nèi)部去,這樣就能簡(jiǎn)化代理的工作。

5)未考慮靜態(tài)屬性和靜態(tài)方法。盡管靜態(tài)成員是不需要繼承的,但在有些場(chǎng)景下,我們還是需要靜態(tài)成員,所以得考慮靜態(tài)成員應(yīng)該添加在哪里。

解決方式:由于js原生并不支持靜態(tài)成員,所以只能借助一些公共的位置來(lái)處理。最佳的位置是添加到構(gòu)造函數(shù)上:

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);
}

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

Employee.prototype = {
    constructor: Employee,
    init: function (name, salary) {
            this.name = name;
            this.salary = salary;
            //調(diào)用靜態(tài)方法
            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

最后的兩行輸出了正確的實(shí)例id,而這個(gè)id是通過(guò)Employee類的靜態(tài)方法生成的。在java的面向?qū)ο缶幊讨?,子類跟父類都可以定義靜態(tài)成員,在調(diào)用的時(shí)候還存在覆蓋的問(wèn)題,在js里面,因?yàn)槭苷Z(yǔ)言的限制,自定義的靜態(tài)成員不可能實(shí)現(xiàn)全面的面向?qū)ο蠊δ埽拖裆厦孢@種,能夠給類提供一些公共的屬性和公共方法,就已經(jīng)足夠了。

2. 期望的調(diào)用方式

從第1部分的分析可以看出,在js里面,類的構(gòu)建與繼承,有很多通用的邏輯,完全可以把這些邏輯封裝成一個(gè)多帶帶的模塊,形成一個(gè)通用的類庫(kù),以便在工作中有需要的時(shí)候,都可以直接拿來(lái)使用。這個(gè)類庫(kù)要求能完成我們需要的功能(類的構(gòu)建與繼承和靜態(tài)成員的添加),同時(shí)在使用時(shí)要足夠簡(jiǎn)潔方便。在利用bootstrap的modal組件自定義alert,confirm和modal對(duì)話框這篇文章里,我曾說(shuō)過(guò)一些從組件期望的調(diào)用方式,去反推組件實(shí)現(xiàn)的一些觀點(diǎn),當(dāng)你明確你需要什么東西時(shí),你才知道這個(gè)東西你該怎么去創(chuàng)造。本文要編寫的這個(gè)繼承組件也會(huì)采取這個(gè)方法來(lái)實(shí)現(xiàn),我先用前面Employee和Manager的例子來(lái)模擬調(diào)用這個(gè)繼承庫(kù)的場(chǎng)景,通過(guò)預(yù)設(shè)的一些組件名稱或者接口名稱以及調(diào)用方式,來(lái)嘗試走通真實(shí)使用這個(gè)繼承庫(kù)的流程,有了這個(gè)東西,下一步我只需要根據(jù)這個(gè)要求去實(shí)現(xiàn)即可,模擬如下:

//通過(guò)調(diào)用Class函數(shù)構(gòu)造一個(gè)類
var Employee = Class({
    //通過(guò)instanceMembers指定這個(gè)類的實(shí)例成員
    instanceMembers: {
        init: function (name, salary) {
                this.name = name;
                this.salary = salary;
                //調(diào)用靜態(tài)方法
                this.id = Employee.getId();
            },
            getName: function () {
                return this.name;
            },
            getSalary: function () {
                return this.salary;
            },
            toString: function () {
                return this.name + ""s salary is " + this.getSalary() + ".";
            }
    },
    //通過(guò)staticMembers指定這個(gè)類的靜態(tài)成員
    //靜態(tài)方法內(nèi)部可通過(guò)this訪問(wèn)其它靜態(tài)成員
    //在外部可通過(guò)Employee.getId這種方式訪問(wèn)到靜態(tài)成員
    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;
            }
    },
    //通過(guò)extend指定要繼承的類
    extend: Employee
});

從模擬的結(jié)果來(lái)看,我想要的繼承庫(kù)對(duì)外提供的名稱只有Class, instanceMembers, staticMembers和extend而已,調(diào)用方式也很簡(jiǎn)單,只要傳遞參數(shù)給Class函數(shù)即可。接下來(lái)就按照這個(gè)目標(biāo),看看如何一步步根據(jù)第一部分羅列的那些問(wèn)題和解決方式,把這個(gè)庫(kù)給寫出來(lái)。

3. 繼承庫(kù)的詳細(xì)實(shí)現(xiàn)

根據(jù)API名稱和接口以及前面第1部分提出的問(wèn)題,這個(gè)繼承庫(kù)要完成的功能有:

1)類的構(gòu)建(關(guān)鍵:init方法)和靜態(tài)成員處理;

2)繼承關(guān)系的構(gòu)建(關(guān)鍵:父類原型的復(fù)制);

3)父類方法的簡(jiǎn)化調(diào)用(關(guān)鍵:父類原型上同名方法的代理)。

所以這個(gè)庫(kù)的實(shí)現(xiàn),可以按照這三點(diǎn)分成三版來(lái)開(kāi)發(fā)。

1)第一版

在第一版里面,僅需要實(shí)現(xiàn)類的構(gòu)架和靜態(tài)成員添加的功能即可,細(xì)節(jié)如下:

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

    //用來(lái)判斷是否為Object的實(shí)例
    function isObject(o) {
        return typeof (o) === "object";
    }

    //用來(lái)判斷是否為Function的實(shí)例
    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;

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

        //添加靜態(tài)成員,這段代碼需在原型設(shè)置的前面執(zhí)行,避免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
})();

這一版核心代碼在于類的構(gòu)建和靜態(tài)成員添加的部分,其它代碼僅僅提供一些提前可以想到的賦值函數(shù)和變量(isObject, isFunction),并做一些參數(shù)合法性校驗(yàn)的處理。添加靜態(tài)成員的代碼一定要在設(shè)置原型的代碼之前,否則就有原型被覆蓋的風(fēng)險(xiǎn)。有了這個(gè)版本,就可以直接構(gòu)建帶靜態(tài)成員的Employee類了:

var Employee = Class({
    instanceMembers: {
        init: function (name, salary) {
                this.name = name;
                this.salary = salary;
                //調(diào)用靜態(tài)方法
                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就能訪問(wèn)到構(gòu)造函數(shù)Employee,是因?yàn)間etId這個(gè)方法是添加到構(gòu)造函數(shù)上的,所以當(dāng)調(diào)用Employee.getId()時(shí),getId方法里面的this指向的就是Employee這個(gè)函數(shù)對(duì)象。

第二版在第一版的基礎(chǔ)上,實(shí)現(xiàn)繼承關(guān)系的構(gòu)建部分:

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

    //用來(lái)判斷是否為Object的實(shí)例
    function isObject(o) {
        return typeof (o) === "object";
    }

    //用來(lái)判斷是否為Function的實(shí)例
    function isFunction(f) {
        return typeof (f) === "function";
    }

    //簡(jiǎn)單復(fù)制
    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;

        //表示要構(gòu)建的類的構(gòu)造函數(shù)
        function TargetClass() {
            if (extend) {
                //如果有要繼承的父類
                //就在每個(gè)實(shí)例中添加baseProto屬性,以便實(shí)例內(nèi)部可以通過(guò)這個(gè)屬性訪問(wèn)到父類的原型
                //因?yàn)閏opy函數(shù)導(dǎo)致原型鏈斷裂,無(wú)法通過(guò)原型鏈訪問(wèn)到父類的原型
                this.baseProto = extend.prototype;
            }
            if (isFunction(this.init)) {
                this.init.apply(this, arguments);
            }
        }

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

        //如果有要繼承的父類,先把父類的實(shí)例方法都復(fù)制過(guò)來(lái)
        extend & (TargetClass.prototype = copy(extend.prototype));

        //添加實(shí)例方法
        for (prop in instanceMembers) {
            if (hasOwn.call(instanceMembers, prop)) {
                TargetClass.prototype[prop] = instanceMembers[prop];
            }
        }

        TargetClass.prototype.constructor = TargetClass;

        return TargetClass;
    }

    return ClassBuilder
})();

這一版關(guān)鍵的部分在于:

if(extend){
    //如果有要繼承的父類
    //就在每個(gè)實(shí)例中添加baseProto屬性,以便實(shí)例內(nèi)部可以通過(guò)這個(gè)屬性訪問(wèn)到父類的原型
    //因?yàn)閏opy函數(shù)導(dǎo)致原型鏈斷裂,無(wú)法通過(guò)原型鏈訪問(wèn)到父類的原型
    this.baseProto = extend.prototype;
}
//如果有要繼承的父類,先把父類的實(shí)例方法都復(fù)制過(guò)來(lái)
extend && (TargetClass.prototype = copy(extend.prototype));

this.baseProto主要目的就是為了讓子類的實(shí)例能夠有一個(gè)屬性可以訪問(wèn)到父類的原型,因?yàn)楹竺娴睦^承方式是復(fù)制方式,會(huì)導(dǎo)致原型鏈斷裂。有了這一版之后,就可以加入Manager類來(lái)演示效果了:

var Employee = Class({
    instanceMembers: {
        init: function (name, salary) {
                this.name = name;
                this.salary = salary;
                //調(diào)用靜態(tài)方法
                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 Manager = Class({
    instanceMembers: {
        init: function (name, salary, percentage) {
                //借用父類的init方法,實(shí)現(xiàn)屬性繼承(name, salary)
                Employee.prototype.init.apply(this, [name, salary]);
                this.percentage = percentage;
            },
            getSalary: function () {
                return this.salary + this.salary * this.percentage;
            }
    },
    extend: Employee
});

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(e.constructor === Employee); //true
console.log(m.constructor === Manager); //true
console.log(e.id); //1
console.log(m.id); //2

不過(guò)在Manager內(nèi)部,調(diào)用父類的方法時(shí)還是apply借用的方式,所以在最后一版里面,需要把它變成我們期望的this.base的方式,反正原理前面也已經(jīng)了解了,無(wú)非是在方法同名的時(shí)候,對(duì)實(shí)例方法加一個(gè)代理而已,實(shí)現(xiàn)如下:

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

    //用來(lái)判斷是否為Object的實(shí)例
    function isObject(o) {
        return typeof (o) === "object";
    }

    //用來(lái)判斷是否為Function的實(shí)例
    function isFunction(f) {
        return typeof (f) === "function";
    }

    //簡(jiǎn)單復(fù)制
    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;

        //表示要構(gòu)建的類的構(gòu)造函數(shù)
        function TargetClass() {
            if (extend) {
                //如果有要繼承的父類
                //就在每個(gè)實(shí)例中添加baseProto屬性,以便實(shí)例內(nèi)部可以通過(guò)這個(gè)屬性訪問(wèn)到父類的原型
                //因?yàn)閏opy函數(shù)導(dǎo)致原型鏈斷裂,無(wú)法通過(guò)原型鏈訪問(wèn)到父類的原型
                this.baseProto = extend.prototype;
            }
            if (isFunction(this.init)) {
                this.init.apply(this, arguments);
            }
        }

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

        //如果有要繼承的父類,先把父類的實(shí)例方法都復(fù)制過(guò)來(lái)
        extend & (TargetClass.prototype = copy(extend.prototype));

        //添加實(shí)例方法
        for (prop in instanceMembers) {

            if (hasOwn.call(instanceMembers, prop)) {

                //如果有要繼承的父類,且在父類的原型上存在當(dāng)前實(shí)例方法同名的方法
                if (extend & isFunction(instanceMembers[prop]) && isFunction(extend.prototype[prop])) {
                    TargetClass.prototype[prop] = (function (name, func) {
                        return function () {
                            //記錄實(shí)例原有的this.base的值
                            var old = this.base;
                            //將實(shí)例的this.base指向父類的原型的同名方法
                            this.base = extend.prototype[name];
                            //調(diào)用子類自身定義的實(shí)例方法,也就是func參數(shù)傳遞進(jìn)來(lái)的函數(shù)
                            var ret = func.apply(this, arguments);
                            //還原實(shí)例原有的this.base的值
                            this.base = old;
                            return ret;
                        }
                    })(prop, instanceMembers[prop]);
                } else {
                    TargetClass.prototype[prop] = instanceMembers[prop];
                }
            }
        }

        TargetClass.prototype.constructor = TargetClass;

        return TargetClass;
    }

    return ClassBuilder
})();

核心部分是:

if (hasOwn.call(instanceMembers, prop)) {

    //如果有要繼承的父類,且在父類的原型上存在當(dāng)前實(shí)例方法同名的方法
    if (extend & isFunction(instanceMembers[prop]) && isFunction(extend.prototype[prop])) {
        TargetClass.prototype[prop] = (function (name, func) {
            return function () {
                //記錄實(shí)例原有的this.base的值
                var old = this.base;
                //將實(shí)例的this.base指向父類的原型的同名方法
                this.base = extend.prototype[name];
                //調(diào)用子類自身定義的實(shí)例方法,也就是func參數(shù)傳遞進(jìn)來(lái)的函數(shù)
                var ret = func.apply(this, arguments);
                //還原實(shí)例原有的this.base的值
                this.base = old;
                return ret;
            }
        })(prop, instanceMembers[prop]);
    } else {
        TargetClass.prototype[prop] = instanceMembers[prop];
    }
}

只有當(dāng)需要繼承父類,且父類原型中有方法與當(dāng)前的實(shí)例方法同名時(shí),才會(huì)去對(duì)當(dāng)前的實(shí)例方法添加代理。更詳細(xì)的原理可以回到文章第1部分回顧相關(guān)內(nèi)容。至此,我們?cè)贛anager類內(nèi)部調(diào)用父類的方法時(shí),就很簡(jiǎn)單了,只要通過(guò)this.base即可:

var Employee = Class({
    instanceMembers: {
        init: function (name, salary) {
                this.name = name;
                this.salary = salary;
                //調(diào)用靜態(tài)方法
                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 Manager = Class({
    instanceMembers: {
        init: function (name, salary, percentage) {
                //通過(guò)this.base調(diào)用父類的構(gòu)造方法
                this.base(name, salary);
                this.percentage = percentage;
            },
            getSalary: function () {
                return this.base() + this.salary * this.percentage;
            }
    },
    extend: Employee
});

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(e.constructor === Employee); //true
console.log(m.constructor === Manager); //true
console.log(e.id); //1
console.log(m.id); //2

注意這兩處調(diào)用:

var Manager = Class({
    instanceMembers: {
        init: function (name, salary, percentage) {
                //通過(guò)this.base調(diào)用父類的構(gòu)造方法
                this.base(name, salary);//要注意的第一處
                this.percentage = percentage;
            },
            getSalary: function () {
                return this.base() + this.salary * this.percentage;//要注意的第二處this.base()
            }
    },
    extend: Employee
});

以上就是本文要實(shí)現(xiàn)的繼承庫(kù)的全部細(xì)節(jié),其實(shí)它所做的事就是把本文第1部分提到的那些問(wèn)題的解決方式和第二部分模擬的調(diào)用場(chǎng)景結(jié)合起來(lái),封裝到一個(gè)模塊內(nèi)部而已,各個(gè)細(xì)節(jié)的原理只要理解了第1部分總結(jié)的那些解決方式就很掌握了。在最后一版的演示中,也能看到,本文實(shí)現(xiàn)的這個(gè)繼承庫(kù),已經(jīng)完全滿足了模擬場(chǎng)景中的需求,今后有任何需要用到繼承的場(chǎng)景,完全可以拿最后一版的實(shí)現(xiàn)去開(kāi)發(fā)。

4. 總結(jié)

本文在三生石上關(guān)于javascript繼承系列博客的思路指引下,實(shí)現(xiàn)了一個(gè)易用的繼承庫(kù),使用它可以更像java語(yǔ)言構(gòu)建面向?qū)ο蟮念惡皖愔g的繼承關(guān)系,我可以預(yù)見(jiàn)在將來(lái)的工作,這個(gè)庫(kù)對(duì)我的代碼質(zhì)量和功能實(shí)現(xiàn)會(huì)起到很重要的作用,因?yàn)樵陂_(kāi)發(fā)中,繼承的編碼思想還是應(yīng)用的非常多,尤其是當(dāng)我們做項(xiàng)目做得多的時(shí)候,一方面肯定想把一些公共的東西寫成可重用的組件,另一方面又必須得滿足各個(gè)項(xiàng)目的個(gè)性要求,所以在寫組件的時(shí)候不能寫的太死,多寫接口,等到具體項(xiàng)目的時(shí)候再通過(guò)繼承等方式來(lái)擴(kuò)展該項(xiàng)目獨(dú)有的功能,這樣寫出的組件才會(huì)更靈活穩(wěn)定??傊辛诉@個(gè)繼承庫(kù),感覺(jué)以后寫的代碼都會(huì)開(kāi)心好多~所以希望本文的內(nèi)容也能對(duì)你有同樣的一些幫助。如果確實(shí)有幫助,求點(diǎn)推薦:)

謝謝閱讀!

文章轉(zhuǎn)載:http://www.cnblogs.com

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/79064.html

相關(guān)文章

  • javaScript原型及原型鏈詳解(二)

    摘要:當(dāng)然這還沒(méi)完,因?yàn)槲覀冞€有重要的一步?jīng)]完成,沒(méi)錯(cuò)就是上面的第行代碼,如果沒(méi)有這行代碼實(shí)例中的指針是指向構(gòu)造函數(shù)的,這樣顯然是不對(duì)的,因?yàn)檎G闆r下應(yīng)該指向它的構(gòu)造函數(shù),因此我們需要手動(dòng)更改使重新指向?qū)ο蟆? 第一節(jié)內(nèi)容:javaScript原型及原型鏈詳解(二) 第一節(jié)中我們介紹了javascript中的原型和原型鏈,這一節(jié)我們來(lái)講利用原型和原型鏈我們可以做些什么。 普通對(duì)象的繼承 ...

    widuu 評(píng)論0 收藏0
  • JavaScript繼承方式詳解

    摘要:可以通過(guò)構(gòu)造函數(shù)和原型的方式模擬實(shí)現(xiàn)類的功能。原型式繼承與類式繼承類式繼承是在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型的構(gòu)造函數(shù)。寄生式繼承這種繼承方式是把原型式工廠模式結(jié)合起來(lái),目的是為了封裝創(chuàng)建的過(guò)程。 js繼承的概念 js里常用的如下兩種繼承方式: 原型鏈繼承(對(duì)象間的繼承) 類式繼承(構(gòu)造函數(shù)間的繼承) 由于js不像java那樣是真正面向?qū)ο蟮恼Z(yǔ)言,js是基于對(duì)象的,它沒(méi)有類的概念。...

    Yangyang 評(píng)論0 收藏0
  • 詳解javascript

    摘要:原文地址詳解的類博主博客地址的個(gè)人博客從當(dāng)初的一個(gè)彈窗語(yǔ)言,一步步發(fā)展成為現(xiàn)在前后端通吃的龐然大物。那么,的類又該怎么定義呢在面向?qū)ο缶幊讨校愂菍?duì)象的模板,定義了同一組對(duì)象又稱實(shí)例共有的屬性和方法。這個(gè)等同于的屬性現(xiàn)已棄用。。 前言 生活有度,人生添壽。 原文地址:詳解javascript的類 博主博客地址:Damonare的個(gè)人博客 ??Javascript從當(dāng)初的一個(gè)彈窗語(yǔ)言,一...

    hufeng 評(píng)論0 收藏0
  • 詳解javascript

    摘要:原文地址詳解的類博主博客地址的個(gè)人博客從當(dāng)初的一個(gè)彈窗語(yǔ)言,一步步發(fā)展成為現(xiàn)在前后端通吃的龐然大物。那么,的類又該怎么定義呢在面向?qū)ο缶幊讨校愂菍?duì)象的模板,定義了同一組對(duì)象又稱實(shí)例共有的屬性和方法。這個(gè)等同于的屬性現(xiàn)已棄用。。 前言 生活有度,人生添壽。 原文地址:詳解javascript的類 博主博客地址:Damonare的個(gè)人博客 ??Javascript從當(dāng)初的一個(gè)彈窗語(yǔ)言,一...

    marek 評(píng)論0 收藏0
  • JavaScript 繼承方式詳解

    摘要:原型繼承與類繼承類繼承是在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用父類型的構(gòu)造函數(shù)原型式繼承是借助已有的對(duì)象創(chuàng)建新的對(duì)象,將子類的原型指向父類。 JavaScript 繼承方式的概念 js 中實(shí)現(xiàn)繼承有兩種常用方式: 原型鏈繼承(對(duì)象間的繼承) 類式繼承(構(gòu)造函數(shù)間的繼承) JavaScript不是真正的面向?qū)ο蟮恼Z(yǔ)言,想實(shí)現(xiàn)繼承可以用JS的原型prototype機(jī)制或者call和apply方法 在面...

    YuboonaZhang 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<