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

資訊專欄INFORMATION COLUMN

[譯] 為什么原型繼承很重要

xiao7cn / 496人閱讀

摘要:使用構(gòu)造函數(shù)的原型繼承相比使用原型的原型繼承更加復(fù)雜,我們先看看使用原型的原型繼承上面的代碼很容易理解。相反的,使用構(gòu)造函數(shù)的原型繼承像下面這樣當(dāng)然,構(gòu)造函數(shù)的方式更簡單。

五天之前我寫了一個關(guān)于ES6標(biāo)準(zhǔn)中Class的文章。在里面我介紹了如何用現(xiàn)有的Javascript來模擬類并且介紹了ES6中類的用法,其實它只是一個語法糖。感謝Om Shakar以及Javascript Room中的各位,我的編程風(fēng)格從那時候開始發(fā)生了改變;就像Dougla Crockford2006年做的一樣,我也學(xué)習(xí)了很多來完全理解基于原型的編程方式。

Javascript是一個多樣化的編程語言。它擁有面向?qū)ο蠛秃瘮?shù)式的編程特點,你可以使用任何一種風(fēng)格來編寫代碼。然而這兩個編程風(fēng)格并不能很好的融合。例如,你不無法同時使用new(典型的面向?qū)ο蟮奶攸c)和apply(函數(shù)式編程的特點).原型繼承一直都作為連接這兩種風(fēng)格的橋梁。

基于類繼承的問題

大部分Javascript程序員會告訴你基于類的繼承不好。然而它們中只有很少一部分知道其中的原因。事實實際上是基于類的基礎(chǔ)并沒有什么不好。Python是基于類繼承的,并且它是一門很好的編程語言。但是,基于類的繼承并不適合用于Javascript。Python正確的使用了類,它們只有簡單的工廠方法不能當(dāng)成構(gòu)造函數(shù)使用。而在Javascript中任何函數(shù)都可以被當(dāng)成構(gòu)造函數(shù)使用。

Javascript中的問題是由于每個函數(shù)都可以被當(dāng)成構(gòu)造函數(shù)使用,所以我們需要區(qū)分普通的函數(shù)調(diào)用和構(gòu)造函數(shù)調(diào)用;我們一般使用new關(guān)鍵字來進(jìn)行區(qū)別。然而,這樣就破壞了Javascript中的函數(shù)式特點,因為new是一個關(guān)鍵字而不是函數(shù)。因而函數(shù)式的特點無法和對象實例化一起使用。

function Person(firstname,lastname){
    this.firstname = firstname ;
    this.lastname = lastname ;
}

考慮上面這段程序。你可以通過new關(guān)鍵字來調(diào)用Person方法來創(chuàng)建一個函數(shù)Person的實例:

var author = new Person("Aadit","Shah") ;

然而,沒有任何辦法來使用apply方法來為構(gòu)造函數(shù)指定參數(shù)列表:

var author = new Person.apply(null,["Aadit","Shah"]);//error

但是,如果new是一個方法那么上面的需求就可以通過下面這種方式實現(xiàn)了:

var author = Person.new.apply(Person,["Aadit","Shah"]) ;

幸運(yùn)的是,因為Javascript有原型繼承,所以我們可以實現(xiàn)一個new的函數(shù):

Function.prototype.new = function () {
    function functor() { return constructor.apply(this, args); }
    var args = Array.prototype.slice.call(arguments);
    functor.prototype = this.prototype;
    var constructor = this;
    return new functor;
};

在像Java這樣對象只能通過new關(guān)鍵字來實例化的語言中,上面這種方式是不可能實現(xiàn)的。

下面這張表列出了原型繼承相比于基于類的基礎(chǔ)的優(yōu)點:

基于類的繼承 原型繼承
類是不可變的。在運(yùn)行時,你無法修改或者添加新的方法 原型是靈活的。它們可以是不可變的也可以是可變的
類可能會不支持多重繼承 對象可以繼承多個原型對象
基于類的繼承比較復(fù)雜。你需要使用抽象類,接口和final類等等 原型繼承比較簡潔。你只有對象,你只需要對對象進(jìn)行擴(kuò)展就可以了
不要再使用關(guān)鍵詞new了

