面試官問:JS的繼承

原文作者若川,掘金鏈接:https://juejin.im/post/5c433e216fb9a049c15f841b

寫于2019年2月20日,現在發到公眾號聲明原創,之前被《前端大全》公眾號等轉載閱讀量超1w+,知乎掘金等累計閱讀量超過1w+。

導讀:文章主要通過ES6的extends,結合ES5的寄生組合繼承,圖文并茂的講述JS的繼承,最后還推薦了一些書籍的繼承的章節,旨在讓讀者掌握JS的繼承。

用過?React的讀者知道,經常用?extends繼承?React.Component

// 部分源碼
function Component(props, context, updater) {// ...
}
Component.prototype.setState = function(partialState, callback){// ...
}
const React = {Component,// ...
}
// 使用
class index extends React.Component{// ...
}

點擊這里查看 React github源碼

面試官可以順著這個問?JS繼承的相關問題,比如:?ES6的?class繼承用ES5如何實現。據說很多人答得不好。

構造函數、原型對象和實例之間的關系

要弄懂extends繼承之前,先來復習一下構造函數、原型對象和實例之間的關系。代碼表示:

function F(){}
var f = new F();
// 構造器
F.prototype.constructor === F; // true
F.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 實例
f.__proto__ === F.prototype; // true
F.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true

筆者畫了一張圖表示:?

ES6extends?繼承做了什么操作

我們先看看這段包含靜態方法的?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、構造器原型鏈
Child.__proto__ === Parent; // true
Parent.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 2、實例原型鏈
child.__proto__ === Child.prototype; // true
Child.prototype.__proto__ === Parent.prototype; // true
Parent.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true

一圖勝千言,筆者也畫了一張圖表示,如圖所示:

?結合代碼和圖可以知道。?ES6extends?繼承,主要就是:

  • 1.把子類構造函數(?Child)的原型(?__proto__)指向了父類構造函數(?Parent),

    2.把子類實例?child的原型對象(?Child.prototype) 的原型(?__proto__)指向了父類?parent的原型對象(?Parent.prototype)。

這兩點也就是圖中用不同顏色標記的兩條線。

  • 3.子類構造函數?Child繼承了父類構造函數?Preant的里的屬性。使用?super調用的(?ES5則用?call或者?apply調用傳參)。也就是圖中用不同顏色標記的兩條線。

看過《JavaScript高級程序設計-第3版》 章節?6.3繼承的讀者應該知道,這?2和3小點,正是寄生組合式繼承,書中例子沒有?第1小點。?1和2小點都是相對于設置了?__proto__鏈接。那問題來了,什么可以設置了?__proto__鏈接呢。

new、?Object.create和?Object.setPrototypeOf可以設置?__proto__

說明一下,?__proto__這種寫法是瀏覽器廠商自己的實現。再結合一下圖和代碼看一下的?new,?new出來的實例的proto指向構造函數的?prototype,這就是?new做的事情。摘抄一下之前寫過文章的一段。面試官問:能否模擬實現JS的new操作符,有興趣的讀者可以點擊查看。

new做了什么:

  1. 創建了一個全新的對象。

  2. 這個對象會被執行?[[Prototype]](也就是?__proto__)鏈接。

  3. 生成的新對象會綁定到函數調用的?this

  4. 通過?new創建的每個對象將最終被?[[Prototype]]鏈接到這個函數的?prototype對象上。

  5. 如果函數沒有返回對象類型?Object(包含?Functoin,?Array,?Date,?RegExg,?Error),那么?new表達式中的函數調用會自動返回這個新的對象。

Object.create?ES5提供的

Object.create(proto,[propertiesObject])?方法創建一個新對象,使用現有的對象來提供新創建的對象的proto。它接收兩個參數,不過第二個可選參數是屬性描述符(不常用,默認是?undefined)。對于不支持?ES5的瀏覽器,?MDN上提供了?ployfill方案。?MDN Object.create()

// 簡版:也正是應用了new會設置__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()?方法設置一個指定的對象的原型 ( 即, 內部?[[Prototype]]屬性)到另一個對象或?null。?Object.setPrototypeOf(obj,prototype)

`ployfill`
// 僅適用于Chrome和FireFox,在IE中不工作:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {obj.__proto__ = proto;return obj;
}

nodejs源碼就是利用這個實現繼承的工具函數的。?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版本實現

