本文講述JavaScript中類繼承的實現方式,並比較實現方式的差異。
一、何為繼承
繼承,是子類繼承父類的特徵和行為,使得子類物件具有父類的例項域和方法。 繼承是物件導向程式設計中,不可或缺的一部分。
1.1 優點
減少程式碼冗餘
父類可以為子類提供通用的屬性,而不必因為增加功能,而逐個修改子類的屬性程式碼複用
同上程式碼易於管理和擴充套件
子類在父類基礎上,可以實現自己的獨特功能
1.2 缺點
耦合度高
如果修改父類程式碼,將影響所有繼承於它的子類影響效能
子類繼承於父類的資料成員,有些是沒有使用價值的。但是,在例項化的時候,已經分配了記憶體。所以,在一定程度上影響程式效能。
二、例子
例子以圖書館中的書入庫歸類為例。
以下是簡化後的父類Book
(也可稱為基類)。
目的是通過繼承該父類,產出Computer
(計算機)子類。
並且,子類擁有新方法say
,輸出自己的書名。
function Book(){
this.name = ''; // 書名
this.page = 0; // 頁數
this.classify = ''; // 型別
}
Book.prototype = {
constructor: Book,
init: function(option){
this.name = option.name || '';
this.page = option.page || 0;
this.classify = option.classify || '';
},
getName: function(){
console.log(this.name);
},
getPage: function(){
console.log(this.page);
},
getClassify: function(){
console.log(this.classify);
}
};
複製程式碼
接下來會講解子類Computer
幾種繼承方式的實現和優化方法。開始飆車~
三、例項式繼承
function Computer(){
Book.apply(this, arguments);
}
Computer.prototype = new Book();
Computer.prototype.constructor = Computer;
Computer.prototype.init = function(option){
option.classify = 'computer';
Book.prototype.init.call(this, option);
};
Computer.prototype.say = function(){
console.log('I\'m '+ this.name);
}
複製程式碼
3.1 呼叫父類構造器進行初始化
function Computer(){
Book.apply(this, arguments);
}
複製程式碼
Computer
的建構函式裡,呼叫父類的建構函式進行初始化操作。使子類擁有父類一樣的初始化屬性。
3.2 將父類的原型傳遞給子類
Computer.prototype = new Book();
使用new操作符對父類Book
進行例項化,並將例項物件賦值給子類的prototype
。
這樣,子類Computer
就可以通過原型鏈訪問到父類的屬性。
3.3 缺點
- 父類
Book
的建構函式被執行了2次- 一次是在
Computer
的建構函式裡Book.apply(this, arguments);
- 一次是在
Computer.prototype = new Book();
這種模式,存在一定的效能浪費。
- 一次是在
- 父類例項化無法傳參
Computer.prototype = new Book();
,這種例項化方式,無法讓Book
父類接收不固定的引數集合。
四、原型式繼承
function Computer(){
Book.apply(this, arguments);
}
Computer.prototype = Object.create(Book.prototype);
Computer.prototype.constructor = Computer;
Computer.prototype.init = function(option){
option.classify = 'computer';
Book.prototype.init(option);
};
Computer.prototype.say = function(){
console.log('I\'m '+ this.name);
}
複製程式碼
這裡的改進:是使用Object.create(Book.prototype)
。它的作用是返回一個繼承自原型物件Book.prototype
的新物件。且該物件下的屬性已經初始化。
用Object.create
生成新物件,並不會呼叫到Book
的建構函式。
這種方式,也可以通過原型鏈實現繼承。
五、Object.create的簡單版相容
由於低版本的瀏覽器是不支援Object.create
的。所以這裡簡單介紹下相容版本:
Object.create = function(prototype){
function F(){}
F.prototype = prototype;
return new F();
}
複製程式碼
原理是定義一個空的建構函式,然後修改其原型,使之成為一個跳板,可以將原型鏈傳遞到真正的prototype。
六、函式化繼承
上述兩種實現方式,都存在一個問題:不存在私有屬性
和私有方法
。也就是說,存在被篡改的風險。
接下來就用函式化來化解這個問題。
function book(spec, my){
var that = {};
// 私有變數
spec.name = spec.name || ''; // 書名
spec.page = spec.page || 0; // 頁數
spec.classify = spec.classify || ''; // 型別
var getName = function(){
console.log(spec.name);
};
var getPage = function(){
console.log(spec.page);
};
var getClassify = function(){
console.log(spec.classify);
};
that.getName = getName;
that.getPage = getPage;
that.getClassify = getClassify;
return that;
}
function computer(spec, my){
spec = spec || {};
spec.classify = 'computer';
var that = book(spec, my);
var say = function(){
console.log('I\'m '+ spec.name);
};
that.say = say;
return that;
}
var Ninja = computer({name: 'JavaScript忍者祕籍', page: 350});
複製程式碼
函式化的優勢,就是可以更好地進行封裝和資訊隱藏。
也許有人疑惑為什麼用以下這種方式宣告和暴露方法:
var say = function(){
console.log('I\'m '+ spec.name);
};
that.say = say;
複製程式碼
其實是為了保護物件自身的完整性。即使that.say
被外部篡改或破壞掉,function computer
內部的say
方法仍然能夠正常工作。
另外,解釋下that
、spec
和my
的作用:
that
是一個公開資料儲存容器,暴露出去的資料介面,都放到這個容器spec
是用來儲存建立新例項所需的資訊,屬於例項之間共同編輯的資料my
是用來儲存父類、子類之間共享的私密資料容器,外部是訪問不到的。
七、ES6繼承
最後,看下現代版ES6的類繼承。不禁感慨以前的刀耕火種,是多麼折磨人?
class Book {
constructor(){
this.name = ''; // 書名
this.page = 0; // 頁數
this.classify = ''; // 型別
}
init(option) {
this.name = option.name || '';
this.page = option.page || 0;
this.classify = option.classify || '';
}
getName() {
console.log(this.name);
}
getPage (){
console.log(this.page);
}
getClassify (){
console.log(this.classify);
}
}
class Computer extends Book{
constructor(...args){
super(...args);
}
init(option) {
super.init(option);
this.classify = 'computer';
}
say() {
console.log('I\'m '+ this.name);
}
}
複製程式碼
結語
雖然ES5終究會被淘汰,但是瞭解下其工作原理,還是很有必要。因為很多原始碼還是有用到裡面的模式。
附帶的價值就是,ES5的繼承玩到飛起,ES6的繼承就是小菜一碟。
喜歡我文章的朋友,可以通過以下方式關注我:
- 「star」 或 「watch」 我的GitHub blog
- RSS訂閱我的個人部落格:王先生的基地