到現(xiàn)在你應(yīng)該知道為什么我覺得new關(guān)鍵字是不會的了吧---你不能把它和函數(shù)式特點混合使用。然后,這并不代表你應(yīng)該停止使用它。new關(guān)鍵字有合理的用處。但是我仍然建議你不要再使用它了。new關(guān)鍵字掩蓋了Javascript中真正的原型繼承,使得它更像是基于類的繼承。就像Raynos說的:

new是Javascript在為了獲得流行度而加入與Java類似的語法時期留下來的一個殘留物

Javascript是一個源于Self的基于原型的語言。然而,為了市場需求,Brendan Eich把它當(dāng)成Java的小兄弟推出:

并且我們當(dāng)時把Javascript當(dāng)成Java的一個小兄弟,就像在微軟語言家庭中Visual Basic相對于C++一樣。

這個設(shè)計決策導(dǎo)致了new的問題。當(dāng)人們看到Javascript中的new關(guān)鍵字,他們就想到類,然后當(dāng)他們使用繼承時就遇到了傻了。就像Douglas Crockford說的:

這個間接的行為是為了使傳統(tǒng)的程序員對這門語言更熟悉,但是卻失敗了,就像我們看到的很少Java程序員選擇了Javascript。Javascript的構(gòu)造模式并沒有吸引傳統(tǒng)的人群。它也掩蓋了Javascript基于原型的本質(zhì)。結(jié)果就是,很少的程序員知道如何高效的使用這門語言

因此我建議停止使用new關(guān)鍵字。Javascript在傳統(tǒng)面向?qū)ο蠹傧笙旅嬗兄訌?qiáng)大的原型系統(tǒng)。然大部分程序員并沒有看見這些還處于黑暗中。

理解原型繼承

原型繼承很簡單。在基于原型的語言中你只有對象。沒有類。有兩種方式來創(chuàng)建一個新對象---“無中生有”對象創(chuàng)建法或者通過現(xiàn)有對象創(chuàng)建。在Javascript中Object.create方法用來創(chuàng)建新的對象。新的對象之后會通過新的屬性進(jìn)行擴(kuò)展。

“無中生有”對象創(chuàng)建法

Javascript中的Object.create方法用來從0開始創(chuàng)建一個對象,像下面這樣:

var object = Object.create(null) ;

上面例子中新創(chuàng)建的object沒有任何屬性。

克隆一個現(xiàn)有的對象

Object.create方法也可以克隆一個現(xiàn)有的對象,像下面這樣:

var rectangle = {
    area : function(){
        return this.width * this.height ;
    }
} ;
var rect = Object.create(rectangle) ;

上面例子中rectrectangle中繼承了area方法。同時注意到rectangle是一個對象字面量。對象字面量是一個簡潔的方法用來創(chuàng)建一個Object.prototype的克隆然后用新的屬性來擴(kuò)展它。它等價于:

var rectangle = Object.create(Object.prototype) ;
rectangle.area = function(){
    return this.width * this.height ;
} ;
擴(kuò)展一個新創(chuàng)建的對象

上面的例子中我們克隆了rectangle對象命名為rect,但是在我們使用rectarea方法之前我們需要擴(kuò)展它的widthheight屬性,像下面這樣:

rect.width = 5 ;
rect.height = 10 ;
alert(rect.area()) ;

然而這種方式來創(chuàng)建一個對象的克隆然后擴(kuò)展它是一個非常傻缺的方法。我們需要在每個rectangle對象的克隆上手動定義widthheight屬性。如果有一個方法能夠為我們來完成這些工作就很好了。是不是聽起來有點熟悉?確實是。我要來說說構(gòu)造函數(shù)。我們把這個函數(shù)叫做create然后在rectangle對象上定義它:

var rectangle = {
    create : function(width,height){
        var self = Object.create(this) ;
        self.height = height ;
        self.width = width ;
        return self ;
    } ,
    area : function(){
        return this.width * this.height ;
    }
} ;
var rect = rectangle.create(5,10) ;
alert(rect.area()) ;
構(gòu)造函數(shù) VS 原型

等等。這看起來很像Javascript中的正常構(gòu)造模式:

function Rectangle(width, height) {
    this.height = height;
    this.width = width;
} ;

Rectangle.prototype.area = function () {
    return this.width * this.height;
};

var rect = new Rectangle(5, 10);
 
alert(rect.area());

