徹底理解js面向?qū)ο笾^承
說道這個繼承,了解object-oriented的朋友都知道,大多oo語言都有兩種,一種是接口繼承(只繼承方法簽名);一種是實現(xiàn)繼承(繼承實際的方法)
奈何js中沒有簽名,因而只有實現(xiàn)繼承,而且靠的是原型鏈實現(xiàn)的。下面正式的說一說js中繼承那點事兒
1、原型鏈
原型鏈:實現(xiàn)繼承的主要方法,利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
回顧:構(gòu)造函數(shù),原型,實例三者的關(guān)系
每一個構(gòu)造函數(shù)都有一個原型對象(Person.prototype);原型對象都包含指向構(gòu)造函數(shù)的指針(constructor);每個實例都包含指向原型對象的指針(看不見的_proto_指針)
原型鏈?zhǔn)窃趺磥淼哪兀?/p>
某個構(gòu)造函數(shù)的原型對象是另一個構(gòu)造函數(shù)的實例;這個構(gòu)造函數(shù)的原型對象就會有個(看不見的_proto_指針)指向另一個構(gòu)造函數(shù)的原型對象;
那么另一個原型對象又是其他的構(gòu)造函數(shù)實例又會怎么樣,就這樣層層遞進,形成原型鏈;來具體看一下吧
//第一個構(gòu)造函數(shù);有一個屬性和一個原型方法 function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property } //第二個構(gòu)造函數(shù);目前有一個屬性 function SubType(){ this.subproperty=false } //繼承了SuperType;SubType原型成了SuperType的實例;實際就是重寫SubType的原型對象;給SuperType原型對象繼承了 SubType.prototype=new SuperType() //現(xiàn)在這個構(gòu)造函數(shù)有兩個屬性(一個本身的subproperty,一個繼承的存在原型對象的property);兩個方法(一個原型對象的getSubValue,一個原型對象的原型對象的getSuperValue) SubType.prototype.getSubValue=function(){ return this.subproperty } var instance=new SubType() //創(chuàng)建第二個構(gòu)造函數(shù)的實例 console.log(instance.getSuperValue()) //true 先查找instance這個實例有沒有此方法;顯然沒有,再查找SubType原型對象有沒有此方法;也沒有,再查找SubType原型對象的原型對象;顯然是存在的
注意:instance的constructor現(xiàn)在指向的是SuperType這個構(gòu)造函數(shù);因為原來的SubType.prototype被重寫了,其內(nèi)部的constructor也就隨著SubType.prototype的原型對象的constructor指向構(gòu)造函數(shù)SuperType;至于原型搜索機制是怎么樣運行的,請仔細看上面的代碼,相信你是可以的
1.1完整的原型
在原型那節(jié)已經(jīng)提了些,還是再說一下。完整的原型包括Object。
所有函數(shù)的默認(rèn)原型都是Object的實例;每個默認(rèn)原型都有個_proto_指針指向Object.prototype;因此自定義類型都繼承如toString,valueOf的方法
而Object.prototype的_proto_指針指向null來結(jié)束原型鏈。以Person構(gòu)造函數(shù)為例,看看完整的原型鏈圖
1.2原型和實例的關(guān)系判斷
第一種使用instanceof操作符: 測試實例和原型鏈中出現(xiàn)的構(gòu)造函數(shù),結(jié)果為true
第二種使用isPrototypeOf()方法: 只要是原型鏈中出現(xiàn)過的原型,都可以說是該原型鏈所派生的實例的原型
console.log(instance instanceof Object) //都為true console.log(instance instanceof SuperType) console.log(instance instanceof SubType) console.log(Object.prototype.isPrototypeOf(instance)) //都為true console.log(SuperType.prototype.isPrototypeOf(instance)) console.log(SubType.prototype.isPrototypeOf(instance))
1.3謹(jǐn)慎定義方法
注意:給原型對象添加方法,一定放在替換原型的后面,因為放在替換原型之前是找不到了,原型會被重寫的;
注意:在通過原型鏈繼承時,不能使用對象字面量創(chuàng)建原型方法,因為也會重寫原型鏈;
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property } function SubType(){ this.subproperty=false } //繼承SuperType SubType.prototype=new SuperType() //使用字面量添加新方法,導(dǎo)致上一行無效 因為現(xiàn)在的原型替換了Object實例而非SuperType的實例,關(guān)系中斷 SubType.prototype={ getSubValue:function(){ return this.subproperty; }, somOtherMethod:function(){ return false } }; var instance=new SubType() console.log(instance.getSuperValue()) //error
1.4原型鏈的問題
1、包含引用類型值的原型:當(dāng)實例是另一函數(shù)的原型時,引用類型值就會變成原型上的屬性,就會被另一函數(shù)的實例所共享。
function SuperType(){ this.colors=["yellow","red","olive"] } function SubType(){ } SubType.prototype=new SuperType() //color實際上就是原型上的了 var instance1=new SubType() instance1.colors.push("purple") var instance2=new SubType() console.log(instance1.colors==instance2.colors) //true
2、創(chuàng)建子類型實例時,不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)(沒有辦法在不影響所有對象實例的情況下,給超類型的構(gòu)造函數(shù)傳遞參數(shù))
2、借助構(gòu)造函數(shù)
為了解決原型中包含引用類型值帶來的問題,利用構(gòu)造函數(shù)來解決
在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)(函數(shù)是特定環(huán)境中執(zhí)行代碼的對象,可以通過apply或call調(diào)用)
function SuperType(){ this.color=["yellow","red","olive"] } function SubType(){ //繼承了SuperType SuperType.call(this) } var instance1=new SubType() instance1.color.push("purple") var instance2=new SubType() console.log(instance1.color) //["yellow","red","olive","purple"] console.log(instance2.color) //["yellow","red","olive"] //傳遞參數(shù) function SuperType(name){ this.name=name } function SubType(){ SuperType.call(this,"double") this.age=12 } var instance1=new SubType() console.log(instance1.name) //double console.log(instance1.age) //12
問題:僅僅借鑒構(gòu)造函數(shù),那么避免不了構(gòu)造函數(shù)的問題,方法都在構(gòu)造函數(shù)定義了,函數(shù)無法復(fù)用
3、組合繼承(常用的還是組合,和原型與構(gòu)造結(jié)合一樣)
function SuperType(name){ this.name=name; this.color=["yellow","red","olive"]; } SuperType.prototype.sayName=function(){ console.log(this.name); } function SubType(name,age){ //繼承屬性,創(chuàng)建屬性副本 SuperType.call(this,name); this.age=age; } //繼承屬性和方法,只是原型中屬性被后來的函數(shù)調(diào)用生成的屬性副本遮蓋 SubType.prototype=new SuperType(); alert(SubType.prototype.constructor) //指向的是SuperType SubType.prototype.constructor=SubType; //將constructor回歸到SubType構(gòu)造函數(shù)身上 SubType.prototype.sayAge=function(){ console.log(this.age) } var instance1=new SubType("double",23) instance1.color.push("pink") console.log(instance1.color) //["yellow","red","olive","pink"] instance1.sayName() //double instance1.sayAge() //23 var instance2=new SubType("single",34) console.log(instance2.color) //["yellow","red","olive"] instance2.sayName() //single instance2.sayAge() //34
還有其他的繼承,花點時間寫一下
1、原型式繼承
克羅克福德寫的;借助原型可以基于已有的對象創(chuàng)建新對象,同時不必創(chuàng)建自定義類型
function object(o){ //本質(zhì)上object()函數(shù)對其中對象的淺復(fù)制 function F(){} //創(chuàng)建一個新的構(gòu)造函數(shù) F.prototype=o //構(gòu)造函數(shù)原型為傳入的對象 return new F() //返回構(gòu)造函數(shù)的實例 } var person={ name:"double", friends:["tom","jack","mike"] } var person1=object(person) //事實上為原型共享 person1.name="grey" person1.friends.push("single") console.log(person1.friends) //["tom", "jack", "mike", "single"] var person2=object(person) person2.name="red" console.log(person2.friends) //["tom", "jack", "mike", "single"]
ES5為了規(guī)范原型式的繼承,有個Object.create()來方便,IE9以上可以;只是想一個對象和另一個對象保持類似的情況,完全可以這種方法
var person={ name:"double", friends:["tom","jack","mike"] } var person1=Object.create(person) person1.name="single" person1.friends.push("singles") var person2=Object.create(person) console.log(person1.friends==person2.friends) //true //Object.create()接受兩個參數(shù),一個為作為新對象原型的對象,一個為新對象定義額外屬性對象 var person={ name:"double", friends:["tom","jack","mike"] } var person1=Object.create(person,{ name:{ value:"single" //每個屬性都是通過自己描述符定義的 } })
2、寄生式繼承
思路和原型式繼承一脈相承,創(chuàng)建一個用于封裝繼承過程的函數(shù),內(nèi)部通過方式增強對象,返回對象;主要考慮對象時使用
function object(o){ function F(){} F.prototype=o return new F() } function createPerson(original){ var clone=object(original) //繼承原型 clone.sayName=function(){ alert("name") } return clone } var person={ name:"double", friends:["single","tom","jack"] } var person1=createPerson(person) person1.sayName() //name 引用類型值還是共享的
3、寄生組合繼承
組合繼承是繼承中常常用到的,但是會調(diào)用兩次超類型構(gòu)造函數(shù);寄生組合繼承就是為了解決這個問題的
function object(o){ function F(){} F.prototype=o return new F() } function inheritPrototype(subType,superType){ var prototype=object(superType) //創(chuàng)建對象 (superType實例) prototype.constructor=subType //增強對象 subType.prototype=prototype //指定對象 (原型賦予實例) } function SuperType(name,sex){ this.name=name this.sex=sex this.colors=["red"] } SuperType.prototype.sayName=function(){ alert(this.name) } function SubType(name,sex,age){ SuperType.call(this,name,sex) this.age=age } inheritPrototype(SubType,SuperType) //目前subType.prototype什么都沒有 SubType.prototype.sayAge=function(){ //為subType.prototype添加個方法 alert(this.age) } var person1=new SubType("double","man",34) console.log(person1.name) //SuperType 這是個Bug console.log(person1.sex) //man console.log(person1.colors) //["red"] person1.sayAge() //34
到此,差不多結(jié)束啦,感謝你對腳本之家的支持,希望我們整理的內(nèi)容能夠幫助到你。
- JavaScript面向?qū)ο笕齻€基本特征實例詳解【封裝、繼承與多態(tài)】
- JS面向?qū)ο蠡A(chǔ)講解(工廠模式、構(gòu)造函數(shù)模式、原型模式、混合模式、動態(tài)原型模式)
- Javascript 面向?qū)ο螅ㄒ唬?共有方法,私有方法,特權(quán)方法)
- 面向?qū)ο蟮腏avascript之二(接口實現(xiàn)介紹)
- js面向?qū)ο笾R妱?chuàng)建對象的幾種方式(工廠模式、構(gòu)造函數(shù)模式、原型模式)
- javascript 面向?qū)ο笕吕砭氈當(dāng)?shù)據(jù)的封裝
- javascript 面向?qū)ο缶幊袒A(chǔ) 多態(tài)
- JavaScript面向?qū)ο缶幊倘腴T教程
- JS 面向?qū)ο蟮?鐘寫法
- js面向?qū)ο笾o態(tài)方法和靜態(tài)屬性實例分析
- JavaScript面向?qū)ο笾叽蠡驹瓌t實例詳解
相關(guān)文章
使用RequireJS優(yōu)化JavaScript引用代碼的方法
這篇文章主要介紹了使用RequireJS優(yōu)化JavaScript引用代碼的方法,RequireJS是一款人氣JS庫,需要的朋友可以參考下2015-07-0710分鐘徹底搞懂Http的強制緩存和協(xié)商緩存(小結(jié))
這篇文章主要介紹了10分鐘徹底搞懂Http的強制緩存和協(xié)商緩存(小結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08圖解prototype、proto和constructor的三角關(guān)系
在javascript中,prototype、constructor以及__proto__之間有著“著名”的剪不斷理還亂的三角關(guān)系,樓主就著自己對它們的淺顯認(rèn)識,來粗略地理理以備忘,有不對之處還望斧正。2016-07-07簡介JavaScript中POSITIVE_INFINITY值的使用
這篇文章主要介紹了簡介JavaScript中POSITIVE_INFINITY值的使用,是JS入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-06-06在JS中操作時間之getUTCMilliseconds()方法的使用
這篇文章主要介紹了在JavaScript中操作時間之getUTCMilliseconds()方法的使用,是JavaScript入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-06-06