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