ES5和ES6中对于继承的实现方法
在ES5繼承的實(shí)現(xiàn)非常有趣的,由于沒(méi)有傳統(tǒng)面向?qū)ο箢?lèi)的概念,Javascript利用原型鏈的特性來(lái)實(shí)現(xiàn)繼承,這其中有很多的屬性指向和需要注意的地方。
原型鏈的特點(diǎn)和實(shí)現(xiàn)已經(jīng)在之前的一篇整理說(shuō)過(guò)了,就是通過(guò)將子類(lèi)構(gòu)造函數(shù)的原型作為父類(lèi)構(gòu)造函數(shù)的實(shí)例,這樣就連通了子類(lèi)-子類(lèi)原型-父類(lèi),原型鏈的特點(diǎn)就是逐層查找,從子類(lèi)開(kāi)始一直往上直到所有對(duì)象的原型Object.prototype,找到屬性方法之后就會(huì)停止查找,所以下層的屬性方法會(huì)覆蓋上層。
一個(gè)基本的基于原型鏈的繼承過(guò)程大概是這樣的:
//先來(lái)個(gè)父類(lèi),帶些屬性 function Super(){ this.flag = true; } //為了提高復(fù)用性,方法綁定在父類(lèi)原型屬性上 Super.prototype.getFlag = function(){ return this.flag; } //來(lái)個(gè)子類(lèi) function Sub(){ this.subFlag = false; } //實(shí)現(xiàn)繼承 Sub.prototype = new Super; //給子類(lèi)添加子類(lèi)特有的方法,注意順序要在繼承之后 Sub.prototype.getSubFlag = function(){ return this.subFlag; } //構(gòu)造實(shí)例 var es5 = new Sub;原型鏈實(shí)現(xiàn)的繼承主要有幾個(gè)問(wèn)題:
1、本來(lái)我們?yōu)榱藰?gòu)造函數(shù)屬性的封裝私有性,方法的復(fù)用性,提倡將屬性聲明在構(gòu)造函數(shù)內(nèi),而將方法綁定在原型對(duì)象上,但是現(xiàn)在子類(lèi)的原型是父類(lèi)的一個(gè)實(shí)例,自然父類(lèi)的屬性就變成子類(lèi)原型的屬性了;
這就會(huì)帶來(lái)一個(gè)問(wèn)題,我們知道構(gòu)造函數(shù)的原型屬性在所有構(gòu)造的實(shí)例中是共享的,所以原型中屬性的改變會(huì)反應(yīng)到所有的實(shí)例上,這就違背了我們想要屬性私有化的初衷;
2、創(chuàng)建子類(lèi)的實(shí)例時(shí),不能向父類(lèi)的構(gòu)造函數(shù)傳遞參數(shù)
為了解決以上兩個(gè)問(wèn)題,有一個(gè)叫借用構(gòu)造函數(shù)的方法
只需要在子類(lèi)構(gòu)造函數(shù)內(nèi)部使用apply或者call來(lái)調(diào)用父類(lèi)的函數(shù)即可在實(shí)現(xiàn)屬性繼承的同時(shí),又能傳遞參數(shù),又能讓實(shí)例不互相影響
結(jié)合借用構(gòu)造函數(shù)和原型鏈的方法,可以實(shí)現(xiàn)比較完美的繼承方法,可以稱(chēng)為組合繼承:
function Super(){ this.flag = true; } Super.prototype.getFlag = function(){ return this.flag; //繼承方法 } function Sub(){ this.subFlag = flase Super.call(this) //繼承屬性 } Sub.prototype = new Super; var obj = new Sub(); Super.prototype.getSubFlag = function(){ return this.flag; }這里還有個(gè)小問(wèn)題,Sub.prototype = new Super; 會(huì)導(dǎo)致Sub.prototype的constructor指向Super;
然而constructor的定義是要指向原型屬性對(duì)應(yīng)的構(gòu)造函數(shù)的,Sub.prototype是Sub構(gòu)造函數(shù)的原型,所以應(yīng)該添加一句糾正:
Sub.prototype.constructor = Sub;
看完ES5的實(shí)現(xiàn),再來(lái)看看ES6的繼承實(shí)現(xiàn)方法,其內(nèi)部其實(shí)也是ES5組合繼承的方式,通過(guò)call借用構(gòu)造函數(shù),在A類(lèi)構(gòu)造函數(shù)中調(diào)用相關(guān)屬性,再用原型鏈的連接實(shí)現(xiàn)方法的繼承
class B extends A { constructor() { return A.call(this); //繼承屬性 } } A.prototype = new B; //繼承方法ES6封裝了class,extends關(guān)鍵字來(lái)實(shí)現(xiàn)繼承,內(nèi)部的實(shí)現(xiàn)原理其實(shí)依然是基于上面所講的原型鏈,不過(guò)進(jìn)過(guò)一層封裝后,Javascript的繼承得以更加簡(jiǎn)潔優(yōu)雅地實(shí)現(xiàn)
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 等同于parent.constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 等同于parent.toString() } }通過(guò)constructor來(lái)定義構(gòu)造函數(shù),用super調(diào)用父類(lèi)的屬性方法
ES6中Class充當(dāng)了ES5中,構(gòu)造函數(shù)在繼承實(shí)現(xiàn)過(guò)程中的作用
同樣有原型屬性prototype,以及在ES5中用來(lái)指向構(gòu)造函數(shù)原型的__proto__屬性,這個(gè)屬性在ES6中的指向有一些主動(dòng)的修改。
一個(gè)繼承語(yǔ)句同時(shí)存在兩條繼承鏈:一條實(shí)現(xiàn)屬性繼承,一條實(shí)現(xiàn)方法繼承。
ES6的子類(lèi)的__proto__是父類(lèi),子類(lèi)的原型的__proto__是父類(lèi)的原型
第二條繼承鏈理解起來(lái)沒(méi)有什么問(wèn)題,對(duì)應(yīng)到ES5中的A.prototype = new B;A.prototype作為B構(gòu)造的實(shí)例,指向構(gòu)造函數(shù)B的原型B.prototype,
但是在ES5中A.__proto__是指向Function.prototype的,因?yàn)槊恳粋€(gè)構(gòu)造函數(shù)其實(shí)都是Function這個(gè)對(duì)象構(gòu)造的,ES6中子類(lèi)的__proto__指向父類(lèi)可以實(shí)現(xiàn)屬性的繼承,在ES5中在沒(méi)有用借用繼承的時(shí)候由于父類(lèi)屬性被子類(lèi)原型繼承,所有的子類(lèi)實(shí)例實(shí)際上都是同一個(gè)屬性引用。
在ES6中實(shí)現(xiàn)了子類(lèi)繼承父類(lèi)屬性,在構(gòu)造實(shí)例的時(shí)候會(huì)直接拿到子類(lèi)的屬性,不需要查找到原型屬性上面去,ES6新的靜態(tài)方法和靜態(tài)屬性(只能在構(gòu)造函數(shù)上訪問(wèn))也是通過(guò)這樣類(lèi)的直接繼承來(lái)實(shí)現(xiàn),至于普通復(fù)用方法還是放到原型鏈上,道理和實(shí)現(xiàn)和ES5是一樣的。
此外我認(rèn)為這里修改A.__proto__的指向是有意區(qū)分ES6中繼承和實(shí)例化,同時(shí)建立子類(lèi)和父類(lèi)直接的關(guān)系,ES5的子類(lèi)的構(gòu)造函數(shù)通過(guò)子類(lèi)的原型與父類(lèi)的構(gòu)造函數(shù)連接,不存在直接的關(guān)系;
可以這么說(shuō),在ES5繼承和構(gòu)造實(shí)例,ES6構(gòu)造實(shí)例的時(shí)候可以理解__proto__原型指針是用來(lái)指向構(gòu)造函數(shù)的原型的,但是在ES6繼承中,__proto__指繼承自哪個(gè)類(lèi)或原型,在A繼承B之后,構(gòu)造一個(gè)實(shí)例 var obj = new A; 會(huì)發(fā)現(xiàn)它所有的屬性指向都是和ES5一致的。
有個(gè)有趣的地方:ES6繼承是在父類(lèi)創(chuàng)建this對(duì)象,在子類(lèi)constructor中來(lái)修飾父類(lèi)的this,ES5是在子類(lèi)創(chuàng)建this,將父類(lèi)的屬性方法綁定到子類(lèi),由于原生的構(gòu)造函數(shù)(Function,Array等)沒(méi)有this,子類(lèi)無(wú)法通過(guò)call/apply(this)獲得其內(nèi)部屬性,所以在ES5無(wú)法繼承,ES6實(shí)現(xiàn)后可以為原生構(gòu)造函數(shù)封裝一些有趣的接口,比方說(shuō)阮一峰老師的這個(gè)給Array實(shí)例封裝一個(gè)版本記錄和回滾的方法:
class VersionedArray extends Array { constructor() { super(); this.history = [[]]; } commit() { this.history.push(this.slice()); } revert() { this.splice(0, this.length, ...this.history[this.history.length - 1]); } } var x = new VersionedArray(); x.push(1); x.push(2); x // [1, 2] x.history // [[]] x.commit(); x.history // [[], [1, 2]] x.push(3); x // [1, 2, 3] x.revert(); x // [1, 2]最后做一個(gè)ES5和ES6的繼承小結(jié):
ES5最經(jīng)典的繼承方法是用組合繼承的方式,原型鏈繼承方法,借用函數(shù)繼承屬性,ES6也是基于這樣的方式,但是封裝了更優(yōu)雅簡(jiǎn)潔的api,讓Javascript越來(lái)越強(qiáng)大,修改了一些屬性指向,規(guī)范了繼承的操作,區(qū)分開(kāi)了繼承實(shí)現(xiàn)和實(shí)例構(gòu)造,此外ES6繼承還能實(shí)現(xiàn)更多的繼承需求和場(chǎng)景。
原文鏈接:http://www.jianshu.com/p/342966fdf816
超強(qiáng)干貨來(lái)襲 云風(fēng)專(zhuān)訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生
總結(jié)
以上是生活随笔為你收集整理的ES5和ES6中对于继承的实现方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。