是的,確實很像。為了使得Javascript看起來更像Java原型模式被迫屈服于構(gòu)造模式。因此每個Javascript中的函數(shù)都有一個prototype對象然后可以用來作為構(gòu)造器(這里構(gòu)造器的意思應(yīng)該是說新的對象是在prototype對象的基礎(chǔ)上進(jìn)行構(gòu)造的)。new關(guān)鍵字允許我們把函數(shù)當(dāng)做構(gòu)造函數(shù)使用。它會克隆構(gòu)造函數(shù)的prototype屬性然后把它綁定到this對象中,如果沒有顯式返回對象則會返回this

原型模式和構(gòu)造模式都是平等的。因此你也許會懷疑為什么有人會困擾于是否應(yīng)該使用原型模式而不是構(gòu)造模式。畢竟構(gòu)造模式比原型模式更加簡潔。但是原型模式相比構(gòu)造模式有許多優(yōu)勢。具體如下:

構(gòu)造模式 原型模式
函數(shù)式特點無法與new關(guān)鍵字一起使用 函數(shù)式特點可以與create結(jié)合使用
忘記使用new會導(dǎo)致無法預(yù)期的bug并且會污染全局變量 由于create是一個函數(shù),所以程序總是會按照預(yù)期工作
使用構(gòu)造函數(shù)的原型繼承比較復(fù)雜并且混亂 使用原型的原型繼承簡潔易懂

最后一點可能需要解釋一下。使用構(gòu)造函數(shù)的原型繼承相比使用原型的原型繼承更加復(fù)雜,我們先看看使用原型的原型繼承:

var square = Object.create(rectangle);
square.create = function (side) {
    return rectangle.create.call(this, side, side);
} ;
var sq = square.create(5) ;
alert(sq.area()) ;

上面的代碼很容易理解。首先我們創(chuàng)建一個rectangle的克隆然后命名為square。接著我們用新的create方法重寫square對象的create方法。最終我們從新的create方法中調(diào)用rectanglecreate函數(shù)并且返回對象。相反的,使用構(gòu)造函數(shù)的原型繼承像下面這樣:

function Square(){
    Rectangle.call(this,side,side) ;
} ;

Square.prototype = Object.create(Rectangle.prototype) ;

Square.prototype.constructor = Square ;

var sq = new Square(5) ;

alert(sq.area()) ;

當(dāng)然,構(gòu)造函數(shù)的方式更簡單。然后這樣的話,向一個不了解情況的人解釋原型繼承就變得非常困難。如果想一個了解類繼承的人解釋則會更加困難。

當(dāng)使用原型模式時一個對象繼承自另一個對象就變得很明顯。當(dāng)使用方法構(gòu)造模式時就沒有這么明顯,因為你需要根據(jù)其他構(gòu)造函數(shù)來考慮構(gòu)造繼承。

對象創(chuàng)建和擴(kuò)展相結(jié)合

在上面的例子中我們創(chuàng)建一個rectangle的克隆然后命名為square。然后我們利用新的create屬性擴(kuò)展它,重寫繼承自rectangle對象的create方法。如果把這兩個操作合并成一個就很好了,就像對象字面量是用來創(chuàng)建Object.prototype的克隆然后用新的屬性擴(kuò)展它。這個操作叫做extend,可以像下面這樣實現(xiàn):

Object.prototype.extend = function(extension){
    var hasOwnProperty = Object.hasOwnProperty ;
    var object = Object.create(this) ;
    
    for(var property in extension){
        if(hasOwnProperty.call(extension,property) ||
            typeof obejct[property] === "undefined")
            //這段代碼有問題,按照文章意思,這里應(yīng)該使用深復(fù)制,而不是簡單的淺復(fù)制,deepClone(extension[property],object[property]),deepClone的實現(xiàn)可以看我之前關(guān)于繼承的博客
            object[properyty] = extension[property] ;
    }
    return object ;
} ;

