前言
js 中的继承: 实例的属性和方法互不干扰,同时享有共同的属性和方法
js 对象属性的搜索是先找自身,然后找原型,接着原型的原型,,,Object.prototype,然后就是 null
原型链
任何函数的默认原型都是 Object() 的实例
构造函数A拥有原型对象A,通过构造函数能够创建实例a。实例上有一个内部指针a __proto__ 指向构造函数A的原型对象A
如果构造函数A的原型对象A是另一个构造函数B的实例,那么原型对象A上也会有一个内部指针A __proto__ 指向构造函数B的原型对象B
function SuperType () {
this.property = true
}
function SubType () {
this.subProperty = false
}
// 原型链继承的核心
SubType.prototype = new SuperType()
const instance = new SubType()
重复上面的过程,当构造函数的原型对象是 Object() 创建的实例会遇到一个定义。
Object.prototype.__proto__ === null // true
以上实例和原型的关系,就是原型链
判断原型和实例的关系
instanceof 操作符
instanceof 可以用来校验构造函数的 prototype 是否出现在实例的原型链上。当然如果原型关系被修改,就可能得到不同的结果
Object instanceof Function // true
Array instanceof Function // true
Function instanceof Function // true
isPrototypeOf
Object.prototype.isPrototypeOf({}) // true
Function.prototype.isPrototypeOf(Array) // true
Function.prototype.isPrototypeOf(Object) // true
Function.prototype.isPrototypeOf(Function) // true
原型链的优点
- 共享原型上的属性和方法
原型链的问题
- 原型上引用类型的属性更改,会导致不可预知的问题。这个问题好像不可避免,只能认为干预
- 实例不能传递参数
构造函数
构造函数分为内置构造函数(如Object Promise等)和自定义构造函数
function SuperType () {
this.age = 11
}
function SubType (name) {
// call/apply 调用 SuperType
// SuperType.call(this)
SuperType.apply(this)
this.name = name
}
const instance = new SubType('Jerry')
传递参数
和原型链继承不同的是构造函数可以传递自定义参数
构造函数的优点
- 能够传递参数
- 引用类型数据单独存在
构造函数的问题
- 继承的属性和方法必须定义在构造函数之内,方法不能共用(内存)
- 父类原型的属性和方法不能继承
组合继承
原型链 + 构造函数 = 组合继承
function SuperType (name) {
this.name = name
}
SuperType.prototype.sayName = function () {
return this.name
}
function SubType (name, age) {
// 构造函数
SuperType.call(this, name)
this.age = age
}
// 原型链
SubType.prototype = new SuperType()
const instance1 = new SubType('Jerry', 12)
组合继承的优点
-
原型上的属性和方法可以继承
-
实例可以传递参数
组合继承的缺点
- 父类被调用两次(内存)
原型式继承
function object (o) {
function F () {}
F.prototype = o
return new F()
}
const obj = {
a: 1,
arr1: [1, 2]
}
const obj1 = object(obj)
const obj2 = object(obj)
obj.a = 3
obj1.a = 2
obj1.arr1.push(3)
obj2.a // 3
obj2.arr1 // [1, 2, 3]
将 obj 这个引用类型的数据作为模版,进行复制。实例指针上的原型对象引用的是 obj,修改 obj 会影响所有实例
原型式继承的问题
- 引用属性数据问题
寄生式继承
function object(o) {
function F () {}
F.prototype = o
return new F()
}
function createAnother(original) {
let clone = object(original)
clone.sayHi = function () {
console.log('hi')
}
return clone
}
let person = {
name: 'Jerry',
friend: ['Tom', 'Lucy']
}
let anotherPerson = createAnother(person)
寄生式组合继承
function inheritPrototype(subType, superType) {
let prototype = object(superType.prototype)
// 将 constructor 重新指向 subType
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)
}
SuperType.prototype.arr1 = [1, 2, 3]
function SubType (name, age) {
SuperType.call(this, name)
this.age = age
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function () {
console.log(this.age)
}
const instance1 = new SubType('Jerry', 15)
const instance2 = new SubType('Jerry2', 14)
instance1.arr1.push(4)
console.log(instance1, instance2)
应用
逻辑和业务复杂且多的情况下,使用继承的概念可以便于我们梳理整合。例如开发框架,父子关系的业务。
小结
-
看起来寄生式继承是这六种里面最好的,但是也不能解决父类原型上放引用类型数据的问题。。。
-
学习理解继承可以帮助我们梳理js中 Object Function 和对应实例之间的关系,同样也是一种编程思维。当然合适的才是最好的,简单的就用一个构造函数解决