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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容