笔者注:一句话引发的基础知识回炉,基础不扎实,还要什么自行车
最近在看 React 方面的一些文章时,看到了这样一个问题,「为什么每个 class 中都要写 super, super 是做什么的?」, 刚看到这个问题时,直接就想到了继承行为在 javascript 中的表现。后面作者的一句话「super 不可以省略,省略的话会报错」。当时脑海中蹦出来一个念头,这个同学是不是写错了,super 不就是用来完成调用父类构造函数,将父类的实例属性挂在到 this 上吗?为什么不写还会报错?
后来自己亲自写了一个 Demo 尝试了一下,还真是会报错,到底是哪里出了问题,找到了阮老师的教程又打开仔细看了一遍,发现里面还真是有这样一句话:
子类必须在 constructor
方法中调用 super
方法,否则新建实例时会报错。这是因为子类自己的 this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this
对象。
原来如此,ES6 中 this
对象的构造方式发生了变化。
ES5 中的继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function Shape ( ) { this .x = 0 ; this .y = 0 ; } Shape .prototype .move = function (x, y ) { this .x += x; this .y += y; console .info ('Shape moved.' ); }; function Rectangle ( ) { Shape .call (this ); } 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 ); console .log ('Is rect an instance of Shape?' , rect instanceof Shape ); rect.move (1 , 1 );
如上所示: 展示了一个 ES5 中实现单继承的例子,在《Javascript 高级程序设计》一书中,给这种继承方式定义为「寄生组合式继承」。不管什么形式,什么命名,在 ES5 中实现继承始终就是要坚持一个原则:将实例属性放在构造函数中挂在this上,将一些方法属性挂在原型对象上,子类可共享。 上面这种继承方式的关键在于两点:
子类构造函数通过 apply
或者 call
的方式运行父类的构造函数,此举将父类的实例属性挂在子类的 this
对象上
以父类的原型对象为基础,与子类的原型对象之间建立原型链关系,使用了 Object.create
,本质在于 Child.prototype.__proto === Parent.prototype
;
ES6 中的继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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); this .color = color; } toString ( ) { return this .color + ' ' + super .toString (); } }
ES6 中的继承使用到了 extends 关键字,function 也变成了 class 关键字。class 的本质还是一个语法糖,这个大家都会脱口而出,但是在继承机制这里到底是如何做到的,我们看一下 babel 在此处是如何帮我们转译的,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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)); _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()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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,否则报错。其次完成以下事情:
1 2 3 ColorPoint .__proto__ === Point ;ColorPoint .prototype .__proto__ === Point .prototype ;
二、 ColorPoint 构造函数中 _classCallCheck(), _possibleConstructorReturn()
1 2 3 4 5 6 function _classCallCheck (instance, Constructor ) { if (!_instanceof (instance, Constructor )) { throw new TypeError ("Cannot call a class as a function" ); } }
主要是用来检测构造函数不能直接调用,必须是通过 new 的方式来调用。
1 2 3 4 5 6 function _possibleConstructorReturn (self, call ) { if (call && (_typeof (call) === "object" || typeof call === "function" )) { return call; } return _assertThisInitialized (self); }
调用父类的构造函数,初始化一些实例属性,并将 this 返回。使用该返回的 this 赋值给子类的this对象,子类通过这一步返回的 this 对象,再该基础之上在添加一些实例属性。
这就是最大的不同之处。如果不经历这一步,子类没有this对象,一旦操作一个不存在的this对象就会报错。
三、 _createClass()
1 2 3 4 5 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