JavaScript中主要有三種實現繼承的方式,分別是函數
其中前兩種方式都有其缺陷。第三種方式組合繼承則將前兩種方式結合起來,取長補短,是JS繼承最經常使用的最佳實踐。本文結合代碼和註釋逐一闡述三種繼承方式。this
構造函數繼承的關鍵: 在Child構造函數中執行Parent.call(this)
。prototype
function Parent(name) { this.name = name; this.hobby = []; this.speak = function() { console.log("Parent speak"); } // 缺點1:new多個Child時,Parent構造函數中的方法會在每一個Child中拷貝一份,浪費內存 } Parent.prototype.say = function() { console.log("Parent say"); } // 缺點2:Parent原型對象上的方法不會被Child繼承 function Child(name, type) { Parent.call(this, name); // 構造函數繼承的關鍵 this.type = type; }
原型繼承的關鍵: 設置Child原型指向ParentChild.prototype = new Parent()
。code
function Parent(name) { this.name = name; this.hobby = []; // 缺點:Parent的引用屬性會被全部Child實例共享,互相干擾 } Parent.prototype.say = function() { console.log("Parent say"); } function Child(type) { this.type = type; } Child.prototype = new Parent(); // 原型繼承的關鍵
組合繼承的關鍵:對象
function Parent(name) { this.name = name; this.hobby = []; // 屬性放在構造函數中 } Parent.prototype.say = function() { // 方法放在原型中 console.log("Parent say"); } function Child(name, type) { Parent.call(this, name); // Child繼承Parent屬性(構造函數繼承) this.type = type; // Child擴展屬性 } Child.prototype = Object.create(Parent.prototype); // Child繼承Parent方法(原型繼承) Child.prototype.speak = function() { // Child擴展方法 console.log("Child speak"); } Child.prototype.constructor = Child; // 修復Child的constructor指向,不然Child的constructor會指向Parent
補充:
對於組合繼承代碼中的Child.prototype = Object.create(Parent.prototype)
,還有兩種常見的相似寫法是Child.prototype = Parent.prototype
和Child.prototype = new Parent()
,但這兩種寫法都是有缺陷的,須要避免:繼承
Child.prototype = Parent.prototype
,修改Child.prototype就等於修改Parent.prototype,會干擾全部Parent實例。Child.prototype = new Parent()
,Parent構造函數重複調用了兩次(另外一處調用是Child構造函數中的Parent.call(this)
),浪費效率,且若是Parent構造函數有反作用,重複調用可能形成不良後果。