继承

# 原型链描述

每个构造函数都有一个原型对象,这个原型对象中有一个内部指针__proto__指向它的上一级的原型对象,这个上一级的原型对象也可能有这么一个内部指针,这样层层递进,直到最终指向null,这个关系就是原型链

# 1. 原型链继承

# 代码实现

function Father () {
  this.property = true
}
Father.prototype.getFatherValue = function () {
  return this.property
}

function Son () {
  this.sonProperty = false
}
Son.prototype = new Father()
Son.prototype.getSonVaule = function () {
  return this.sonProperty
}

var instance = new Son()
console.log(instance.getFatherValue())//true

# 问题

  1. 当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;

  2. 在创建子类型(例如创建Son的实例)时,不能向超类型(例如Father)的构造函数中传递参数.

# 总结

实践中很少会单独使用原型链

# 2. 借用构造函数

# 代码实现

function Father () {
  this.colors = ["red", "blue", "green"]
}
function Son () {
  Father.call(this) // 继承了Father,且向父类型传递参数
}

var instance1 = new Son()
instance1.colors.push("black")
console.log(instance1.colors) // "red,blue,green,black"

var instance2 = new Son()
console.log(instance2.colors) // "red,blue,green" 可见引用类型值是独立的

# 问题

  1. 方法都在构造函数中定义, 因此函数复用也就不可用了。

  2. 而且超类型(如Father)中定义的方法,对子类型而言也是不可见的。

# 总结

借用构造函数的技术也很少单独使用

# 3. 组合继承

基本思路: 使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。

# 代码实现

function Father (name) {
  this.name = name
  this.colors = ["red", "blue", "green"]
}
Father.prototype.sayName = function () {
  console.log(this.name)
}

function Son (name, age) {
  Father.call(this, name) // 继承实例属性,第一次调用Father()
  this.age = age
}
Son.prototype = new Father() // 继承父类方法,第二次调用Father()
Son.prototype.sayAge = function () {
  console.log(this.age)
}

var instance1 = new Son("louis", 5)
instance1.colors.push("black")
console.log(instance1.colors) // "red,blue,green,black"
instance1.sayName() // louis
instance1.sayAge() // 5

var instance1 = new Son("zhai", 10)
console.log(instance1.colors) // "red,blue,green"
instance1.sayName() // zhai
instance1.sayAge() // 10

# 问题

  1. 调用了两次父类构造函数, 造成了不必要的消耗

# 4. 原型式继承

# 代码实现

var person = {
  name: "Van"
}
var anotherPerson = Object.create(person, {
  name: {
    value: "Louis"
  }
})
console.log(anotherPerson.name) // "Louis"

# 问题

  1. Object.create第二个参数指定的任何属性都会覆盖原型对象上的同名属性

  2. 和原型链方式一样,引用类型值会被共享

# 优点

  1. 在没有必要兴师动众地创建构造函数,只是想让一个对象与另一个对象保持类似的情况下,这种方式完全可以胜任

# 5. 寄生组合式继承

寄生组合式继承就是为了降低调用父类构造函数的开销而出现的

function Father (name) {
  this.name = name
  this.colors = ["red", "blue", "green"]
}
Father.prototype.sayName = function () {
  console.log(this.name)
}

function Son (name, age) {
  Father.call(this, name) // 继承实例属性,第一次调用Father()
  this.age = age
}
// Son.prototype = new Father() // 继承父类方法,第二次调用Father()
Son.prototype = Object.create(Father.prototype, {
  constructor: { // 别忘了把Son的构造函数指回来
    value: Son,
  },
})
Son.prototype.sayAge = function () {
  console.log(this.age)
}

var instance1 = new Son("louis", 5)
instance1.colors.push("black")
console.log(instance1.colors) // "red,blue,green,black"
instance1.sayName() // louis
instance1.sayAge() // 5

var instance1 = new Son("zhai", 10)
console.log(instance1.colors) // "red,blue,green"
instance1.sayName() // zhai
instance1.sayAge() // 10

# 题外话:ES6 的 class

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

但区别于传统的面向对象,js中的class还是有一些容易被忽略的点。

(1)静态属性可以被继承

class Parent {
  static hello () {
    console.log('parent')
  }
}
Parent.world = 'world'

class Child extends Parent {
}

Child.hello() // parent
console.log(Child.world) // world

(2)属性比方法有更高的调用优先级

class Parent {
  hello = 'hello'

  constructor () {
    this.world = 'world'
    this.data = [1, 2, 3]
  }

  hello () {
    console.log('fn hello')
  }

  world () {
    console.log('fn world')
  }
}

const ins = new Parent()
const com = new Parent()

console.log(ins.hello) // hello
console.log(ins.world) // world
console.log(typeof Parent.prototype.hello) // function
console.log(typeof Parent.prototype.world) // function
console.log(ins.data === com.data) // false

说明:

  1. class中定义的方法是挂在原型对象上,而属性是在构造函数中声明的。实例属性显然有比原型对象上的属性更高的调用优先级

  2. 示例中属性hello的声明只是另一种写法而已,只是看上去会更舒服一些,本质上它还是在构造函数中声明的

所以,在实际工作中,应该尽量小心避免把函数当成属性声明,除非本意如此,否则可能给后期调试造成一定困扰。

# 总结

也没什么好总结的了,就最后写一个常用的完整的继承

function Father (name) {
  this.name = name
  this.colors = ["red", "blue", "green"]
}
Father.prototype.sayName = function () {
  console.log(this.name)
}
Father.wahaha = 'wahaha'

function Son (name, age) {
  Father.call(this, name)
  this.age = age
}
Son.prototype = Object.create(Father.prototype, {
  constructor: {
    value: Son,
  },
})
Son.prototype.sayAge = function () {
  console.log(this.age)
}
// 继承静态属性和方法
Object.keys(Father).forEach(key => {
  Son[key] = Father[key]
})