知道了?ES6extends繼承做了什么操作和設置?__proto__的知識點后,把上面?ES6例子的用?ES5就比較容易實現了,也就是說實現寄生組合式繼承,簡版代碼就是:

// ES5 實現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){// 相當于superParent.call(this, name);this.age = age;
}
// new
function object(){function F() {}F.prototype = proto;return new F();
}
function _inherits(Child, Parent){// Object.createChild.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轉碼成?ES5來查看,更嚴謹的實現。

// 對轉換后的代碼進行了簡要的注釋
"use strict";
// 主要是對當前環境支持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)函數返回值 是否為null或者函數或者對象。
function _possibleConstructorReturn(self, call) {if (call && (_typeof(call) === "object" || typeof call === "function")) {return call;}return _assertThisInitialized(self);
}
// 如何 self 是void 0 (undefined) 則報錯
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()方法創建一個新對象,使用現有的對象來提供新創建的對象的__proto__。// 也就是說執行后 subClass.prototype.__proto__ === superClass.prototype; 這條語句為truesubClass.prototype = Object.create(superClass && superClass.prototype, {constructor: {value: subClass,writable: true,configurable: true}});if (superClass) _setPrototypeOf(subClass, superClass);
}
// 設置__proto__
function _setPrototypeOf(o, p) {_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {o.__proto__ = p;return o;};return _setPrototypeOf(o, p);
}
// instanceof操作符包含對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");}
}
// 按照它們的屬性描述符 把方法和靜態屬性賦值到構造函數的prototype和構造器函數上
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);}
}
// 把方法和靜態屬性賦值到構造函數的prototype和構造器函數上
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// 所以也就是相當于Parent.call(this, name); 是super(name)的一種轉換// _possibleConstructorReturn 判斷Parent.call(this, name)函數返回值 是否為null或者函數或者對象。_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

如果對JS繼承相關還是不太明白的讀者,推薦閱讀以下書籍的相關章節,可以自行找到相應的?pdf版本。

推薦閱讀JS繼承相關的書籍章節

《JavaScript高級程序設計第3版》-第6章 面向對象的程序設計,6種繼承的方案,分別是原型鏈繼承、借用構造函數繼承、組合繼承、原型式繼承、寄生式繼承、寄生組合式繼承。圖靈社區本書地址,后文放出?github鏈接,里面包含這幾種繼承的代碼?demo

《JavaScript面向對象編程第2版》-第6章 繼承,12種繼承的方案。1.原型鏈法(仿傳統)、2.僅從原型繼承法、3.臨時構造器法、4.原型屬性拷貝法、5.全屬性拷貝法(即淺拷貝法)、6.深拷貝法、7.原型繼承法、8.擴展與增強模式、9.多重繼承法、10.寄生繼承法、11.構造器借用法、12.構造器借用與屬性拷貝法。

ES6標準入門-第21章class的繼承

《深入理解?ES6》-第9章?JavaScript中的類

《你不知道的?JavaScript-上卷》第6章 行為委托和附錄A?ES6中的class

總結

繼承對于JS來說就是父類擁有的方法和屬性、靜態方法等,子類也要擁有。子類中可以利用原型鏈查找,也可以在子類調用父類,或者從父類拷貝一份到子類等方案。繼承方法可以有很多,重點在于必須理解并熟 悉這些對象、原型以及構造器的工作方式,剩下的就簡單了。寄生組合式繼承是開發者使用比較多的。回顧寄生組合式繼承。主要就是三點:

  • 1.子類構造函數的?__proto__指向父類構造器,繼承父類的靜態方法。

    2.子類構造函數的?prototype的?__proto__指向父類構造器的?prototype,繼承父類的方法。

    3.子類構造器里調用父類構造器,繼承父類的屬性。行文到此,文章就基本寫完了。文章代碼和圖片等資源放在這里github inhert和?demo展示?es6-extends,結合?console、source面板查看更佳。

讀者發現有不妥或可改善之處,歡迎評論指出。另外覺得寫得不錯,可以點贊、評論、轉發,也是對筆者的一種支持。

關于

作者:常以若川為名混跡于江湖。前端路上 | PPT愛好者 | 所知甚少,唯善學。
個人博客 http://lxchuan12.github.io?使用?vuepress重構了,閱讀體驗可能更好些
https://github.com/lxchuan12/blog,相關源碼和資源都放在這里,求個 star^_^~

微信交流群,加我微信lxchuan12,注明來源,拉您進前端視野交流群

下圖是公眾號二維碼:若川視野,一個可能比較有趣的前端開發類公眾號,目前前端內容不多

