ES6發(fā)布以來,給前端開發(fā)人員帶了不少的驚喜,它包含了一些很贊的特性,極大的方便了開發(fā),class便是其中之一。在我的工作中,也用到了ES6這一新特性,雖然一直在使用,但是并沒有去認(rèn)真了解過它,于是做了一些功課,在這里跟大家分享自己的一些心得和看法。
在ES6以前,JS作為一門非面向?qū)ο蟮恼Z(yǔ)言,并沒有給我們提供對(duì)類的支持,JS開發(fā)者傳統(tǒng)的常用做法是用構(gòu)造函數(shù)來模擬類的實(shí)現(xiàn),通過將屬性和方法定義在原型上將自身的屬性共享給它的實(shí)例。
一個(gè)用構(gòu)造函數(shù)實(shí)現(xiàn)類的例子:
function Dog(name) {
this.name = name;
}
Dog.prototype.saySomething = function() {
console.log('汪汪汪');
};
var dog = new Dog('LiLy');
dog.name //LiLy
dog.saySomething //汪汪汪
1.class的定義
而ES6引入了class,這個(gè)在面向?qū)ο笳Z(yǔ)言中常見的關(guān)鍵字,和上面的傳統(tǒng)方法相比有什么不同?這里我先用class的寫法將上面的例子改寫一下:
class Dog{
constructor(name){
this.name = name;
}
saySomething(){
console.log('汪汪汪');
}
}
var dog = new Dog('LiLy'); //也是通過new實(shí)例化
dog.name //LiLy
dog.saySomething() //汪汪汪
上面的代碼用class關(guān)鍵字定義了一個(gè)類,這個(gè)類有一個(gè)構(gòu)造函數(shù)constructor,當(dāng)通過new實(shí)例化一個(gè)對(duì)象時(shí)會(huì)自動(dòng)調(diào)用。還定義了一個(gè)saySomething方法,該方法不需要添加function也不需要prototype關(guān)鍵字。
我們已經(jīng)知道,第一種方法中通過構(gòu)造函數(shù)創(chuàng)建的類,本質(zhì)是一個(gè)函數(shù),實(shí)際是將方法添加到了函數(shù)的原型上,那通過class定義的類又如何呢,于是我試著做了以下輸出:
typeof Dog // "function"
Dog.prototype.saySomething(); //汪汪汪
Dog.prototype.constructor === Dog //ture
這就表明,class其實(shí)跟構(gòu)造函數(shù)的實(shí)現(xiàn)沒有什么本質(zhì)的差別,類本身也是一個(gè)函數(shù),類的所有方法也是定義在prototype上,類本身指向它的構(gòu)造函數(shù),class的實(shí)現(xiàn)還是基于prototype的,它只是把prototype藏起來了。
不少人管這種寫法叫做ES6的語(yǔ)法糖,因?yàn)閏lass的本質(zhì)還是ES5的語(yǔ)法,但是更加的簡(jiǎn)潔易懂、更加接近面向?qū)ο蟮膶懛ā?/p>
2.class中的屬性和方法
與面向?qū)ο笳Z(yǔ)言中類不同的是,ES6的class中只有實(shí)例屬性,無法定義類成員屬性,只能定義方法。
實(shí)例屬性######
在ES6中,類的實(shí)例屬性只能定義在構(gòu)造函數(shù)中,用this關(guān)鍵字定義只屬于實(shí)例對(duì)象本身的屬性,實(shí)例之間互不影響。千萬不要在類中直接定義成員屬性,至少目前來說是不支持的。
class Dog{
name = 'LiLy'; //錯(cuò)誤用法,但ES7已有草案,未來可能支持這種寫法
constructor(name){
this.name = name; //正確
}
說到class中的屬性就不得不提面向?qū)ο笳Z(yǔ)言中類的屬性,通常來說,類的屬性是可以被實(shí)例繼承的,而在class中,如果我們想定義一個(gè)實(shí)例之間共享的屬性,那就只能用搬出我們的.prototype把屬性定義到類的原型對(duì)象上。
Dog.prototype.kind = 'animal'
這么看來class真是有點(diǎn)雞肋啊,還是有很多事做不了,要用到prototype才能解決問題。
原型方法與靜態(tài)方法######
原型方法能夠被類的實(shí)例繼承,實(shí)例擁有類的一切原型方法。下面的例子中,可以直接訪問通過實(shí)例調(diào)用方法saySomething()。
靜態(tài)方法在函數(shù)名稱前添加static關(guān)鍵字,這一類方法不能被實(shí)例直接調(diào)用,只能通過類名稱訪問。各位可以參照如下的例子理解一下:
class Dog{
constructor(name){
this.name = name;
}
saySomething(){
console.log('汪汪汪');
}
static sayHello(){
console.log('hello');
}
}
var dog = new Dog('LiLy');
dog.saySomething(); //汪汪汪
Dog.sayHello(); //hello
dog.sayHello(); //dog.sayHello is not a function
3.繼承更加方便
ES6中可以用extend很方便的實(shí)現(xiàn)class之間的繼承,相比ES5中的繼承讓人愉悅得多。
class Corgi extends Dog{
constructor(name,age){
super(name);
this.name = 'Tom';
this.age = age;
}
}
var corgi = new Corgi('corgi',2);
corgi.name //Tom
corgi.age // 2,不調(diào)super()則會(huì)因?yàn)檎也坏絫his對(duì)象無法初始化age的值
corgi.saySomething() //汪汪汪
在這一段代碼中,Corgi類繼承了Dog類,它能夠改寫實(shí)例屬性還能調(diào)用父類的方法。
需要注意的是,子類的構(gòu)造函數(shù)中,用了super方法調(diào)用父類的構(gòu)造函數(shù)并繼承this對(duì)象,再在自己的構(gòu)造函數(shù)中對(duì)this對(duì)象增加屬性和方法。子類不能自己生成this,而super最關(guān)鍵的作用就是生成this對(duì)象。
4.項(xiàng)目應(yīng)用
在我們金融壹賬通前端H5的項(xiàng)目中,將class作為組織頁(yè)面js的一種方式,通常來說,一個(gè)js中包含一個(gè)類的聲明和實(shí)例化。一個(gè)常見的index.js的結(jié)構(gòu)看起來會(huì)是這樣子的:
class Index {
constructor() {
this._init();//初始化
this.onBack();//監(jiān)聽回調(diào)
this.bindEvent();//事件綁定
//更多代碼
}
_init(){
//代碼塊
}
onBack(){
//代碼塊
}
bindEvent(){
//代碼塊
}
new Index();
這樣組織JS最大的好處便是結(jié)構(gòu)清晰、可讀性強(qiáng)。在構(gòu)造函數(shù)中,調(diào)用了類中的幾個(gè)方法,這樣一來,我們new一個(gè)Index類的時(shí)候,會(huì)自動(dòng)運(yùn)行constructor這個(gè)函數(shù),并在里面進(jìn)行初始化、綁定事件和設(shè)置監(jiān)聽等等。
但是也看到有的同事在constructor函數(shù)中設(shè)置埋點(diǎn)、請(qǐng)求接口、初始化變量等等,這種用法雖說沒有什么錯(cuò)誤,但是違背了我們使用class的初衷,在代碼結(jié)構(gòu)上,顯得雜亂無章,不利于閱讀理解。
5.總結(jié)
缺點(diǎn):在JS中,類是基于原型繼承實(shí)現(xiàn)的,與面向?qū)ο蟮墓δ懿顒e很大,很多功能在這里都是不支持的,比如類屬性、類嵌套、多重繼承等等。兩者可以說完全不是一個(gè)東西,沒有什么可比性。
優(yōu)勢(shì):語(yǔ)法更加地簡(jiǎn)潔,不再需要每次都引用prototype去定義類的方法,也方便了編輯器檢查代碼語(yǔ)法。