本文講述JavaScript中類繼承的實現方式,並比較實現方式的差異。 一、何為繼承 繼承,是子類繼承父類的特征和行為,使得子類對象具有父類的實例域和方法。 繼承是面向對象編程中,不可或缺的一部分。 1.1 優點 父類可以為子類提供通用的屬性,而不必因為增加功能,而逐個修改子類的屬性 同上 子類在父 ...
本文講述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的類繼承。不禁感慨以前的刀耕火種,是多麼折磨人