ES6為JavaScript引入了類,但它們對(duì)于復(fù)雜的應(yīng)用程序來(lái)說(shuō)太簡(jiǎn)單了。Class Fields(也稱為類屬性)旨在提供具有私有和靜態(tài)成員的更簡(jiǎn)單的構(gòu)造函數(shù)。該提案目前處于TC39第3階段:候選人,可能出現(xiàn)在ES2019(ES10)中。
在我們更詳細(xì)地檢查類字段之前,快速回顧一下ES6類是很有用的。
ES6類基礎(chǔ)知識(shí)
JavaScript的原型繼承模型可能會(huì)讓開(kāi)發(fā)人員感到困惑,因?yàn)樗麄兞私釩 ++,C#,Java和PHP等語(yǔ)言中使用的經(jīng)典繼承。JavaScript類主要是語(yǔ)法糖,但它們提供了更熟悉的面向?qū)ο缶幊谈拍睢?/p>
類是一個(gè)模板,用于定義該類型的對(duì)象的行為方式。下面的Animal類定義了通用動(dòng)物(類通常用首字母大寫表示,以區(qū)別于對(duì)象和其他類型):
class Animal {
constructor(name = 'anonymous', legs = 4, noise = 'nothing') {
this.type = 'animal';
this.name = name;
this.legs = legs;
this.noise = noise;
}
speak() {
console.log(`${this.name} says "${this.noise}"`);
}
walk() {
console.log(`${this.name} walks on ${this.legs} legs`);
}
}
類聲明以嚴(yán)格模式執(zhí)行;沒(méi)有必要添加'use strict'。
創(chuàng)建此類型的對(duì)象時(shí)運(yùn)行constructor方法,并且通常在其中定義初始屬性。speak()并且walk()是添加其他功能的方法。
現(xiàn)在可以使用new關(guān)鍵字從此類創(chuàng)建對(duì)象:
const rex = new Animal('Rex', 4, 'woof');
rex.speak(); // Rex says "woof"
rex.noise = 'growl';
rex.speak(); // Rex says "growl"
Getter和Setter
Setter是用于僅定義值的特殊方法。同樣,Getters是用于僅返回值的特殊方法。例如:
class Animal {
constructor(name = 'anonymous', legs = 4, noise = 'nothing') {
this.type = 'animal';
this.name = name;
this.legs = legs;
this.noise = noise;
}
speak() {
console.log(`${this.name} says "${this.noise}"`);
}
walk() {
console.log(`${this.name} walks on ${this.legs} legs`);
}
// setter
set eats(food) {
this.food = food;
}
// getter
get dinner() {
return `${this.name} eats ${this.food || 'nothing'} for dinner.`;
}
}
const rex = new Animal('Rex', 4, 'woof');
rex.eats = 'anything';
console.log( rex.dinner ); // Rex eats anything for dinner.
MDN setter The set syntax binds an object property to a function to be called when there is an attempt to set that property.
MDN getter The get syntax binds an object property to a function that will be called when that property is looked up.
子對(duì)象或子類
使用一個(gè)類作為另一個(gè)類的基礎(chǔ)通常是實(shí)用的。如果我們主要?jiǎng)?chuàng)建狗對(duì)象,那就Animal太通用了,我們必須每次都指定相同屬性值的4(legs)和“woof”(noise)。
Dog類可以使用extends從Animal類繼承所有屬性和方法??梢愿鶕?jù)需要添加或刪除特定于狗的屬性和方法:
class Dog extends Animal {
constructor(name) {
// call the Animal constructor
super(name, 4, 'woof');
this.type = 'dog';
}
// override Animal.speak
speak(to) {
super.speak();
if (to) console.log(`to ${to}`);
}
}
super指的是父類,通常在constructor中調(diào)用。在此示例中,Dog speak()方法會(huì)覆蓋在Animal類中定義的方法。
Dog現(xiàn)在可以創(chuàng)建對(duì)象實(shí)例:
const rex = new Dog('Rex');
rex.speak('everyone'); // Rex says "woof" to everyone
rex.eats = 'anything';
console.log( rex.dinner ); // Rex eats anything for dinner.
靜態(tài)方法和屬性
使用static關(guān)鍵字定義方法允許在類上調(diào)用它而不創(chuàng)建對(duì)象實(shí)例。JavaScript不像其他語(yǔ)言那樣支持靜態(tài)屬性,但可以向類定義添加屬性(a class本身就是一個(gè)JavaScript對(duì)象?。?。
該Dog級(jí)可適應(yīng)保留多少狗對(duì)象已經(jīng)創(chuàng)建了一個(gè)數(shù):
class Dog extends Animal {
constructor(name) {
// call the Animal constructor
super(name, 4, 'woof');
this.type = 'dog';
// update count of Dog objects
Dog.count++;
}
// override Animal.speak
speak(to) {
super.speak();
if (to) console.log(`to ${to}`);
}
// return number of dog objects
static get COUNT() {
return Dog.count;
}
}
// static property (added after class is defined)
Dog.count = 0;
類的靜態(tài)屬性的COUNT的getter返回創(chuàng)建的狗的數(shù)量:
console.log(`Dogs defined: ${Dog.COUNT}`); // Dogs defined: 0
const don = new Dog('Don');
console.log(`Dogs defined: ${Dog.COUNT}`); // Dogs defined: 1
const kim = new Dog('Kim');
console.log(`Dogs defined: ${Dog.COUNT}`); // Dogs defined: 2
有關(guān)更多信息,請(qǐng)參閱面向?qū)ο蟮腏avaScript:深入了解ES6類。
ESnext類字段
類字段(class field)提議允許在類的頂部初始化屬性:
class MyClass {
a = 1;
b = 2;
c = 3;
}
這相當(dāng)于:
class MyClass {
constructor() {
this.a = 1;
this.b = 2;
this.c = 3;
}
}
初始化程序在任何構(gòu)造函數(shù)運(yùn)行之前執(zhí)行(假設(shè)仍然需要構(gòu)造函數(shù))。
靜態(tài)類字段
類字段允許在類中聲明靜態(tài)屬性。例如:
class MyClass {
x = 1;
y = 2;
static z = 3;
}
console.log( MyClass.z ); // 3
不雅的ES6等價(jià)物:
class MyClass {
constructor() {
this.x = 1;
this.y = 2;
}
}
MyClass.z = 3;
console.log( MyClass.z ); // 3
私有類字段
ES6類中的所有屬性默認(rèn)都是公共的,可以在類外檢查或修改。在Animal上面的示例中,無(wú)法阻止直接修改food,而不是通過(guò)調(diào)用eats的setter:
class Animal {
constructor(name = 'anonymous', legs = 4, noise = 'nothing') {
this.type = 'animal';
this.name = name;
this.legs = legs;
this.noise = noise;
}
set eats(food) {
this.food = food;
}
get dinner() {
return `${this.name} eats ${this.food || 'nothing'} for dinner.`;
}
}
const rex = new Animal('Rex', 4, 'woof');
rex.eats = 'anything'; // standard setter
rex.food = 'tofu'; // bypass the eats setter altogether
console.log( rex.dinner ); // Rex eats tofu for dinner.
其他語(yǔ)言允許private聲明屬性。這在ES6中是不可能的,盡管開(kāi)發(fā)人員可以使用下劃線約定(_propertyName)來(lái)解決它。
在ESnext中,使用哈希#前綴定義私有類字段:
class MyClass {
a = 1; // .a is public
#b = 2; // .#b is private
static #c = 3; // .#c is private and static
incB() {
this.#b++;
}
}
const m = new MyClass();
m.incB(); // runs OK
m.#b = 0; // error - private property cannot be modified outside class
請(qǐng)注意,雖然TC39第2階段:提案草案建議#在名稱上使用哈希前綴,但無(wú)法定義私有方法,getter和setter 。例如:
class MyClass {
// private property
#x = 0;
// private method (can only be called within the class)
#incX() {
this.#x++;
}
// private setter (can only be called within the class)
set #setX(x) {
this.#x = x;
}
// private getter (can only be called within the class)
get #getX() {
return this.$x;
}
}
直接受益:更清潔的反應(yīng)代碼!
React組件通常具有與DOM事件關(guān)聯(lián)的方法。為了確保this解析組件,相應(yīng)的bind每個(gè)方法都是必要的。例如:
class App extends Component {
constructor() {
super();
state = { count: 0 };
// bind all methods
this.incCount = this.incCount.bind(this);
}
incCount() {
this.setState(ps => ({ count: ps.count + 1 }));
}
render() {
return (
<div>
<p>{ this.state.count }</p>
<button onClick={this.incCount}>add one</button>
</div>
);
}
}
如果incCount定義為類字段,則可以使用ES6 =>將其設(shè)置為函數(shù),該箭頭會(huì)自動(dòng)將其綁定到定義對(duì)象。該state還可以聲明為類字段,因此沒(méi)有構(gòu)造是必需的:
class App extends Component {
state = { count: 0 };
incCount = () => {
this.setState(ps => ({ count: ps.count + 1 }));
};
render() {
return (
<div>
<p>{ this.state.count }</p>
<button onClick={this.incCount}>add one</button>
</div>
);
}
}
今天使用類字段
瀏覽器或Node.js當(dāng)前不支持類字段。但是,可以使用Babel來(lái)轉(zhuǎn)換語(yǔ)法,Babel在使用Create React App時(shí)默認(rèn)啟用?;蛘?,可以使用以下終端命令安裝和配置Babel:
mkdir class-properties
cd class-properties
npm init -y
npm install --save-dev babel-cli babel-plugin-transform-class-properties
echo '{ "plugins": ["transform-class-properties"] }' > .babelrc
build在以下scripts部分添加命令package.json:
"scripts": {
"build": "babel in.js -o out.js"
},
然后運(yùn)行npm run build以將ESnext文件in.js轉(zhuǎn)換為跨瀏覽器兼容的文件out.js。
已經(jīng)提出了 Babel對(duì)私有方法,getter和setter的支持。
類字段:是一種改進(jìn)?
ES6類定義過(guò)于簡(jiǎn)單化。類字段應(yīng)該有助于提高可讀性并啟用一些有趣的選項(xiàng)。我并不特別喜歡使用哈希#來(lái)表示私有成員,但是如果沒(méi)有它就會(huì)產(chǎn)生意外的行為和性能問(wèn)題(有關(guān)詳細(xì)說(shuō)明,請(qǐng)參閱JavaScript的新#private類字段)。
也許這是不言而喻的,但無(wú)論如何我都會(huì)說(shuō):本文中討論的概念可能會(huì)有所變化,可能永遠(yuǎn)不會(huì)實(shí)現(xiàn)!也就是說(shuō),JavaScript類字段具有實(shí)際的好處,并且興趣正在上升。這是一個(gè)安全的賭注。