JavaScript六种继承方式记录

1.原型链

利用原型让一个引用类型B继承另一个引用类型A的属性和方法

  function A(){
    this.Aa = 'Aaaa';
  }
  A.prototype.getAaValue = function(){
    return this.Aa;
  };

  function B(){
    this.Bb = 'Bbbb';
  }
  
 
//B继承了A.原来存在于 A 实例中的所有属性和方法,现在也存在于 B.prototype 中了
B.prototype = new A();
//在继承A的属性和方法的基础上,B.prototype添加新的方法
  B.prototype.getBbValue = function(){
    return this.Bb;
  };

  var C = new B();
  alert(C.getAaValue()); //Aaaa

 C继承B,B继承A,由此形成了一个原型链。

 给原型添加方法的代码一定要放在替换原型的语句之后。在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。

  function A(){
    this.Aa = 'Aaaa';
  }
  A.prototype.getAaValue = function(){
    return this.Aa;
  };

  function B(){
    this.Bb = 'Bbbb';
  }
  B.prototype = new A();
  //使用字面量添加新方法,会导致上一行代码无效
  B.prototype = {
    getBbValue : function (){
      return this.Bb;
    },
    OtherMethod : function (){
      return '1111';
    }
  };

  var C = new B();
  alert(C.getAaValue()); //报错

原型链问题 

  function A1(){
    this.colors = ["red", "blue", "green"];
  }
  function A2(){}
  //继承了 A1
  A2.prototype = new A1();
  var A3 = new A2();
  A3.colors.push("black");
  console.log(A3.colors); //"red,blue,green,black"
  var A4 = new A2();
  console.log(A4.colors); //"red,blue,green,black"

 所有A2的实例都会共享同一个属性;无法在不影响所有对象实例的情况下,像A2传递参数;因此实践中很少会单独使用原型链

 

2.借用构造函数(又称伪造对象或经典继承)

 在B构造函数内部调用A构造函数,可通过apply()或call()

  function A1(name){
    this.colors = ["red", "blue", "green"];
    this.name = name;
  }
  function A2(){
    //继承了A1,同时可传递参数
    A1.call(this,'Tom');
    //还可定义自己的属性
    this.age = 18;
  }
  var A3 = new A2();
  A3.colors.push("black");
  console.log(A3.colors); //"red,blue,green,black"
  var A4 = new A2();
  console.log(A4.colors); //"red,blue,green"
  console.log(A4.name); //Tom
  console.log(A4.age); //18

借用构造函数问题:方法都在构造函数中定义,函数复用无从谈起;且在原型中定义的方法,对子类型而言也是不可见的(这点不是很明白什么意思),导致所有类型只能使用构造函数模式;因此借用构造函数很少单独使用。

 

3.组合继承(伪经典继承)

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

  function A1(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
  }
  A1.prototype.sayName = function(){
    console.log(this.name);
  };
  function A2(name, age){
    //借用构造函数继承实例属性
    A1.call(this, name);
    this.age = age;
  }
  //原型链继承原型属性和方法
  A2.prototype = new A1();
  A2.prototype.constructor = A2; //为了让A2的构造函数重新指回这个类本身,否则的话会变成A1的构造函数
  A2.prototype.sayAge = function(){
    console.log(this.age);
  };
  var A3 = new A2("Nicholas", 29);
  A3.colors.push("black");
  console.log(A3.colors); //"red,blue,green,black" 
  A3.sayName();           //"Nicholas";
  A3.sayAge();            //29
  var A4 = new A2("Greg", 27);
  console.log(A4.colors); //"red,blue,green" 
  A4.sayName();           //"Greg";
  A4.sayAge();            //27

组合继承避免了原型链和借用构造函数的缺陷,融合了优点,成为JavaScript中最常用的继承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。

 

4.原型式继承 

ES5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与 object()方法的行为相同。

//若使用object()需先定义
  function object(o){
    function F(){}
    F.prototype = o;
    return new F();
  }
  var person1 = {
    name: "B1",
    friends: ["A1", "A2", "A3"]
  };
  //一个参数,Object.create()与object()结果一样
  var person2 = object(person1); // var person2 = Object.create(person1);
person2.name = "B2";
person2.friends.push(
"A4");

var person3 = object(person1);
person3.name
= "B3";
person3.friends.push(
"A5");

console.log(person1.name); //"B1"
console.log(person1.friends);
//"A1,A2,A3,A4,A5"

 

Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。

  var person = {
    name: "Nicholas",
    friends: ["A1", "A2", "A3"]
  };
  var person2 = Object.create(person, {
    friends: {
      value: "A4"
    }
  });
  var person3 = Object.create(person);

  console.log(person2.friends); //"A4"
  console.log(person3.friends); //"A1,A2,A3"

 支持 Object.create()方法的浏览器有 IE9+、Firefox 4+、Safari 5+、Opera 12+和 Chrome。在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。

不过,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样

 

5.寄生式继承

使用寄生式继承,会由于不能做到函数复用而降低效率;与借用构造函数类似

  function A2(original){
    var clone = Object.create(original); //通过调用函数创建一个新对象
    clone.sayHi = function(){ //以某种方式来增强这个对象
      console.log("hi");
    };
    return clone; //返回这个对象
  }
  var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
  };
  var Person2 = A2(person);
  console.log(Person2.friends); //"Shelby", "Court", "Van"
  Person2.sayHi(); //"hi"

 

6.寄生组合继承

  function A3(A2, A1){
    var obj = Object.create(A1.prototype); //创建对象
    obj.constructor = A2; //增强对象
    A2.prototype = obj; //指定对象
  }
  function A1(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
  }
  A1.prototype.sayName = function(){
    console.log(this.name);
  };
  function A2(name, age){
    //继承A1
    A1.call(this, name);
    this.age = age;
  }
  A3(A2, A1);
  A2.prototype.sayAge = function(){
    console.log(this.age);
  };

  var A3 = new A2("Nicholas", 29);
  A3.colors.push("black");
  console.log(A3.colors); //"red,blue,green,black"
  A3.sayName();           //"Nicholas";
  A3.sayAge();            //29
  
  var A4 = new A2("Greg", 27);
  console.log(A4.colors); //"red,blue,green"
  A4.sayName();           //"Greg";
  A4.sayAge();            //27

这个例子的高效率体现在它只调用了一次 A1构造函数(组合继承调用了两次A1),并且因此避免了在 A2. prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变,能正常使用instanceof 和 isPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

 

所有引用类型默认都继承了 Object,而这个继承也是通过原型链实现的。

所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype。这也正是所有自定义类型都会继承 toString()、valueOf()等默认方法的根本原因。

 

 

 

本文为原创文章,转载请保留原出处,方便溯源,如有错误地方,谢谢指正。

posted @ 2018-03-17 17:27  笔记珊珊珊  阅读(95)  评论(2编辑  收藏  举报