JS继承

172 阅读2分钟

说到继承,就会想到这三个改变上下文的函数

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

总结

实现继承主要是两点

  1. 获取父类的属性方法
  2. 继承父类的原型链