譯者注:我覺得博主這里的實現(xiàn)有點不符合邏輯,正常extend的實現(xiàn)應(yīng)該是可以配置當(dāng)被擴(kuò)展對象和用來擴(kuò)展的對象屬性重復(fù)時是否覆蓋原有屬性,而博主的實現(xiàn)就只是簡單的覆蓋。同時博主的實現(xiàn)在if判斷中的做法個人覺得是值得學(xué)習(xí)的,首先判斷extension屬性是否是對象自身的,如果是就直接復(fù)制到object上,否則再判斷object上是否有這個屬性,如果沒有那么也會把屬性復(fù)制到object上,這種實現(xiàn)的結(jié)果就使得被擴(kuò)展的對象不僅僅只擴(kuò)展了extension中的屬性,還包括了extension原型中的屬性。不難理解,extension原型中的屬性會在extension中表現(xiàn)出來,所以它們也應(yīng)該作為extension所具有的特性而被用來擴(kuò)展object。所以我對這個方法進(jìn)行了改寫:

    Object.prototype.extend = function(extension,override){
    var hasOwnProperty = Object.hasOwnProperty ;
    var object = Object.create(this) ;
    for(var property in extension){
        if(hasOwnProperty.call(extension,property) || 
            typeof object[property] === "undefined"){
            if(object[property] !== "undefined"){
                if(override){
                    deepClone(extension[property],object[property]) ;
                }
            }else{
                deepClone(extension[property],object[property]) ;
            }    
        }
    }
}; 

利用上面的extend方法,我們可以重寫square的代碼:

var square = rectangle.extend({
    create : function(side){
        return rectangle.create.call(this,side,side) ;
    }
}) ;

var sq = square.create(5) ;
alert(sq.area()) ;

extend方法是原型繼承中唯一需要的操作。它是Object.create函數(shù)的超集,因此它可以用在對象的創(chuàng)建和擴(kuò)展上。因此我們可以用extend來重寫rectangle,使得create函數(shù)更加結(jié)構(gòu)化看起來就像模塊模式。

var rectangle = {
    create : function(width,height){
        return this.extend({
            height : height ,
            width : width
        }) ;
    }
} ;

var rect = rectangle.create(5,10) ;
alert(rect.area()) ;
原型繼承的兩種方法

一些人可能已經(jīng)注意到extend函數(shù)返回的對象實際上是繼承了兩個對象的屬性,一個是被擴(kuò)展的對象,另一個是用來擴(kuò)展的對象。另外從兩個對象繼承屬性的方式也不一樣。第一種情況下是通過委派來繼承屬性(也就是使用Object.create()來繼承屬性),第二種情況下使用合并屬性的方式來繼承屬性。

委派(差異化繼承)

很多Javascript程序員對于差別繼承比較熟悉。維基百科是這么解釋的:

大部分對象是從其他更一般的對象中得到的,只是在一些很小的地方進(jìn)行了修改。每個對象通常在內(nèi)部維護(hù)一個指向其他對象的引用列表,這些對象就是該對象本身進(jìn)行差異化繼承的對象。

Javascript中的原型繼承是基于差異化繼承的。每個對象都有個內(nèi)部指針叫做[[proto]] (在大部分瀏覽器中可以通過__proto__屬性訪問),這個指針指向?qū)ο蟮脑汀6鄠€對象之間通過內(nèi)部[[proto]]屬性鏈接起來形成了原型鏈,鏈的最后指向null

當(dāng)你試圖獲取一個對象的屬性時Javascript引擎會首先查找對象自身的屬性。如果在對象上沒找到該屬性,那么它就會去對象的原型中去查找。以此類推,它會沿著原型鏈一直查找知道找到或者到原型鏈的末尾。

function get(object,property){
    if(!Object.hasOwnProperty.call(object,property)){
        var prototype = Object.getPrototypeOf(object) ;
        if(prototype) return get(prototype,property) ;
    }else{
        return object[property] ;
    }
} ;

Javascript中屬性查找的過程就像上面的程序那樣。

克隆(合并式繼承)

大多數(shù)Javascript程序員會覺得復(fù)制一個對象的屬性到另一個對象上并不是一個正確的繼承的方式,因為任何對原始對象的修改都不會反映在克隆的對象上。五天前我會同意這個觀點。然而現(xiàn)在我相信合并式繼承是原型繼承的一種正確方式。對于原始對象的修改可以發(fā)送到它的副本來實現(xiàn)真正的原型繼承。

合并式繼承和代理有他們的優(yōu)點和缺點。下表列出了它們的優(yōu)缺點:

