繼承行為在 ES5 與 ES6 中的區(qū)別詳解
筆者注:一句話引發(fā)的基礎(chǔ)知識回爐,基礎(chǔ)不扎實,還要什么自行車
最近在看 React 方面的一些文章時,看到了這樣一個問題,「為什么每個 class 中都要寫 super, super 是做什么的?」, 剛看到這個問題時,直接就想到了繼承行為在 javascript 中的表現(xiàn)。后面作者的一句話「super 不可以省略,省略的話會報錯」。當時腦海中蹦出來一個念頭,這個同學(xué)是不是寫錯了,super 不就是用來完成調(diào)用父類構(gòu)造函數(shù),將父類的實例屬性掛在到 this 上嗎?為什么不寫還會報錯?
后來自己親自寫了一個 Demo 嘗試了一下,還真是會報錯,到底是哪里出了問題,找到了阮老師的教程又打開仔細看了一遍,發(fā)現(xiàn)里面還真是有這樣一句話:
子類必須在 constructor 方法中調(diào)用 super 方法,否則新建實例時會報錯。這是因為子類自己的 this 對象,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實例屬性和方法,然后再對其進行加工,加上子類自己的實例屬性和方法。如果不調(diào)用 super 方法,子類就得不到 this 對象。
原來如此,ES6 中 this 對象的構(gòu)造方式發(fā)生了變化。
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. } // 子類續(xù)承父類 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); // true console.log('Is rect an instance of Shape?', rect instanceof Shape); // true rect.move(1, 1); // Outputs, 'Shape moved.'
如上所示: 展示了一個 ES5 中實現(xiàn)單繼承的例子,在《Javascript 高級程序設(shè)計》一書中,給這種繼承方式定義為「寄生組合式繼承」。不管什么形式,什么命名,在 ES5 中實現(xiàn)繼承始終就是要堅持一個原則:將實例屬性放在構(gòu)造函數(shù)中掛在this上,將一些方法屬性掛在原型對象上,子類可共享。 上面這種繼承方式的關(guān)鍵在于兩點:
- 子類構(gòu)造函數(shù)通過 apply 或者 call 的方式運行父類的構(gòu)造函數(shù),此舉將父類的實例屬性掛在子類的 this 對象上
- 以父類的原型對象為基礎(chǔ),與子類的原型對象之間建立原型鏈關(guān)系,使用了 Object.create,本質(zhì)在于 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); // 調(diào)用父類的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); } }
ES6 中的繼承使用到了 extends 關(guān)鍵字,function 也變成了 class 關(guān)鍵字。class 的本質(zhì)還是一個語法糖,這個大家都會脫口而出,但是在繼承機制這里到底是如何做到的,我們看一下 babel 在此處是如何幫我們轉(zhuǎn)譯的,
var ColorPoint = /*#__PURE__*/ function (_Point) { _inherits(ColorPoint, _Point); function ColorPoint(x, y, color) { var _this; _classCallCheck(this, ColorPoint); _this = _possibleConstructorReturn(this, _getPrototypeOf(ColorPoint).call(this, x, y)); // 調(diào)用父類的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);
如上是經(jīng)過babel轉(zhuǎn)譯后的代碼,有幾個關(guān)鍵點:
一、 _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 構(gòu)造函數(shù)中 _classCallCheck(), _possibleConstructorReturn()
function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
主要是用來檢測構(gòu)造函數(shù)不能直接調(diào)用,必須是通過new的方式來調(diào)用。
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
調(diào)用父類的構(gòu)造函數(shù),初始化一些實例屬性,并將this返回。使用該返回的this賦值給子類的this對象,子類通過這一步返回的this對象,再該基礎(chǔ)之上在添加一些實例屬性。
這就是最大的不同之處。如果不經(jīng)歷這一步,子類沒有this對象,一旦操作一個不存在的this對象就會報錯。
三、 _createClass()
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
最后一步完成原型屬性與靜態(tài)屬性的掛載,如果是原型屬性,掛在在Constructor上的prototype上,如果是靜態(tài)屬性或者靜態(tài)方法,則掛在Constuctor 上。
總結(jié)
基礎(chǔ)知識要打牢,不是為了面試,前期打不勞,后面很多事情就會變的模棱兩可,別人問到的時候,就會是「可能」、「也許」。不積跬步何以至千里 ,加油。
參考鏈接
http://es6.ruanyifeng.com/#docs/class-extends
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
在原生不支持的舊環(huán)境中添加兼容的Object.keys實現(xiàn)方法
下面小編就為大家?guī)硪黄谠恢С值呐f環(huán)境中添加兼容的Object.keys實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09javascript自適應(yīng)寬度的瀑布流實現(xiàn)思路
這里主要介紹瀑布流的一種實現(xiàn)方法:絕對定位(css)+javascript+ajax+json。簡單一點如果不做滾動加載的話就是絕對定位(css)+javascript了,ajax和json是滾動加載更多內(nèi)容的時候用到的,感興趣的你可以參考下哦2013-02-02javascript實現(xiàn)倒計時跳轉(zhuǎn)頁面
本文給大家介紹了如何使用javascript實現(xiàn)倒計時跳轉(zhuǎn)到其他頁面的方法以及實現(xiàn)原理,非常的簡單實用,有需要的小伙伴可以參考下。2016-01-01JavaScript中的scrollTop詳解(滾動到頂部)
scrollTop是JavaScript中一個非常有用且重要的方法,它用于獲取或設(shè)置元素的垂直滾動條位置,這篇文章主要給大家介紹了關(guān)于JavaScript中scrollTop詳解(滾動到頂部)的相關(guān)資料,需要的朋友可以參考下2023-12-12