亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 網站 > WEB開發 > 正文

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

2024-04-27 15:15:21
字體:
來源:轉載
供稿:網友

javascript是一個多樣化的編程語言。它擁有面向對象和函數式的編程特點,你可以使用任何一種風格來編寫代碼。然而這兩個編程風格并不能很好的融合。例如,你不無法同時使用new(典型的面向對象的特點)和apply(函數式編程的特點).原型繼承一直都作為連接這兩種風格的橋梁。

基于類繼承的問題

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

Javascript中的問題是由于每個函數都可以被當成構造函數使用,所以我們需要區分普通的函數調用和構造函數調用;我們一般使用new關鍵字來進行區別。然而,這樣就破壞了Javascript中的函數式特點,因為new是一個關鍵字而不是函數。因而函數式的特點無法和對象實例化一起使用。

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

 

考慮上面這段程序。你可以通過new關鍵字來調用Person方法來創建一個函數Person的實例:

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

 

然而,沒有任何辦法來使用apply方法來為構造函數指定參數列表:

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

 

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

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

 

幸運的是,因為Javascript有原型繼承,所以我們可以實現一個new的函數:

復制代碼
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關鍵字來實例化的語言中,上面這種方式是不可能實現的。

下面這張表列出了原型繼承相比于基于類的基礎的優點:

基于類的繼承原型繼承
類是不可變的。在運行時,你無法修改或者添加新的方法原型是靈活的。它們可以是不可變的也可以是可變的
類可能會不支持多重繼承對象可以繼承多個原型對象
基于類的繼承比較復雜。你需要使用抽象類,接口和final類等等原型繼承比較簡潔。你只有對象,你只需要對對象進行擴展就可以了

不要再使用關鍵詞new了

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

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

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

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

這個設計決策導致了new的問題。當人們看到Javascript中的new關鍵字,他們就想到類,然后當他們使用繼承時就遇到了傻了。就像Douglas Crockford說的:

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

因此我建議停止使用new關鍵字。Javascript在傳統面向對象假象下面有著更加強大的原型系統。然大部分程序員并沒有看見這些還處于黑暗中。

理解原型繼承

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

“無中生有”對象創建法

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

var object = Object.create(null) ;

 

上面例子中新創建的object沒有任何屬性。

克隆一個現有的對象

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

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

 

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

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

 

擴展一個新創建的對象

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

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

 

然而這種方式來創建一個對象的克隆然后擴展它是一個非常傻缺的方法。我們需要在每個rectangle對象的克隆上手動定義widthheight屬性。如果有一個方法能夠為我們來完成這些工作就很好了。是不是聽起來有點熟悉?確實是。我要來說說構造函數。我們把這個函數叫做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()) ;復制代碼

 

構造函數 VS 原型

等等。這看起來很像Javascript中的正常構造模式:

復制代碼
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原型模式被迫屈服于構造模式。因此每個Javascript中的函數都有一個prototype對象然后可以用來作為構造器(這里構造器的意思應該是說新的對象是在prototype對象的基礎上進行構造的)。new關鍵字允許我們把函數當做構造函數使用。它會克隆構造函數的prototype屬性然后把它綁定到this對象中,如果沒有顯式返回對象則會返回this。

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

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

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

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

 

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

復制代碼
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()) ;復制代碼

 

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

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

對象創建和擴展相結合

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

復制代碼
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')            //這段代碼有問題,按照文章意思,這里應該使用深復制,而不是簡單的淺復制,deepClone(extension[property],object[property]),deepClone的實現可以看我之前關于繼承的博客            object[properyty] = extension[property] ;    }    return object ;} ;復制代碼

 

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

復制代碼
    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函數的超集,因此它可以用在對象的創建和擴展上。因此我們可以用extend來重寫rectangle,使得create函數更加結構化看起來就像模塊模式。

復制代碼
var rectangle = {    create : function(width,height){        return this.extend({            height : height ,            width : width        }) ;    }} ;var rect = rectangle.create(5,10) ;alert(rect.area()) ;復制代碼

 