代理 合并
任何對于原型的修改都會反映在所有副本上 任何對于原型的修改都需要手動更新到副本中
屬性查找效率較低因為需要進(jìn)行原型鏈查找 屬性查找更搞笑因為繼承的屬性是通過復(fù)制的方式附加在對象本身的
使用Object.create()方法只能繼承單一對象 對象可以從任意數(shù)量的對象中通過復(fù)制繼承屬性
從多個原型繼承

上表中最后一點告訴我們對象可以通過合并的方式從多個原型中繼承屬性。這是一個重要的特點因為這證明原型繼承比Java中的類繼承更強(qiáng)大并且與C++中的類繼承一樣強(qiáng)大。為了實現(xiàn)多重繼承,你只需要修改extend方法來從多個原型中復(fù)制屬性。

Object.prototype.extend = function(){
    var hasOwnProperty = Object.hasOwnProperty ;
    var object = Object.create(this) ;
    var length = arguments.length ;
    var index = length ;
    
    while(index){
        var extension = arguments[length - (index--)] ;
        for(var property in extension){
            if(hasOwnProperty.call(extension,property)||
                typeof object[property] === "undefined"){
                //這里同樣應(yīng)該使用深復(fù)制
                object[property] = extension[property] ;
            }
        }
    }
    return object;
} ;

多重繼承是非常有用的因為它提高了代碼的可重用性和模塊化。對象通過委派繼承一個原型對象然后通過合并繼承其他屬性。比如說你有一個事件發(fā)射器的原型,像下面這樣:

var eventEmitter = {
    on : function(event,listener){
        if(typeof this[event] !== "undefined")
            this[event].push(listener) ;
        else
            this[event] = [listener] ;
    } ,
    emit : function(event){
        if(typeof this[event] !== "undefined"){
            var listeners = this[event] ;
            var length = listeners.length,index = length ;
            var args = Array.prototype.slice.call(arguments,1) ;
            
            while(index){
                var listener = listeners[length - (index--)] ;
                listener.apply(this,args) ;
            }
        }
    }
} ;

現(xiàn)在你希望square表現(xiàn)得像一個事件發(fā)射器。因為square已經(jīng)通過委派的方式繼承了rectangle,所以它必須通過合并的方式繼承eventEmitter。這個修改可以很容易地通過使用extend方法實現(xiàn):

var square = rectangle.extend(eventEmitter,{
    create : function(side){
        return rectangle.create.call(this,side,side) ;
    } ,
    resize : function(newSize){
        var oldSize = this.width ;
        this.width = this.height = newSize ;
        this.emit("resize",oldSize,newSize) ;
    }
}) ;
var sq = square.create(5) ;
sq.on("resize",function(oldSize,newSize){
    alert("sq resized from " + oldSize + "to" + newSize + ".") ;
}) ;

sq.resize(10) ;
alert(sq.area()) ;

在Java中是不可能實現(xiàn)上面的程序的,因為它不支持多重繼承。相應(yīng)的你必須另外再創(chuàng)建一個EventEmitter類或者使用一個EventEmitter接口并且在每個實現(xiàn)該接口的類中分別實現(xiàn)onemit方法。當(dāng)然你在C++中不需要面對這個問題。我們都知道Java sucks(呵呵呵)。

Mixin的藍(lán)圖(Buleprint)

在上面的例子中你肯定注意到eventEmitter原型并沒有一個create方法。這是因為你不應(yīng)該直接創(chuàng)建一個eventEmitter對象。相反eventEmitter是用來作為其他原型的原型。這類原型稱為mixin。它們等價于抽象類。mixin用來通過提供一系列可重用的方法來擴(kuò)展對象的功能。

然而有時候mixin需要私有的狀態(tài)。例如eventEmitter如果能夠把它的事件監(jiān)聽者列表放在私有變量中而不是放在this對象上會安全得多。但是mixin沒有create方法來封裝私有狀態(tài)。因此我們需要為mixin創(chuàng)建一個藍(lán)圖(blueprint)來創(chuàng)建閉包。藍(lán)圖(blueprint)看起來會像是構(gòu)造函數(shù)但是它們并不用像構(gòu)造函數(shù)那樣使用。例如:

