成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

再談Javascript原型繼承

ThinkSNS / 2888人閱讀

摘要:原型繼承基本模式這種是最簡(jiǎn)單實(shí)現(xiàn)原型繼承的方法,直接把父類(lèi)的對(duì)象賦值給子類(lèi)構(gòu)造函數(shù)的原型,這樣子類(lèi)的對(duì)象就可以訪問(wèn)到父類(lèi)以及父類(lèi)構(gòu)造函數(shù)的中的屬性。

真正意義上來(lái)說(shuō)Javascript并不是一門(mén)面向?qū)ο蟮恼Z(yǔ)言,沒(méi)有提供傳統(tǒng)的繼承方式,但是它提供了一種原型繼承的方式,利用自身提供的原型屬性來(lái)實(shí)現(xiàn)繼承。Javascript原型繼承是一個(gè)被說(shuō)爛掉了的話題,但是自己對(duì)于這個(gè)問(wèn)題一直沒(méi)有徹底理解,今天花了點(diǎn)時(shí)間又看了一遍《Javascript模式》中關(guān)于原型實(shí)現(xiàn)繼承的幾種方法,下面來(lái)一一說(shuō)明下,在最后我根據(jù)自己的理解提出了一個(gè)關(guān)于繼承比較完整的實(shí)現(xiàn),如果大家有不同意見(jiàn),歡迎建議。

原型與原型鏈

說(shuō)原型繼承之前還是要先說(shuō)說(shuō)原型和原型鏈,畢竟這是實(shí)現(xiàn)原型繼承的基礎(chǔ)。
在Javascript中,每個(gè)函數(shù)都有一個(gè)原型屬性prototype指向自身的原型,而由這個(gè)函數(shù)創(chuàng)建的對(duì)象也有一個(gè)__proto__屬性指向這個(gè)原型,而函數(shù)的原型是一個(gè)對(duì)象,所以這個(gè)對(duì)象也會(huì)有一個(gè)__proto__指向自己的原型,這樣逐層深入直到Object對(duì)象的原型,這樣就形成了原型鏈。下面這張圖很好的解釋了Javascript中的原型和原型鏈的關(guān)系。

每個(gè)函數(shù)都是Function函數(shù)創(chuàng)建的對(duì)象,所以每個(gè)函數(shù)也有一個(gè)__proto__屬性指向Function函數(shù)的原型。這里需要指出的是,真正形成原型鏈的是每個(gè)對(duì)象的__proto__屬性,而不是函數(shù)的prototype屬性,這是很重要的。

