摘要:用對象冒充繼承構(gòu)造函數(shù)的屬性,用原型鏈繼承的方法。但是屬性暴露出了問題,每一個對象都有一個屬性指向它的構(gòu)造函數(shù),而實例的構(gòu)造函數(shù)卻指向了,這會導(dǎo)致繼承鏈的錯亂。
ECMAScript 實現(xiàn)繼承的方式不止一種。這是因為 JavaScript 中的繼承機(jī)制并不是明確規(guī)定的,而是通過模仿實現(xiàn)的。這意味著所有的繼承細(xì)節(jié)并非完全由解釋程序處理。可以根據(jù)需求決定適合的繼承方式。
原文鏈接
對象冒充構(gòu)造函數(shù)使用this關(guān)鍵字給所有屬性和方法賦值(即采用類聲明的構(gòu)造函數(shù)方式)。因為構(gòu)造函數(shù)只是一個函數(shù),所以可使ClassA構(gòu)造函數(shù)成為ClassB的方法,然后調(diào)用它。ClassB就會收到ClassA的構(gòu)造函數(shù)中定義的屬性和方法。
function ClassA(name) { this.name = name; this.sayName = function () { console.log(this.name); }; } function ClassB(name,age) { this.classA = ClassA; this.classA(name); delete this.classA; this.age = age; this.sayAge = function(){ console.log(this.age); } } var tom = new ClassA("Tom"); var jerry = new ClassB("Jerry",25); tom.sayName(); //"Tom" jerry.sayName(); //"Jerry" jerry.sayAge(); //25 console.log(tom instanceof ClassA); //true console.log(jerry instanceof ClassA); //false console.log(jerry instanceof ClassB); //true
所有新屬性和新方法都必須在刪除了新方法的代碼行后定義,因為可能會覆蓋超類的相關(guān)屬性和方法
對象冒充可以實現(xiàn)多重繼承
如果存在ClassA和ClassB,這時ClassC想繼承這兩個類,如下:
function ClassA(name){ this.name = name; this.sayName = function (){ console.log(this.name); } } function ClassB(age){ this.age = age; this.sayAge = function(){ console.log(this.age); } } function ClassC(name,age){ this.method = ClassA; this.method(name); this.method = ClassB; this.method(age); delete this.method; } var tom = new ClassC("Tom",25); tom.sayName(); //"Tom"; tom.sayAge(); //25 console.log(tom instanceof ClassA); //false console.log(tom instanceof ClassB); //false console.log(tom instanceof ClassC); //true
這種實現(xiàn)方式的缺陷是:如果兩個類ClassA和ClassB具有同名的屬性或方法,ClassB具有高優(yōu)先級,因為它從后面的類繼承。
由于這種繼承方法的流行,ECMAScript的第三版為Function對象加入了兩個方法,即call()和apply()。
call方法是與經(jīng)典的對象冒充方法最相似的方法。它的第一個參數(shù)用作this的對象,其他參數(shù)都直接傳遞給函數(shù)自身
function sayName(prefix) { console.log(prefix + this.name); }; var tom = {}; tom.name = "Tom"; sayName.call(tom, "This is "); //"This is Tom"
函數(shù)sayName在對象外定義,但也可以引用this。
call方法改寫對象冒充
function ClassA(name){ this.name = name; this.sayName = function(){ console.log(this.name); } } function ClassB(name,age){ //this.method = ClassA; //this.method(name); //delete this.method; ClassA.call(this,name); this.age = age; this.sayAge = function (){ console.log(this.age); } } var tom = new ClassB("Tom",25); tom.sayName(); //"Tom" tom.sayAge(); //25 console.log(tom instanceof ClassA); //false console.log(tom instanceof ClassB); //true
call方法替代了使用屬性引用ClassA的方式。
applyapply方法有兩個參數(shù),用作this的對象和要傳遞給函數(shù)的參數(shù)數(shù)組
function sayName(prefex,mark) { console.log(prefex+ this.name+ mark); }; var tom = {}; tom.name = "Tom"; sayName.apply(tom, ["This is ","!"]); //"This is Tom!"
同樣可以使用apply改寫對象冒充
function ClassA(name){ this.name = name; this.sayName = function(){ console.log(this.name); } } function ClassB(name,age){ ClassA.apply(this,arguments); this.age = age; this.sayAge = function (){ console.log(this.age); } } var tom = new ClassB("Tom",25); tom.sayName(); //"Tom" tom.sayAge(); //25 console.log(tom instanceof ClassA); //false console.log(tom instanceof ClassB); //true
只有超類中參數(shù)順序和子類中的參數(shù)完全一致時才可以傳遞參數(shù)數(shù)組
原型鏈prototype對象是個模板,要實例化的對象都以這個模板為基礎(chǔ),prototype對象的任何屬性和方法都被傳遞給這個類的所有實例,原型鏈就是利用這種功能來實現(xiàn)繼承機(jī)制。
function ClassA() {} ClassA.prototype.name = "Tom"; ClassA.prototype.sayName = function () { console.log(this.name); }; function ClassB() {} ClassB.prototype = new ClassA(); var tom = new ClassB(); tom.sayName(); //"Tom" console.log(tom instanceof ClassA); //true console.log(tom instanceof ClassB); //true
這里把ClassB的prototype屬性設(shè)置稱ClassA的實例,避免逐個賦值prototpye屬性。
在調(diào)用ClassA時沒有設(shè)置參數(shù),因為在原型鏈中要確保構(gòu)造函數(shù)是無參的。
在原型鏈中,instanceof的結(jié)果也有了變化,對于ClassA和ClassB都返回了true。
因為prototype屬性的重指定,子類中的新屬性都必須出現(xiàn)在prototype被賦值后。
function ClassA() {} ClassA.prototype.name = "Tom"; ClassA.prototype.sayName = function () { console.log(this.name); }; function ClassB() {} ClassB.prototype = new ClassA(); ClassB.prototype.age = 25; ClassB.prototype.sayAge = function () { console.log(this.age); }; var tom = new ClassA(); var jerry = new ClassB(); tom.sayName(); //"Tom" jerry.sayName(); //"Tom" jerry.name = "Jerry"; tom.sayName(); //"Tom" jerry.sayName(); //"Jerry" jerry.sayAge(); //25 console.log(tom instanceof ClassA); //true console.log(jerry instanceof ClassA); //true console.log(jerry instanceof ClassB); //true
原型鏈的缺陷是不能實現(xiàn)多重繼承,因為類的prototype會被重寫。
混合方式對象冒充的問題是必須使用構(gòu)造函數(shù)方式,而使用原型鏈就無法使用帶參數(shù)的構(gòu)造函數(shù),不過,可以試試兩者結(jié)合。
用對象冒充繼承構(gòu)造函數(shù)的屬性,用原型鏈繼承prototype的方法。
function ClassA(name) { this.name = name; } ClassA.prototype.sayName = function () { console.log(this.name); }; function ClassB(name, age) { ClassA.call(this, name); this.age = age; } ClassB.prototype = new ClassA(); ClassB.prototype.sayAge = function () { console.log(this.age); }; var tom = new ClassA("Tom"); var jerry = new ClassB("Jerry",25); console.log(tom instanceof ClassA); //true console.log(jerry instanceof ClassA); //true console.log(jerry instanceof ClassB); //true console.log(jerry.constructor === ClassA); //true console.log(ClassB.prototype.constructor === ClassA); //true
在ClassB構(gòu)造函數(shù)中用對象冒充繼承了ClassA的name屬性,用原型鏈繼承了ClassA的sayName方法,由于使用了原型鏈繼承方式,instanceof運行方式正常。
但是constructor屬性暴露出了問題,每一個prototype對象都有一個constructor屬性指向它的構(gòu)造函數(shù),而ClassB實例的構(gòu)造函數(shù)卻指向了ClassA,這會導(dǎo)致繼承鏈的錯亂。可以手動修改constructor的指向。
function ClassA(name) { this.name = name; } ClassA.prototype.sayName = function () { console.log(this.name); }; function ClassB(name, age) { ClassA.call(this, name); this.age = age; } ClassB.prototype = new ClassA(); ClassB.prototype.constructor = ClassB; ClassB.prototype.sayAge = function () { console.log(this.age); }; var tom = new ClassA("Tom"); var jerry = new ClassB("Jerry",25); console.log(tom instanceof ClassA); //true console.log(jerry instanceof ClassA); //true console.log(jerry instanceof ClassB); //true console.log(ClassA.constructor === ClassB); //false console.log(jerry.constructor === ClassA); //false console.log(ClassB.prototype.constructor === ClassA); //false直接繼承原型鏈
為了節(jié)省內(nèi)存,可以不執(zhí)行創(chuàng)建ClassA實例,直接讓ClassB的原型指向ClassA的原型
function ClassA(name) { this.name = name; } ClassA.prototype.sayName = function () { console.log(this.name); }; function ClassB(name, age) { ClassA.call(this, name); this.age = age; } ClassB.prototype = ClassA.prototype; ClassB.prototype.constructor = ClassB; ClassB.prototype.sayAge = function () { console.log(this.age); }; var tom = new ClassA("Tom"); var jerry = new ClassB("Jerry",25); console.log(ClassA.prototype.hasOwnProperty("sayAge")); //true console.log(ClassA.prototype.constructor === ClassB); //true
這樣的缺陷是由于直接修改原型鏈指向,對于ClassB原型鏈中的屬性也會影響到ClassA上,于是就出現(xiàn)了ClassA具有sayAge方法、ClassA的構(gòu)造函數(shù)屬性為ClassB。
空對象作中介為解決直接繼承原型鏈的缺點,可以利用一個空對象作為中介。
function ClassA(name) { this.name = name; } ClassA.prototype.sayName = function () { console.log(this.name); }; function ClassB(name, age) { ClassA.call(this, name); this.age = age; } var fn = function(){}; fn.prototype = ClassA.prototype; ClassB.prototype = new fn(); ClassB.prototype.constructor = ClassB; ClassB.prototype.sayAge = function () { console.log(this.age); }; console.log(ClassA.prototype.hasOwnProperty("sayAge")); //false console.log(ClassA.prototype.constructor === ClassB); //false
雖然還是創(chuàng)建了對象實例,但由于空對象幾乎不占內(nèi)存,修改ClassB的原型也不會影響到ClassA。
封裝成extends方法
function extends(child,parent){ var fn = function (){}; fn.prototype = parent.prototype; child.prototype = new fn(); child.prototype.constructor = child; child.super = parent.prototype; }
JS的靈活性使我們可以通過多種方式實現(xiàn)繼承,了解其中的原理和實現(xiàn)可以幫助我們在不同的場景中選擇適合的方法。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/67752.html
摘要:我從今年的月份開始在知乎上連續(xù)回答前端開發(fā)相關(guān)的問題,至今已有將近三個月,回顧寫過的一百多條回答,不少是給迷茫的前端工作者的建議。今天我把我的思考提煉整理成文,希望能給予在迷茫中前行中的前端學(xué)習(xí)工作者一些有用的建議。 本文首發(fā)于知乎專欄——前端指南作者:Mark MFS老師轉(zhuǎn)載請注明來源。 我從今年的2月份開始在知乎上連續(xù)回答前端開發(fā)相關(guān)的問題,至今已有將近三個月,回顧寫過的一百多條回...
摘要:面向過程函數(shù)式編程面向?qū)ο缶幊痰诙€并不是大家理解的那樣,我們先說舉個現(xiàn)實例子就明白了。多說一句函數(shù)是編程是非常強(qiáng)大也是我最喜歡的,以后再說,我們先說面向?qū)ο缶幊獭? 概述 當(dāng)大家已經(jīng)把js的語言基礎(chǔ)理解了,然后能夠?qū)懗鲆恍┖唵蔚睦恿耍@個時候基本上達(dá)到了一年工作經(jīng)驗的水平,而自己能夠獨立的寫一些小功能,完成一些小效果,或者臨摹修改一些比較復(fù)雜的插件的時候差不多就是兩年工作經(jīng)驗的水平,...
摘要:有需要還可以修改指向謙龍寄生組合式繼承思路是通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混合形式來繼承方法改變執(zhí)行環(huán)境實現(xiàn)繼承有需要還可以修改指向謙龍謙龍拷貝繼承該方法思路是將另外一個對象的屬性和方法拷貝至另一個對象使用遞歸 前言 js中實現(xiàn)繼承的方式只支持實現(xiàn)繼承,即繼承實際的方法,而實現(xiàn)繼承主要是依靠原型鏈來完成的。 原型鏈?zhǔn)嚼^承 該方式實現(xiàn)的本質(zhì)是重寫原型對象,代之以一個新類型的實例...
摘要:這是因為子類沒有自己的對象,而是繼承父類的對象,然后對其進(jìn)行加工。 溫馨提示:作者的爬坑記錄,對你等大神完全沒有價值,別在我這浪費生命溫馨提示-續(xù):你們要非得看,我也攔不住,但是至少得準(zhǔn)備個支持ES6的Chrome瀏覽器吧?溫馨提示-再續(xù):ES6簡直了,放著不用簡直令人發(fā)指! 書接上回,即便是程序員,也還是能夠通過自己的努力辛辛苦苦找到合適對象的,見前文《javascript對象不完全...
閱讀 933·2021-09-07 09:58
閱讀 1484·2021-09-07 09:58
閱讀 2869·2021-09-04 16:40
閱讀 2501·2019-08-30 15:55
閱讀 2404·2019-08-30 15:54
閱讀 1364·2019-08-30 15:52
閱讀 423·2019-08-30 10:49
閱讀 2598·2019-08-29 13:21