function eventEmitter(){
    var evnets = Object.create(null) ;
    
    this.on = function(event,listener){
        if(typeof events[event] !== "undefined")
            events[event].push(listener) ;
        else
            events[event] = [listener] ;
    } ;
    this.emit = function(event){
        if(typeof events[event] !== "undefined"){
            var listeners = events[event] ;
            var length = listeners.length ,index = length ;
            var args = Array.prototype.slice.call(arguments,1) ;
        }
    } ;
} ;

一個藍(lán)圖用來在一個對象創(chuàng)建之后通過合并來擴(kuò)展它(我覺得有點像裝飾者模式)。Eric Elliot把它們叫做閉包原型。我們可以使用藍(lán)圖版本的eventEmitter來重寫square的代碼,如下:

var square = rectangle.extend({
    create : function(side){
        var self = rectangle.create.call(this,side,side) ;
        eventEmitter.call(self) ;
        return self ;
    } ,
    resize : function(newSize){
        var oldSize = this.width ;
        this.width = this.height = newSize ;
        this.emit("resize",oldSize,newSize) ;
    }
}) ;
var sq = square.create(5) ;

sq.on("resize",function(oldSize,newSize){
    alert("sq resized from " + oldSize + "to" + newSize + ".") ;
}) ;

sq.resize(10) ;

alert(sq.area()) ;

藍(lán)圖在Javascript中是獨(dú)一無二的。它是一個很強(qiáng)大的特性。然而它們也有自己的缺點。下表列出了mixin和藍(lán)圖的優(yōu)缺點:

Mixin 藍(lán)圖
它們用來擴(kuò)展對象的原型。因此對象共享同一個原型 它們用來擴(kuò)展新創(chuàng)建的對象。因此每個對象都是在自己對象本身進(jìn)行修改
因為缺少封裝方法所以不存在私有狀態(tài) 它們是函數(shù),所以可以封裝私有狀態(tài)
它們是靜態(tài)原型并且不能被自定義 它們可以傳遞參數(shù)來自定義對象,可以向藍(lán)圖函數(shù)傳遞一些用來自定義的參數(shù)
修復(fù)instanceof操作

許多Javascript程序員會覺得使用原型模式來繼承違背了語言的精髓。他們更偏向于構(gòu)造模式因為他們覺得通過構(gòu)造函數(shù)創(chuàng)建的對象才是真正的實例,因為instanceof操作會返回true。然而,這個爭論是沒有意義的,因為instanceof操作可以像下面這樣實現(xiàn):

Object.prototype.instanceof = function(prototype){
    var object = this ;
    do{
        if(object === prototype) return true ;
        var object = Object.getPrototypeOf(object) ;
    }while(object) ;
    return false ;
}

這個instanceof方法現(xiàn)在可以被用來測試一個對象是否是通過委派從一個原型繼承的。例如:

sq.instanceof(square) ;

然而還是沒有辦法判斷一個對象是否是通過合并的方式從一個原型繼承的,因為實例的關(guān)聯(lián)信息丟失了。為了解決這個問題我們將一個原型的所有克隆的引用保存在原型自身中,然后使用這個信息來判斷一個對象是否是一個原型的實例。這個可以通過修改extend方法來實現(xiàn):

Object.prototype.extend = function(){
    var hasOwnProperty = Object.hasOwnProperty ; 
    var object = Object.create(this) ;
    var length = arguments.lenght ;
    var index = length ;

    while(index){
        var extension = arguments[length - (index--)] ;

        for(var property in extension){
            if(property !== "clones" &&
                hasOwnProperty.call(extension,property) ||
                typeof object[property] === "undefined")
                object[property] = extension[property] ;

        if(hasOwnProperty.call(extension,"clones")})
            extension.clones.unshift(object) ;
        else
            extension.clones = [object] ;
        }
    }
    return object;
} ;

通過合并繼承自原型的對象形成了一個克隆樹,這些樹從根對象開始然后向下一直到葉子對象。一個克隆鏈?zhǔn)且粋€從根對象到葉子對象的單一路徑,這跟遍歷原型鏈很相似。我們可以使用這個信息來判斷一個對象是否是通過合并繼承自一個原型。

Object.prototype.instanceof = function(prototype){
    if (Object.hasOwnProperty.call(prototype, "clones"))
        var clones = prototype.clones;
    var object = this;
    
    do {
        if (object === prototype ||
            clones && clones.indexOf(object) >= 0)
            return true;
        var object = Object.getPrototypeOf(o  bject);
    } while (object);

    return false;
} ;

