继承行为在 ES5 与 ES6 中的区别详解

时间:2021-05-18

笔者注:一句话引发的基础知识回炉,基础不扎实,还要什么自行车

最近在看 React 方面的一些文章时,看到了这样一个问题,「为什么每个 class 中都要写 super, super 是做什么的?」, 刚看到这个问题时,直接就想到了继承行为在 javascript 中的表现。后面作者的一句话「super 不可以省略,省略的话会报错」。当时脑海中蹦出来一个念头,这个同学是不是写错了,super 不就是用来完成调用父类构造函数,将父类的实例属性挂在到 this 上吗?为什么不写还会报错?

后来自己亲自写了一个 Demo 尝试了一下,还真是会报错,到底是哪里出了问题,找到了阮老师的教程又打开仔细看了一遍,发现里面还真是有这样一句话:

子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。

原来如此,ES6 中 this 对象的构造方式发生了变化。

ES5 中的继承

// Shape - 父类(superclass)function Shape() { this.x = 0; this.y = 0;}// 父类的方法Shape.prototype.move = function(x, y) { this.x += x; this.y += y; console.info('Shape moved.');};// Rectangle - 子类(subclass)function Rectangle() { Shape.call(this); // call super constructor.}// 子类续承父类Rectangle.prototype = Object.create(Shape.prototype);Rectangle.prototype.constructor = Rectangle;var rect = new Rectangle();console.log('Is rect an instance of Rectangle?', rect instanceof Rectangle); // trueconsole.log('Is rect an instance of Shape?', rect instanceof Shape); // truerect.move(1, 1); // Outputs, 'Shape moved.'

如上所示: 展示了一个 ES5 中实现单继承的例子,在《Javascript 高级程序设计》一书中,给这种继承方式定义为「寄生组合式继承」。不管什么形式,什么命名,在 ES5 中实现继承始终就是要坚持一个原则:将实例属性放在构造函数中挂在this上,将一些方法属性挂在原型对象上,子类可共享。 上面这种继承方式的关键在于两点:

  • 子类构造函数通过 apply 或者 call 的方式运行父类的构造函数,此举将父类的实例属性挂在子类的 this 对象上
  • 以父类的原型对象为基础,与子类的原型对象之间建立原型链关系,使用了 Object.create,本质在于 Child.prototype.__proto === Parent.prototype;
  • ES6 中的继承

    class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; }}class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); }}

    ES6 中的继承使用到了 extends 关键字,function 也变成了 class 关键字。class 的本质还是一个语法糖,这个大家都会脱口而出,但是在继承机制这里到底是如何做到的,我们看一下 babel 在此处是如何帮我们转译的,

    var ColorPoint =function (_Point) { _inherits(ColorPoint, _Point); function ColorPoint(x, y, color) { var _this; _classCallCheck(this, ColorPoint); _this = _possibleConstructorReturn(this, _getPrototypeOf(ColorPoint).call(this, x, y)); // 调用父类的constructor(x, y) _this.color = color; return _this; } _createClass(ColorPoint, [{ key: "toString", value: function toString() { return this.color + ' ' + _get(_getPrototypeOf(ColorPoint.prototype), "toString", this).call(this); } }]); return ColorPoint;}(Point);

    如上是经过babel转译后的代码,有几个关键点:

    一、 _inherits()

    function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass);}

    首先完成extends对象的校验,必须是function 或者null,否则报错。其次完成以下事情:

    ColorPoint.__proto__ === Point;ColorPoint.prototype.__proto__ === Point.prototype;

    二、 ColorPoint 构造函数中 _classCallCheck(), _possibleConstructorReturn()

    function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); }}

    主要是用来检测构造函数不能直接调用,必须是通过new的方式来调用。

    function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self);}

    调用父类的构造函数,初始化一些实例属性,并将this返回。使用该返回的this赋值给子类的this对象,子类通过这一步返回的this对象,再该基础之上在添加一些实例属性。

    这就是最大的不同之处。如果不经历这一步,子类没有this对象,一旦操作一个不存在的this对象就会报错。

    三、 _createClass()

    function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor;}

    最后一步完成原型属性与静态属性的挂载,如果是原型属性,挂在在Constructor上的prototype上,如果是静态属性或者静态方法,则挂在Constuctor 上。

    总结

    基础知识要打牢,不是为了面试,前期打不劳,后面很多事情就会变的模棱两可,别人问到的时候,就会是「可能」、「也许」。不积跬步何以至千里 ,加油。

    参考链接

    http://es6.ruanyifeng.com/#docs/class-extends

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create

    https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015%2Creact%2Cstage-2&targets=&browsers=&builtIns=false&debug=false&code_lz=Q

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

    声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

    相关文章