【图解】ES6 Class类继承原理及其与ES5继承方式的区别
上一篇文章我们详细讨论了ES5的各种继承方式,及其优缺点。最终总结出一种最优的继承方式--寄生组合式继承。虽然它克服了其他继承方式的缺点,但代码比较复杂,功能也比较单一。而这篇所讨论的Class类继承是一种更好的继承方式,不过基本上,Class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到。新的class写法,只是让于对象原型的写法更加清晰,更像面向对象编程的语法而已。(注:本文只探讨Class的原理,具体使用方法有很多相关资料可查阅)
1. 类的实现
ES6写法:
1 class Parent { 2 constructor(a){ 3 this.filed1 = a; 4 } 5 filed2 = 2; 6 func1 = function(){} 7 }
Babel转换后:(可以看到其底层仍然是构造函数实现的)
function _classCallCheck(instance, Constructor) { // instanceof 检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。 if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 7 var Parent = function Parent(a) { // (1) 调用_classCallCheck方法判断当前函数调用前是否有new关键字,构造函数执行前有new关键字,
会在构造函数内部创建一个空对象,将 这个空对象的__prpto__指向构造函数的原型,并将this指向这个空
对象。如上,_classCallCheck中:this instanceof Parent,返回true. _classCallCheck(this, Parent); this.filed2 = 2; // (2) 将class内部的变量函数赋值给this this.func1 = function () { }; this.filed1 = a; // (3) 执行constructor内部的逻辑 };
2. 继承的实现
ES6写法:
1 class Child extends Parent { 2 constructor(a,b) { 3 super(a); 4 this.filed3 = b; 5 } 6 7 filed4 = 1; 8 func2 = function(){} 9 }
Babel转化后:
"use strict";var Child = function (_Parent) { _inherits(Child, _Parent); // (1) 调用_inherits函数继承父类的prototype // (2) 用一个闭包保存父类引用,在闭包内部做子类构造逻辑 function Child(a, b) { _classCallCheck(this, Child); // (3) 校验是否使用new调用 var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, a)); // (4) 用当前this调用父类构造函数. _this.filed4 = 1; // (5) 执行子类class内部的变量和函数赋给this _this.func2 = function () {}; _this.filed3 = b; // (6) 执行子类constructor内部逻辑 return _this; } return Child; }(Parent);
function _inherits(subClass, superClass) { // (1) 校验父构造函数 if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } // (2) 利用寄生继承的方法继承父类原型 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // (3) 将子构造函数的_proto_指向父构造函数,这里的setPrototypeOF()方法与create()类似,
到这步可以看出class继承同时存在两条继承链,子类构造函数的__proto__指向父类,子类原型的__proto__指向父类原型 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _possibleConstructorReturn(self, call) { // 校验this是否被初始化,super是否调用,并返回父类已经赋值完的this if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
super
super代表父类构造函数, super.fun1() 等同于 Parent.fun1() 或 Parent.prototype.fun1()。super() 等同于Parent.prototype.construtor(). 默认的构造函数中会主动调用父类构造函数,并默认把当前constructor传递的参数传给了父类。所以当我们声明了constructor后必须主动调用super(),否则无法调用父构造函数,无法完成继承。
3. 总结
1. class类内部定义的所有方法都是不可枚举的。这点和ES5行为不一致。
2. 类和模块的内部默认使用严格模式,所以不需要使用use strict指定运行模式。
3. 类必须使用 new 来调用,否则会报错。这是他跟普通构造函数的一个主要区别,后者不用 new 也可以执行。
4. 类内部不存在变量提升,这一点与ES5完全不同。
5. class继承可以实现原生构造函数的继承,而ES5不可以。