這個instanceof方法現(xiàn)在可以用來判斷一個對象是否是通過合并繼承自一個原型。例如:

sq.instanceof(eventEmitter);

在上面的程序中instanceof會返回true如果我媽使用mixin版本的eventEmitter。然而如果我們使用藍(lán)圖版本的eventEmitter它會返回false。為了解決這個問題我創(chuàng)建了一個藍(lán)圖函數(shù),這個函數(shù)接收一個藍(lán)圖作為參數(shù),向它添加一個clones屬性然后返回一個記錄了它的克隆的新藍(lán)圖:

function blueprint(f){
    var g = function(){
        f.apply(this,arguments) ;
        g.clones.unshift(this) ;
    } ;
    g.clones = [] ;
    return g ;
} ;
var eventEmitter = blueprint(function(){
    var events = Object.create(null);
    this.on = function (event, listener) {
        if (typeof events[event] !== "undefined")
            events[event].push(listener);
        else events[event] = [listener];
    };

    this.emit = function (event) {
        if (typeof events[event] !== "undefined") {
            var listeners = events[event];
            var length = listeners.length, index = length;
            var args = Array.prototype.slice.call(arguments, 1);

            while (index) {
                var listener = listeners[length - (index--)];
                listener.apply(this, args);
            }
        }
    };
}) ;
向原型發(fā)送變化

上面例子中的clones屬性有雙重作用。它可以用來判斷一個對象是否是通過合并繼承自一個原型的,然后他可以用來發(fā)送原型改變給所有它的克隆。原型繼承相比類繼承最大的優(yōu)勢就是你可以修改一個原型在它創(chuàng)建之后。為了使克隆可以繼承對于原型的修改,我們創(chuàng)建了一個叫做define的函數(shù):

Object.prototype.define = function (property, value) {
    this[property] = value;

    if (Object.hasOwnProperty.call(this, "clones")) {
        var clones = this.clones;
        var length = clones.length;

        while (length) {
            var clone = clones[--length];
            if (typeof clone[property] === "undefined")
                clone.define(property, value);
        }
    }
};

現(xiàn)在我們可以修改原型然后這個修改會反映在所有的克隆上。例如我們可以創(chuàng)建創(chuàng)建一個別名addEventListener針對eventEmitter上的on方法:

var square = rectangle.extend(eventEmitter, {
    create: function (side) {
        return rectangle.create.call(this, side, side);
    },
    resize: function (newSize) {
        var oldSize = this.width;
        this.width = this.height = newSize;
        this.emit("resize", oldSize, newSize);
    }
});

var sq = square.create(5);

eventEmitter.define("addEventListener", eventEmitter.on);

sq.addEventListener("resize", function (oldSize, newSize) {
    alert("sq resized from " + oldSize + " to " + newSize + ".");
});

sq.resize(10);
 
alert(sq.area());

藍(lán)圖需要特別注意。盡管對于藍(lán)圖的修改會被發(fā)送到它的克隆,但是藍(lán)圖的新的克隆并不會反映這些修改。幸運(yùn)的是這個問題的解決方法很簡單。我們只需要對blueprint方法進(jìn)行小小的修改,然后任何對于藍(lán)圖的修改就會反映在克隆上了。

function blueprint(f) {
    var g = function () {
        f.apply(this, arguments);
        g.clones.unshift(this);

        var hasOwnProperty = Object.hasOwnProperty;

        for (var property in g)
            if (property !== "clones" &&
                hasOwnProperty.call(g, property))
                    this[property] = g[property];
    };

    g.clones = [];

    return g;
};
結(jié)論

恭喜你。如果你讀完了整篇文章并且理解了我所說的東西,你現(xiàn)在就了解了 原型繼承并且為什么它很重要。很感謝你們看完了這篇文章。我希望這個博客能幫到你們。原型繼承是強(qiáng)大的并且值得更多的信任。然后大部分人從來不明白這個因為Javascript中的原型繼承被構(gòu)造模式所掩蓋了。

譯者注

