1.构造函数继承
在子类构造函数内部使用call或apply来调用父类构造函数
我们先来复习一下call和apply方法。
通过call()、apply()或者bind()方法直接指定this的绑定对象, 如foo.call(obj)使用.call()或者.apply()的函数是会直接执行的,而bind()是创建一个新的函数,需要手动调用才会执行
.call()和.apply()用法基本类似,不过call接收若干个参数,而apply接收的是一个数组
举个🌰:
const obj = {
a:'abc'
};
function sayA(){
console.log(this.a)
}
sayA.call(obj) // 'abc'
1.1 题目一
构造函数的基本原理
function Parent (name) {
this.name = name
}
function Child () {
this.sex = 'boy'
Parent.call(this, 'child')
}
const child1 = new Child()
console.log(child1)
答案:
Child {sex: "boy", name: "child"}
理解:
- 调用Parent函数在Child里面运行,而Parent函数的作用是为当前函数添加name属性,并且属性值是参数name
伪代码:
function Child(){
this.sex = 'boy'
// 伪代码
this.name = 'child'
}
1.2 题目二
现在我在子类和父类中都加上name这个属性
function Parent (name) {
this.name = name
}
function Child () {
this.sex = 'boy'
Parent.call(this, 'good boy')
this.name = 'bad boy'
}
const child1 = new Child()
console.log(child1)
按照JS的执行顺序,所以答案为
Child {sex: "boy", name: "bad boy"}
当然,如果你把Parent.call(this, 'good boy')
和this.name = 'bad boy'
换下位置,那肯定name就是good boy
function Parent (name) {
this.name = name
}
function Child () {
this.sex = 'boy'
this.name = 'bad boy'
Parent.call(this, 'good boy')
}
const child1 = new Child()
console.log(child1)
答案:
Child {sex: "boy", name: "good boy"}
1.3 题目三
构造函数继承的优点
解决了原型链继承中子类共享父类引用对象的问题
function Parent (name, sex) {
this.name = name
this.sex = sex
this.colors = ['white', 'black']
}
function Child (name, sex) {
Parent.call(this, name, sex)
}
const child1 = new Child('child1', 'boy')
child1.colors.push('yellow')
const child2 = new Child('child2', 'girl')
console.log(child1)
console.log(child2)
在原型链继承中我们知道,子类构造函数创建的实例是会查找到原型链上的colors的,而且改动它会影响到其它的实例,这是原型链继承的一大缺点。
现在使用构造函数继承,我们看下结果
Child{ name: 'child1', sex: 'boy', colors: ['white', 'black', 'yellow'] }
Child{ name: 'child1', sex: 'boy', colors: ['white', 'black'] }
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类,所以现在child1和child2现在分别有它们各自的colors了,就不共享了。
而且这种拷贝属于深拷贝,验证的方式是你可以把colors数组中的每一项改为一个对象,然后修改它看看。
举个🌰:
function Parent () {
this.colors = [{ title: 'white' }, { title: 'black' }]
}
function Child(){
Parent.call(this)
}
const child1 = new Child()
child1.colors[0].name = 'andy'
const child2 = new Child()
console.log(child1);
console.log(child2);
/*
Child { colors: [ { title: 'white', name: 'andy' }, { title: 'black' } ]}
Child { colors: [ { title: 'white' }, { title: 'black' } ] }
*/
1.4 题目四
构造函数继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法
function Parent (name) {
this.name = name
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child () {
this.sex = 'boy'
Parent.call(this, 'good boy')
}
Child.prototype.getSex = function () {
console.log(this.sex)
}
var child1 = new Child()
console.log(child1)
child1.getSex()
child1.getName()
答案:
Child {sex: "boy", name: "good boy"}
boy
`Uncaught TypeError: child1.getName is not a function`
理解:
- sex、name属性都有这个我们都可以理解
- getSex属于Child构造函数原型对象上的方法,我们可能是能用它的,这个也好理解
- 而getName呢?它属于父类构造函数原型对象上的方法,使用Parent.call(this, ‘good boy’)只是让复制了一下Parent构造函数里的属性和方法,没让复制原型对象上的属性和方法。
1.5 题目五
实例并不是父类的实例,只是子类的实例
child1 instanceof Parent ==> false
这句话意思是:Parent.protorype不在实例child1的原型链上,因此也就有了题目1.4中实例不能使用构造函数原型对象上的属性和方法
function Parent (name) {
this.name = name
}
function Child () {
this.sex = 'boy'
Parent.call(this, 'child')
}
var child1 = new Child()
console.log(child1) // Child {sex: "boy", name: "child"}
console.log(child1 instanceof Child) // true
console.log(child1 instanceof Parent) // false
console.log(child1 instanceof Object) // true
总结
构造函数继承:
-
优点:
- 解决了原型链继承中子类实例共享父类引用对象的问题
- 创建子类实例时,可以向父类传递参数(见题目1.3)
-
缺点:
- 构造继承只能继承父类的实例属性和方法,不能继承父类原型的属性和方法(见题目1.4)
- 实例并不是父类的实例,只是子类的实例(见题目1.5)