类
- 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阶段生成的对象
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。