往期文章

工作一年后,我有些感悟(寫于2017年)

高考七年后、工作三年后的感悟

學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫

學習underscore源碼整體架構,打造屬于自己的函數式編程類庫

學習 lodash 源碼整體架構,打造屬于自己的函數式編程類庫

由于公眾號限制外鏈,點擊閱讀原文,或許閱讀體驗更佳,覺得文章不錯,可以點個在看呀^_^

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276510.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276510.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276510.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

qt 快速按行讀取文件_這是知識點之Linux下分割文件并保留文件頭

點擊上方"開發者的花花世界"&#xff0c;選擇"設為星標"技術干貨不定時送達&#xff01;這是一個知識點方便快捷的給結構化數據文件分割大小并保留文件的表頭&#xff0c;幾十個G的結構化文件不僅閱讀編輯麻煩&#xff0c;而且使用受限&#xff0c;因此高效…

mono 調用windows webService

1. 實現linux mono Develop中調用windows 中的webService l linux 與 windows 在一個局域網的網段中 l windows 的IIs中發布webService 2. windows 中的設置 l webService 的代碼 using System; using System.Collections.Generic; using System.Linq; using S…

Linux 內存機制

轉載鏈接&#xff1a;http://blog.csdn.net/tianlesoftware/article/details/5463790 一. 內存使用說明 Free 命令相對于top 提供了更簡潔的查看系統內存使用情況&#xff1a; [rootrac1 ~]# free total used free shared buffers cached Mem: …

network中的請求信息,headers中的每一項分別是什么意義?

這里是修真院前端小課堂&#xff0c;每篇分享文從 【背景介紹】【知識剖析】【常見問題】【解決方案】【編碼實戰】【擴展思考】【更多討論】【參考文獻】 八個方面深度解析前端知識/技能&#xff0c;本篇分享的是&#xff1a; 【network中的請求信息&#xff0c;headers中的每…

學習 sentry 源碼整體架構,打造屬于自己的前端異常監控SDK

前言這是學習源碼整體架構第四篇。整體架構這詞語好像有點大&#xff0c;姑且就算是源碼整體結構吧&#xff0c;主要就是學習是代碼整體結構&#xff0c;不深究其他不是主線的具體函數的實現。文章學習的是打包整合后的代碼&#xff0c;不是實際倉庫中的拆分的代碼。其余三篇分…

巴西龜吃什么

1、活蝦&#xff0c;哈哈&#xff0c;巴西龜最喜歡的食物&#xff0c;超市很多雞尾蝦買的&#xff0c;就那種&#xff0c;要活的&#xff0c;鍛煉它們的天性&#xff0c;一次一只可以吃一、兩天&#xff1b; 2、蚶子&#xff0c;貝殼類&#xff0c;活的&#xff0c;整個扔進去&…

綁定dictionary 給定關鍵字不再字典中_VBA代碼集錦-利用字典做兩列數據的對比并對齊...

源數據&#xff1a;代碼&#xff1a;Sub 對比()Dim arr, brr, crrDim i, j, n, lastrowA, lastrowB As Integer建立字典對象Set d CreateObject("scripting.dictionary")獲取數據區域最后一行的行數lastrowA Sheets("對比對齊兩列數據").Cells(Rows.Coun…

linux啟動時掛載rootfs的幾種方式 .

轉載鏈接&#xff1a;http://blog.csdn.net/zuokong/article/details/9022707 根文件系統&#xff08;在樣例錯誤消息中名為 rootfs&#xff09;是 Linux 的最基本的組件。根文件系統包含支持完整的 Linux 系統所需的所有內容。它包含所有應用程序、配置、設備、數據等 Linux 中…

PHP 手冊

by:Mehdi AchourFriedhelm BetzAntony DovgalNuno LopesHannes MagnussonGeorg RichterDamien SeguyJakub Vrana其他貢獻者2018-06-19Edited By: Peter Cowburn中文翻譯人員&#xff1a;肖盛文洪建家穆少磊宋琪黃嘯宇王遠之肖理達喬楚戴劼褚兆瑋周夢康袁玉強段小強© 1997-…

前端使用puppeteer 爬蟲生成《React.js 小書》PDF并合并

前端也可以爬蟲&#xff0c;寫于2018年08月29日&#xff0c;現在發布到微信公眾號申明原創。掘金若川 本文章鏈接&#xff1a;https://juejin.im/post/5b86732451882542af1c80821、 puppeteer 是什么&#xff1f;puppeteer: Google 官方出品的 headless Chrome node 庫puppetee…

