原型链继承

特点:
  • 基本思想:利用原型链让子类继承父类的属性和方法
  function Parent(name,country) {    this.name = name    this.sex = 'male'    this.country = country    this.identities = ["philosopher", "scientist", "artist"]    this.sayName = function() {      return 'I am a ' + this.name    }  }  Parent.prototype.honor = 'Champion'  Parent.prototype.getPersonValue = function() {    return this.name  }  function Child(name,city) {    this.name = name    this.age = 24    this.city = city  }  // Child的原型指向Parent的实例  // 意味着 Child的原型拥有Parent的实例的全部属性和方法  // 也意味着 Child的原型的__proto__属性指向Parent的原型  // 所以最后 根据原型链的规则,Child的实例拥有Parent实例的全部属性和方法,以及Parent的原型的全部属性和方法  Child.prototype = new Parent('father')  // 此时Child的原型的constructor 指向为 Parent    // 所以要修复Child的原型的constructor 指向为 Child  // 目的是 防止原型链的混乱  Child.prototype.constructor = Child  Child.prototype.getChildValue = function() {    return this.city  }  Child.prototype.getParentValue = function() {    return this.name  }  var parent = new Parent('father')  var child1 = new Child('son','BeiJing')  var child2 = new Child('son','BeiJing')    child1.identities.push('doctor')  console.log(child1.name) //son  console.log(child1.sex) //male  console.log(child1.country) //undefined  // 所有Child的实例共享identities属性  console.log(child1.identities) //["philosopher", "scientist", "artist", "doctor"]  console.log(child2.identities) //["philosopher", "scientist", "artist", "doctor"]  console.log(child1.sayName()) //I am a son  console.log(child1.age) //24  console.log(child1.city) //BeiJing  console.log(child1.honor) //Champion  // 子类原型重写父类原型方法,子类实例调用该方法会覆盖父类原型的这个方法,  // 但是父类实例调用该方法还是父类原型的这个方法  console.log('父类实例调用输出:' + parent.getParentValue()) //父类实例调用输出:father  console.log('子类实例调用输出:' + child1.getParentValue()) //子类实例调用输出:son  console.log(child1.getChildValue()) //BeiJing
缺点:
  • 根据 child.country 的输出的值是 undefined 可知,根据原型链继承原理,创建子类实例时,不能向父类的构造函数传递参数
  • Child.prototype.getChildValueChild.prototype.getParentValue 必须是写在 Child.prototype = new Parent('father') 之后,不然就会被报错

Q1:为什么要写 Child.prototype.constructor = Child ?不写,好像对输出结果没影响,那为什么呢?

为什么要修复子类原型的constructor的指向https://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor#

借用构造函数式继承

