用過React
的讀者知道,常常用extends
繼承React.Component
。html
// 部分源碼 function Component(props, context, updater) { // ... } Component.prototype.setState = function(partialState, callback){ // ... } const React = { Component, // ... } // 使用 class index extends React.Component{ // ... }
面試官能夠順着這個問JS
繼承的相關問題,好比:ES6
的class
繼承用ES5如何實現。聽說不少人答得很差。<br/>html5
要弄懂extends繼承以前,先來複習一下構造函數、原型對象和實例之間的關係。
代碼表示:node
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
筆者畫了一張圖表示:react
ES6 extends
繼承作了什麼操做咱們先看看這段包含靜態方法的ES6
繼承代碼:git
// 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
其中這段代碼裏有兩條原型鏈,不信看具體代碼。es6
// 一、構造器原型鏈 Child.__proto__ === Parent; // true Parent.__proto__ === Function.prototype; // true Function.prototype.__proto__ === Object.prototype; // true Object.prototype.__proto__ === null; // true // 二、實例原型鏈 child.__proto__ === Child.prototype; // true Child.prototype.__proto__ === Parent.prototype; // true Parent.prototype.__proto__ === Object.prototype; // true Object.prototype.__proto__ === null; // true
一圖勝千言,筆者也畫了一張圖表示,如圖所示:github
結合代碼和圖能夠知道。ES6 extends
繼承,主要就是:面試
Child
)的原型(__proto__
)指向了父類構造函數(Parent
),child
的原型對象(Child.prototype
) 的原型(__proto__
)指向了父類parent
的原型對象(Parent.prototype
)。這兩點也就是圖中用不一樣顏色標記的兩條線。express
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
作了什麼:
- 建立了一個全新的對象。
- 這個對象會被執行
[[Prototype]]
(也就是__proto__
)連接。- 生成的新對象會綁定到函數調用的
this
。- 經過
new
建立的每一個對象將最終被[[Prototype]]
連接到這個函數的prototype
對象上。- 若是函數沒有返回對象類型
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()
方法設置一個指定的對象的原型 ( 即, 內部[[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
版本實現知道了ES6 extends
繼承作了什麼操做和設置__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){ // 至關於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
轉碼成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; 這條語句爲true subClass.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
版本。
《JavaScript高級程序設計第3版》-第6章 面向對象的程序設計,6種繼承的方案,分別是原型鏈繼承、借用構造函數繼承、組合繼承、原型式繼承、寄生式繼承、寄生組合式繼承。圖靈社區本書地址,後文放出github
連接,裏面包含這幾種繼承的代碼demo
。
《JavaScript面向對象編程第2版》-第6章 繼承,12種繼承的方案。1.原型鏈法(仿傳統)、2.僅從原型繼承法、3.臨時構造器法、4.原型屬性拷貝法、5.全屬性拷貝法(即淺拷貝法)、6.深拷貝法、7.原型繼承法、8.擴展與加強模式、9.多重繼承法、10.寄生繼承法、11.構造器借用法、12.構造器借用與屬性拷貝法。
《深刻理解ES6
》-第9章 JavaScript
中的類
《你不知道的JavaScript
-上卷》第6章 行爲委託和附錄A ES6中的class
繼承對於JS來講就是父類擁有的方法和屬性、靜態方法等,子類也要擁有。子類中能夠利用原型鏈查找,也能夠在子類調用父類,或者從父類拷貝一份到子類等方案。
繼承方法能夠有不少,重點在於必須理解並熟
悉這些對象、原型以及構造器的工做方式,剩下的就簡單了。寄生組合式繼承是開發者使用比較多的。
回顧寄生組合式繼承。主要就是三點:
__proto__
指向父類構造器,繼承父類的靜態方法prototype
的__proto__
指向父類構造器的prototype
,繼承父類的方法。行文到此,文章就基本寫完了。文章代碼和圖片等資源放在這裏github inhert和demo
展現es6-extends
,結合console、source
面板查看更佳。
讀者發現有不妥或可改善之處,歡迎評論指出。另外以爲寫得不錯,能夠點贊、評論、轉發,也是對筆者的一種支持。
做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
我的博客segmentfault
前端視野專欄,開通了前端視野專欄,歡迎關注
掘金專欄,歡迎關注
知乎前端視野專欄,開通了前端視野專欄,歡迎關注
github,歡迎follow
~