原型链继承
function Parent () {
this.name = 'wyt'
this.age = 300
this.array = [1, 2, 3]
}
Parent.prototype.getInfo = function () {
console.log(this.name, this.age)
}
function Child () {
this.type = 'child'
}
/**
将Child函数的原型指向Parent的实例,
这样的话相当于new Parent()返回了一个对象,将这个对象取名为obj:
obj = {
name: 'wyt',
age: 300,
array: [1, 2, 3]
}
将此对象赋值给Child.prototype,这样在Child的实例就能够通过原型链访问到其内部属性值了。
同时,在new的时候,obj.__proto__属性将指向Parent.prototype,就相当于:
obj = {
name: 'wyt',
age: 300,
array: [1, 2, 3],
__proto__: Parent.prototype
}
所以Parent原型上的属性也能被Child的实例访问到
*/
Child.prototype = new Parent()
/**
根据new具体实现,这一步就相当于
var son = {}
son.__proto__ = Child.prototype
Child.apply(son)
*/
var son = new Child()
son.getInfo()
console.log(son)
存在问题:
- 父类
Parent中的引用类型的属性被其中某个子类的实例修改之后,会导致其他所有子类的改属性也发生改变 - 子类的实例在创建时无法传递参数
其中问题一发生的情况如下:
var obj1 = new Child()
var obj2 = new Child()
console.log(obj1.array) // [1, 2, 3]
console.log(obj2.array) // [1, 2, 3]
obj1.array.push(4)
console.log(obj1.array) // [1, 2, 3, 4]
// 我们其实不想更改obj2.array,但是它在这里也被改变了,这不是我们想要的
console.log(obj2.array) // [1, 2, 3, 4]
为什么导致这个问题:
因为
Child.prototype = new Parent()在执行的时候,new Parent()在就相当于进行了一次浅拷贝,新建了一个对象,对象内部值为基本类型的属性不会受到影响,但是当属性值为引用类型时,会将引用类型的地址存到对象中,因此改变引用类型值后,所有的子类实例上的该属性值都会受到影响。
构造函数继承
构造函数继承主要就借用了一个call来实现。
function Parent () {
this.name = 'wyt'
this.age = 300
this.array = [1, 2, 3]
}
Parent.prototype.getInfo = function () {
console.log(this.name, this.age)
}
function Child () {
this.type = 'child'
/**
这一步参考call的原理,执行了以下几步,将Parent中的this指向改变成Child
this.fun = Parent
this.fun()
delete this.fun
*/
Parent.call(this)
}
var son = new Child()
console.log(son)
son.getInfo() // 会报错
存在问题:
- 无法继承原型中的属性,比如上述调用
son.getInfo()就会因找不到getInfo方法而报错。
组合继承
简简单单,组合上面两种继承方式就行了。
function Parent () {
this.name = 'wyt'
this.age = 300
this.array = [1, 2, 3]
}
Parent.prototype.getInfo = function () {
console.log(this.name, this.age)
}
function Child () {
this.type = 'child'
Parent.call(this)
}
Child.prototype = new Parent()
var son = new Child()
son.getInfo()
console.log(son)
存在问题:
Parent会执行两次,第一次是调用call时执行,第二次是new Parent()时执行
原型式继承
就是借用一下Object.create()方法。
var parent = {
name: 'wyt',
age: 300,
friends: ['kyh', 'lxl', 'szf'],
getInfo: function () {
console.log(this.name, this.age)
}
}
/**
根据Object.create()的实现原理
这里相当于执行了以下步骤:
var son = {}
son.__proto__ = parent
*/
var son = Object.create(parent)
console.log(son)
son.getInfo()
存在问题:
- 会像原型链继承一样,因为浅拷贝的原因,导致parent对象中值为引用类型的属性被一起修改
var obj1 = Object.create(parent)
var obj2 = Object.create(parent)
console.log(obj1.friends) // ['kyh', 'lxl', 'szf']
console.log(obj2.friends) // ['kyh', 'lxl', 'szf']
obj1.friends.push('lp')
console.log(obj1.friends) // ['kyh', 'lxl', 'szf', 'lp']
console.log(obj2.friends) // ['kyh', 'lxl', 'szf', 'lp']
寄生式继承
只不过是名字看着有点唬人的感觉,其实就是单纯在原型式继承上面封装了一下,加强了原本浅拷贝的一些能力,但它还是浅拷贝,所以浅拷贝存在的问题它还是没有解决。
var parent = {
name: 'wyt',
age: 300,
friends: ['kyh', 'lxl', 'szf'],
getInfo: function () {
console.log(this.name, this.age)
}
}
// 就是单纯的封装了一下原型式继承,扩展了一下属性添加的渠道
function clone (obj) {
var res = Object.create(parent)
res.getName = function () {
console.log(this.name)
}
return res
}
var son = clone(parent)
/**
可以换个写法:
var son = Object.create(parent, {
getName: {
value: function () {
console.log(this.name)
}
}
})
*/
console.log(son)
son.getInfo()
son.getName()
存在问题:
- 还是没有解决原型式继承的问题
var obj1 = clone(parent)
var obj2 = clone(parent)
console.log(obj1.friends) // ['kyh', 'lxl', 'szf']
console.log(obj2.friends) // ['kyh', 'lxl', 'szf']
obj1.friends.push('lp')
console.log(obj1.friends) // ['kyh', 'lxl', 'szf', 'lp']
console.log(obj2.friends) // ['kyh', 'lxl', 'szf', 'lp']
寄生组合式继承
只能说是最优的继承方式了吧,但是实际也就是把组合继承跟寄生式继承结合了一下而已。
function Parent () {
this.name = 'wyt'
this.age = 300
this.array = [1, 2, 3]
}
Parent.prototype.getInfo = function () {
console.log(this.name, this.age)
}
function Child () {
this.type = 'child'
Parent.call(this)
}
/**
更改Child.prototype的值为Object.create()返回的对象,
为了Child的实例能够访问Parent原型上的属性。
相当于通过以下步骤改变Child.prototype
var obj = {}
obj.__proto__ = Parent.prototype
Child.prototype = obj
这里为什么不使用Child.prototype = new Parent()?
原因就是new Parent()和Parent.call(this)一起使用会使Parent执行两次
*/
Child.prototype = Object.create(Parent.prototype)
/**
上一步将会使Child.prototype.constructor变为Parent,这里将其指回Child
其实在上面的原型链继承和组合继承中,Child.prototype.constructor都会变成Parent
简单点来说:
var obj = new Child()
console.log(obj instanceof Child) // true
不然无法使用 instanceof 确定对象类型
*/
Child.prototype.constructor = Child
Child.prototype.getType = function () {
console.log(this.type)
}
var son = new Child()
console.log(son)
son.getInfo()
son.getType()
var obj1 = new Child()
var obj2 = new Child()
console.log(obj1.array) // [1, 2, 3]
console.log(obj2.array) // [1, 2, 3]
obj1.array.push(4)
console.log(obj1.array) // [1, 2, 3, 4]
console.log(obj2.array) // [1, 2, 3]
小总结
-
原型链继承就是把
Child.prototype指向了Parent的实例。 -
构造函数继承就是在
Child构造函数内部增加了一个call()方法的调用。 -
组合继承就是简单的1 + 1 = 2,前两种继承方式的结合罢了。
-
原型式继承跟使用
Object.create()创建一个对象没啥区别。 -
寄生式继承就是在
Object.create()创建一个对象的同时还加了点自定义属性进去, 个人感觉使用Object.create()的第二个参数也能解决,所以本质还是用Object.create()创建了一个对象。 -
寄生组合式啊,把组合跟寄生结合了下,然后改变一下
Child.prototype.constructor的值,使得instanceof能够正确的判断。 -
寄生组合继承是所有继承方式里面相对最优的继承方式。
-
要比较轻松容易继承的话,要先学习以下几点: