继承的分类
- js里面的继承主要分为 原型属性继承、静态属性继承、实例属性继承,一个原型上面定义的方法一般都是基于其实例的用途来定义的,也就是说,原型的方法应该是实例经常用到的通用方法,而构造器方法一般是特定情况下可能会用到的方法,可按需调用,原型方法只能供其实例来使用。
- 继承可以让原型链丰富,根据需求定制不同的原型链,不会存在内存浪费的情况,原型只会保留一份,用到的时候调用就行,还能节省空间。
- 原型一般是一些共有的特性,实例是特有的特性,继承的越多越具体,原型链的最顶端是最抽象的,越底端越具体,这样一来我们可以根据需求在恰当位置继承来实现个性化的定制属性,统一而又有多样化。
一、原型之间的继承
function Parent(){} // 定义父类构造器
function Children(){} // 定义子类构造器
let ChildPrototype = Children.prototype // 构造器原型
let ChildPrototypeProto = Children.prototype.__proto__ // 构造器原型的对象原型
// 方法一
// 父类构造器原型作为子类构造器原型(ChildPrototype)的对象原型(ChildPrototypeProto)
ChildPrototypeProto = Parent.prototype
//方法二
// Object.create返回一个对象,其__proto__指向传入的参数,也就实现返回的对象继承参数对象
ChildPrototype = Object.create(Parent.prototype)
//方法三
// 直接设置参数1的原型(__proto__)为参数2
Object.setPrototypeOf(ChildPrototype, Parent.prototype)
二、静态属性继承
- 静态属性的继承,意味着父构造器中定义的静态属性,在子构造器中可以直接调用。不仅实例可以通过对象原型实现继承,构造器也可以通过对象原型继承。之前提到过函数有
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()函数就是实现这个继承 ```javascript // 定义父构造函数 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} ,实现实例属性继承
<a name="nkB84"></a>
##
<a name="pMxwd"></a>
## 四、继承的实现
- 通过es6的extends关键字来继承原型
- 手动实现原型继承
```javascript
// 定义父构造函数,功能:初始化实例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)