這篇文章針對幾種繼承方式進(jìn)行了對比。文章中說到的幾種擴(kuò)展的方法我覺得是比較有用的。藍(lán)圖(blueprint,這個實在不知道該怎么翻譯)的擴(kuò)展方式比較像設(shè)計模式中的裝飾者模式,通過函數(shù)對對象進(jìn)行擴(kuò)展,這個是一種比較好玩的擴(kuò)展方式,可以跟原型繼承配合使用。另外文中提到了new關(guān)鍵字的弊端,個人覺得主要的原因還是new關(guān)鍵字的出現(xiàn)掩蓋了Javascript本身原型繼承的特點,人們自然而然就會想到傳統(tǒng)的類繼承,這樣就無法發(fā)揮原型繼承的最大威力。最后說到的屬性修改傳播的問題也挺有意思的,應(yīng)該會有相應(yīng)的應(yīng)用場景。總之,我覺得原型繼承相比于傳統(tǒng)的類繼承提供了更大的靈活性,可以給我們開發(fā)者提供很大的發(fā)揮空間,不過不管怎樣,到最后還是要涉及到基本的原型繼承的原理上,所以掌握了原型繼承的原理就可以根據(jù)不同的應(yīng)用場景使用各種各樣的擴(kuò)展方式。

原文地址:http://aaditmshah.github.io/why-prototypal-inheritance-matters/

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

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

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/85552.html

相關(guān)文章

  • 】每個JavaScript 開發(fā)者應(yīng)該了解的10個面試題

    摘要:避免脆弱的基類問題。紅牌警告沒有提到上述任何問題。單向數(shù)據(jù)流意味著模型是單一的事實來源。單向數(shù)據(jù)流是確定性的,而雙向綁定可能導(dǎo)致更難以遵循和理解的副作用。原文地址 1. 你能說出兩種對 JavaScript 應(yīng)用開發(fā)者而言的編程范式嗎? 希望聽到: 2. 什么是函數(shù)編程? 希望聽到: 3. 類繼承和原型繼承的不同? 希望聽到 4. 函數(shù)式編程和面向?qū)ο缶幊痰膬?yōu)缺點? ...

    mykurisu 評論0 收藏0
  • 】【Javascript - 真正的原型繼承

    摘要:操作符構(gòu)造步驟有三步構(gòu)造一個類的實例這個實例是一個空對象,并且他的屬性指向構(gòu)造函數(shù)的原型。不優(yōu)化原生的或自定義的作為構(gòu)造函數(shù)是及其不高效的。 原文地址:Javascript – How Prototypal Inheritance really works 在網(wǎng)上可以看到各種關(guān)于Javascript原型繼承的文章,但Javascript規(guī)范中只提供了new操作符這一種實現(xiàn)原型繼承的方法...

    zoomdong 評論0 收藏0
  • 】《精通使用AngularJS開發(fā)Web App》(三)--- 深入scope,繼承結(jié)構(gòu),事件系

    摘要:比如,我們可以監(jiān)聽事件由實例發(fā)出,然后在任何瀏覽器中就是變化的時候都會得到通知,如下所示每一個作用域?qū)ο蠖紩羞@個方法,可以用來注冊一個作用域事件的偵聽器。這個函數(shù)所扮演的偵聽器在被調(diào)用時會有一個對象作為第一個參數(shù)。 上一篇:【譯】《精通使用AngularJS開發(fā)Web App》(二) 下一篇:【譯】《精通使用AngularJS開發(fā)Web App》(四) 書名:Mastering W...

    wind5o 評論0 收藏0
  • [] 你應(yīng)了解的4種JS設(shè)計模式

    摘要:盡管特定環(huán)境下有各種各樣的設(shè)計模式,開發(fā)者還是傾向于使用一些習(xí)慣性的模式。原型設(shè)計模式依賴于原型繼承原型模式主要用于為高性能環(huán)境創(chuàng)建對象。對于一個新創(chuàng)建的對象,它將保持構(gòu)造器初始化的狀態(tài)。這樣做主要是為了避免訂閱者和發(fā)布者之間的依賴。 2016-10-07 每個JS開發(fā)者都力求寫出可維護(hù)、復(fù)用性和可讀性高的代碼。隨著應(yīng)用不斷擴(kuò)大,代碼組織的合理性也越來越重要。設(shè)計模式為特定環(huán)境下的常見...

    awokezhou 評論0 收藏0
  • JavasScript重難點知識

    摘要:忍者級別的函數(shù)操作對于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對于而言,匿名函數(shù)是一個很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...

    forsigner 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<