JavaScript对象继承
liang👍2020-05-15 21:34:060

JavaScript对象继承

8种继承方式(包括ES6中的extends

需要提前了解的知识点

  1. __proto__

引用《JavaScript权威指南》的一段描述:

Every JavaScript object has a second JavaScript object (or null ,
but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.

翻译出来就是每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。(简单理解为该实例所继承的的原型对象)

  1. prototype

    首先来说说prototype属性,不像每个对象都有__proto__属性来标识自己所继承的原型,只有函数才有prototype属性。

    当你创建函数时,JS会为这个函数自动添加prototype属性, 值是一个有 constructor 属性的对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)。

  2. constructor

    constructor属性指向创建当前对象的构造函数


  1. 原型链继承

    构造函数、原型和实例之间的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个原型对象的指针。

    继承的本质就是复制,即重写原型对象,代之以一个新类型的实例

   function SuperType() {
       this.property = true;
   }

   SuperType.prototype.getSuperValue = function() {
       return this.property;
   }

   function SubType() {
       this.subproperty = false;
   }

   // 核心代码 创建SuperType的实例,并将该实例赋值给SubType.prototype
   SubType.prototype = new SuperType();

   SubType.prototype.getSubValue = function() {
       return this.subproperty;
   }

   var instance = new SubType();
   console.log(instance.getSuperValue()); // true

不足

  • 多个实例对引用类型的操作会被篡改。
  1. 借用构造函数继承

    使用父类的构造函数来增强子类实例,等同于复制父类的实例给子类(不使用原型)

   function  SuperType(){
       this.color=["red","green","blue"];
   }
   function  SubType(){
       // 核心代码 继承自SuperType
       SuperType.call(this);
   }
   var instance1 = new SubType();
   instance1.color.push("black");
   alert(instance1.color);//"red,green,blue,black"

   var instance2 = new SubType();
   alert(instance2.color);//"red,green,blue"

不足

  • 只能继承父类的实例属性和方法,不能继承原型属性/方法
  • 无法实现复用,每个子类都有父类实例函数的副本,影响性能
  1. 组合继承

    组合上述两种方法就是组合继承。用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

   function SuperType(name){
     this.name = name;
     this.colors = ["red", "blue", "green"];
   }
   SuperType.prototype.sayName = function(){
     alert(this.name);
   };

   function SubType(name, age){
     // 继承属性
     // 核心代码 第二次调用SuperType()
     SuperType.call(this, name);
     this.age = age;
   }

   // 继承方法
   // 构建原型链
   // 核心代码 第一次调用SuperType()
   SubType.prototype = new SuperType();
   // 重写SubType.prototype的constructor属性,指向自己的构造函数SubType
   SubType.prototype.constructor = SubType;
   SubType.prototype.sayAge = function(){
       alert(this.age);
   };

   var instance1 = new SubType("Nicholas", 29);
   instance1.colors.push("black");
   alert(instance1.colors); //"red,blue,green,black"
   instance1.sayName(); //"Nicholas";
   instance1.sayAge(); //29

   var instance2 = new SubType("Greg", 27);
   alert(instance2.colors); //"red,blue,green"
   instance2.sayName(); //"Greg";
   instance2.sayAge(); //27

不足

  • 第一次调用SuperType():给SubType.prototype写入两个属性name,color。
  • 第二次调用SuperType():给instance1写入两个属性name,color。
  1. 原型式继承

    利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

   /**
    * object()对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象。
    * ES5中存在Object.create()的方法,能够代替object方法。
    */
   function object(obj){
     function F(){}
     F.prototype = obj;
     return new F();
   }

   var person = {
     name: "Nicholas",
     friends: ["Shelby", "Court", "Van"]
   };

   var anotherPerson = object(person);
   anotherPerson.name = "Greg";
   anotherPerson.friends.push("Rob");

   var yetAnotherPerson = object(person);
   yetAnotherPerson.name = "Linda";
   yetAnotherPerson.friends.push("Barbie");

   alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

不足

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数
  1. 寄生式继承

    核心:在原型式继承的基础上,增强对象,返回构造函数

   function object(obj){
     function F(){}
     F.prototype = obj;
     return new F();
   }

   function createAnother(original){
     var clone = object(original); // 通过调用 object() 函数创建一个新对象
     clone.sayHi = function(){  // 以某种方式来增强对象
       alert("hi");
     };
     return clone; // 返回这个对象
   }

   var person = {
     name: "Nicholas",
     friends: ["Shelby", "Court", "Van"]
   };
   var anotherPerson = createAnother(person);
   anotherPerson.sayHi(); //"hi"

不足(同原型式继承)

  • 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
  • 无法传递参数
  1. 寄生组合式继承

    结合借用构造函数传递参数和寄生模式实现继承

   function inheritPrototype(subType, superType){
     var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
     prototype.constructor = subType;                    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
     subType.prototype = prototype;                      // 指定对象,将新创建的对象赋值给子类的原型
   }

   // 父类初始化实例属性和原型属性
   function SuperType(name){
     this.name = name;
     this.colors = ["red", "blue", "green"];
   }
   SuperType.prototype.sayName = function(){
     alert(this.name);
   };

   // 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
   function SubType(name, age){
     SuperType.call(this, name);
     this.age = age;
   }

   // 将父类原型指向子类
   inheritPrototype(SubType, SuperType);

   // 新增子类原型属性
   SubType.prototype.sayAge = function(){
     alert(this.age);
   }

   var instance1 = new SubType("xyc", 23);
   var instance2 = new SubType("lxy", 23);

   instance1.colors.push("2"); // ["red", "blue", "green", "2"]
   instance1.colors.push("3"); // ["red", "blue", "green", "3"]

这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceofisPrototypeOf()

这是最成熟的方法,也是现在库实现的方法

  1. 混入方式继承多个对象

    继承多个对象

   function MyClass() {
        SuperClass.call(this);
        OtherSuperClass.call(this);
   }

   // 继承一个类
   MyClass.prototype = Object.create(SuperClass.prototype);
   // 混合其它
   Object.assign(MyClass.prototype, OtherSuperClass.prototype);
   // 重新指定constructor
   MyClass.prototype.constructor = MyClass;

   MyClass.prototype.myMethod = function() {
        // do something
   };

Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。

  1. ES6类继承extends

    extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。

   class Rectangle {
       // constructor
       constructor(height, width) {
           this.height = height;
           this.width = width;
       }

       // Getter
       get area() {
           return this.calcArea()
       }

       // Method
       calcArea() {
           return this.height * this.width;
       }
   }

   const rectangle = new Rectangle(10, 20);
   console.log(rectangle.area);
   // 输出 200

   -----------------------------------------------------------------
   // 继承
   class Square extends Rectangle {

     constructor(length) {
       super(length, length);

       // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
       this.name = 'Square';
     }

     get area() {
       return this.height * this.width;
     }
   }

   const square = new Square(10);
   console.log(square.area);
   // 输出 100

参考链接

  1. proto和prototype来深入理解JS对象和原型链
    1. JavaScript常用八种继承方案

更新时间:2022-04-06 21:42:40

文章分类:JavaScript

喜欢

暂时还没有评论 不如来抢个沙发