继承的方式

javascript的继承方式

007-03-继承 - 图1

原型链继承

原型链继承的主要思想是:重写子类的prototype属性,将其指向父类的实例。

  • 原型链继承的优点
    • 简单,易于实现
      • 只需要设置子类的prototype属性为父类的实例即可,实现起来简单。
    • 继承关系纯粹
      • 生成的实例既是子类的实例,也是父类的实例。
    • 可通过子类直接访问父类原型链属性和函数
      • 通过原型链集成的子类,可以直接访问到父亲原型链上新增的函数和属性
  • 原型链继承的缺点
    • 子类的所有实例将共享父类的属性
      • Cat.prototype = new Anmial()
      • 在使用原型链继承时,是直接改写饿了cat的prototype属性,将其指向了Animal的实例,那么所有生成Cat对象的实例都将共享Animal实例的属性
      • 会带来一个严重的问题,如果父类中有个值为引用数据类型,那么在改变Cat某个实例的属性值,将会影响其他实例的属性值
    • 在创建子类实例时,无法向父类的构造函数传递参数
      • 在通过new操作符创建子类的实例时,会调用子类的构造函数,而在子类的构造函数中并没有设置与父类的关联,从而导致无法向父类的构造函数传递参数。
    • 无法实现多继承
      • 由于子类Cat的prototype属性只能设置为一个值,如果同事设置多个值得话,后面的值回覆盖前面的值,导致Cat只能继承一个父类,而无法实现多继承。
    • 为子类增加原型对象上的属性和函数时,必须放在new Animal()函数之后。
      • 实现继承的关键语句时下面的这句代码,它实现了对子类prototype属性的改写。
      • Cat.prototyp = new Animal()
      • 如果想为子类新增原型对象上的属性和函数,那么需要在这个语句之后进行添加,因为如果在这个语句之前设置了prototype属性,后面执行的语句会在直接重写prototype,导致之前的设置的全部失效。

构造继承

