JS 繼承

用過 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{

? ? // ...

}

React github源碼

面試官可以順著這個(gè)問 JS繼承的相關(guān)問題,比如: ES6 的 class 繼承用 ES5 如何實(shí)現(xiàn)阳液。據(jù)說很多人答得不好鹏浅。

構(gòu)造函數(shù)貌亭、原型對(duì)象和實(shí)例之間的關(guān)系

要弄懂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

筆者畫了一張圖表示:

構(gòu)造函數(shù)

ES6 extends 繼承做了什么操作

我們先看看這段包含靜態(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

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

構(gòu)造函數(shù)(Parent)

結(jié)合代碼和圖可以知道榕栏, ES6extends 繼承,主要就是:

把子類構(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鏈接呢恨溜。

設(shè)置 proto

new符衔、 Object.create 和 Object.setPrototypeOf 可以設(shè)置__proto__。

說明一下糟袁, __proto__這種寫法是瀏覽器廠商自己的實(shí)現(xiàn)判族。

再結(jié)合一下圖和代碼看一下的 new, new出來的實(shí)例的 __proto__指向構(gòu)造函數(shù)的 prototype项戴,這就是 new做的事情形帮。

new 做了什么

創(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:ES5提供的

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() 方法設(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ù)的。

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);

}

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種繼承的方案:

原型鏈法(仿傳統(tǒng))

僅從原型繼承法

臨時(shí)構(gòu)造器法

原型屬性拷貝法

全屬性拷貝法(即淺拷貝法)

深拷貝法

原型繼承法

擴(kuò)展與增強(qiáng)模式

多重繼承法

寄生繼承法

構(gòu)造器借用法

構(gòu)造器借用與屬性拷貝法

龍華大道1號(hào)http://www.kinghill.cn/LongHuaDaDao1Hao/index.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市腺阳,隨后出現(xiàn)的幾起案子落君,更是在濱河造成了極大的恐慌,老刑警劉巖亭引,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绎速,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡焙蚓,警方通過查閱死者的電腦和手機(jī)纹冤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來购公,“玉大人萌京,你說我怎么就攤上這事『旰疲” “怎么了知残?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)比庄。 經(jīng)常有香客問我求妹,道長(zhǎng)乏盐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任制恍,我火速辦了婚禮父能,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吧趣。我一直安慰自己法竞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布强挫。 她就那樣靜靜地躺著岔霸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俯渤。 梳的紋絲不亂的頭發(fā)上呆细,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音八匠,去河邊找鬼絮爷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梨树,可吹牛的內(nèi)容都是我干的坑夯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼抡四,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼柜蜈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起指巡,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤淑履,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后藻雪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秘噪,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年勉耀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了指煎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瑰排,死狀恐怖贯要,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情椭住,我是刑警寧澤崇渗,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響宅广,放射性物質(zhì)發(fā)生泄漏葫掉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一跟狱、第九天 我趴在偏房一處隱蔽的房頂上張望俭厚。 院中可真熱鬧,春花似錦驶臊、人聲如沸挪挤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扛门。三九已至,卻和暖如春纵寝,著一層夾襖步出監(jiān)牢的瞬間论寨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工爽茴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留葬凳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓室奏,卻偏偏與公主長(zhǎng)得像火焰,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胧沫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 繼承6種套餐 參照紅皮書荐健,JS繼承一共6種 1.原型鏈繼承 核心思想:子類的原型指向父類的一個(gè)實(shí)例 Son.pro...
    燈不梨喵閱讀 3,142評(píng)論 1 2
  • 原文鏈接:zhuanlan.zhihu.com (一) 原型鏈繼承: function Parent(name) ...
    越努力越幸運(yùn)_952c閱讀 296評(píng)論 0 2
  • 1:原型繼承 為了讓子類繼承父類的屬性(也包括方法),首先需要定義一個(gè)構(gòu)造函數(shù)琳袄。然后,將父類的新實(shí)例賦值給構(gòu)造函數(shù)...
    codeSirCao閱讀 249評(píng)論 0 0
  • web前端面試之js繼承與原型鏈(碼動(dòng)未來) 3.2.1纺酸、JavaScript原型窖逗,原型鏈 ? 有什么特點(diǎn)? 每個(gè)...
    share_tiger閱讀 803評(píng)論 0 1
  • 引言:前端小白一枚餐蔬,此文集用于個(gè)人前端知識(shí)點(diǎn)總結(jié)碎紊,記錄實(shí)際項(xiàng)目開發(fā)過程中踩過的抗。 一點(diǎn)一滴樊诺,匯聚成河仗考! JS常用...
    Xxx子韻閱讀 166評(píng)論 0 1