js 原型链

203 阅读3分钟

前言

每一次以为自己弄懂一个知识点的时候,其实只是触及其冰山一角而已。

js 继承的演化

1、原型链简单继承

继承的概念:子类拥有父类所有的属性和方法,使用 new 操作符调用子类,才会创建子类实例,才会初始属性(包括引用属性),即每一个实例的引用类型值应该是不同的。

// 父类
function father () {
    this.val = [1, 2]
}

// 子类
function son () {}

// 子类继承父类
son.prototype = new father()

var son1 = new son()
var son2 = new son()

son1.val.push(3) 
son2.val.push(4) 

console.log(son1.val) // [1, 2, 3, 4]

规范链接

这样实现继承虽然简单但是有两个问题,

  • 所有的实例都会共享父类的引用属性,改一个其他的都会被改变
  • 无法传递参数给父类构造函数

2、借用构造函数

// 父类
function father () {
   this.val = [1, 2]
   this.logVal = function () {
       console.log(this.val)
   }
}
// 子类
function son () {
   father.call(this)
}
var son1 = new son()
son1.val.push(3)
console.log(son1.val) // [1, 2, 3]

var son2 = new son()
console.log(son2.val) // [1, 2]

// father原型上面的方法无法继承到子类
father.prototype.testFun = () => {console.log(233)}
son.testFun() // Uncaught TypeError: son.testFun is not a function

(new father()).testFun() // 233

优点:

  • 每个实例具有自己的val属性,每个实例不同
  • 通过call可以传递任意参数给父类

缺点:

  • 方法必须在构造函数中定义,如果有大量对象创建,就会为每个对象创建函数属性,函数的复用性被打破
  • 父类原型上定义的方法,无法继承到子类

因为这两点缺点,我们必须继续改进

3、组合继承

// 父类
function father () {
   this.val = [1, 2]
}
father.prototype.logVal = function () {
   console.log(this.val)
}

// 子类
function son () {
   // 继承属性
   father.call(this) // 第二次调用父类
}
// 继承方法
son.prototype = new father()  // 第一次调用父类

var son1 = new son()
son1.push(3)
son1.logVal(son1.val) // [1, 2, 3]

var son2 = new son()
son2.logVal(son2.val) // [1, 2]

优点:

  • 实现了各自的引用属性
  • 实现父类原型属性的继承
  • 可以用instanceOf、isPrototypeOf判断其原型关系

缺点:

  • 会调用两次父类构造函数

4、原型式继承

var base = {
    val: [1, 2]
    logVal: function () {
        console.log(this.val)
    }
}

var instance1 = Object.create(base)
instance1.val.push(3)

var instance2 = Object.create(base)
instance1.val.push(4)

instance2.logVal() // [1, 2, 3, 4]

跟原型链继承一样,引用类型的值会被共享

5、寄生式继承

function initObj (base) {
    var instance1 = Object.create(base)
    instance1.logVal = function () {
        console.log(this.val)
    }
    return instance1
}
var base = {
    val: [1, 2]
}

var obj = new initObj(base)
obj.logVal() // [1, 2]

寄生式继承只是把原型式继承封装了一遍,创建一个封装过程的函数,该函数在内部以某种方式增强对象,然后返回。

原型式继承和寄生式继承只为引出最终的寄生组合式继承

6、寄生组合式继承

/* 使用father 的原型创建son的原型,继承原型方法
* son.prototype.__proto__ === father.prototype
* @params son 子类
* @params father 父类
*/
function inheritPrototype (son, father) {
    var prototype = Object.create(father.prototype)
    prototype.constructor = son
    son.prototype = prototype
    // 此处prototype 是新创建的对象,可以在此处为其(son的原型)添加任意方法、此处添加的方法是原型链方法
}
// 父类
function father () {
    this.val = [1, 2]
}
// 为父类添加方法
father.prototype.logVal = function () {
    console.log(this.val)
}

// 子类
function son () {
    // 继承属性
    father.call(this) // 第一次调用父类
    // 也可以在此处用this为son添加方法,此处添加的方法是实例方法
}
inheritPrototype(son, father) 

var son1 = new son()
son1.val.push(3)
son1.logVal(son1.val) // [1, 2, 3]

var son2 = new son()
son2.logVal(son2.val) // [1, 2]

这个继承才是比较全面的继承,原型链可以通过instanceOf、isPrototypeOf判断其原型关系,父类构造函数也只调用了一次。