原型繼承的兩種方法

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

委派(差異化繼承)

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

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

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

當你試圖獲取一個對象的屬性時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中屬性查找的過程就像上面的程序那樣。

克隆(合并式繼承)

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

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

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

從多個原型繼承

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

復制代碼
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'){                //這里同樣應該使用深復制                object[property] = extension[property] ;            }        }    }    return object;} ;復制代碼

 

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

復制代碼
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) ;            }        }    }} ;復制代碼

 

 

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

復制代碼
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中是不可能實現上面的程序的,因為它不支持多重繼承。相應的你必須另外再創建一個EventEmitter類或者使用一個EventEmitter接口并且在每個實現該接口的類中分別實現onemit方法。當然你在C++中不需要面對這個問題。我們都知道Java sucks(呵呵呵)。

Mixin的藍圖(Buleprint)

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

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

復制代碼
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) ;        }    } ;} ;復制代碼

 

一個藍圖用來在一個對象創建之后通過合并來擴展它(我覺得有點像裝飾者模式)。Eric Elliot把它們叫做閉包原型。我們可以使用藍圖版本的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()) ;復制代碼

 

藍圖在Javascript中是獨一無二的。它是一個很強大的特性。然而它們也有自己的缺點。下表列出了mixin和藍圖的優缺點:

Mixin藍圖
它們用來擴展對象的原型。因此對象共享同一個原型它們用來擴展新創建的對象。因此每個對象都是在自己對象本身進行修改
因為缺少封裝方法所以不存在私有狀態它們是函數,所以可以封裝私有狀態
它們是靜態原型并且不能被自定義它們可以傳遞參數來自定義對象,可以向藍圖函數傳遞一些用來自定義的參數

修復instanceof操作

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

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

 

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

sq.instanceof(square) ;

 

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

復制代碼
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;} ;復制代碼

 

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

復制代碼
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方法現在可以用來判斷一個對象是否是通過合并繼承自一個原型。例如:

sq.instanceof(eventEmitter);

 

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

復制代碼
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);            }        }    };}) ;復制代碼

 

向原型發送變化

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

復制代碼
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);        }    }};復制代碼

 

現在我們可以修改原型然后這個修改會反映在所有的克隆上。例如我們可以創建創建一個別名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());復制代碼

 

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

復制代碼
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;};復制代碼

 

結論

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