蜘蛛與佛的故事

最近閉關,空面四壁,窗外層巒疊嶂,窗臺上只有一盆花每日陪著我&#xff0c;朋友們都說我要成佛了,想想也是&#xff01; 于是在閉關即將結束的時候找了一篇佛的故事送給自己&#xff0c;希望自己能夠頓悟一些"禪"機。 從前&#xff0c;有一座圓音寺&#xff0c;每天都…

信息安全管理與評估_計算機工程學院教師參加“信息安全管理與評估賽項”說明會...

看了就要關注我&#xff0c;喵嗚~2019年3月15日下午&#xff0c;2019年陜西省高等職業院校技能大賽“信息安全管理與評估賽項說明會”在咸陽職業技術學院舉行。出席本次會儀的有咸陽職業技術學院教務處長楊新宇、神州數碼范永強經理、神州數碼信息安全工程師高峰和各院校指導教…

haproxy概念和負載均衡

https://pan.baidu.com/s/1Sq2aJ35zrW2Xn7Th9j7oOA //軟件百度網盤連接 在80.100虛擬機上 systemctl stop firewalld //關閉防火墻 setenforce 0 //關閉監控 yum install lrz* -y //安裝上傳軟件 tar xf haproxy-1.5.15.tar.gz -C /opt/ //解壓壓縮包到/opt/ cd /op…

PHP用戶注冊郵箱驗證激活帳號

轉載鏈接&#xff1a;http://www.helloweba.com/view-blog-228.html 本文將結合實例&#xff0c;講解如何使用PHPMysql完成注冊帳號、發送激活郵件、驗證激活帳號、處理URL鏈接過期的功能。 業務流程 1、用戶提交注冊信息。 2、寫入數據庫&#xff0c;此時帳號狀態未激活。 …

知乎問答:一年內的前端看不懂前端框架源碼怎么辦?

知乎問答&#xff1a;一年內的前端看不懂前端框架源碼怎么辦&#xff1f;以下是我的回答&#xff0c;閱讀量 1000。現在轉載到微信公眾號中。鏈接&#xff1a;https://www.zhihu.com/question/350289336/answer/910970733其他回答的已經很好了。剛好最近在寫學習源碼整體架構系…

幫自己發個求職簡歷

幫自己發個求職簡歷 發個求職信息。本人擅長Web開發&#xff0c;尤其擅長Flex&#xff0c;愿從事Web開發&#xff0c;最好是Web前端開發&#xff0c;下面是我的詳細個人簡歷&#xff1a; 個人信息&#xff1a; 姓名&#xff1a;伍國耀 年齡&#xff1a;23 性別&#xff1a;男 專…

python函數 global_**Python的函數參數傳遞 和 global

函數的參數到底是傳遞的一份復制的值&#xff0c;還是對內存的引用&#xff1f;我們看下面一段代碼&#xff1a;a []def fun(x):x.append(1)fun(a)print(a)想想一下&#xff1a;如果傳遞的是一份復制的值&#xff0c;那么列表a應該是不會變化的&#xff0c;還是空列表&#xf…

冷啟動問題:如何構建你的機器學習組合?

作為即將告別大學的機器學習畢業狗的你&#xff0c;會不會有種迷茫的感覺&#xff1f;你知道 HR 最看重的是什么嗎&#xff1f;在求職季到來之前&#xff0c;畢業狗要怎么做&#xff0c;才能受到 HR 的青睞、拿到心儀的 Offer 呢&#xff1f;負責幫助應屆生找到機器學習工作的 …

JavaScript 對象所有API解析【2020版】

寫于 2017年08月20日&#xff0c;雖然是2017年寫的文章&#xff0c;但現在即將2020年依舊不過時&#xff0c;現在補充了2019年新增的ES10 Object.fromEntries()。發到公眾號申明原創。若川順便在此提前祝大家&#xff1a;2020年更上一層樓。近日發現有挺多人對對象基礎API不熟悉…

javascript操作符之new 也瘋狂 (2)

JavaScript本是一種基于原形的&#xff08;prototypal&#xff09;語言&#xff0c;但它的“new”操作符看起來有點像經典語言。這迷惑了廣大程序員們&#xff0c;并導致了很多使用上的問題。 在JavaScript中&#xff0c;不要用到new Object()這種操作&#xff0c;該用{ }來代替…