原型繼承 基本模式
var Parent = function(){
    this.name = "parent" ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(){
    this.name = "child" ;
} ;
Child.prototype = new Parent() ;

var parent = new Parent() ;
var child = new Child() ;

console.log(parent.getName()) ; //parent
console.log(child.getName()) ; //child

這種是最簡(jiǎn)單實(shí)現(xiàn)原型繼承的方法,直接把父類(lèi)的對(duì)象賦值給子類(lèi)構(gòu)造函數(shù)的原型,這樣子類(lèi)的對(duì)象就可以訪問(wèn)到父類(lèi)以及父類(lèi)構(gòu)造函數(shù)的prototype中的屬性。 這種方法的原型繼承圖如下:

這種方法的優(yōu)點(diǎn)很明顯,實(shí)現(xiàn)十分簡(jiǎn)單,不需要任何特殊的操作;同時(shí)缺點(diǎn)也很明顯,如果子類(lèi)需要做跟父類(lèi)構(gòu)造函數(shù)中相同的初始化動(dòng)作,那么就得在子類(lèi)構(gòu)造函數(shù)中再重復(fù)一遍父類(lèi)中的操作:

var Parent = function(name){
    this.name = name || "parent" ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    this.name = name || "child" ;
} ;
Child.prototype = new Parent() ;

var parent = new Parent("myParent") ;
var child = new Child("myChild") ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

上面這種情況還只是需要初始化name屬性,如果初始化工作不斷增加,這種方式是很不方便的。因此就有了下面一種改進(jìn)的方式。

借用構(gòu)造函數(shù)
var Parent = function(name){
    this.name = name || "parent" ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = new Parent() ;

var parent = new Parent("myParent") ;
var child = new Child("myChild") ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

上面這種方法在子類(lèi)構(gòu)造函數(shù)中通過(guò)apply調(diào)用父類(lèi)的構(gòu)造函數(shù)來(lái)進(jìn)行相同的初始化工作,這樣不管父類(lèi)中做了多少初始化工作,子類(lèi)也可以執(zhí)行同樣的初始化工作。但是上面這種實(shí)現(xiàn)還存在一個(gè)問(wèn)題,父類(lèi)構(gòu)造函數(shù)被執(zhí)行了兩次,一次是在子類(lèi)構(gòu)造函數(shù)中,一次在賦值子類(lèi)原型時(shí),這是很多余的,所以我們還需要做一個(gè)改進(jìn):

var Parent = function(name){
    this.name = name || "parent" ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = Parent.prototype ;

var parent = new Parent("myParent") ;
var child = new Child("myChild") ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

這樣我們就只需要在子類(lèi)構(gòu)造函數(shù)中執(zhí)行一次父類(lèi)的構(gòu)造函數(shù),同時(shí)又可以繼承父類(lèi)原型中的屬性,這也比較符合原型的初衷,就是把需要復(fù)用的內(nèi)容放在原型中,我們也只是繼承了原型中可復(fù)用的內(nèi)容。上面這種方式的原型圖如下:

臨時(shí)構(gòu)造函數(shù)模式(圣杯模式)

上面借用構(gòu)造函數(shù)模式最后改進(jìn)的版本還是存在問(wèn)題,它把父類(lèi)的原型直接賦值給子類(lèi)的原型,這就會(huì)造成一個(gè)問(wèn)題,就是如果對(duì)子類(lèi)的原型做了修改,那么這個(gè)修改同時(shí)也會(huì)影響到父類(lèi)的原型,進(jìn)而影響父類(lèi)對(duì)象,這個(gè)肯定不是大家所希望看到的。為了解決這個(gè)問(wèn)題就有了臨時(shí)構(gòu)造函數(shù)模式。

var Parent = function(name){
    this.name = name || "parent" ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent("myParent") ;
var child = new Child("myChild") ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

該方法的原型繼承圖如下:

很容易可以看出,通過(guò)在父類(lèi)原型和子類(lèi)原型之間加入一個(gè)臨時(shí)的構(gòu)造函數(shù)F,切斷了子類(lèi)原型和父類(lèi)原型之間的聯(lián)系,這樣當(dāng)子類(lèi)原型做修改時(shí)就不會(huì)影響到父類(lèi)原型。

我的方法

《Javascript模式》中到圣杯模式就結(jié)束了,可是不管上面哪一種方法都有一個(gè)不容易被發(fā)現(xiàn)的問(wèn)題。大家可以看到我在"Parent"的prototype屬性中加入了一個(gè)obj對(duì)象字面量屬性,但是一直都沒(méi)有用。我們?cè)谑ケJ降幕A(chǔ)上來(lái)看看下面這種情況:

var Parent = function(name){
    this.name = name || "parent" ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent("myParent") ;
var child = new Child("myChild") ;

console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = 2 ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //2

在上面這種情況中,當(dāng)我修改child對(duì)象obj.a的時(shí)候,同時(shí)父類(lèi)的原型中的obj.a也會(huì)被修改,這就發(fā)生了和共享原型同樣的問(wèn)題。出現(xiàn)這個(gè)情況是因?yàn)楫?dāng)訪問(wèn)child.obj.a的時(shí)候,我們會(huì)沿著原型鏈一直找到父類(lèi)的prototype中,然后找到了obj屬性,然后對(duì)obj.a進(jìn)行修改。再看看下面這種情況:

var Parent = function(name){
    this.name = name || "parent" ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent("myParent") ;
var child = new Child("myChild") ;

console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = 2 ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //2

這里有一個(gè)關(guān)鍵的問(wèn)題,當(dāng)對(duì)象訪問(wèn)原型中的屬性時(shí),原型中的屬性對(duì)于對(duì)象來(lái)說(shuō)是只讀的,也就是說(shuō)child對(duì)象可以讀取obj對(duì)象,但是無(wú)法修改原型中obj對(duì)象引用,所以當(dāng)child修改obj的時(shí)候并不會(huì)對(duì)原型中的obj產(chǎn)生影響,它只是在自身對(duì)象添加了一個(gè)obj屬性,覆蓋了父類(lèi)原型中的obj屬性。而當(dāng)child對(duì)象修改obj.a時(shí),它先讀取了原型中obj的引用,這時(shí)候child.objParent.prototype.obj是指向同一個(gè)對(duì)象的,所以child對(duì)obj.a的修改會(huì)影響到Parent.prototype.obj.a的值,進(jìn)而影響父類(lèi)的對(duì)象。AngularJS中關(guān)于$scope嵌套的繼承方式就是模范Javasript中的原型繼承來(lái)實(shí)現(xiàn)的。
根據(jù)上面的描述,只要子類(lèi)對(duì)象中訪問(wèn)到的原型跟父類(lèi)原型是同一個(gè)對(duì)象,那么就會(huì)出現(xiàn)上面這種情況,所以我們可以對(duì)父類(lèi)原型進(jìn)行拷貝然后再賦值給子類(lèi)原型,這樣當(dāng)子類(lèi)修改原型中的屬性時(shí)就只是修改父類(lèi)原型的一個(gè)拷貝,并不會(huì)影響到父類(lèi)原型。具體實(shí)現(xiàn)如下:

var deepClone = function(source,target){
    source = source || {} ;
    target = target || {};
    var toStr = Object.prototype.toString ,
        arrStr = "[object array]" ;
    for(var i in source){
        if(source.hasOwnProperty(i)){
            var item = source[i] ;
            if(typeof item === "object"){
                target[i] = (toStr.apply(item).toLowerCase() === arrStr) ? [] : {} ;
                deepClone(item,target[i]) ;    
            }else{
                target[i] = item;
            }
        }
    }
    return target ;
} ;
var Parent = function(name){
    this.name = name || "parent" ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : "1"} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = deepClone(Parent.prototype) ;

var child = new Child("child") ;
var parent = new Parent("parent") ;

console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = "2" ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //1

綜合上面所有的考慮,Javascript繼承的具體實(shí)現(xiàn)如下,這里只考慮了Child和Parent都是函數(shù)的情況下:

var deepClone = function(source,target){
    source = source || {} ;
    target = target || {};
    var toStr = Object.prototype.toString ,
        arrStr = "[object array]" ;
    for(var i in source){
        if(source.hasOwnProperty(i)){
            var item = source[i] ;
            if(typeof item === "object"){
                target[i] = (toStr.apply(item).toLowerCase() === arrStr) ? [] : {} ;
                deepClone(item,target[i]) ;    
            }else{
                target[i] = item;
            }
        }
    }
    return target ;
} ;

var extend = function(Parent,Child){
    Child = Child || function(){} ;
    if(Parent === undefined)
        return Child ;
    //借用父類(lèi)構(gòu)造函數(shù)
    Child = function(){
        Parent.apply(this,argument) ;
    } ;
    //通過(guò)深拷貝繼承父類(lèi)原型    
    Child.prototype = deepClone(Parent.prototype) ;
    //重置constructor屬性
    Child.prototype.constructor = Child ;
} ;
總結(jié)

說(shuō)了這么多,其實(shí)Javascript中實(shí)現(xiàn)繼承是十分靈活多樣的,并沒(méi)有一種最好的方法,需要根據(jù)不同的需求實(shí)現(xiàn)不同方式的繼承,最重要的是要理解Javascript中實(shí)現(xiàn)繼承的原理,也就是原型和原型鏈的問(wèn)題,只要理解了這些,自己實(shí)現(xiàn)繼承就可以游刃有余。

最后,安利下我的個(gè)人博客,歡迎訪問(wèn): http://bin-playground.top

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/85377.html

相關(guān)文章

  • 再談JavaScript面向?qū)ο笏枷爰?em>繼承

    摘要:面向?qū)ο笾杏腥筇卣?,封裝,繼承,多態(tài)。這不僅無(wú)法做到數(shù)據(jù)共享,也是極大的資源浪費(fèi),那么引入對(duì)象實(shí)例對(duì)象的屬性指向其構(gòu)造函數(shù),這樣看起來(lái)實(shí)例對(duì)象好像繼承了對(duì)象一樣。實(shí)例對(duì)象的原型指向其構(gòu)造函數(shù)的對(duì)象構(gòu)造器的指向。 前言 為什么說(shuō)是再談呢,網(wǎng)上講解這個(gè)的博客的很多,我開(kāi)始學(xué)習(xí)也是看過(guò),敲過(guò)就沒(méi)了,自以為理解了就結(jié)束了,書(shū)到用時(shí)方恨少啊。實(shí)際開(kāi)發(fā)中一用就打磕巴,于是在重新學(xué)習(xí)了之后分享出來(lái)...

    svtter 評(píng)論0 收藏0
  • JS程序

    摘要:設(shè)計(jì)模式是以面向?qū)ο缶幊虨榛A(chǔ)的,的面向?qū)ο缶幊毯蛡鹘y(tǒng)的的面向?qū)ο缶幊逃行┎顒e,這讓我一開(kāi)始接觸的時(shí)候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續(xù)了解設(shè)計(jì)模式必須要先搞懂面向?qū)ο缶幊?,否則只會(huì)讓你自己更痛苦。 JavaScript 中的構(gòu)造函數(shù) 學(xué)習(xí)總結(jié)。知識(shí)只有分享才有存在的意義。 是時(shí)候替換你的 for 循環(huán)大法了~ 《小分享》JavaScript中數(shù)組的那些迭代方法~ ...

    melody_lql 評(píng)論0 收藏0
  • 理清javascript中的面向?qū)ο螅ㄒ唬?em>原型繼承

    摘要:有一函數(shù)若是用來(lái)生成對(duì)象,則稱(chēng)為構(gòu)造函數(shù)名。屬性指定了使用該構(gòu)造函數(shù)生成的對(duì)象實(shí)例繼承了哪個(gè)對(duì)象實(shí)例。因此,只要利用,就能在構(gòu)造函數(shù)中,為未來(lái)利用此構(gòu)造函數(shù)生成的對(duì)象實(shí)例,添加成員屬性和成員方法了。 與其它編程語(yǔ)言不一樣的是,javascript的面向?qū)ο蟛⒎且蕾囉诔橄蟮念?lèi),而是通過(guò)原型鏈,將一個(gè)個(gè)具體的對(duì)象實(shí)例進(jìn)行連接,位于原型鏈下游的對(duì)象實(shí)例可以讀取/使用位于上游的對(duì)象實(shí)例的屬性/...

    beita 評(píng)論0 收藏0
  • 你真的弄明白 new 了嗎

    摘要:如果構(gòu)造函數(shù)有返回值呢一般情況下構(gòu)造函數(shù)沒(méi)有返回值,但是我們依舊可以得到該對(duì)象的實(shí)例如果構(gòu)造函數(shù)有返回值,憑直覺(jué)來(lái)說(shuō)情況應(yīng)該會(huì)不一樣。歡迎光臨小弟博客我的博客原文你真的弄明白了嗎參考再談面向?qū)ο缶幊痰膶?shí)例化與繼承請(qǐng)停止使用關(guān)鍵字 好久沒(méi)有寫(xiě)點(diǎn)東西了,總覺(jué)得自己應(yīng)該寫(xiě)點(diǎn)牛逼的,卻又不知道如何下筆。既然如此,還是回歸最基本的吧,今天就來(lái)說(shuō)一說(shuō)這個(gè)new。關(guān)于javascript的new關(guān)鍵...

    tolerious 評(píng)論0 收藏0
  • 淺談JavaScript面向?qū)ο?/b>

    摘要:不必在構(gòu)造函數(shù)中定義對(duì)象實(shí)例的信息。其次,按照一切事物皆對(duì)象的這餓極本的面向?qū)ο蟮姆▌t來(lái)說(shuō),類(lèi)本身并不是一個(gè)對(duì)象,然而原型方式的構(gòu)造函數(shù)和原型本身也是個(gè)對(duì)象。第二個(gè)問(wèn)題就是在創(chuàng)建子類(lèi)型的實(shí)例時(shí),不能向超類(lèi)型的構(gòu)造函數(shù)中傳遞參數(shù)。 前言 對(duì)象(Object)應(yīng)該算是js中最為重要的部分,也是js中非常難懂晦澀的一部分。更是面試以及框架設(shè)計(jì)中各出沒(méi)。寫(xiě)這篇文章,主要參考與JavaScrip...

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

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

0條評(píng)論

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