最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JS中的继承-2【总结】

    正文概述 掘金(沵算what)   2021-03-05   327

    相关知识点

    • 原型链继承
    • 构造函数继承
    • 组合继承
    • 寄生组合继承
    • 原型式继承
    • 寄生式继承
    • class 中的继承

    上一期说到构造函数继承 JS中的继承-1【总结】,接下来我们继续总结~~

    组合继承

    组合继承的概念

    组合继承就是将原型链继承与构造函数继承组合在一起,从而发挥两者之长的一种继承模式。

    // 原型链继承
    Child.prototype = new Parent();
    
    // 构造继承
    function Child () {
      Parent.call(this, ...arguments);
    }
    

    思路

    • 使用原型链继承来保证子类能继承到父类原型中的属性和方法;
    • 使用构造函数继承来保证子类能继承父类的实例属性和方法;

    基本操作

    • 通过 call/apply 在子类构造函数内部调用父类构造函数(构造函数继承);
    • 将子类构造函数的原型对象指向父类构造函数创建的一个匿名实例(原型链继承);
    • 修正子类构造函数原型对象的 constructor 属性,将它指向子类构造函数;

    题目一

    理解 constructor 有的作用

    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.getName = function () {
      console.log(this.name);
    };
    
    function Child(name) {
      this.sex = "boy";
      Parent.call(this, name);
    }
    
    Child.prototype = new Parent();
    Child.prototype.getSex = function () {
      console.log(this.sex);
    };
    
    var child1 = new Child("child1");
    var parent1 = new Parent("parent1");
    console.log(child1.constructor); // f Parent () {}
    console.log(parent1.constructor); // f Parent () {}
    

    parent1.constructorParent 函数这个还好理解,只要通过原型链查找,parent1 实例自身没有 constructor 属性,那么就顺着向上找,拿原型上的 constructor,发现它指向的是构造函数 Parnent,因此第二个打印出 Parent 函数。

    而对于 child1,原型链继承切断了原本 ChildChild 原型对象的关系,而是重新指向了匿名实例。使得实例 child1 能够使用匿名实例原型链上的属性和方法。

    当我们想要获取 child1.constructor,肯定是向上查找,通过 __proto__ 找它构造函数的原型对象匿名实例,但是匿名实例它自身是没有 constructor 属性,它只是 Parent 构造函数创建出来的一个对象而已,所以它也会继续向上查找,然后就找到了 Parent 原型对象上的 constructor ,也就是 Parent 了。

    所以:constructor 它不过是给我们一个提示,用来标示实例对象是由哪个构造函数创建的。

    从常理来讲,child1Child 构建的,parent1Parent 构建的。

    那么 child1 它的 constructor 就应该是 Child ,但是现在却变成了 Parent,貌似并不太符合常理。

    所以才有了这么一句:

    Child.prototype.constructor = Child;
    

    用以修复 constructor 的指向。

    JS中的继承-2【总结】

    总结来说:

    • constructor 它是构造函数原型对象中的一个属性,正常情况下它指向的是原型对象;
    • 它并不会影响任何 JS 内部属性,只是用来标示一下某个实例是由哪个构造函数产生的而已;
    • 如果我们使用了原型链继承或者组合继承无意间修改了 constructor 指向,那么出于编程的习惯,我们最好将它修正为正确的构造函数;

    题目二

    constructor 的使用场景

    var a;
    (function () {
      function A() {
        this.a = 1;
        this.b = 2;
      }
      A.prototype.logA = function () {
        console.log(this.a);
      };
      a = new A();
    })();
    a.logA(); // => a.a => 1
    
    • 定义了一个全局的变量 a,和一个构造函数 A
    • 在立即执行函数中,是可以访问到全局变量 a 的,因此 a 被赋值为了一个构造函数 A 生成的对象;
    • 并且 a 对象中有两个属性:ab
    • 之后在外层调用 a.logA(),打印出的就是 a.a,也就是 1

    问题:如果要在匿名函数外给 A 这个构造函数的原型对象中添加一个方法 logB 用以打印出 this.b

    解决思路:虽然我们在外层访问不到 A,但是我们可以通过原型链查找,来获取 A 的原型对象。

    方法1:通过 a.__proto__ 来访问到原型对象

    a.__proto__.logB = function () {
      console.log(this.b);
    };
    a.logB();
    

    方法2:通过 a.constructor.prototype 来访问到原型对象

    a.constructor.prototype.logB = function () {
      console.log(this.b);
    };
    a.logB();
    

    虽然 a 实例上没有 constructor,但是原型对象上有,所以 a.constructor 实际上拿的是原型对象上的 constructor

    题目三

    function Parent(name, colors) {
      this.name = name;
      this.colors = colors;
    }
    Parent.prototype.features = ["cute"];
    
    function Child(name, colors) {
      this.sex = "boy";
      Parent.apply(this, [name, colors]);
    }
    Child.prototype = new Parent();
    Child.prototype.constructor = Child;
    
    var child1 = new Child("child1", ["white"]);
    child1.colors.push("yellow");
    child1.features.push("sunshine");
    var child2 = new Child("child2", ["black"]);
    
    console.log(child1); // Child{ sex: "boy", name: "child1", colors: ["white", "yellow"] }
    console.log(child2); // Child{ sex: "boy", name: "child2", colors: ["black"] }
    console.log(Child.prototype); // Parent{ name: undefined, colors: undefined, constructor: f Child () {} }
    
    console.log(child1 instanceof Child); // true
    console.log(child1 instanceof Parent); // true
    

    child1child2sexname 都有值,分别为 boychild1/child2,这个没有什么疑问,而 colors 可能有一些疑惑,因为 colors 是通过构造继承与父类的,并且是复制出来的属性,所以改变 child1.colors 并不会影响 child2.colors

    Child.prototype 是使用 new Parent 生成的,并且生成的时候是没有传到参数进去的,因此 namecolors 都是 undefined

    最后两个为 true,是因为 child1 可以沿着它的原型链查找到 Child.prototypeParent.prototype

    现在可以看出组合继承的优点,它其实就是将两种继承方式的优点结合了起来:

    • 可以继承父类实例属性和方法,也可以继承父类原型属性和方法;
    • 弥补了原型链继承中引用属性共享的问题;
    • 可传参、可复用;

    题目四

    function Parent(name) {
      console.log(name);
      this.name = name;
    }
    function Child(name) {
      Parent.call(this, name);
    }
    Child.prototype = new Parent();
    var child1 = new Child("child1");
    console.log(child1);
    console.log(Child.prototype);
    

    执行结果为:

    undefined
    'child1'
    Child{ name: 'child1' }
    Parent{ name: undefined }
    

    我们虽然只调用了 new Child() 一次,但是在 Parent 中却打印了两次 name.

    • 第一次是原型链继承的时候,new Parent()
    • 第二次是构造继承的时候,Parent.call() 调用的;

    也就是说,在使用组合继承的时候,会多余的调用一次父类的构造函数。

    另外,我们想要继承父类构造函数里面的属性和方法采用的是构造继承,也就是复制一份到子类实例对象中,而此时由于调用了 new Parent(),所以 Child.prototype 中也会有一份一模一样的属性,就例如这里的 name: undefined,可是子类实例对象自己已经有一份了,所以就用不上 Child.prototype 上的了,那么就会造成内存浪费。

    因此我们可以看出组合继承的缺点:

    • 使用组合继承时,父类构造函数会被调用两次;
    • 生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。

    总结-组合继承

    实现方式:

    • 使用原型链继承来保证子类能够继承到父类原型中的属性和方法;
    • 使用构造继承来保证子类能够继承到父类的实例属性和方法;

    优点:

    • 可以继承父类实例的属性和方法,也能够继承父类原型的属性和方法;
    • 弥补了原型链继承中引用属性共享的问题;
    • 可传参、可复用

    缺点:

    • 使用组合继承时,父类构造函数会被调用两次;
    • 生成了两个实例,子类实例中的属性和方法会覆盖子类原型(父类实例)上的属性和方法,所以增加了不必要的内存。

    constructor 总结;

    • constructor 它是构造函数原型对象中的一个属性,正常情况下它指向的是原型对象的构造器的而引用;
    • 它并不会影响任何 JS 内部属性,只是用来标示一下某个实例是由哪个构造函数产生的而已;
    • 如果使用了原型链继承或者组合继承无意间修改了 constructor 的指向,那么出于编程习惯,我们最好将它修改为正确的构造函数;

    寄生组合继承【重点】

    组合继承的缺点是:

    • 父类构造函数会被调用两次;
    • 生成了两个实例,在父类实例上产生了无用废弃的属性;

    需要一个干净的实例对象,来作为子类的原型。并且这个干净的实例对象还得能继承父类原型对象里的属性。

    可以使用:Object.create();

    用法:Object.create(proto, propertiesObject);

    • 参数一:需要指定的原型对象
      • 参数 proto:作用就是能指定你要新建的这个对象它的原型对象是谁
    • 参数二:可选参数,给新对象自身添加新属性以及描述器

    题目一

    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.getName = function () {
      console.log(this.name);
    };
    
    function Child(name) {
      this.sex = "boy";
      Parent.call(this, name);
    }
    // 与组合继承的区别
    Child.prototype = Object.create(Parent.prototype);
    
    var child1 = new Child("child1");
    console.log(child1); // Child {sex: "boy", name: "child1"}
    child1.getName(); // child1
    console.log(child1.__proto__); // Parent {}
    

    上面就是一个标准的寄生组合继承,它与*组合继承的区别仅仅是 Child.prototype 不同。

    • 使用寄生组合继承,child1 不仅仅有自己的实例属性 sex,而且还复制了父类中的属性 name
    • 寄生组合继承使得实例 child1 能通过原型链查找,使用到 Parent.prototype 上的方法,因此打印出child1

    我们使用了 Object.create(Parent.prototype) 创建了一个空的对象,并且这个对象的 __proto__ 属性是指向 Parent.prototype 的。

    JS中的继承-2【总结】

    现在 Parent() 已经和 child 没有关系了,仅仅是用了 Parent.call(this, xxx) 来复制 Parent 里面的属性和方法。

    题目二

    function Parent(name) {
      this.name = name;
      this.face = "cry";
      this.colors = ["white", "black"];
    }
    Parent.prototype.features = ["cute"];
    Parent.prototype.getFeatures = function () {
      console.log(this.features);
    };
    
    function Child(name) {
      Parent.call(this, name);
      this.sex = "boy";
      this.face = "smile";
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    
    var child1 = new Child("child1");
    child1.colors.push("yellow");
    var child2 = new Child("child2");
    child2.features = ["sunshine"];
    
    console.log(child1); // Child{ name: 'child1', face: 'smile', sex: 'boy', colors: ['white', 'black', 'yellow'] }
    console.log(child2); // Child{ name: 'child2', face: 'smile', sex: 'boy', colors: ['white', 'black'], features: ['sunshine'] }
    child1.getFeatures(); // ["cute"]
    child2.getFeatures(); // ["sunshine"]
    
    • namefacesex 三个属性都没有啥问题,要注意的只是 face 属性,后面写的会覆盖前面的;
    • colors 属性是通过构造继承复制过来的,所以改变 child1.colors 对其他实例没有影响;
    • 要注意的就是这里的 features,在没有执行 child2.features = ['sunshine'] 这段代码之前,child1child2 都是共用原型链上的 features,但是执行了这段代码之后,就相当于是给 child2 对象上新增了一个名为 features 属性,所以这时候 child2 取的就是它自身的了;

    总结-寄生组合继承

    寄生组合继承算是 ES6 之前一种比较完美的继承方法。

    它避免了组合继承中调用两次父类构造函数,初始化两次实例属性的缺点。

    所以它拥有了所有继承方式的优点:

    • 只调用了一次父类构造函数,只创建了一份父类属性;
    • 子类能够访问到父类原型链上的属性和方法;
    • 能够正常的使用 instanceOfisPrototypeOf 方法;

    原型式继承

    该方法的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。

    伪代码如下:

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

    题目一

    var cat = {
      heart: "心",
      colors: ["white", "black"]
    };
    
    var guaiguai = Object.create(cat);
    var huaihuai = Object.create(cat);
    
    console.log(guaiguai); // {}
    console.log(huaihuai); // {}
    
    console.log(guaiguai.heart); // 心
    console.log(huaihuai.colors); // ["white", "black"]
    

    这里用到了我们之前提到过的 Object.create() 方法。

    在这道题中,Object.create(cat) 会创建出一个 __proto__ 属性为 cat 的空对象。

    Object.create() 的作用:

    • 它接受的是一个对象;
    • 返回的是一个新对象;
    • 新对象的原型链中必须能找到传进来的对象;

    总结-原型式继承

    实现方式:

    该方法的原理是创建一个构造函数,构造函数的原型指向对象,然后调用 new 操作符创建实例,并返回这个实例,本质是一个浅拷贝。

    优点:

    再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。

    缺点:

    • 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类;
    • 谨慎定义方法,以免定义方法也继承对象原型的方法重名;
    • 无法直接给父级构造函数使用参数;

    寄生式继承

    寄生式继承就是在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。

    伪代码如下:

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

    题目一

    例如我现在想要继承某个对象上的属性,同时又想在新创建的对象中新增上一些其它的属性。

    var cat = {
      heart: "心",
      colors: ["white", "black"]
    };
    
    function createAnother(original) {
      var clone = Object.create(original);
      clone.actingCute = function () {
        console.log("我是一只会卖萌的猫咪");
      };
      return clone;
    }
    
    var guaiguai = createAnother(cat);
    var huaihuai = Object.create(cat);
    
    guaiguai.actingCute(); // 我是一只会卖萌的猫咪
    console.log(guaiguai); // { actingCute: ƒ }
    console.log(huaihuai); // {}
    console.log(guaiguai.heart); // 心
    console.log(guaiguai.colors); // ["white", "black"]
    
    • guaiguai 是一直经过加工的小猫咪,所以它会卖萌,因此调用 actingCute() 会打印卖萌;
    • 两只猫都是通过 Object.create() 进行过原型式继承 cat 对象的,所以是共享使用 cat 对象中的属性;
    • guaiguai 经过 createAnother 新增了自身的实例方法 actingCute,所以会有这个方法
    • huaihuai 是一只空猫,因为 heart、colors 都是原型对象 cat 上的属性;

    总结-寄生式继承

    实现方式:

    在原型式继承的基础上再封装一层,来增强对象,之后将这个对象返回。

    优点:

    再不用创建构造函数的情况下,实现了原型链继承,代码量减少一部分。

    缺点:

    • 一些引用数据操作的时候会出问题,两个实例会公用继承实例的引用数据类;
    • 谨慎定义方法,以免定义方法也继承对象原型的方法重名;
    • 无法直接给父级构造函数使用参数;

    class 中的继承【重点】

    class 中继承主要是依靠两个东西:

    • extends
    • super

    而且对于该继承的效果和之前我们介绍过的寄生组合继承方式一样。

    题目一

    class 中的继承

    class Parent {
      constructor(name) {
        this.name = name;
      }
      getName() {
        console.log(this.name);
      }
    }
    class Child extends Parent {
      constructor(name) {
        super(name);
        this.sex = "boy";
      }
    }
    
    var child1 = new Child("child1");
    console.log(child1); // Child {name: "child1", sex: "boy"}
    child1.getName(); // child1
    console.log(child1 instanceof Child); // true
    console.log(child1 instanceof Parent); // true
    

    JS中的继承-2【总结】

    寄生组合继承

    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.getName = function () {
      console.log(this.name);
    };
    
    function Child(name) {
      this.sex = "boy";
      Parent.call(this, name);
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    
    var child1 = new Child("child1");
    console.log(child1); // Child {sex: "boy", name: "child1"}
    child1.getName(); // child1
    console.log(child1 instanceof Child); // true
    console.log(child1 instanceof Parent); // true
    

    JS中的继承-2【总结】

    class 继承和寄生组合继承相对比,可以看到 class 的继承方式完全满足于寄生组合继承。

    题目二

    可以看到上面那道题,我们用到了两个关键的东西:extendssuper

    extends 从字面上来看还是很好理解的,对某个东西的延伸,继承。

    那如果我们单单只用 extends 不用 super 呢?

    class Parent {
      constructor(name) {
        this.name = name;
      }
      getName() {
        console.log(this.name);
      }
    }
    class Child extends Parent {
      // constructor (name) {
      //   super(name)
      //   this.sex = 'boy'
      // }
      sex = "boy"; // 实例属性sex放到外面来
    }
    var child1 = new Child("child1");
    console.log(child1);
    child1.getName();
    

    其实这里的执行结果和没有隐去之前一样。

    执行结果:

    JS中的继承-2【总结】

    那我们是不是可以认为:

    class Child extends Parent {}
    
    // 等同于
    class Child extends Parent {
      constructor (...args) {
        super(...args)
      }
    }
    

    class 中如果没有定义 constructor 方法的话,这个方法是会被默认添加的,那么这里我们没有使用 constructor,它其实已经被隐式的添加和调用了。

    所以我们可以看出 extends 的作用:

    • class 可以通过 extends 关键字实现继承父类的所有属性和方法;
    • 若是使用了 extends 实现继承的子类内部没有 constructor 方法,则会被默认添加 constructorsuper

    题目三

    通过上面那道题看来,constructor 貌似是可有可无的角色。

    那么 super 呢,它在 class 中扮演的是一个什么角色?

    还是上面的题目,但是这次我不使用 super,看看会有什么效果:

    class Parent {
      constructor() {
        this.name = "parent";
      }
    }
    class Child extends Parent {
      constructor() {
        // super(name) // 把super隐去
      }
    }
    var child1 = new Child();
    console.log(child1);
    // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor at new Child
    
    • ES5 中的继承(例如构造继承、寄生组合继承),实质上是先创造子类的实例对象 this,然后再将父类的属性和方法添加到 this 上(使用的是 Parent.call(this));
    • 而在 ES6 中却不是这样的,它实质是先创造父类的实例对象 this(也就是使用 super()),然后再用子类的构造函数去修改 this

    通俗理解就是,子类必须得在 constructor 中调用 super 方法,否则新建实例就会报错,因为子类自己没有自己的 this 对象,而是继承父类的 this 对象,然后对其加工,如果不调用 super 的话子类就得不到 this 对象。

    题目四

    super 其实有两种用法,一种是当作函数来调用,还有一种是当做对象来使用。

    之前那道题就是将它当成函数来调用的,而且我们知道在 constructor 中还必须得执行 super()

    其实,当 super 被当作函数调用时,代表着父类的构造函数。

    虽然它代表着父类的构造函数,但是返回的却是子类的实例,也就是说 super 内部的 this 指向的是 Child。(new.target 指向当前正在执行的那个函数,你可以理解为 new 后面的那个函数)

    class Parent {
      constructor() {
        console.log(new.target.name);
      }
    }
    
    class Child extends Parent {
      constructor() {
        var instance = super();
        console.log(instance);
        console.log(this);
        console.log(instance === this);
      }
    }
    
    var child1 = new Child();
    var parent1 = new Parent();
    console.log(child1);
    console.log(parent1);
    

    在父类的 constructor 中打印出 new.target.name

    并且用了一个叫做 instance 的变量来存放 super() 的返回值。

    super 的调用代表着父类构造函数,那么在调用 new Child 的时候,它里面也执行了父类的 constructor 函数,所以 console.log(new.target.name) 肯定被执行了两遍了(一遍是 new Child,一遍是 new Parent)。

    这里的执行结果为:

    'Child'
    Child{}
    Child{}
    true
    'Parent'
    Child{}
    Parent{}
    
    • new.target 代表的是 new 后面的那个函数,那么 new.target.name 表示的是这个函数名,所以在执行new Child 的时候,由于调用了 super(),所以相当于执行了 Parent 中的构造函数,因此打印出了 'Child'
    • 另外,关于 super() 的返回值 instance ,它返回的是子类的实例,因此 instance 会打印出 Child{};并且 instance 和子类 construtor 中的 this 相同,所以打印出 true
    • 而执行 new Parent 的时候,new.target.name 打印出的就是 'Parent' 了;

    通过这道题可以看出:

    • super 当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时 super 内部的 this 指向子类;
    • 在子类的 constructorsuper() 就相当于是 Parent.constructor.call(this)

    题目五

    已经说明了 super 当成函数调用的时候就相当于是用 call 来改变了父类构造函数中的 this 指向,那么它的使用有什么限制呢?

    • 子类 constructor 中如果要使用 this 的话就必须放到 super() 之后;
    • super 当成函数调用时只能在子类的 construtor 中使用;
    class Parent {
      constructor(name) {
        this.name = name;
      }
    }
    
    class Child extends Parent {
      constructor(name) {
        this.sex = "boy";
        super(name);
      }
    }
    
    var child1 = new Child("child1");
    console.log(child1);
    // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor at new Child
    

    这也就符合了刚刚说到的第一点:子类 constructor 中如果要使用 this 的话就必须放到 super() 之后。

    这点其实非常好理解,还记得 super 的作用吗?在 constructor 中必须得有 super(),它就是用来产生实例this 的,那么再调用它之前,肯定是访问不到 this 的。

    题目六

    super 当成对象来使用时:

    • 在子类的普通函数中 super 对象指向父类的原型对象;
    • 在子类的静态方法中 super 对象指向父类;
    class Parent {
      constructor(name) {
        this.name = name;
      }
      // 父类原型对象上的方法
      getName() {
        console.log(this.name);
      }
    }
    // 父类原型对象上的方法
    Parent.prototype.getSex = function () {
      console.log("boy");
    };
    // 父类的静态方法
    Parent.getColors = function () {
      console.log(["white"]);
    };
    
    class Child extends Parent {
      constructor(name) {
        super(name);
        super.getName();
      }
      // 子类原型对象上方法
      instanceFn() {
        super.getSex();
      }
      // 子类的静态方法
      static staticFn() {
        super.getColors();
      }
    }
    var child1 = new Child("child1");
    child1.instanceFn();
    Child.staticFn();
    console.log(child1);
    
    • 在使用 new Child('child1') 创建 child1 的时候,会执行子类 constructor 中的方法,因此会执行super.getName(),而依靠准则一,此时的 constructor 中的第二个 super 指向的是父类的原型对象,因此此时 super.getName() 会被成功调用,并打印出 'child1' ;(第一个 super 是当成函数来调用)
    • child1 创建完之后,执行了 child1.instanceFn(),这时候依据准则一,instanceFn 函数中的super 指向的还是父类的原型对象,因此 super.getSex() 也会被成功调用,并打印出 'boy'
    • staticFn 属于子类的静态方法,所以需要使用 Child.staticFn() 来调用,且依据准则二,此时staticFn 中的 super 指向的是父类,也就是 Parent 这个类,因此调用其静态方法 getColors 成立,打印出 ['white']
    • 最后需要打印出 child1,我们只需要知道哪些是 child1 的实例属性和方法就可以了,通过比较很容易就发现,child1 中就只有一个 name 属性是通过调用 super(name) 从父级那里复制来的,其它方法都不能被 child1 "表现"出来,但是可以调用;

    所以执行结果为:

    'child1'
    'boy'
    ['white']
    Child{ name: 'child1' }
    

    题目七

    super 当成对象调用父类方法时 this 的指向:

    既然 super.getName()getName 是被 super 调用的,而我却说此时的 super 指向的是父类原型对象。那么getName 内打印出的应该是父类原型对象上的 name,也就是 undefined 呀,怎么会打印出 child1 呢?

    class Parent {
      constructor() {}
    }
    Parent.prototype.sex = "boy";
    Parent.prototype.getSex = function () {
      console.log(this.sex);
    };
    class Child extends Parent {
      constructor() {
        super();
        this.sex = "girl";
        super.getSex();
      }
    }
    var child1 = new Child();
    console.log(child1);
    

    现在父类原型对象和子类实例对象 child1 上都有 sex 属性,且不相同。

    如果按照 this 指向来看,调用 super.getSex() 打印出的应该是 Parent.prototype 上的 sex'boy'

    就像是这样调用一样:Parent.prototype.getSex()

    但是结果却是:

    'girl'
    Child{ sex: 'girl' }
    

    ES6 规定,通过 super 调用父类的方法时,super 会绑定子类的 this

    super.getSex.call(this)
    // 即
    Parent.prototype.getSex.call(this)
    

    而且 super 其实还有一个特性,就是你在使用它的时候,必须得显式的指定它是作为函数使用还是对象来使用,否则会报错的。

    比如下面这样就不可以:

    class Child extends Parent {
      constructor () {
        super() // 不报错
        super.getSex() // 不报错
        console.log(super) // 这里会报错
      }
    }
    

    题目八

    了解 extends 的继承目标。

    extends 后面接着的继承目标不一定要是个 class

    class B extends A {},只要 A 是一个有 prototype 属性的函数,就能被 B 继承。

    由于函数都有 prototype 属性,因此 A 可以是任意函数。

    function Parent() {
      this.name = "parent";
    }
    
    class Child1 extends Parent {}
    class Child2 {}
    class Child3 extends Array {}
    var child1 = new Child1();
    var child2 = new Child2();
    var child3 = new Child3();
    child3[0] = 1;
    
    console.log(child1); // Child1 {name: "parent"}
    console.log(child2); // Child2 {}
    console.log(child3); // Child3 [1]
    
    • 可以继承构造函数 Parent
    • 不存在任何继承,就是一个普通的函数,所以直接继承 Function.prototype
    • 可以继承原生构造函数

    总结-class 继承

    ES6 中的继承:

    • 主要是依赖 extends 关键字来实现继承,且继承的效果类似于寄生组合继承;
    • 使用了 extends 实现继承不一定要 constructorsuper,因为没有的话会默认产生并调用它们;
    • extends 后面接着的目标不一定是 class,只要是个有 prototype 属性的函数就可以了;

    super 相关:

    • 在实现继承时,如果子类中有 constructor 函数,必须得在 constructor 中调用一下 super 函数,因为它就是用来产生实例 this 的;
    • super 有两种调用方式:当成函数调用和当成对象来调用;
    • super 当成函数调用时,代表父类的构造函数,且返回的是子类的实例,也就是此时 super 内部的 this 指向子类;在子类的 constructorsuper() 就相当于是 Parent.constructor.call(this)
    • super 当成对象调用时,普通函数中 super 对象指向父类的原型对象,静态函数中指向父类,且通过 super 调用父类的方法时,super 会绑定子类的 this,就相当于Parent.prototype.fn.call(this)

    ES5 继承和 ES6 继承的区别:

    • ES5 中的继承(例如构造继承、寄生组合继承),实质上是先创造子类的实例对象 this,然后再将父类的属性和方法添加到 this 上(使用的是 Parent.call(this) );
    • 而在 ES6 中却不是这样的,它实质是先创造父类的实例对象 this(也就是使用 super()),然后再用子类的构造函数去修改 this

    起源地下载网 » JS中的继承-2【总结】

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元