• ES5中的类是通过function定义的
function Person (name, age) {
    this.name = name;
    this.age = age;
}

const p = new Person()
  • ES6中的类是通过class定义的
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

const p = new Person()
  • 可以看到class定义的类,内部的constructor函数和function定义的Person是一样的,通过new进行实例化的时候,function是调用他自己,而class则是调用其内部定义的constructor函数;
  • 另外class的写法上,Person后面不带括号,实例化时的参数是在constructor里面进行传递的;

继承

  • 继承在JS里的含义即,子类可以访问到父类的属性
  • ES5里的继承是通过修改原型链实现的
function Person (name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.getAge = function () {
    return this.age;
};

function Teacher (name, age, salary) {
    const obj = Object.create(new Person(name, age));
    obj.salary = salary;
    return obj;
}

const t = new Teacher(...);
  • 如上通过将实例化后的父类作为子类实例化的原型,即t可以通过t.__proto__.__proto__访问到Person的原型,从而访问到getAge方法,而name和age是直接挂在p上面的,t也可以访问到
  • es6的继承是通过extends进行的,系统底层帮你关联了类的原型,无需自己手动修改
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

class Teacher extends Person {
    constructor(name, age, salary) {
        super(name, age);
        this.salary = salary;
    }
}

const t = new Teacher(...)
  • 注意这里出现了一个super,这是ES6内置的函数,只能在class的构造函数constructor上面定义,其本质代表父类的构造函数;
  • 我们可以通过babel转译上面的代码来理解ES6的继承,babel官网提供在线转码功能,方便平时对照,转换后的代码如下:
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

class Teacher extends Person {
    constructor(name, age, salary) { }
}
-------------------------------------
"use strict";

function _inheritsLoose(subClass, superClass) {
    subClass.prototype = Object.create(superClass.prototype);
    subClass.prototype.constructor = subClass;
    subClass.__proto__ = superClass;
}

var Person = function Person(name, age) {
  this.name = name;
  this.age = age;
};

var Teacher = /*#__PURE__*/function (_Person) {
  _inheritsLoose(Teacher, _Person);

  function Teacher(name, age, salary) {
    var _this;

    _this = _Person.call(this, name, age) || this;
    _this.salary = salary;
    return _this;
  }

  return Teacher;
}(Person);
  • 首先可以看到,转译后,父类被转换成了普通的函数,而子类首先执行了_inheritsLoose方法,做了以下三个处理:

    • 将父类的原型对象设置成了子类原型对象的原型
    • 设置子类的原型对象上的构造函数指向
    • 将子类和父类通过__proto__关联了起来
  • 这三个操作和ES5最大的不同在于将子类和父类通过__proto__关联了起来
  • 处理完原型链,子类作为一个函数被返回,该函数内,会以调用该函数的对象作为上下文调用一遍父类的构造函数,并在其返回上加上自己内部的属性,最终返回这个对象
  • 因为最终类是通过new实例化的,结合new的原理,也就是最终子类会内部创建一个对象,作为this的指向,并调用一遍父类,将父类的属性挂载上去,并返回,然后挂载上自己的属性,最后返回,这样就完成了继承
  • 这里需要注意一点_Person的结果没有返回的情况下,_this等于this,但如果_Person返回了一个对象,_this将会等于那个对象而非new的时候创建的this
  • 以上是有super的转码结果,试着把super去掉后转译得到:

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }

var Person = function Person(name, age) {
  this.name = name;
  this.age = age;
};

var Teacher = /*#__PURE__*/function (_Person) {
  _inheritsLoose(Teacher, _Person);

  function Teacher(name, age, salary) {
    var _this;

    _this.salary = salary;
    return _assertThisInitialized(_this);
  }

  return Teacher;
}(Person);
  • 可以看到super去掉之后,出现了一段抛出异常的方法,以及原本调用Person的代码不见了;
  • 抛错前的判断void 0 其实就是输出undefined,但因为js里面undifined是一个可被覆盖的变量而非常量或保留变量,所以通常使用void 0来获取undeifned
  • 同时也可以看出super就像对应与以当前上下文调用一遍父类函数,super的入参也是父类构造函数的入参
  • 这里可以看出ES6规定了如果使用了constructor并且使用了继承,就必须要在constructor里面调用super,并且调用super必须在调用this之前,因为没有super之前,_this等于undefined,无法在上面绑定属性,转译后代码会报错,而直接用class时会提示必须在调用this前调用super

super存在的意义

  • 以下这里是纯粹的个人理解
  • super没有自动执行,而是需要用户手动添加的原因是,程序无法知道调用父类构造函数时,使用哪个参数,而暴露super给用户也使得用户在传入参数之前可以做一些定制化的处理;
  • 另外super必须要在this前调用,一是因为上面说的没调用super前,_this是undefined,那我们可能会疑问为什么不直接先默认把this赋值给_this,那就不会有undefined的问题,但也如前所说,父类的构造函数是有可能返回一个对象的,当返回一个对象的时候,_this将会被重新赋值,那么之前对_this做的所有操作都是没有意义的,所以ES6硬性规定了,super必须在this前调用
  • 父类构造函数有可能返回非对象但是非空的值吗,是有可能的,这种情况下ES5转译出来的代码执行后,salary是永远也无法被赋值的,但实际上直接运行class的写法,发现salary可以正常被赋值,这是因为转义的代码是用es2015-loose转的,直接用es2015转的话发现,代码是会对父类构造函数返回结果判断是否为对象的,这也符合new一个function时,判断其是否为对象来作为最后结果的返回的逻辑,有兴趣的可以上babel官网看下es2015转出来的代码,会添加很多兼容性的判断,更为严谨一点

ES5/ES6继承区别

  • 写法不同,ES5要自己操作原型链,ES6直接帮你做了;
  • ES6,子类的构造函数可以通过__proto__访问到父类;
  • ES6,使用了构造函数且用了继承,必须在构造函数里用super
  • ES6,子类构造函数里的this,取决于父类,父类的构造函数返回一个对象的情况下,子类的实例就是这个返回的对象,es5的则是new阶段生成的对象

zengrc
28 声望0 粉丝

« 上一篇
vue收集依赖
下一篇 »
JS 隐式转换