1. 简单原型链继承

  1. function Animal(){
  2. this.name = '动物';
  3. this.skill = [1];
  4. }
  5. function Cat(){
  6. this.age = '13';
  7. }
  8. Cat.prototype = new Animal(); // 核心(拿父类实例充当子类的原型对象)
  9. var amyCat = new Cat();
  10. var tomCat = new Cat();
  11. amyCat.skill.push(2);
  12. console.dir(Animal);
  13. console.dir(Cat);
  14. console.log(amyCat.name);
  15. console.log(tomCat.skill);
  16. console.log(amyCat.skill);
  17. // 优点
  18. // 简单易于实现。
  19. // 存在问题
  20. // 1.原型对象的引用属性是所有实例共享的。
  21. // 2.创建子类实例时,无法向父类构造函数传参。

2. 借用构造函数

  1. function Animal(name){
  2. this.name = name;
  3. this.skill = [1];
  4. this.fun = function(){};
  5. }
  6. function Cat(name,age){
  7. // Animal.call(this,name); // 核心
  8. Animal.apply(this,[name]); // 此处区分了call和apply的区别,apply用的是数组参数。
  9. this.age = age;
  10. }
  11. var smallCat = new Cat('small Cat',12);
  12. var bigCat = new Cat('Big Cat', 15);
  13. smallCat.skill.push(2);
  14. console.dir(smallCat);
  15. console.dir(bigCat);
  16. console.log(smallCat.name);
  17. console.log(bigCat.name);
  18. console.log(bigCat.skill);
  19. // 核心
  20. // 借用父类构造函数来增强子类的实例,等于将父类的实例属性复制一份到子类实例中,没有用到原型。
  21. // 优点
  22. // 1. 解决了子类实例共享父类引用属性的问题。
  23. // 2. 创建子类实例时,可以向父类构造函数传参。
  24. // 缺点
  25. // 1. 无法实现函数复用,每个子类实例都持有一个新的fun函数,太多会影响性能。

3. 组合继承(组合继承)

  1. function Animal(name){
  2. this.name = name;
  3. this.skill = [1];
  4. // this.fun = function(){};
  5. }
  6. Animal.prototype.fun = function(){
  7. console.log(this.name);
  8. };
  9. function Cat(name,age){
  10. Animal.call(this, name); // 核心
  11. this.age = age;
  12. }
  13. Cat.prototype = new Animal(); // 核心
  14. var cat1 = new Cat('asdf', 10);
  15. var cat2 = new Cat('xxx', 23);
  16. console.dir(cat1);
  17. console.dir(cat2);
  18. cat1.fun();
  19. cat2.fun();
  20. // 核心
  21. // 将实例函数放在原型对象上,来实现函数复用。同时保留借用构造函数方式的优点。
  22. // Animal.call(this, name); 继承父类的基本属性,并且向父类构造函数传参。
  23. // Cat.prototype = new Animal(); 继承父类函数,实现函数复用。
  24. // 优点
  25. // 1.不存在引用属性(函数等)共享的问题。
  26. // 2.可传参。
  27. // 3.函数可复用。
  28. // 缺点
  29. // 1.子类原型上有一份多余的父类实例属性,因为父类构造函数被调用了两次,
  30. // 子类实例上的属性屏蔽了子类原型上的属性,造成内存浪费。

4. 寄生组合继承(最佳方式)

  1. function beget(obj){
  2. var F = function(){};
  3. F.prototype = obj;
  4. return new F();
  5. }
  6. function Animal(name){
  7. this.name = name;
  8. this.skill = [1];
  9. }
  10. Animal.prototype.fun1 = function(){
  11. console.log(1);
  12. };
  13. Animal.prototype.fun2 = function(){};
  14. function Cat(name,age){
  15. Animal.call(this,name); // 核心
  16. this.age = age;
  17. }
  18. var proto = beget(Animal.prototype); // 核心
  19. proto.constructor = Cat; // 核心
  20. Cat.prototype = proto; // 核心
  21. var cat1 = new Cat();
  22. console.dir(Cat);
  23. console.dir(cat1);
  24. cat1.fun1();
  25. // 核心
  26. // beget(Animal.prototype)。去掉了原型对象上多余的父类实例属性。
  27. // 优点:包括上述3中方式的优点。
  28. // 缺点:(据说组合继承是比较常用的。)

5. 原型式继承

  1. function beget(obj){
  2. var F = function(){};
  3. F.prototype = obj;
  4. return new F();
  5. }
  6. function Animal(name){
  7. this.name = name;
  8. this.skill = [1];
  9. }
  10. Animal.prototype.fun1 = function(){};
  11. var animal = new Animal('animal xxx');
  12. var cat = beget(animal);
  13. cat.age = 123;
  14. console.dir(cat);
  15. console.log(cat.age);
  16. // 核心
  17. // 使用beget函数得到一个新对象(无实例属性),再逐步增强(填充实例属性)。
  18. // ES5提供了Object.create()函数,内部就是原型式继承。IE9+支持。
  19. // 优点:
  20. // 从已有对象衍生新的对象,不需要创建自定义类型。
  21. // 缺点:
  22. // 1. 原型继承引用属性会被所有实例共享,因为用整个父类对象来充当子类原型对象,所以会有这个缺陷。
  23. // 2. 无法实现函数复用。(属性现添的)

5. 寄生式(是一种模式,并不只能用来实现继承)

  1. function beget(obj){
  2. var F = function(){};
  3. F.prototype = obj;
  4. return new F();
  5. }
  6. function Animal(name){
  7. this.name = name;
  8. this.skill = [1];
  9. }
  10. Animal.prototype.fun1 = function(){};
  11. function getSubObject(obj){
  12. // 创建新对象
  13. var clone = beget(obj); // 核心
  14. // 增强
  15. clone.age = 1;
  16. return clone;
  17. }
  18. var sub = getSubObject(new Animal('zfd'));
  19. console.dir(sub);
  20. console.log(sub.name);
  21. console.log(sub.age);
  22. // 核心
  23. // 给原型式继承穿个马甲。(上面的原型继承更像是对象复制,等下写写对象复制。)
  24. // beget()函数不是必须的,创建新对象->增强->返回该对象,这样的过程叫 寄生式继承,
  25. // 新对象如何创建的并不重要(beget生的,new出来的,字面量都可以)。
  26. // 优点:
  27. // 不要创建自定义类型(不用写子类构造函数吗)
  28. // 缺点:
  29. // 无法实现函数复用(没有用到原型,所以不行)
  30. // ------寄生式继承+组合继承 = 寄生组合式继承。

6种继承方式的联系

JavaScript的几种继承方式 - 图1

P.S.虚线表示辅助作用,实线表示决定性作用

阅读文章

  1. 重新理解js的6种继承方式
  2. JS中new到底发生了什么/