繼承
原型鏈繼承
- 實現方式如下
function Parent() {
this.name = '微言';
}
Parent.prototype.getName = function () {
console.log(this.name);
};
function Child() {}
// new Parent產生的一個parent 的實例,同時包含了,實例屬性以及原型方法。
Child.prototype = new Parent();
console.log(Child.prototype.constructor); // [Function: Parent]
// <1>
Child.prototype.constructor = Child;
console.log(Child.prototype.constructor); // [Function: Child]
const child = new Child();
child.getName();
-
為什麼需要<1>?
存在的問題,直接把 Child 的原型對象給覆蓋了,
此處指向了 parent 通過 修改 constructor 來實現 指向 Child
缺點
- 如果有屬性是引用類型的一旦某個實例修改了屬性,所有的實例都會改變
- 無法傳遞參數
function Parent() {
this.name = '微言';
this.obj = { name: 'obj' };
}
Parent.prototype.getName = function () {
console.log(this.name);
};
function Child() {}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child();
const child1 = new Child();
child1.obj.name = 'child1';
// 因為都是找的原型對象上的同一個東西,所以都會受到影響
console.log(child.obj); // { name: 'child1' }
console.log(child1.obj); // { name: 'child1' }
- 創建 Child 實例的時候不可以進行傳參
構造函數繼承
缺點
- 屬性或者方法如果想被繼承,只能在構造函數中定義.
如果方法在構造函數中定義了。每次創建實例都會創建一遍方法,多佔用一塊內存。
function Parent(name, obj) {
this.name = name;
this.obj = obj;
this.eat = function () {};
}
function Child(id, name, obj) {
Parent.call(this, name, obj);
this.id = id;
}
const child = new Child(1, 'c1', { name: '微言', age: '18' });
const child1 = new Child(2, 'c2', { name: '微言' });
console.log(child.eat === child1.eat); // false 造成了內存泄露,內存浪費
組合繼承
組合了構造函數繼承和原型鏈繼承。
原型鏈繼承:方法存在 prototype,子類可以隨時調用,引用類型的屬性會被放在所有實例上共享,並且不可以傳參。
構造函數繼承: 使用 call 在子構造中重複一遍屬性和方法的操作,可以傳參了。
function Parent(name, obj, actions) {
this.name = name;
this.obj = obj;
this.actions = actions;
}
Parent.prototype.eat = function () {
console.log(`${this.name} - eat`);
};
// 採用解構的方式,不用每次都增刪值的傳入
function Child(id, ...args) {
// 為了實例屬性的不共享
Parent.apply(this, args); // 1
this.id = id;
}
Child.prototype = new Parent(); // 2
Child.prototype.constructor = Child;
const child = new Child(1, 'c1', { name: '微言', age: '18' }, [1]);
const child1 = new Child(2, 'c2', { name: '微言1' }, [2, 43]);
console.log(child.eat === child1.eat); // true
child.obj.name = 'asdfasdf';
child.actions.pop();
console.log(child.obj); // { name: 'asdfasdf', age: '18' }
console.log(child1.obj); // { name: '微言1' }
console.log(child.actions); // []
console.log(child1.actions); // [2, 43]
缺點
- parent 構造函數被調用了兩次
寄生組合式繼承
function Parent(name, obj, actions) {
this.name = name;
this.obj = obj;
this.actions = actions;
}
Parent.prototype.eat = function () {
console.log(`${this.name} - eat`);
};
// 採用解構的方式,不用每次都增刪值的傳入
function Child(id, ...args) {
// 為了實例屬性的不共享
Parent.apply(this, args);
this.id = id;
}
// Child.prototype = new Parent();
// 第一個方法. temp構造函數
// let TempFunction = function () {};
// // 通過曲線救國,讓臨時構造函數和parent構造函數都一樣
// TempFunction.prototype = Parent.prototype; // 這裏只是繼承了原型對象, parent裏面整個構造函數的創建並不會執行, (其實並沒有parent裏面的執行)
// // 讓臨時構造函數的實例指向Child.prototype
// Child.prototype = new TempFunction();
// 第二個方法. Object.create
Child.prototype = Object.create(Parent.prototype);
// 為什麼不能直接這樣寫? 修改 子類 會影響到父類
// Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;
const child = new Child(1, 'c1', { name: '微言', age: '18' }, [1]);
const child1 = new Child(2, 'c2', { name: '微言1' }, [2, 43]);
console.log(child.eat === child1.eat); // true
child.obj.name = '新的名字';
child.actions.pop();
console.log(child.obj); // { name: '新的名字', age: '18' }
console.log(child1.obj); // { name: '微言1' }
console.log(child.actions); // []
console.log(child1.actions); // [2, 43]
總結
本文涉及到的知識點,原型,原型鏈,Object