繼承是 OOP 語言中一個比較重要的概念,繼承可使得子類具備父類的屬性和方法或者從新定義、新追加屬性和方法等,因爲 Javascript 語言沒有真正的對象類,因此其實現繼承的方法相對而言會比較特殊,實現繼承主要是依靠原型鏈來實現的。 實現繼承的方法主要有如下幾種:vue
將一個原型對象的實例賦值給另外一個原型對象的原型,從而繼承該原型對象的屬性和方法。git
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValeu = function () {
return this.property;
}
function SubType() {
this.subproperty = false;
}
// 建立 SuperType 實例,並將該實例賦值給 SubType.prototype
SubType.prototype.getSubValvue = function () {
return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue()); // true
複製代碼
【注意事項】github
優勢:閉包
缺點:函數
包含引用類型值的原型屬性會被全部實例共享,多個實例對引用類型的操做會被篡改this
function SuperType() {
this.colors = ['red', 'blue', 'green'];
}
function SubType() {
}
// 繼承 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // 'red, blue, green, black'
var instance2 = new SubType();
console.log(instance2.colors); // 'red, blue, green, black'
複製代碼
在子類構造函數中調用執行父類構造函數,並將this指針指向子類的構造函數的做用域, 使得子類的每一個實例都會複製一份父類函數中的屬性。spa
function SuperType() {
this.colors = ['red', 'blue' , 'green'];
}
function SubType() {
// 執父類構造函數,繼承父類
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push('blck');
console.log(instance1.colors); // 'red, blue, green, black'
var instance2 = new SubType();
console.log(instance2.colors); // 'red, blue, green'
複製代碼
優勢:prototype
在子類的構造函數中能夠向父類函數傳遞參數設計
function SuperType(name) {
this.name = name;
}
function SubType() {
// 繼承 SuperType,同時傳遞參數
SuperType.call(this, 'Nicholas');
// 實例屬性
this.age = 29;
}
var instance = new SubType()
console.log(instance.name); // 'Nicholas'
console.log(instance.age); // 29
複製代碼
缺點:指針
組合繼承指的是組合原型鏈和借用構造函數技術的繼承方法,使用原型鏈實現對原型屬性和方法的繼承, 經過借用構造函數來實現對實例屬性的繼承。
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
// 繼承屬性
// 第二次借用構造函數,調用 SuperType
SuperType.call(this, name);
this.age = age;
}
// 繼承方法
// 第一次構造原型鏈,調用 SuperType
SubType.prototype = new SuperType();
// 重寫 SubType.prototype 的 constructor 屬性,指向本身的構造函數 SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
};
var instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
console.log(instance1.colors); // 'red, blue, green, black'
instance1.sayName(); // 'Nicholas'
instance1.sayAge(); // 29
var instance12 = new SubType('Greg', 27);
console.log(instance2.colors); // 'red, blue, green'
instance2.sayName(); // 'Greg'
instance2.sayAge(); // 27
複製代碼
缺點:
利用空對象做爲中介,將某個對象直接複製給空對象搞糟函數的原型。
function object(obj) {
function F() {}
F.prototype = obj;
return new F();
}
複製代碼
object()對傳入的對象執行了一次淺複製,將構造函數的原型直接指向傳入的對象。
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');
var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');
console.log(person.friends); // 'Shelby, Court, Van, Rob, Barbie'
複製代碼
另外,ES5 存在 Object.create() 的方法,可以代替上面的 object 方法。
在原型式繼承的基礎上,加強對象,返回構造函數
funciton createAnother(original) {
var clone = object(orginal); // 經過調用 object() 函數建立一個新對象
clone.sayHi = function () { // 以某種方式來加強對象
console.log('HI);
}
return clone; // 返回這個對象
}
複製代碼
經過函數的做用加強新對象,即給新對象添加屬性和方法
var person = {
name: 'Nicholas',
friends: ['Shelby', 'Court', 'Van']
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'HI'
複製代碼
缺點:
結合借用構造函數傳遞參數和寄生模實現繼承
function inheritPrototype(subType, superType) {
var prototype = Object.create(superType.prototype); // 建立對象
prototype.constructor = subType; // 加強對象
subType.prototype = prototype; // 指定對象
}
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
console.log(this.age);
};
複製代碼
優勢:
寄生組合式繼承式引用類型最理想的繼承範式。
聖盃模式:其原理依然遵循的是寄生組合式
// 聖盃模式
function inherit(subType, superType) {
function F() {};
F.prototype = superType.prototype;
subType.prototype = new F();
subType.prototype.constructor = superType;
subType.prototype.uber = superType.prototype;
}
// 高級聖盃
// 經過閉包函數實現屬性私有化的做用
var inherit = (function () {
var F = function () {};
return function (subType, superType) {
// 定義私有屬性
//var prop
F.prototype = superType.prototype;
subType.prototype = new F();
subType.prototype.constructor = superType;
subType.prototype.uber = superType.prototype;
// 獲取私有屬性
subType.prototype.getProp = function () {
// get prop
}
}
})
複製代碼
經過 Object.assign 把其餘原型構造函數拷貝到實例子類原型上。
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 繼承 SuperClass
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其餘類
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 從新指定 constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function () {
// do something
}
複製代碼
extends關鍵字主要用於類聲明或者類表達式中,以建立一個類,該類是另外一個類的子類。其中constructor表示構造函數,一個類中只能有一個構造函數,有多個會報出SyntaxError錯誤,若是沒有顯式指定構造方法,則會添加默認的 constructor方法
class Rectangle {
// constructor
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea()
}
// Method
calcArea() {
return this.height * this.width;
}
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 輸出 200
-----------------------------------------------------------------
// 繼承
class Square extends Rectangle {
constructor(length) {
super(length, length);
// 若是子類中存在構造函數,則須要在使用「this」以前首先調用 super()。
this.name = 'Square';
}
get area() {
return this.height * this.width;
}
}
const square = new Square(10);
console.log(square.area);
// 輸出 100
複製代碼
extends 繼承的核心代碼以下,其實現和上述的寄生組合式繼承方式同樣
function _inherits(subType, superType) {
// 建立對象,建立父類原型的一個副本
// 加強對象,彌補因重寫原型而失去的默認的constructor 屬性
// 指定對象,將新建立的對象賦值給子類的原型
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;
}
}
複製代碼
ES5繼承和ES6繼承的區別
ES5的繼承實質上是先建立子類的實例對象,而後再將父類的方法添加到this上(Parent.call(this)).
ES6的繼承有所不一樣,實質上是先建立父類的實例對象this,而後再用子類的構造函數修改this。由於子類沒有本身的this對象,因此必須先調用父類的super()方法,不然新建實例報錯。
NicholasC.Zakas. JavaScript高級程序設計. JAVASCRIPT高級程序設計. 2012.