摘要:使用構造函數的原型繼承相比使用原型的原型繼承更加復雜,我們先看看使用原型的原型繼承上面的代碼很容易理解。相反的,使用構造函數的原型繼承像下面這樣當然,構造函數的方式更簡單。
五天之前我寫了一個關于ES6標準中Class的文章。在里面我介紹了如何用現有的Javascript來模擬類并且介紹了ES6中類的用法,其實它只是一個語法糖。感謝Om Shakar以及Javascript Room中的各位,我的編程風格從那時候開始發生了改變;就像Dougla Crockford2006年做的一樣,我也學習了很多來完全理解基于原型的編程方式。
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關鍵字掩蓋了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) ;
上面例子中rect從rectangle中繼承了area方法。同時注意到rectangle是一個對象字面量。對象字面量是一個簡潔的方法用來創建一個Object.prototype的克隆然后用新的屬性來擴展它。它等價于:
var rectangle = Object.create(Object.prototype) ;
rectangle.area = function(){
return this.width * this.height ;
} ;
擴展一個新創建的對象
上面的例子中我們克隆了rectangle對象命名為rect,但是在我們使用rect的area方法之前我們需要擴展它的width和height屬性,像下面這樣:
rect.width = 5 ; rect.height = 10 ; alert(rect.area()) ;
然而這種方式來創建一個對象的克隆然后擴展它是一個非常傻缺的方法。我們需要在每個rectangle對象的克隆上手動定義width和height屬性。如果有一個方法能夠為我們來完成這些工作就很好了。是不是聽起來有點熟悉?確實是。我要來說說構造函數。我們把這個函數叫做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方法中調用rectangle的create函數并且返回對象。相反的,使用構造函數的原型繼承像下面這樣:
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接口并且在每個實現該接口的類中分別實現on和emit方法。當然你在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 | 藍圖 |
|---|---|
| 它們用來擴展對象的原型。因此對象共享同一個原型 | 它們用來擴展新創建的對象。因此每個對象都是在自己對象本身進行修改 |
| 因為缺少封裝方法所以不存在私有狀態 | 它們是函數,所以可以封裝私有狀態 |
| 它們是靜態原型并且不能被自定義 | 它們可以傳遞參數來自定義對象,可以向藍圖函數傳遞一些用來自定義的參數 |
許多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本身原型繼承的特點,人們自然而然就會想到傳統的類繼承,這樣就無法發揮原型繼承的最大威力。最后說到的屬性修改傳播的問題也挺有意思的,應該會有相應的應用場景。總之,我覺得原型繼承相比于傳統的類繼承提供了更大的靈活性,可以給我們開發者提供很大的發揮空間,不過不管怎樣,到最后還是要涉及到基本的原型繼承的原理上,所以掌握了原型繼承的原理就可以根據不同的應用場景使用各種各樣的擴展方式。
最后,安利下我的個人博客,歡迎訪問: http://bin-playground.top原文地址:http://aaditmshah.github.io/why-prototypal-inheritance-matters/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.hztianpu.com/yun/85552.html
摘要:避免脆弱的基類問題。紅牌警告沒有提到上述任何問題。單向數據流意味著模型是單一的事實來源。單向數據流是確定性的,而雙向綁定可能導致更難以遵循和理解的副作用。原文地址 1. 你能說出兩種對 JavaScript 應用開發者而言的編程范式嗎? 希望聽到: 2. 什么是函數編程? 希望聽到: 3. 類繼承和原型繼承的不同? 希望聽到 4. 函數式編程和面向對象編程的優缺點? ...
摘要:操作符構造步驟有三步構造一個類的實例這個實例是一個空對象,并且他的屬性指向構造函數的原型。不優化原生的或自定義的作為構造函數是及其不高效的。 原文地址:Javascript – How Prototypal Inheritance really works 在網上可以看到各種關于Javascript原型繼承的文章,但Javascript規范中只提供了new操作符這一種實現原型繼承的方法...
摘要:比如,我們可以監聽事件由實例發出,然后在任何瀏覽器中就是變化的時候都會得到通知,如下所示每一個作用域對象都會有這個方法,可以用來注冊一個作用域事件的偵聽器。這個函數所扮演的偵聽器在被調用時會有一個對象作為第一個參數。 上一篇:【譯】《精通使用AngularJS開發Web App》(二) 下一篇:【譯】《精通使用AngularJS開發Web App》(四) 書名:Mastering W...
摘要:盡管特定環境下有各種各樣的設計模式,開發者還是傾向于使用一些習慣性的模式。原型設計模式依賴于原型繼承原型模式主要用于為高性能環境創建對象。對于一個新創建的對象,它將保持構造器初始化的狀態。這樣做主要是為了避免訂閱者和發布者之間的依賴。 2016-10-07 每個JS開發者都力求寫出可維護、復用性和可讀性高的代碼。隨著應用不斷擴大,代碼組織的合理性也越來越重要。設計模式為特定環境下的常見...
摘要:忍者級別的函數操作對于什么是匿名函數,這里就不做過多介紹了。我們需要知道的是,對于而言,匿名函數是一個很重要且具有邏輯性的特性。通常,匿名函數的使用情況是創建一個供以后使用的函數。 JS 中的遞歸 遞歸, 遞歸基礎, 斐波那契數列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執行機制 本文的目的就是要保證你徹底弄懂javascript的執行機制,如果...
閱讀 985·2021-11-22 09:34
閱讀 4000·2021-09-22 15:42
閱讀 1600·2021-09-03 10:28
閱讀 1323·2021-08-26 14:13
閱讀 2152·2019-08-29 15:41
閱讀 1612·2019-08-29 14:12
閱讀 3540·2019-08-26 18:36
閱讀 3529·2019-08-26 13:47