譯者注

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


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


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩欧美亚洲国产一区| 清纯唯美日韩制服另类| 国产精品91久久久| 欧美激情亚洲自拍| 欧美日韩国产中文精品字幕自在自线| 欧美电影在线观看高清| 国产欧美一区二区三区久久| 国产精品久久久久久久av大片| 亚洲美女精品成人在线视频| 久久久久久成人| 中文字幕久精品免费视频| 国产精品综合久久久| 久久精品99久久久久久久久| 成人羞羞国产免费| 欧美精品videossex88| 国产日本欧美一区二区三区在线| 亚洲一区二区三区香蕉| 欧美日韩国产影院| 亚洲老板91色精品久久| 91精品久久久久久久久久| 欧美性视频网站| 亚洲自拍偷拍色片视频| 日韩精品视频在线| 日韩av免费在线播放| 国产在线观看91精品一区| 久久久视频在线| 欧美成年人视频网站欧美| 欧美激情免费看| 亚洲色图国产精品| 日韩精品在线观看一区二区| 亚洲天堂av图片| 日韩电视剧在线观看免费网站| 国模极品一区二区三区| 久久久精品2019中文字幕神马| 日韩一区二区久久久| 在线观看欧美日韩| 欧美二区在线播放| 久久久视频在线| 欧美精品一区三区| 国产精品99久久久久久白浆小说| 国产精品99久久久久久白浆小说| 亚洲国产成人精品久久| 欧美视频在线看| 色噜噜国产精品视频一区二区| 97精品视频在线播放| 91在线观看免费观看| 中文字幕自拍vr一区二区三区| 亚洲国产精品久久精品怡红院| 国产91久久婷婷一区二区| 亚洲网址你懂得| 国产主播在线一区| 国产精品吹潮在线观看| 亚洲最大福利视频网| 最近中文字幕mv在线一区二区三区四区| 亚洲性猛交xxxxwww| 日韩禁在线播放| 欧美第一页在线| 亚洲国产精品久久91精品| 成人久久18免费网站图片| 久久久999精品| 国产精品日韩在线播放| 91久久精品国产91久久性色| 国产亚洲精品久久久久久牛牛| 日韩中文字幕在线观看| 日韩美女福利视频| 亚洲国产精品yw在线观看| 51视频国产精品一区二区| 欧洲成人免费aa| 成人福利在线观看| 原创国产精品91| 亚洲国产小视频在线观看| 色综合亚洲精品激情狠狠| 久久久精品国产网站| 亚洲乱亚洲乱妇无码| 亚洲成人教育av| 91成人在线播放| 亚洲性日韩精品一区二区| 影音先锋欧美精品| 国产精品7m视频| 日本免费久久高清视频| 欧美体内谢she精2性欧美| 欧美又大粗又爽又黄大片视频| 成人乱色短篇合集| 国产一区二区丝袜高跟鞋图片| 欧美日本中文字幕| 国产成人自拍视频在线观看| 欧美性xxxxx极品娇小| 亚洲欧美日韩图片| 91视频国产精品| 欧美电影免费观看网站| 亚洲一区二区三区在线视频| 丝袜亚洲欧美日韩综合| 国产色视频一区| 成人观看高清在线观看免费| 日韩精品小视频| 日韩69视频在线观看| 中文国产亚洲喷潮| 欧美精品少妇videofree| 久久99国产精品自在自在app| 亚洲精品美女在线观看播放| 国产精品视频午夜| 亚洲二区在线播放视频| 精品在线小视频| 97av在线播放| 欧美成人精品一区二区三区| 97久久精品在线| 欧美日韩国产一区二区| 欧美重口另类videos人妖| 国产日韩精品入口| 欧美激情女人20p| 国产香蕉精品视频一区二区三区| 久久精品成人动漫| 欧美国产日韩在线| 久久av.com| 久热在线中文字幕色999舞| 国产精品美女www| 亚洲乱码av中文一区二区| 欧美电影在线观看高清| 国产精品极品在线| 午夜精品三级视频福利| 精品亚洲aⅴ在线观看| 国产精品日韩在线观看| 亚洲国产第一页| 欧美成人在线免费| 伊人久久男人天堂| 欧美国产日韩二区| 成人深夜直播免费观看| 91av视频在线| 全球成人中文在线| 久久视频在线免费观看| 亚洲va男人天堂| 日韩亚洲精品视频| 最近2019年手机中文字幕| 国产视频精品自拍| 中文字幕成人精品久久不卡| 国产99在线|中文| 国产一区二区三区直播精品电影| 亚洲色图av在线| 欧美激情a在线| 午夜免费日韩视频| 午夜精品久久久久久久白皮肤| 亚洲精品一区中文字幕乱码| 日韩综合中文字幕| 久久久久久久久久久人体| 欧美日韩免费在线| 日韩一区视频在线| 国产精品久久久久久久久久小说| 欧美日韩成人在线观看| 亚洲国产欧美久久| 国产香蕉精品视频一区二区三区| 国产精品视频一区二区三区四| 91av视频导航| 日韩免费av一区二区| 国产亚洲精品久久久久动| 精品国产一区久久久| 久久久久久久激情视频| 亚洲第一视频在线观看| 亚洲国产日韩欧美在线图片| 久久久久久国产精品| 中文字幕欧美日韩| 韩国视频理论视频久久| 精品日本美女福利在线观看| 国产成+人+综合+亚洲欧洲| 精品国产区一区二区三区在线观看|