摘要:用過的讀者知道,經(jīng)常用繼承。部分源碼使用點(diǎn)擊這里查看源碼面試官可以順著這個(gè)問繼承的相關(guān)問題,比如的繼承用如何實(shí)現(xiàn)。主要就是三點(diǎn)子類構(gòu)造函數(shù)的指向父類構(gòu)造器,繼承父類的靜態(tài)方法子類構(gòu)造函數(shù)的的指向父類構(gòu)造器的,繼承父類的方法。
用過React的讀者知道,經(jīng)常用extends繼承React.Component。
// 部分源碼 function Component(props, context, updater) { // ... } Component.prototype.setState = function(partialState, callback){ // ... } const React = { Component, // ... } // 使用 class index extends React.Component{ // ... }
點(diǎn)擊這里查看 React github源碼
面試官可以順著這個(gè)問JS繼承的相關(guān)問題,比如:ES6的class繼承用ES5如何實(shí)現(xiàn)。據(jù)說很多人答得不好。
要弄懂extends繼承之前,先來復(fù)習(xí)一下構(gòu)造函數(shù)、原型對(duì)象和實(shí)例之間的關(guān)系。
代碼表示:
function F(){} var f = new F(); // 構(gòu)造器 F.prototype.constructor === F; // true F.__proto__ === Function.prototype; // true Function.prototype.__proto__ === Object.prototype; // true Object.prototype.__proto__ === null; // true // 實(shí)例 f.__proto__ === F.prototype; // true F.prototype.__proto__ === Object.prototype; // true Object.prototype.__proto__ === null; // true
筆者畫了一張圖表示:
我們先看看這段包含靜態(tài)方法的ES6繼承代碼:
// ES6 class Parent{ constructor(name){ this.name = name; } static sayHello(){ console.log("hello"); } sayName(){ console.log("my name is " + this.name); return this.name; } } class Child extends Parent{ constructor(name, age){ super(name); this.age = age; } sayAge(){ console.log("my age is " + this.age); return this.age; } } let parent = new Parent("Parent"); let child = new Child("Child", 18); console.log("parent: ", parent); // parent: Parent?{name: "Parent"} Parent.sayHello(); // hello parent.sayName(); // my name is Parent console.log("child: ", child); // child: Child?{name: "Child", age: 18} Child.sayHello(); // hello child.sayName(); // my name is Child child.sayAge(); // my age is 18
其中這段代碼里有兩條原型鏈,不信看具體代碼。
// 1、構(gòu)造器原型鏈 Child.__proto__ === Parent; // true Parent.__proto__ === Function.prototype; // true Function.prototype.__proto__ === Object.prototype; // true Object.prototype.__proto__ === null; // true // 2、實(shí)例原型鏈 child.__proto__ === Child.prototype; // true Child.prototype.__proto__ === Parent.prototype; // true Parent.prototype.__proto__ === Object.prototype; // true Object.prototype.__proto__ === null; // true
一圖勝千言,筆者也畫了一張圖表示,如圖所示:
結(jié)合代碼和圖可以知道。
ES6 extends 繼承,主要就是:
把子類構(gòu)造函數(shù)(Child)的原型(__proto__)指向了父類構(gòu)造函數(shù)(Parent),
把子類實(shí)例child的原型對(duì)象(Child.prototype) 的原型(__proto__)指向了父類parent的原型對(duì)象(Parent.prototype)。
這兩點(diǎn)也就是圖中用不同顏色標(biāo)記的兩條線。
子類構(gòu)造函數(shù)Child繼承了父類構(gòu)造函數(shù)Preant的里的屬性。使用super調(diào)用的(ES5則用call或者apply調(diào)用傳參)。
也就是圖中用不同顏色標(biāo)記的兩條線。
看過《JavaScript高級(jí)程序設(shè)計(jì)-第3版》 章節(jié)6.3繼承的讀者應(yīng)該知道,這2和3小點(diǎn),正是寄生組合式繼承,書中例子沒有第1小點(diǎn)。
1和2小點(diǎn)都是相對(duì)于設(shè)置了__proto__鏈接。那問題來了,什么可以設(shè)置了__proto__鏈接呢。
說明一下,__proto__這種寫法是瀏覽器廠商自己的實(shí)現(xiàn)。
再結(jié)合一下圖和代碼看一下的new,new出來的實(shí)例的__proto__指向構(gòu)造函數(shù)的prototype,這就是new做的事情。
摘抄一下之前寫過文章的一段。面試官問:能否模擬實(shí)現(xiàn)JS的new操作符,有興趣的讀者可以點(diǎn)擊查看。
Object.create ES5提供的創(chuàng)建了一個(gè)全新的對(duì)象。
這個(gè)對(duì)象會(huì)被執(zhí)行[[Prototype]](也就是__proto__)鏈接。
生成的新對(duì)象會(huì)綁定到函數(shù)調(diào)用的this。
通過new創(chuàng)建的每個(gè)對(duì)象將最終被[[Prototype]]鏈接到這個(gè)函數(shù)的prototype對(duì)象上。
如果函數(shù)沒有返回對(duì)象類型Object(包含Functoin, Array, Date, RegExg, Error),那么new表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新的對(duì)象。
Object.create(proto, [propertiesObject])
方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的__proto__。
它接收兩個(gè)參數(shù),不過第二個(gè)可選參數(shù)是屬性描述符(不常用,默認(rèn)是undefined)。對(duì)于不支持ES5的瀏覽器,MDN上提供了ployfill方案。
MDN Object.create()
// 簡(jiǎn)版:也正是應(yīng)用了new會(huì)設(shè)置__proto__鏈接的原理。 if(typeof Object.create !== "function"){ Object.create = function(proto){ function F() {} F.prototype = proto; return new F(); } }Object.setPrototypeOf ES6提供的
Object.setPrototypeOf MDN
Object.setPrototypeOf() 方法設(shè)置一個(gè)指定的對(duì)象的原型 ( 即, 內(nèi)部[[Prototype]]屬性)到另一個(gè)對(duì)象或 null。
Object.setPrototypeOf(obj, prototype)
`ployfill` // 僅適用于Chrome和FireFox,在IE中不工作: Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) { obj.__proto__ = proto; return obj; }
nodejs源碼就是利用這個(gè)實(shí)現(xiàn)繼承的工具函數(shù)的。
nodejs utils inherits
function inherits(ctor, superCtor) { if (ctor === undefined || ctor === null) throw new ERR_INVALID_ARG_TYPE("ctor", "Function", ctor); if (superCtor === undefined || superCtor === null) throw new ERR_INVALID_ARG_TYPE("superCtor", "Function", superCtor); if (superCtor.prototype === undefined) { throw new ERR_INVALID_ARG_TYPE("superCtor.prototype", "Object", superCtor.prototype); } Object.defineProperty(ctor, "super_", { value: superCtor, writable: true, configurable: true }); Object.setPrototypeOf(ctor.prototype, superCtor.prototype); }ES6的extends的ES5版本實(shí)現(xiàn)
知道了ES6 extends繼承做了什么操作和設(shè)置__proto__的知識(shí)點(diǎn)后,把上面ES6例子的用ES5就比較容易實(shí)現(xiàn)了,也就是說實(shí)現(xiàn)寄生組合式繼承,簡(jiǎn)版代碼就是:
// ES5 實(shí)現(xiàn)ES6 extends的例子 function Parent(name){ this.name = name; } Parent.sayHello = function(){ console.log("hello"); } Parent.prototype.sayName = function(){ console.log("my name is " + this.name); return this.name; } function Child(name, age){ // 相當(dāng)于super Parent.call(this, name); this.age = age; } // new function object(){ function F() {} F.prototype = proto; return new F(); } function _inherits(Child, Parent){ // Object.create Child.prototype = Object.create(Parent.prototype); // __proto__ // Child.prototype.__proto__ = Parent.prototype; Child.prototype.constructor = Child; // ES6 // Object.setPrototypeOf(Child, Parent); // __proto__ Child.__proto__ = Parent; } _inherits(Child, Parent); Child.prototype.sayAge = function(){ console.log("my age is " + this.age); return this.age; } var parent = new Parent("Parent"); var child = new Child("Child", 18); console.log("parent: ", parent); // parent: Parent?{name: "Parent"} Parent.sayHello(); // hello parent.sayName(); // my name is Parent console.log("child: ", child); // child: Child?{name: "Child", age: 18} Child.sayHello(); // hello child.sayName(); // my name is Child child.sayAge(); // my age is 18
我們完全可以把上述ES6的例子通過babeljs轉(zhuǎn)碼成ES5來查看,更嚴(yán)謹(jǐn)?shù)膶?shí)現(xiàn)。
// 對(duì)轉(zhuǎn)換后的代碼進(jìn)行了簡(jiǎn)要的注釋 "use strict"; // 主要是對(duì)當(dāng)前環(huán)境支持Symbol和不支持Symbol的typeof處理 function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } // _possibleConstructorReturn 判斷Parent。call(this, name)函數(shù)返回值 是否為null或者函數(shù)或者對(duì)象。 function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } // 如何 self 是void 0 (undefined) 則報(bào)錯(cuò) function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn"t been initialised - super() hasn"t been called"); } return self; } // 獲取__proto__ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } // 寄生組合式繼承的核心 function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } // Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的__proto__。 // 也就是說執(zhí)行后 subClass.prototype.__proto__ === superClass.prototype; 這條語句為true subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } // 設(shè)置__proto__ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } // instanceof操作符包含對(duì)Symbol的處理 function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // 按照它們的屬性描述符 把方法和靜態(tài)屬性賦值到構(gòu)造函數(shù)的prototype和構(gòu)造器函數(shù)上 function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } // 把方法和靜態(tài)屬性賦值到構(gòu)造函數(shù)的prototype和構(gòu)造器函數(shù)上 function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } // ES6 var Parent = function () { function Parent(name) { _classCallCheck(this, Parent); this.name = name; } _createClass(Parent, [{ key: "sayName", value: function sayName() { console.log("my name is " + this.name); return this.name; } }], [{ key: "sayHello", value: function sayHello() { console.log("hello"); } }]); return Parent; }(); var Child = function (_Parent) { _inherits(Child, _Parent); function Child(name, age) { var _this; _classCallCheck(this, Child); // Child.__proto__ => Parent // 所以也就是相當(dāng)于Parent.call(this, name); 是super(name)的一種轉(zhuǎn)換 // _possibleConstructorReturn 判斷Parent.call(this, name)函數(shù)返回值 是否為null或者函數(shù)或者對(duì)象。 _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name)); _this.age = age; return _this; } _createClass(Child, [{ key: "sayAge", value: function sayAge() { console.log("my age is " + this.age); return this.age; } }]); return Child; }(Parent); var parent = new Parent("Parent"); var child = new Child("Child", 18); console.log("parent: ", parent); // parent: Parent?{name: "Parent"} Parent.sayHello(); // hello parent.sayName(); // my name is Parent console.log("child: ", child); // child: Child?{name: "Child", age: 18} Child.sayHello(); // hello child.sayName(); // my name is Child child.sayAge(); // my age is 18
如果對(duì)JS繼承相關(guān)還是不太明白的讀者,推薦閱讀以下書籍的相關(guān)章節(jié),可以自行找到相應(yīng)的pdf版本。
推薦閱讀JS繼承相關(guān)的書籍章節(jié)《JavaScript高級(jí)程序設(shè)計(jì)第3版》-第6章 面向?qū)ο蟮某绦蛟O(shè)計(jì),6種繼承的方案,分別是原型鏈繼承、借用構(gòu)造函數(shù)繼承、組合繼承、原型式繼承、寄生式繼承、寄生組合式繼承。圖靈社區(qū)本書地址,后文放出github鏈接,里面包含這幾種繼承的代碼demo。
《JavaScript面向?qū)ο缶幊痰?版》-第6章 繼承,12種繼承的方案。1.原型鏈法(仿傳統(tǒng))、2.僅從原型繼承法、3.臨時(shí)構(gòu)造器法、4.原型屬性拷貝法、5.全屬性拷貝法(即淺拷貝法)、6.深拷貝法、7.原型繼承法、8.擴(kuò)展與增強(qiáng)模式、9.多重繼承法、10.寄生繼承法、11.構(gòu)造器借用法、12.構(gòu)造器借用與屬性拷貝法。
ES6標(biāo)準(zhǔn)入門-第21章class的繼承
《深入理解ES6》-第9章 JavaScript中的類
《你不知道的JavaScript-上卷》第6章 行為委托和附錄A ES6中的class
總結(jié)繼承對(duì)于JS來說就是父類擁有的方法和屬性、靜態(tài)方法等,子類也要擁有。子類中可以利用原型鏈查找,也可以在子類調(diào)用父類,或者從父類拷貝一份到子類等方案。
繼承方法可以有很多,重點(diǎn)在于必須理解并熟
悉這些對(duì)象、原型以及構(gòu)造器的工作方式,剩下的就簡(jiǎn)單了。寄生組合式繼承是開發(fā)者使用比較多的。
回顧寄生組合式繼承。主要就是三點(diǎn):
子類構(gòu)造函數(shù)的__proto__指向父類構(gòu)造器,繼承父類的靜態(tài)方法
子類構(gòu)造函數(shù)的prototype的__proto__指向父類構(gòu)造器的prototype,繼承父類的方法。
子類構(gòu)造器里調(diào)用父類構(gòu)造器,繼承父類的屬性。
行文到此,文章就基本寫完了。文章代碼和圖片等資源放在這里github inhert和demo展示es6-extends,結(jié)合console、source面板查看更佳。
讀者發(fā)現(xiàn)有不妥或可改善之處,歡迎評(píng)論指出。另外覺得寫得不錯(cuò),可以點(diǎn)贊、評(píng)論、轉(zhuǎn)發(fā),也是對(duì)筆者的一種支持。
關(guān)于作者:常以若川為名混跡于江湖。前端路上 | PPT愛好者 | 所知甚少,唯善學(xué)。
個(gè)人博客
segmentfault前端視野專欄,開通了前端視野專欄,歡迎關(guān)注
掘金專欄,歡迎關(guān)注
知乎前端視野專欄,開通了前端視野專欄,歡迎關(guān)注
github,歡迎follow~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101904.html
摘要:之前寫過一篇文章面試官問能否模擬實(shí)現(xiàn)的和方法就是利用對(duì)象上的函數(shù)指向這個(gè)對(duì)象,來模擬實(shí)現(xiàn)和的。雖然實(shí)際使用時(shí)不會(huì)顯示返回,但面試官會(huì)問到。非嚴(yán)格模式下,和,指向全局對(duì)象 前言 面試官出很多考題,基本都會(huì)變著方式來考察this指向,看候選人對(duì)JS基礎(chǔ)知識(shí)是否扎實(shí)。讀者可以先拉到底部看總結(jié),再谷歌(或各技術(shù)平臺(tái))搜索幾篇類似文章,看筆者寫的文章和別人有什么不同(歡迎在評(píng)論區(qū)評(píng)論不同之處),...
摘要:譯立即執(zhí)行函數(shù)表達(dá)式處理支持瀏覽器環(huán)境微信小程序。學(xué)習(xí)整體架構(gòu),利于打造屬于自己的函數(shù)式編程類庫。下一篇文章可能是學(xué)習(xí)的源碼整體架構(gòu)。也可以加微信,注明來源,拉您進(jìn)前端視野交流群。 前言 上一篇文章寫了jQuery整體架構(gòu),學(xué)習(xí) jQuery 源碼整體架構(gòu),打造屬于自己的 js 類庫 雖然看過挺多underscore.js分析類的文章,但總感覺少點(diǎn)什么。這也許就是紙上得來終覺淺,絕知此...
摘要:年求職面經(jīng)及總結(jié)我的求職之路差不多走到盡頭了感覺真是精疲力盡了把這大半年的經(jīng)歷和面試總結(jié)寫下來希望能給和我一樣在求職路上煎熬的人一點(diǎn)幫助先說背景微電子科學(xué)與工程專業(yè)學(xué)過兩門和相關(guān)的課程語言和單片機(jī)這個(gè)專業(yè)的唯一好處就是大部分人并不知道這個(gè)專 18年求職面經(jīng)及總結(jié) 我的求職之路差不多走到盡頭了,感覺真是精疲力盡了.把這大半年的經(jīng)歷和面試總結(jié)寫下來,希望能給和我一樣在求職路上煎熬的人一點(diǎn)幫...
摘要:年求職面經(jīng)及總結(jié)我的求職之路差不多走到盡頭了感覺真是精疲力盡了把這大半年的經(jīng)歷和面試總結(jié)寫下來希望能給和我一樣在求職路上煎熬的人一點(diǎn)幫助先說背景微電子科學(xué)與工程專業(yè)學(xué)過兩門和相關(guān)的課程語言和單片機(jī)這個(gè)專業(yè)的唯一好處就是大部分人并不知道這個(gè)專 18年求職面經(jīng)及總結(jié) 我的求職之路差不多走到盡頭了,感覺真是精疲力盡了.把這大半年的經(jīng)歷和面試總結(jié)寫下來,希望能給和我一樣在求職路上煎熬的人一點(diǎn)幫...
閱讀 1272·2021-11-23 09:51
閱讀 2662·2021-09-03 10:47
閱讀 2244·2019-08-30 15:53
閱讀 2430·2019-08-30 15:44
閱讀 1383·2019-08-30 15:44
閱讀 1206·2019-08-30 10:57
閱讀 1936·2019-08-29 12:25
閱讀 1098·2019-08-26 11:57