说到继承,就会想到这三个改变上下文的函数
call apply bind
| function | 参数 | 返回值 | 调用时间 |
|---|---|---|---|
| call | obj, arg1, arg2 ... | 对应函数调用后的返回值 | 立即调用 |
| apply | obj, [arg1, arg2, ...] | 对应函数调用后的返回值 | 立即调用 |
| bind | thisArg[, arg1[, arg2[, ...]]] | 返回对应函数 | 不立即调用 |
除了这三个函数之外,用new调用函数也是改变了函数的上下文,函数的this指向是要看他调用的位置的,谁调用了它,他的this指向就是指向谁,而用new调用的构造函数,this的指向就是对象本身。
JS的继承
借助构造函数实现继承
/**
* 借助构造函数实现继承
*/
function Parent1 () {
this.name = 'parent1'
}
function Child1 () {
// call/apply 让 parent1 的上下文变成了Child1的实例
// 会导致父类的属性挂在到子类当中去
Parent1.call(this)
this.type = 'child1'
}
Parent1.prototype.add = function () {
console.log('ADD')
return 1
}
console.log(child1)
console.log(child1.add) // undefined
console.log(parent1.add)// ADD
console.log(child1 instanceof Parent1) // true
缺点:这种方式实现继承的结果是父类原型链上的东西并没有被子类继承,只是将父类上的属性挂到子类当中去。
通过原型链实现继承
/**
* 借助原型链实现继承
* 子类实例的__proto__属性指向父类实例
* 如果函数没有继承,__proto__指向的是构造函数
*/
function Parent2 () {
this.name = 'parent2'
this.play = [1, 2, 3]
}
function Child2 () {
this.name = 'child2'
}
// 将子类的原型指向父类
Child2.prototype = new Parent2()
var c1 = new Child2()
var c2 = new Child2()
c2.play.push(4)
console.log(c1.play, c2.play)
console.log(c1.play === c2.play) // true
console.log(c1 instanceof Parent2) // true
缺点:因为改变子类的prototype属性是引用类型,所以改变父类实例的属性就会改变子类实例的属性,多个子类实例的prototype也是指向同一个引用。
构造函数和原型结合
/**
* 构造函数和原型结合
*/
function Parent5 () {
this.name = 'parent5'
this.play = [1, 2]
}
function Child5 () {
Parent5.call(this)
this.type = 'child5'
}
// 子类的原形复制父类的的原形,这样子修改子类的原形也不会影响到父类的原形
Child5.prototype = Object.create(Parent5.prototype)
var c7 = new Child5()
var c8 = new Child5()
c7.play.push(6)
console.log(c7.play, c8.play)
console.log(c7.__proto__ === c8.__proto__) // true
console.log(c7 instanceof Parent5) // true
总结
实现继承主要是两点
- 获取父类的属性方法
- 继承父类的原型链