繼承
- js中的繼承一般分為三部分:原型屬性繼承、靜態屬性繼承、實例屬性繼承,一個原型上面定義的方法一般都是基于其實例的用途來定義的,也就是說,原型的方法應該是實例經常用到的通用方法,而構造器方法一般是特定情況下可能會用到的方法,可按需調用,原型方法只能供其實例來使用
- 繼承可以讓原型鏈豐富,根據需求定制不同的原型鏈,不會存在內存浪費的情況,原型只會保留一份,用到的時候調用就行,還能節省空間
原型繼承
- 可以看出原型一般是一些共有的特性,實例是特有的特性,繼承的越多越具體,原型鏈的最頂端是最抽象的,越底端越具體,這樣一來我們可以根據需求在恰當位置繼承來實現個性化的定制屬性,統一而又有多樣化
原型之間的繼承
function Parent(){} // 定義父類構造器
function Children(){} // 定義子類構造器
let ChildPrototype = Children.prototype // 構造器原型
let ChildPrototypeProto = Children.prototype.__proto__ // 構造器原型的對象原型
// 方法一
ChildPrototypeProto = Parent.prototype // 父類構造器原型作為子類構造器原型(ChildPrototype)的對象原型(ChildPrototypeProto)
//方法二
ChildPrototype = Object.create(Parent.prototype) // Object.create返回一個對象,其__proto__指向傳入的參數,也就實現返回的對象繼承參數對象
//方法三
Object.setPrototypeOf(ChildPrototype, Parent.prototype) // 直接設置參數1的原型(__proto__)為參數2
復制代碼
以上僅實現了原型之間的繼承
靜態屬性繼承
- 靜態屬性的繼承,意味著父構造器中定義的靜態屬性,在子構造器中可以直接調用。不僅實例可以通過對象原型實現繼承,構造器也可以通過對象原型繼承。之前提到過函數有prototype與__proto__,其中prototype是給實例用的,而__proto__是給自己用的。
- 默認的構造函數的對象原型都指向原始函數構造器原型(即Function.prototype),可以理解所有函數都是由原始函數構造器生成
- 通過構造函數自身的對象原型(__proto__),來實現靜態屬性繼承
function Parent() {} // 定義父構造函數
function Children() {} //定義子構造函數
// 定義父構造函數的靜態方法
Parent.foo = function () {
console.log(this.name)
}
// 方法一
Children.__proto__ = Parent // 子構造函數的對象原型指向父構造函數,也就實現繼承
// 方法二
Object.setPrototypeOf(Children, Parent) // 同原型繼承
console.log(Children.foo) // function(){ console.log(this.name) } ,實現繼承
復制代碼
以上即為構造函數之間通過對象原型繼承靜態屬性,注:函數也是對象
實例屬性繼承
- 實例自帶的屬性是由構造函數實例化時默認生成的,那么要實現實例屬性的繼承,勢必要實現子構造函數中調用父構造函數,這樣才能實現子構造函數實例化出來的對象也具備父構造函數給予的默認屬性
- 在class語法糖的constructor中的super()函數就是實現這個繼承
// 定義父構造函數
function Parent(name) {
this.name = name
}
//定義子構造函數
function Children(name,age) {
Parent.call(this,name) // 這里調用父構造器,實現實例屬性繼承
this.age = age
}
const obj = new Children('tom', 5)
console.log(obj) // {name: 'tom', age: 5} ,實現實例屬性繼承
復制代碼
通過實例屬性繼承,可以把父構造器中默認生成的實例屬性追加到子構造器實例化出來的對象上
綜合以上繼承,現在實現真正的繼承
繼承的實現
- 通過es6的extends關鍵字來繼承原型
- 手動實現原型繼承
// 定義父構造函數,功能:初始化實例name屬性
function Parent(name) {
'use strict'
this.name = name
}
// 定義父構造函數的靜態方法,功能:設置調用對象的name屬性
Parent.setName = function setName(obj, name) {
obj.name = name
}
// 定義父構造器原型(prototype)的方法,功能:獲取調用對象的name屬性
Parent.prototype.getName = function getName() {
return this.name
}
/*-----以上已定義父類的原型方法(獲取name),父類靜態方法(設置name),以及構造器默認初始化的屬性name------*/
// 定義子構造函數,功能:初始化實例age屬性,以及通過父構造器初始化實例name屬性
function Children(name, age) {
'use strict'
Parent.call(this, name) // 調用父構造器,初始化name屬性
this.age = age // 子構造器初始化age屬性
}
// 定義子構造函數的靜態方法,功能:設置調用對象的age屬性
Children.setAge = function setAge(obj, age) {
obj.age = age
}
// 原型繼承
// 設置Children.prototype['[[Prototype]]']= Parent.prototype,此處的'[[Prototype]]'與設置__proto__相同
Children.prototype = Object.create(Parent.prototype)
// 注意此處原型繼承之后,不帶有constructor屬性,應該手動指明為Children
Object.defineProperty(Children.prototype, 'constructor', {
value: Children,
writable: true, // 可寫
enumerable: false, // 不可枚舉
configurable: true, // 可配置
})
//以上2句可以直接寫成一句
/*
Children.prototype = Object.create(Parent.prototype, {
constructor: {
value: Children,
writable: true, // 可寫
enumerable: false, // 不可枚舉
configurable: true, // 可配置
}
})
*/
// 由于子構造器原型方法必須在繼承之后再定義,否則會被繼承覆蓋
// 定義子構造器原型(prototype)的方法,功能:獲取調用對象的age屬性
Children.prototype.getAge = function getAge() {
return this.age
}
// 構造函數(繼承靜態屬性)繼承
// 設置Children.__proto__ = Parent,注意此處不能使用Children = Object.create(Parent),因為Object.create返回的是一個對象不能替換構造函數
Object.setPrototypeOf(Children, Parent)
// 測試父級
const obj = new Parent('tom') // 實例化父級實例
console.log(obj.getName()) // tom
Parent.setName(obj, 'jerry') // 通過父級靜態方法設置name
console.log(obj.getName()) // jerry
console.log(obj instanceof Parent) // true
// 測試子級
const obj1 = new Children(null, 5) // 實例化子級實例
console.log(obj1.getAge()) // 5
Children.setAge(obj1, 8) // 通過子級靜態方法設置age
console.log(obj1.getAge()) // 8
console.log(obj1 instanceof Parent) // true
console.log(obj1 instanceof Children) // true
// 完整測試繼承
const test = new Children('tom', 5) // 實例化子級實例,name='tom',age=5
console.log(test.getName()) // tom
Parent.setName(test, 'jerry') // 通過父級靜態方法設置name=jerry
console.log(test.getName()) // jerry
console.log(test.getAge()) // 5
Children.setAge(test, 8) // 通過子級靜態方法設置age=8
console.log(test.getAge()) // 8
class P {
constructor(name) {
this.name = name
}
static setName(obj, name) {
obj.name = name
}
getName() {
return this.name
}
}
class C extends P {
constructor(name, age) {
super(name)
this.age = age
}
static setAge(obj, age) {
obj.age = age
}
getAge() {
return this.age
}
}
// 這里就不帶測試了,可以自行驗證,比對一下有什么區別
console.dir(Children)
console.dir(C)
復制代碼
實現繼承,需要對原型、構造器、實例屬性都加以實現繼承