构造继承的主要思想是在子类的构造函数中通过call()函数改变this的指向,调用父类的构造函数,从而能将父类的实例的属性和函数绑定到子类的this上。

  1. 优点
    1. 可解决子类实例共享父类属性的问题
      1. call()函数实际是改变了父类Animal构造函数中this的指向,调用后this指向了子类Cat,相当于将父类的type、age和sleep等属性和函数直接绑定到子类的this中,成了子类实例的属性和函数,因此生成的子类实例中是各自拥有自己的属性和函数。
    2. 创建子类的实例时,可以向父类传递参数-见代码
    3. 可以实现多继承
      1. 在子类的构造函数中,可以通过多次调用call()函数来继承多个父对象,每调用一次call()函数就会将父类的实例的属性和函数绑定到子类的this中。
  2. 缺点
    1. 实例知识子类的实例,不是父类的实例
      1. 因为没有通过原型对象将子类与父类惊醒串联,所以生成的实例与父类并没有关系,失去了继承的意义
    2. 只能继承父类实例的属性和函数,并不能继承原型对象上的属性和函数
      1. 子类的实例不能访问到父类原型对象上的属性和函数
    3. 无法复用父类的实例函数
      1. 由于父类的实例函数将通过call()函数绑定到子类的this中,因此子类生成的每个实例都拥有父类实例函数的引用,这会造成不必要的内存消耗,影响性能。 ```javascript // 构造继承 优点 // 父类 function Animal(age) { // 属性 this.name = ‘Animal’; this.get = age; // 实例函数 this.sleep = function() { return this.name + ‘正在睡觉’; } }

// 父类原型函数 Animal.prototype.eat = function(food) { return this.name + ‘正在吃’ + food; }

function Cat(name) { // 核心: 通过call()函数实现Animal的实例的属性和函数继承 Animal.call(this); this.name = name || ‘tom’; }

// 生成子类的实例 var cat = new Cat(‘tony’); // 可以正常调用父类实例函数 console.log(cat.sleep()); // 不能调用父类的原型函数 console.log(cat.eat());

// 构造函数的优点,创建子类时可以向父类传递参数 function BigCat(name, parentAge) { // 在子类生成实例时,传递参数给call()函数,简介的传递给父类,然后别子类继承 Animal.call(this, parentAge); this.name = name|| ‘tom’; }

var bigCat = new BigCat(‘tony12’, 11); console.log(bigCat);

// 可以实现多继承 function Tiger() { this.jump = function() { console.log(‘我这就跳’); } }

function TigerCat(name) { Animal.call(this); Tiger.call(this); this.name = name|| ‘tom’; }

var tigerCat = new TigerCat(‘大猫’); tigerCat.jump();

  1. <a name="ybjeV"></a>
  2. ### 复制继承
  3. > 复制继承的主要思想是首先生成父类的实例,然后通过for...in遍历父类实例的属性和函数,并将其依次设置为子类实例的属性和函数或者原型对象上的属性和函数。
  4. >
  5. > 在子类的构造函数中,对父类实例的所有属性进行for...in遍历,如果animal.hasOwnProperty(key)返回“true”,则表示是实例的属性和函数,则直接绑定到子类的this上,成为子类实例的属性和函数;如果animal.hasOwnProperty(key)返回“false”,则表示是原型对象上的属性和函数,则将其添加至子类的prototype属性上,成为子类的原型对象上的属性和函数。生成的子类实例cat可以访问到继承的age属性,同时还能够调用继承的sleep()函数与自身原型对象上的eat()函数
  6. 1. 优点
  7. 1. 支持多继承
  8. 1. 只需要在子类的构造函数中生成多个父类的实例,然后通过相同的for...in处理即可。
  9. 2. 能同时继承实例的属性和函数与原型对象上的属性和函数
  10. 1. 因为对所有的属性进行for...in处理时,会通过hasOwnProperty()函数判断其是实例的属性和函数还是原型对象上的属性和函数,并根据结果进行不同的设置,从而既能继承实例的属性和函数又能继承原型对象上的属性和函数。
  11. 3. 可以向父类构造函数中传递值
  12. 1. 生成子类的实例时,可以在构造函数中传递父类的属性值,然后在子类构造函数中,直接将值传递给父类的构造函数。
  13. 2. 缺点
  14. 1. 父类的所有属性都需要复制,消耗内存
  15. 1. 对于父类的所有属性都需要复制一遍,这会造成内存的重复利用,降低性能。
  16. 2. 实例只是子类的实例,并不是父类的实例
  17. 1. 实际上我们只是通过遍历父类的属性和函数并将其复制至子类上,并没有通过原型对象串联起父类和子类,因此子类的实例不是父类的实例。
  18. <a name="eNlon"></a>
  19. ### 组合继承
  20. > 组合继承的主要思想是组合了构造继承和原型继承两种方法,一方面在子类的构造函数中通过call()函数调用父类的构造函数,将父类的实例的属性和函数绑定到子类的this中;另一方面,通过改变子类的prototype属性,继承父类的原型对象上的属性和函数。
  21. 1. 优点
  22. 1. 既能继承父类实例的属性和函数,又能继承原型对象上的属性和函数
  23. 2. 既是子类的实例,又是父类的实例
  24. 3. 不存在引用属性共享的问题
  25. 1. 因为在子类的构造函数中已经将父类的实例属性指向了子类的this,所以即使后面将父类的实例属性绑定到子类的prototype属性中,也会因为构造函数作用域优先级比原型链优先级高,所以不会出现引用属性共享的问题
  26. 4. 可以向父类的构造函数中传递参数
  27. 1. 通过call()函数可以向父类的构造函数中传递参数。
  28. 2. 缺点
  29. 1. 组合继承的缺点为父类的实例属性会绑定两次。
  30. 2. 在子类的构造函数中,通过call()函数调用了一次父类的构造函数;在改写子类的prototype属性、生成父类的实例时调用了一次父类的构造函数。
  31. 3. 通过两次调用,父类实例的属性和函数会进行两次绑定,一次会绑定到子类的构造函数的this中,即实例属性和函数,另一次会绑定到子类的prototype属性中,即原型对象上的属性和函数,但是实例属性优先级会比原型对象上的属性优先级高,因此实例属性会覆盖原型对象上的属性。
  32. <a name="UKgZ0"></a>
  33. ##
  34. <a name="YFfwt"></a>
  35. ## 寄生组合继承
  36. > 在进行子类的prototype属性的设置时,可以去掉父类实例的属性和函数。
  37. ```javascript
  38. // 寄生组合继承
  39. // 父类
  40. function Animal(age) {
  41. // 属性
  42. this.name = 'Animal';
  43. this.get = age;
  44. // 实例函数
  45. this.sleep = function () {
  46. return this.name + '正在睡觉';
  47. }
  48. }
  49. // 父类原型函数
  50. Animal.prototype.eat = function (food) {
  51. return this.name + '正在吃' + food;
  52. }
  53. function Cat(name) {
  54. Animal.call(this);
  55. this.name = name;
  56. }
  57. (function(){
  58. // 设置任意函数Super();
  59. var Super = function(){};
  60. // 关键语句 Super()函数的原型指向父类Animal的原型,去掉父类的实例属性
  61. Super.prototype = Animal.prototype;
  62. Cat.prototype = new Super();
  63. Cat.prototype.constructor = Cat;
  64. })
  65. // 关键语句 Super.prototype = Animal.prototype;
  66. // 只取父类Animal的prototype属性,过滤掉Animal的实例属性,从而避免了父类的实例属性绑定两次。