上一篇我講了下繼承的基礎知識-原型和原型鏈。看到有人讀完個人技術分享後而有所得,我很開心;看到有人提意見我也虛心接受。前端
在講繼承的幾種方式前我打算先說一下——《孔乙己》。es6
《孔乙己》一文中我印象最深的是孔己乙的一個動做和一句對白一個提問。面試
一個動做:排出九文大錢
一句對白:竊書不能算偷……讀書人的事,能算偷麼
一個提問:回香豆的回字,怎樣寫的bash
孔乙己這種深受科舉教育毒害的讀書人,常會注意一些沒有用的字,並且把這當作學問和本領。會‘回’的幾種寫法就是有本領嗎?微信
我正思考這個問題時。好像有一個面試官在回答: 會‘回’的幾種寫法是否是本領我不清楚,不過我想知道你會幾種繼承的寫法。網絡
So 正月初七開工大吉,瞭解繼承的幾種方式,不失爲一種有趣的迎新方式。數據結構
在 JavaScript
中繼承是很是重要的一個概念。咱們有必要去了解,請你們多指教。app
目的:簡化代碼邏輯和結構,實現代碼重用函數
接下來咱們一塊兒學習下 8 種 JavaScript
實現繼承的方法。post
推薦組合繼承(四)、寄生組合式繼承(七)、ES6 繼承(八)
基本思想是利用原型讓一個引用類型繼承另外一個引用類型的方法和實例。
function staff(){
this.company = 'ABC';
}
staff.prototype.companyName = function(){
return this.company;
}
function employee(name,profession){
this.employeeName = name;
this.profession = profession;
}
// 繼承 staff
employee.prototype = new staff();
// 將這個對象的 constructor 手動改爲 employee,不然還會是 staff
employee.prototype.constructor = employee;
// 不使用對象字面量方式建立原型方法,會重寫原型鏈
employee.prototype.showInfo = function(){
return this.employeeName + "'s profession is " + this.profession;
}
let instance = new employee('Andy','front-end');
// 測試
console.log(instance.companyName()); // ABC
console.log(instance.showInfo()); // "Andy's profession is front-end"
// 經過 hasOwnProperty() 方法來肯定自身屬性與其原型屬性
console.log(instance.hasOwnProperty('employeeName')) // true
console.log(instance.hasOwnProperty('company')) // false
// 經過 isPrototypeOf() 方法來肯定原型和實例的關係
console.log(employee.prototype.isPrototypeOf(instance)); // true
console.log(staff.prototype.isPrototypeOf(instance)); // true
console.log(Object.prototype.isPrototypeOf(instance)); // true
複製代碼
原型鏈實現繼承最大的問題是:
當原型中存在引用類型值時,實例能夠修改其值。
function staff(){
this.test = [1,2,3,4];
}
function employee(name,profession){
this.employeeName = name;
this.profession = profession;
}
employee.prototype = new staff();
let instanceOne = new employee();
let instanceTwo = new employee();
instanceOne.test.push(5);
console.log(instanceTwo.test); // [1, 2, 3, 4, 5]
複製代碼
鑑於此問題:因此咱們在實踐中會少單獨使用原型鏈實現繼承。
hasOwnProperty()
方法來肯定自身屬性與其原型屬性isPrototypeOf()
方法來肯定原型和實例的關係此方法和方法一區別就是將:
employee.prototype = new staff();
複製代碼
改爲:
Employee.prototype = Person.prototype;
複製代碼
此方法能夠解決原型中引用類型值被修改的問題
function staff(){
this.test = [1,2,3];
}
staff.prototype.companyName = function(){
return this.company;
}
function employee(name,profession){
staff.call(this);
this.employeeName = name;
this.profession = profession;
}
// 不使用對象字面量方式建立原型方法,會重寫原型鏈
employee.prototype.showInfo = function(){
return this.employeeName + "'s profession is " + this.profession;
}
let instanceOne = new employee('Andy','front-end');
let instanceTwo = new employee('Mick','after-end');
instanceOne.test.push(4);
// 測試
console.log(instanceTwo.test); // [1,2,3]
// console.log(instanceOne.companyName()); // 報錯
// 經過 hasOwnProperty() 方法來肯定自身屬性與其原型屬性
console.log(instanceOne.hasOwnProperty('test')) // true
// 經過 isPrototypeOf() 方法來肯定原型和實例的關係
console.log(staff.prototype.isPrototypeOf(instanceOne)); // false
複製代碼
從上面的結果能夠看出:
instanceOne
與 staff
已經沒有原型鏈的關係了指的是將原型鏈技術和借用構造函數技術結合起來,兩者皆取其長處的一種經典繼承方式。
function staff(){
this.company = "ABC";
this.test = [1,2,3];
}
staff.prototype.companyName = function(){
return this.company;
}
function employee(name,profession){
// 繼承屬性
staff.call(this);
this.employeeName = name;
this.profession = profession;
}
// 繼承方法
employee.prototype = new staff();
employee.prototype.constructor = employee;
employee.prototype.showInfo = function(){
return this.employeeName + "'s profession is " + this.profession;
}
let instanceOne = new employee('Andy','front-end');
let instanceTwo = new employee('Mick','after-end');
instanceOne.test.push(4);
// 測試
console.log(instanceTwo.test); // [1,2,3]
console.log(instanceOne.companyName()); // ABC
// 經過 hasOwnProperty() 方法來肯定自身屬性與其原型屬性
console.log(instanceOne.hasOwnProperty('test')) // true
// 經過 isPrototypeOf() 方法來肯定原型和實例的關係
console.log(staff.prototype.isPrototypeOf(instanceOne)); // true
複製代碼
staff
會被調用 2 次:第 1 次是employee.prototype = new staff();
,第 2 次是調用 staff.call(this)
。利用一個臨時性的構造函數(空對象)做爲中介,將某個對象直接賦值給構造函數的原型。
function object(obj){
function F(){}
F.prototype = obj;
return new F();
}
複製代碼
本質上 object()
對傳入其中的對象執行了一次淺複製,將構造函數 F
的原型直接指向傳入的對象。
var employee = {
test: [1,2,3]
}
let instanceOne = object(employee);
let instanceTwo = object(employee);
// 測試
instanceOne.test.push(4);
console.log(instanceTwo.test); // [1, 2, 3, 4]
複製代碼
另,ES5 中存在 Object.create()
的方法規範化了原型式繼承,可以代替 object
方法。
要點:在原型式繼承的基礎上,經過封裝繼承過程的函數加強對象,返回對象
function createAnother(original){
var clone = object(original); // 經過調用 object() 函數建立一個新對象
clone.sayHi = function(){ // 以某種方式來加強對象
alert("hi");
};
return clone; // 返回這個對象
}
複製代碼
createAnother
函數的主要做用是爲構造函數新增屬性和方法,以加強函數。
該方法主要是解決組合繼承調用兩次超類構造函數的問題。
function inheritPrototype(sub, super){
var prototype = Object.create(super.prototype); // 建立對象,父原型的副本
prototype.constructor = sub; // 加強對象
sub.prototype = prototype; // 指定對象,賦給子的原型
}
function staff(){
this.company = "ABC";
this.test = [1,2,3];
}
staff.prototype.companyName = function(){
return this.company;
}
function employee(name,profession){
staff.call(this, name);
this.employeeName = name;
this.profession = profession;
}
// 將父類原型指向子類
inheritPrototype(employee,staff)
let instanceOne = new employee("Andy", "A");
let instanceTwo = new employee("Rose", "B");
instanceOne.test.push(4);
// 測試
console.log(instanceTwo.test); // [1,2,3]
console.log(instanceOne.companyName()); // ABC
// 經過 hasOwnProperty() 方法來肯定自身屬性與其原型屬性
console.log(instanceOne.hasOwnProperty('test')) // true
// 經過 isPrototypeOf() 方法來肯定原型和實例的關係
console.log(staff.prototype.isPrototypeOf(instanceOne)); // true
複製代碼
開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式,
Class 能夠經過 extends
關鍵字實現繼承,這比 ES5 的經過修改原型鏈實現繼承,要清晰和方便不少。
class staff {
constructor(){
this.company = "ABC";
this.test = [1,2,3];
}
companyName(){
return this.company;
}
}
class employee extends staff {
constructor(name,profession){
super();
this.employeeName = name;
this.profession = profession;
}
}
// 將父類原型指向子類
let instanceOne = new employee("Andy", "A");
let instanceTwo = new employee("Rose", "B");
instanceOne.test.push(4);
// 測試
console.log(instanceTwo.test); // [1,2,3]
console.log(instanceOne.companyName()); // ABC
// 經過 Object.getPrototypeOf() 方法能夠用來從子類上獲取父類
console.log(Object.getPrototypeOf(employee) === staff)
// 經過 hasOwnProperty() 方法來肯定自身屬性與其原型屬性
console.log(instanceOne.hasOwnProperty('test')) // true
// 經過 isPrototypeOf() 方法來肯定原型和實例的關係
console.log(staff.prototype.isPrototypeOf(instanceOne)); // true
複製代碼
super
關鍵字,它在這裏表示父類的構造函數,用來新建父類的 this
對象。
- 子類必須在
constructor
方法中調用super
方法,不然新建實例時會報錯。這是由於子類沒有本身的this
對象,而是繼承父類的this
對象,而後對其進行加工。- 只有調用
super
以後,纔可使用this
關鍵字,不然會報錯。這是由於子類實例的構建,是基於對父類實例加工,只有super
方法才能返回父類實例。
`super` 雖然表明了父類 `A` 的構造函數,可是返回的是子類 `B` 的實例,即` super` 內部的 `this ` 指的是 `B`,所以 `super()` 在這裏至關於 A.prototype.constructor.call(this)
ES5 的繼承,實質是先創造子類的實例對象 this
,而後再將父類的方法添加到 this
上面(Parent.apply(this)
)。
ES6 的繼承機制徹底不一樣,實質是先創造父類的實例對象 this
(因此必須先調用 super()
方法),而後再用子類的構造函數修改 this
。
function _inherits(subType, superType) {
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
enumerable: false,
writable: true,
configurable: true
}
});
if (superType) {
Object.setPrototypeOf
? Object.setPrototypeOf(subType, superType)
: subType.__proto__ = superType;
}
}
複製代碼
由此能夠看出:
__proto__
屬性,表示構造函數的繼承,老是指向父類。prototype
屬性的 __proto__
屬性,表示方法的繼承,老是指向父類的 prototype
屬性。另:ES6 能夠自定義原生數據結構(好比Array、String等)的子類,這是 ES5 沒法作到的。
以上八種繼承方式是比較常見的繼承方式,假若瞭解了這些方式的機制,在之後的面試中原型鏈與繼承的問題也就不在話下了。
先後寫了兩個多星期,最主要的緣由寶寶剛進入個人生活,無休的照顧寶寶,換尿布、餵奶、換衣之類花費了大量精力和時間。這篇文章也是在寶寶睡覺的間隙寫成的,文章的內容若是以爲簡陋,也請你們多包涵,提出寶貴的意見,往後有時間必定修改。
新年伊始,不忘初心
《前端詞典》這個系列會持續更新,每一期我都會講一個出現頻率較高的知識點。但願你們在閱讀的過程中能夠斧正文中出現不嚴謹或是錯誤的地方,本人將不勝感激;若經過本系列而有所得,本人亦將不勝欣喜。
若是你以爲個人文章寫的還不錯,能夠關注個人微信公衆號,公衆號裏會提早劇透呦。
你也能夠添加個人微信 wqhhsd, 歡迎交流。
【前端詞典】前端須要理解的網絡基礎