編寫Javascript的開發者都知道,JS雖然沒有類(ES6添加了class語法),可是能夠模擬出OOP語言的類和麪向對象的概念,好比咱們都知道的一句話,Javascript中到處是對象,而面嚮對象語言的特性是繼承,封裝,多態,抽象,而本文討論的是Javascript的繼承,Javascript的繼承方式有原型繼承,組合繼承,寄生繼承等等,在平常開發中,哪一種繼承方式更好用在於開發者對於程序的結果以及性能的考慮。筆者在下面列舉出原型繼承中常常容易被忽略的錯誤。c++
常見錯誤一:函數
function Fa(name){ this.name=name; } Fa.prototype.myname=function(){ //設置Fa的原型方法 console.log(this.name); } function Son(name){ Fa.call(this,name); //將Fa的this顯示綁定給Son,這裏的this顯示綁定機制後續筆者會更新 } Son.prototype=Fa.prototype; //將son的原型對象引用到fa的原型對象,其實就是c++裏面的指針概念,直接指向fa的原型 //注意!!!!!:當你修改Son.prototype的constructor時
// Son.prototype.constructor=Son; // 這樣會致使Fa建立出來的對象的constructor也指向了Son,會使對象的類型變的很混亂 Son.prototype.sayhello=function(){ //設置son的原型方法 console.log("this is son"); } //建立對象 var son=new Son("son"); son.sayhello(); //"this is son" console.log(son); var fa=new Fa("fa"); fa.sayhello(); //"this is son" console.log(fa); //這裏發如今由Fa建立的對象中也存在sayhello方法,這是由於son.prototype直接引用了Fa.prototype // Son.prototype=Fa.prototype; 並不會建立一個關聯到 Son.prototype 的新對象,它只 //是讓 Son.prototype 直接引用 Fa.prototype 對象。所以當你執行相似 Son.prototype. //sayhello = ... 的賦值語句時也會直接修改 Fa.prototype 對象自己。顯然這不是你想要的結 //果,不然你根本不須要 Son 對象,直接使用 Fa就能夠了,這樣一來代碼也會更簡單一些。
常見錯誤二:性能
function Fa(name){ this.name=name; } Fa.prototype.myname=function(){ console.log(this.name); } function Son(name){ Fa.call(this,name); } Son.prototype=new Fa("fa"); //調用Fa的構造函數new一個新的對象關聯給Son.prototype Son.prototype.sayhello=function(){ console.log("this is son"); } var son=new Son("son"); console.log(son); son.myname(); //son var fa=new Fa("fa"); console.log(fa); fa.sayhello(); //這裏會報錯。Uncaught TypeError: fa.sayhello is not a function //咱們發如今son的原型建立的方法並無影響到Fa的原型。可是在Son.prototype = new Fa()後, // var son=new Son("son");咱們輸出son.name的值爲son,在原型上有一個Fa實例對象,這個實例對象也有name屬性 //而輸出son是由於原型鏈上的隱式屏蔽,這一層的屬性會屏蔽上一層相同屬性的值。 // Son.prototype = new Fa() 的確會建立一個關聯到 Son.prototype 的新對象。可是它使用 //了 Fa(..) 的「構造函數調用」,若是函數 Foo 有一些反作用(好比寫日誌、修改狀態、注 //冊到其餘對象、給 this 添加數據屬性,等等)的話,就會影響到 Son() 的「後代」,後果 //不堪設想 也會讓原型變的臃腫
正確作法:this
function Fa(name){ this.name=name; } Fa.prototype.myname=function(){ console.log(this.name); } function Son(name){ Fa.call(this,name); }
Son.prototype=Object.create(Fa.prototype); //注意這一句 //當修改son的原型時。son的constructor也會指向fa,這裏須要手動修改constructor //ES6方法 屬性描述符 //IE8如下不兼容 Object.defineProperty(Son.prototype,"constructor",{ writable:true, //讀寫屬性 ,爲false時爲只讀,外界沒法修改 configurable:true, //配置屬性,爲false時,外界沒法刪除該屬性,好比delete Son.prototype.constructor會失效 在嚴格模式下會報錯
enumerable:false, //枚舉屬性,此時爲false,在外界的for in訪問方法不會列舉出該屬性,爲true時反之; value:Son //將constructor關聯到Son }); var son=new Son("son"); console.log(son); son.myname(); console.log(son instanceof Son); //true // 這段代碼的核心部分就是語句 son.prototype = Object.create( fa.prototype ) 。調用 // Object.create(..) 會憑空建立一個「新」對象並把新對象內部的 Prototype 關聯到你 // 指定的對象(本例中是 fa.prototype )。 // 換句話說,這條語句的意思是:「建立一個新的 son.prototype 對象並把它關聯到 fa. // prototype 」。
這裏咱們對比一下三個方法:spa
和你想要的機制不同!
Son.prototype = Fa.prototype;
基本上知足你的需求,可是可能會產生一些反作用 :(
Son.prototype = new Fa();prototype
使用 Object.create(..) 而不是使用具備副指針
做用的 Fa(..) 。這樣作惟一的缺點就是須要建立一個新對象而後把舊對象拋棄掉,不能
直接修改已有的默認對象。日誌
若是能有一個標準而且可靠的方法來修改對象的 Prototype關聯就行了。在 ES6 以前,
咱們只能經過設置 .__proto__ 屬性來實現,可是這個方法並非標準而且沒法兼容全部瀏
覽器。ES6 添加了輔助函數 Object.setPrototypeOf(..) ,能夠用標準而且可靠的方法來修
改關聯。code
ES6 以前須要拋棄默認的 Son.prototype
Son.ptototype = Object.create( Fa.prototype );對象
ES6 開始能夠直接修改現有的 Son.prototype
Object.setPrototypeOf( Son.prototype,Fa.prototype );
若是忽略掉 Object.create(..) 方法帶來的輕微性能損失(拋棄的對象須要進行垃圾回收)
它實際上比 ES6 及其以後的方法更短並且可讀性更高。不過不管如何,這是兩種完
全不一樣的語法。
以上是筆者對於原型繼承中經常忽略的錯誤的總結以及更好的解決方法,至於寄生繼承之類的方法並非本文討論的範圍,後續筆者也會更新寄生繼承的方法,若是忽視這些細節上的錯誤,對後續程序運行的結果也會產生一些不可預知的結果,細節決定成敗。