1.簡(jiǎn)介
JavaScript 語(yǔ)言中距潘,生成實(shí)例對(duì)象的傳統(tǒng)方法是通過(guò)構(gòu)造函數(shù)稽犁。下面是一個(gè)例子虑椎。
上面這種寫法跟傳統(tǒng)的面向?qū)ο笳Z(yǔ)言(比如 C++ 和 Java)差異很大星虹,很容易讓新學(xué)習(xí)這門語(yǔ)言的程序員感到困惑忽妒。
ES6 提供了更接近傳統(tǒng)語(yǔ)言的寫法,引入了 Class(類)這個(gè)概念喧务,作為對(duì)象的模板。通過(guò)class關(guān)鍵字衡载,可以定義類娜饵。
基本上,ES6 的class可以看作只是一個(gè)語(yǔ)法糖,它的絕大部分功能贷痪,ES5 都可以做到尚镰,新的class寫法只是讓對(duì)象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z(yǔ)法而已唬渗。上面的代碼用 ES6 的class改寫座菠,就是下面這樣降宅。
上面代碼定義了一個(gè)“類”,可以看到里面有一個(gè)constructor方法,這就是構(gòu)造方法,而 this關(guān)鍵字則代表實(shí)例對(duì)象。也就是說(shuō)涎拉,ES5 的構(gòu)造函數(shù)Person酪我,對(duì)應(yīng) ES6 的Person類的構(gòu)造方法带饱。
Person類除了構(gòu)造方法教寂,還定義了一個(gè)toString方法轨淌。注意媳拴,定義“類”的方法的時(shí)候僻造,前面不需要加上function這個(gè)關(guān)鍵字,直接把函數(shù)定義放進(jìn)去了就可以了立膛。另外揪罕,方法之間不需要逗號(hào)分隔梯码,加了會(huì)報(bào)錯(cuò)。
上面代碼表明好啰,類的數(shù)據(jù)類型就是函數(shù)轩娶,類本身就指向構(gòu)造函數(shù)。
使用的時(shí)候框往,也是直接對(duì)類使用new命令鳄抒,跟構(gòu)造函數(shù)的用法完全一致。
構(gòu)造函數(shù)的prototype屬性椰弊,在 ES6 的“類”上面繼續(xù)存在许溅。事實(shí)上,類的所有方法都定義在類的prototype屬性上面秉版。
在類的實(shí)例上面調(diào)用方法贤重,其實(shí)就是調(diào)用原型上的方法。
prototype對(duì)象的constructor屬性清焕,直接指向“類”的本身并蝗,這與 ES5 的行為是一致的。
另外耐朴,類的內(nèi)部所有定義的方法借卧,都是不可枚舉的(non-enumerable)。
上面代碼中筛峭,toString方法是Person類內(nèi)部定義的方法铐刘,它是不可枚舉的。這一點(diǎn)與 ES5 的行為不一致影晓。
上面代碼采用 ES5 的寫法镰吵,toString方法就是可枚舉的。
2.嚴(yán)格模式
類和模塊的內(nèi)部挂签,默認(rèn)就是嚴(yán)格模式疤祭,所以不需要使用use strict指定運(yùn)行模式。只要你的代碼寫在類或模塊之中饵婆,就只有嚴(yán)格模式可用勺馆。
考慮到未來(lái)所有的代碼,其實(shí)都是運(yùn)行在模塊之中侨核,所以 ES6 實(shí)際上把整個(gè)語(yǔ)言升級(jí)到了嚴(yán)格模式草穆。
3.constructor 方法
constructor方法是類的默認(rèn)方法,通過(guò)new命令生成對(duì)象實(shí)例時(shí)搓译,自動(dòng)調(diào)用該方法悲柱。
// 定義類
class Person{
constructor(name,age){
this.name=name;
this.age=age;
console.log(2213) // 2213
}
}
let p1=new Person('Andy',31)
可以看到控制臺(tái)打印了2213, 證明通過(guò)new命令生成實(shí)例對(duì)象時(shí),自動(dòng)調(diào)用constructor方法
另外,類里面的this指代的都是實(shí)例對(duì)象,請(qǐng)看下面這個(gè)例子
// 定義類
class Person{
constructor(name,age){
this.name=name;
this.age=age;
}
toString(){
console.log(this)
}
}
let p1=new Person('Andy',31) // Person {name: "Andy", age: 31}
p1.toString()
一個(gè)類必須有constructor方法些己,如果沒有顯式定義豌鸡,一個(gè)空的constructor方法會(huì)被默認(rèn)添加嘿般。
上面代碼中,定義了一個(gè)空的類Person涯冠,JavaScript 引擎會(huì)自動(dòng)為它添加一個(gè)空的constructor方法炉奴。
constructor方法默認(rèn)返回實(shí)例對(duì)象(即this),完全可以指定返回另外一個(gè)對(duì)象功偿。
上面代碼中盆佣,constructor函數(shù)返回一個(gè)全新的對(duì)象往堡,結(jié)果導(dǎo)致實(shí)例對(duì)象不是Foo類的實(shí)例械荷。
類必須使用new調(diào)用,否則會(huì)報(bào)錯(cuò)虑灰。這是它跟普通構(gòu)造函數(shù)的一個(gè)主要區(qū)別吨瞎,后者不用new也可以執(zhí)行。
4.類的實(shí)例對(duì)象
生成類的實(shí)例對(duì)象的寫法穆咐,與 ES5 完全一樣颤诀,也是使用new命令。
需要注意的是对湃,需要加上new崖叫,如果忘記加上new,像函數(shù)那樣調(diào)用Class拍柒,將會(huì)報(bào)錯(cuò)心傀。
與 ES5 一樣,實(shí)例的屬性除非顯式定義在其本身(即定義在this對(duì)象上)拆讯,否則都是定義在原型上(即定義在class上)脂男。
上面代碼中,定義了一個(gè) Person類,constructor中的sex
和age
是new出來(lái)的實(shí)例對(duì)象的屬性种呐,打印 Person.hasOwnProperty('sex')結(jié)果是false
, 打印 p1.hasOwnProperty('sex')結(jié)果是true
宰翅。 這也證明了 constructor中的this指代的是實(shí)例對(duì)象。
與 ES5 一樣爽室,類的所有實(shí)例共享一個(gè)原型對(duì)象汁讼。
上面代碼中,p1和p2都是Person的實(shí)例阔墩,它們的原型都是Person.prototype嘿架,所以 _proto_屬性是相等的。
這也意味著戈擒,可以通過(guò)實(shí)例的_proto_屬性為“類”添加方法眶明。
上面代碼在p1的原型上添加了一個(gè)printName方法,由于p1的原型就是p2的原型筐高,
因此p2也可以調(diào)用這個(gè)方法搜囱。而且丑瞧,此后新建的實(shí)例p3也可以調(diào)用這個(gè)方法。
這意味著蜀肘,使用實(shí)例的_proto屬性改寫原型绊汹,必須相當(dāng)謹(jǐn)慎,
不推薦使用扮宠,因?yàn)檫@會(huì)改變“類”的原始定義西乖,影響到所有實(shí)例。
5.class表達(dá)式
與函數(shù)一樣坛增,類也可以使用表達(dá)式的形式定義获雕。
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
上面代碼使用表達(dá)式定義了一個(gè)類。
需要注意的是收捣,這個(gè)類的名字是MyClass而不是Me届案,Me只在 Class 的內(nèi)部代碼可用,指代當(dāng)前類罢艾。
6.不存在變量提升
類不存在變量提升(hoist)楣颠,這一點(diǎn)與 ES5 完全不同
new Foo(); // ReferenceError
class Foo {}
上面代碼中,F(xiàn)oo類使用在前咐蚯,定義在后童漩,這樣會(huì)報(bào)錯(cuò),因?yàn)?ES6 不會(huì)把類的聲明提升到代碼頭部春锋。這種規(guī)定的原因與下文要提到的繼承有關(guān)矫膨,必須保證子類在父類之后定義。
{
let Foo = class {};
class Bar extends Foo {
}
}
上面的代碼不會(huì)報(bào)錯(cuò)看疙,因?yàn)锽ar繼承Foo的時(shí)候豆拨,F(xiàn)oo已經(jīng)有定義了。但是能庆,如果存在class的提升施禾,上面代碼就會(huì)報(bào)錯(cuò),因?yàn)閏lass會(huì)被提升到代碼頭部搁胆,而let命令是不提升的弥搞,所以導(dǎo)致Bar繼承Foo的時(shí)候,F(xiàn)oo還沒有定義渠旁。
7.私有方法和私有屬性
私有方法是常見需求攀例,但 ES6 不提供,只能通過(guò)變通方法模擬實(shí)現(xiàn)顾腊。
8.this的指向
類的方法內(nèi)部如果含有this粤铭,它默認(rèn)指向類的實(shí)例。但是杂靶,必須非常小心梆惯,一旦單獨(dú)使用該方法酱鸭,很可能報(bào)錯(cuò)。
上面的例子說(shuō)明垛吗,在單獨(dú)執(zhí)行printName這個(gè)函數(shù)的時(shí)候凹髓,由于this的指向問(wèn)題,函數(shù)中的代碼 this.print()當(dāng)前上下文環(huán)境中找不到print這個(gè)函數(shù)怯屉,所以報(bào)錯(cuò)蔚舀。
一個(gè)比較簡(jiǎn)單的解決方法是,在構(gòu)造方法中綁定this锨络,這樣就不會(huì)找不到print方法了赌躺。
請(qǐng)看下面這個(gè)更為復(fù)雜的情況。
這個(gè)例子中足删,在class Logger的constructor中我們使用了bind方法寿谴,把printName這個(gè)方法的執(zhí)行上下文環(huán)境綁定到了class Logger的實(shí)例上锁右。
bind方法是新創(chuàng)建一個(gè)函數(shù)失受,然后把它的上下文綁定到bind()括號(hào)中的參數(shù)上,然后將它返回咏瑟。
所以拂到,bind后函數(shù)不會(huì)執(zhí)行,而只是返回一個(gè)改變了上下文的函數(shù)副本码泞,而call和apply是直接執(zhí)行函數(shù)兄旬。
后面的代碼中,我們new了一個(gè) Logger的實(shí)例余寥,打印出來(lái)领铐。
發(fā)現(xiàn)實(shí)例中有userName passwords 和 printName三個(gè)屬性,前面兩個(gè)是key value屬性宋舷,最后的一個(gè) printName是Logger實(shí)例的一個(gè)方法绪撵。
通過(guò)var {printName}=logger把printName單獨(dú)拿出來(lái),打印printName發(fā)現(xiàn)它是一個(gè)函數(shù)祝蝠,它是實(shí)例logger的一個(gè)OwenProperty音诈,其原型proto指向Function的prototype屬性。
注意到printName._proto與實(shí)例 logger._proto顯然是不等的绎狭。
它與Function的原型才相等细溅!
前面提到,我們使用bind方法儡嘶,將printName的上下文環(huán)境this改變成了class Logger的實(shí)例對(duì)象上喇聊,所以直接調(diào)用 printName方法可行了,它會(huì)打印出hello lv的信息蹦狂。
關(guān)于bind()函數(shù)
這個(gè)例子中誓篱,我們?cè)赾onstructor中使用了bind方法邻耕,將實(shí)例對(duì)象logger的上下文環(huán)境this綁定到了一個(gè)新的對(duì)象上面,新的對(duì)象上面也有print方法
我們把實(shí)例對(duì)象logger的print方法賦值給printName方法燕鸽,單獨(dú)調(diào)用printName方法時(shí)兄世,發(fā)現(xiàn)打印的都是 敵法師,新的對(duì)象里面的name,sex屬性值啊研。
9.name 屬性
由于本質(zhì)上御滩,ES6 的類只是 ES5 的構(gòu)造函數(shù)的一層包裝,所以函數(shù)的許多特性都被Class繼承党远,包括name屬性削解。
class Point {}
Point.name // "Point"
前面有提到過(guò)。
Person.hasOwnProperty('name');打印的是true沟娱,是因?yàn)榍珊暇褪沁@個(gè)原因氛驮。實(shí)際上打印 Person.hasOwnProperty('age');打印的就是false
10.Class 的取值函數(shù)(getter)和存值函數(shù)(setter)
與 ES5 一樣,在“類”的內(nèi)部可以使用get和set關(guān)鍵字济似,對(duì)某個(gè)屬性設(shè)置存值函數(shù)和取值函數(shù)矫废,攔截該屬性的存取行為。
上面代碼中砰蠢,prop屬性有對(duì)應(yīng)的存值函數(shù)和取值函數(shù)蓖扑,因此賦值和讀取行為都被自定義了。
存值函數(shù)和取值函數(shù)是設(shè)置在屬性的 Descriptor 對(duì)象上的台舱。
12.class的靜態(tài)方法
類相當(dāng)于實(shí)例的原型律杠,所有在類中定義的方法,都會(huì)被實(shí)例繼承竞惋。如果在一個(gè)方法前柜去,加上static關(guān)鍵字,就表示該方法不會(huì)被實(shí)例繼承拆宛,而是直接通過(guò)類來(lái)調(diào)用嗓奢,這就稱為“靜態(tài)方法”。
上面代碼中胰挑,Person類有個(gè)靜態(tài)方法hello(),可以在Person類上調(diào)用蔓罚,但是不能在Person的實(shí)例上調(diào)用。
嘗試 p.hello()是報(bào)錯(cuò)的瞻颂。
注意豺谈,如果靜態(tài)方法包含this關(guān)鍵字,這個(gè)this指的是類贡这,而不是實(shí)例茬末。
上面代碼中,靜態(tài)方法bar調(diào)用了this.baz,這里的this指的是Person類丽惭,而不是Person的實(shí)例击奶,等同于調(diào)用Person.baz。
另外责掏,從這個(gè)例子還可以看出柜砾,靜態(tài)方法可以與非靜態(tài)方法重名。
父類的靜態(tài)方法换衬,可以被子類繼承痰驱。
靜態(tài)方法也是可以從super對(duì)象上調(diào)用的。
13.class的靜態(tài)屬性和實(shí)例屬性
靜態(tài)屬性指的是 Class 本身的屬性瞳浦,即Class.propName担映,而不是定義在實(shí)例對(duì)象(this)上的屬性。
// 定義類
class Person{
}
Person.prop=1;
console.log(Person.prop); // 1
上面的寫法為Person類定義了一個(gè)靜態(tài)屬性prop叫潦。
目前蝇完,只有這種寫法可行,因?yàn)?ES6 明確規(guī)定矗蕊,Class 內(nèi)部只有靜態(tài)方法短蜕,沒有靜態(tài)屬性。
// 以下兩種寫法都無(wú)效
class Person {
// 寫法一
prop: 2
// 寫法二
static prop: 2
}
Person.prop // undefined