特点
  • 基本思想:子类构造函数的内部用超类构造函数,通过call,apply改变对象的this来继承
 function Parent(name,country) {    this.name = name    this.sex = 'male'    this.country = country    this.identities = ["philosopher", "scientist", "artist"]    this.sayName = function() {      return 'I am a ' + this.name    }  }  Parent.prototype.honor = 'Champion'  Parent.prototype.getParentValue = function() {    return this.name  }  function Child(city) {    // 通过call,apply改变对象的this来继承    Parent.call(this,'father')    this.age = 24    this.city = city  }  Child.prototype.getChildValue = function() {    return this.city  }  var child = new Child('BeiJing')  child.identities.push('doctor')    console.log(child.name) //father  console.log(child.sex) //male  console.log(child.country) //undefined  console.log(child.identities) //["philosopher", "scientist", "artist", "doctor"]  console.log(child.sayName()) //I am a father  console.log(child.age) //24  console.log(child.city) //BeiJing  console.log(child.getChildValue()) //BeiJing  console.log(child.honor) //Uncaught TypeError: child.honor is not a function  console.log(child.getParentValue()) //Uncaught TypeError: child.getParentValue is not a function

分析:

相当于Parent 这个构造函数在 Child 构造函数中执行了一遍,并且将Parent构造函数的 this 绑定的变量都切换到 Child 构造函数上,这样就克服了第一种方式带来的问题
缺点:
  • 虽然解决了原型链继承中不能传值的问题,子类实例无法复用父类原型对象的方法和属性

组合式继承

特点:
  • 基本思想:既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有自己的属性
  function Parent(name,country) {    this.name = name    this.sex = 'male'    this.country = country    this.identities = ["philosopher", "scientist", "artist"]    this.sayName = function() {      return 'I am a ' + this.name    }  }  Parent.prototype.honor = 'Champion'  Parent.prototype.getParentValue = function() {    return this.name  }  function Child(city) {    // 通过call,apply改变对象的this来继承    // 第二次调用父类构造函数,为了获取父类构造函数上的属性    Parent.call(this,'father')    this.age = 24    this.city = city  }  // Child的原型指向Parent的实例  // 意味着 Child的原型拥有Parent的实例的全部属性和方法  // 也意味着 Child的原型的__proto__属性指向Parent的原型  // 所以最后 根据原型链的规则,Child的实例拥有Parent实例的全部属性和方法,以及Parent的原型的全部属性和方法  // 第一次调用父类构造函,为了获取子类原型获取父类原型上的方法  Child.prototype = new Parent()  // 此时Child的原型的constructor 指向为 Parent    // 所以要修复Child的原型的constructor 指向为 Child  // 目的是 防止原型链的混乱  Child.prototype.constructor = Child  Child.prototype.getChildValue = function() {    return this.city  }    var child = new Child('BeiJing')  child.identities.push('doctor')    console.log(child.name) //father  console.log(child.sex) //male  console.log(child.country) //undefined  console.log(child.identities) //["philosopher", "scientist", "artist", "doctor"]  console.log(child.sayName()) //I am a father  console.log(child.age) //24  console.log(child.city) //BeiJing  console.log(child.getChildValue()) //BeiJing  console.log(child.honor) //Champion  console.log(child.getParentValue()) //father
缺点:
  • 组合是继承是JS最常用的继承模式,但组合式集成使用过程中会把父类构造函数被调用两次:第一次创建父类实例执行子类原型时候;第二次在子类构造函数内部执行时候。如果父类构造函数属性极多,会导致速度减慢

原型式继承

特点:
  • 背景:道格拉斯·克罗克福德 2006 提出的一种继承方式
  • 基本思想:基于一个对象,把这个对象作为原型,再根据需求将得到的对象进行修改
    (借助原型基于已有的对象创建新对象,同时还不必因此创建自定义类型)
  • 使用场景:在没有必要大量地创建构造函数,只想让一个对象与另一个对象保持类似情况下使用
//如果传入一个参数时,这个函数的object(o)  和 Object.creat(o)  效果一样function object(o){    // 创建一个临时构造函数F    function F(){}    // 构造函数F的原型对象指向传入的对象    F.prototype = o    // 返回一个临时构造函数为F的对象    return new F()  }  var person = {    name: 'xiaoming',    firends: ['lisi','zhangshan']  }  // 从返回值看,实际上 object(person) => new F()  // 意味着 var anotherPerson => new F()  // 意味着 anotherPerson.__proto__ => F.prototype  // 又因为  F.prototype = o  // 意味着 F.prototype => person  // 意味着 anotherPerson.__proto__ => person  // 意味着 anotherPerson 拥有对象 person 的方法和属性  var anotherPerson = object(person)  anotherPerson.name = 'yaoming'  anotherPerson.sayName = function() {          return this.name  }  anotherPerson.firends.push('wangwu')  var yetAnotherPerson = object(person)  console.log(anotherPerson.name) //yaoming  console.log(anotherPerson.firends) //["lisi", "zhangshan", "wangwu"]  console.log(anotherPerson.sayName()) //yaoming  console.log(yetAnotherPerson.name) //xiaoming  console.log(yetAnotherPerson.firends) //["lisi", "zhangshan", "wangwu"]  console.log(yetAnotherPerson.sayName()) //Uncaught TypeError: yetAnotherPerson.sayName is not a function// ECMAScript 5 有个新的规范写法:// 如果传入一个参数,object() 和 Object.create() 效果是一样的,由此文中代码可转变为:// var anotherPerson = object(person) => var anotherPerson = Object.create(person)

Q1:为什么 yetAnotherPerson.name 为 xiaoming 而不是 yaoming?

因为 anotherPerson 实例自身添加 name 属性的值,但是没有修改 anotherPerson 实例(notherPerson.proto)原型上的name的值,所以 yetAnotherPerson 实例的name还是 xiaoming

Q2:为什么anotherPerson.firends.push('wangwu'),yetAnotherPerson.firends 输出的结果跟anotherPerson.firends 一样?

因为 anotherPerson 实例先从自身去查找属性,发现没有找到,然后根据继承关系往上找,发现在原型有这个属性,后给属性追加值,又因为 anotherPerson 实例和 yetAnotherPerson 实例指向同一个原型,所以当 anotherPerson 实例修改后, yetAnotherPerson 实例调用也发生变化

Q3:为什么 yetAnotherPerson.sayName() 会报错?

因为 anotherPerson 实例自身添加 sayName 方法,但是不是给 anotherPerson 实例(notherPerson.proto)原型上添加 sayName 方法,所以yetAnotherPerson.sayName()会报错
缺点:
  • 引用类型的属性始终都是共享相应的值,多个实例对引用类型进行操作就会把值修改

寄生式继承

特点:
  • 基本思想:创建一个仅用于封装继承过程的函数,在函数内部以某种方式增强对象
  • 使用场景:如果考虑的对象不是自定义类型和构造函数情况下,寄生式继承还是有用的,任何能够返回新对象的函数都适用于此模式。但是使用寄生式继承给对象添加函数,由于不能做到函数复用而降低效率,这一点跟构造函数式继承的效果一样
//如果传入一个参数时,这个函数的object(o)  和 Object.creat(o)  效果一样 function object(o){    // 创建一个临时构造函数F    function F(){}    // 构造函数F的原型对象指向传入的对象    F.prototype = o    // 返回一个临时构造函数为F的对象    return new F()  }  var person = {    name: 'xiaoming',    firends: ['lisi','zhangshan']  }  function createAnother(o){    // 通过调用函数创建一个新对象    var clone = object(o)    // 以为对象新增方法的方式增强对象    clone.sayHi = function(){      return this.name    }    // 返回这个对象    return clone  }  var anotherPerson = createAnother(person)  var yetAnotherPerson = createAnother(person)  // 具有实例原型的person的所有属性和方法,也可以有自己的方法  console.log(anotherPerson.firends) //["lisi", "zhangshan"]  console.log(anotherPerson.sayHi()) //xiaoming  console.log(yetAnotherPerson.firends) //["lisi", "zhangshan"]  console.log(yetAnotherPerson.sayHi()) //xiaoming// ECMAScript 5 有个新的规范写法:// 如果传入一个参数,object() 和 Object.create() 效果是一样的,由此文中代码可转变为://  var clone = object(o) => var clone = Object.create(o)

寄生组合式继承

特点:
  • 背景:组合继承是JS最常用的继承模式,因为子类构造函数的原型上拥有了父类构造函数的实例属性,所以在原型连上出现多一份属性,造成内存消耗问题。无论什么情况下,都会去调用两次父类构造函数。寄生组合继承就是为了降低调用父类构造函数的内存开销
  • 基本思想:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。不必为了指定的子类型的原型而调用超类的构造函数。换句话说,用一个 F 空的构造函数去取代执行了 Parent 这个构造函数
  • 区别:寄生组合式继承 和 组合式继承区别在于,不在一次实例的调用中执行两次父类的构造函数
  function inheritPrototype(Child,Parent){    // 将新创建的对象(即副本)赋值给子类型的原型    Child.prototype = Object.create(Parent.prototype)    // 修复子类的原型的constructor的指向,避免原型链混乱    Child.prototype.constructor = Child  }  function Parent(name,country) {    this.name = name    this.sex = 'male'    this.country = country    this.identities = ["philosopher", "scientist", "artist"]    this.sayName = function() {      return 'I am a ' + this.name    }  }  Parent.prototype.honor = 'Champion'  Parent.prototype.getParentValue = function(){    return this.age  }  function Child(name,city) {    // 通过call,apply改变对象的this来继承    Parent.call(this,name)    this.age = 24    this.city = city  }  inheritPrototype(Child,Parent)  Child.prototype.getChildValue = function() {    return this.name  }  var child = new Child('father','Beijing')  child.identities.push('doctor')  console.log(child.name) //father  console.log(child.sex) //male  console.log(child.country) //undefined  console.log(child.identities) //["philosopher", "scientist", "artist", "doctor"]  console.log(child.sayName()) //I am a father  console.log(child.age) //24  console.log(child.city) //BeiJing  console.log(child.getChildValue()) //father  console.log(child.honor) //Champion  console.log(child.getParentValue()) //24  console.log(child.getChildValue()) //father