01.靜態(tài)方法
類相當(dāng)于實例的原型颜说,所有在類中定義的方法儒将,都會被實例繼承闹丐。如果在一個方法前鹦付,加上static關(guān)鍵字,就表示該方法不會被實例繼承蝗蛙,而是直接通過類來調(diào)用蝇庭,這就稱為“靜態(tài)方法”。
class Foo{
static classMethod() {
return 'hello';
? }
}
Foo.classMethod()// 'hello'
varfoo=new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
上面代碼中捡硅,F(xiàn)oo類的classMethod方法前有static關(guān)鍵字哮内,表明該方法是一個靜態(tài)方法,可以直接在Foo類上調(diào)用(Foo.classMethod()),而不是在Foo類的實例上調(diào)用北发。如果在實例上調(diào)用靜態(tài)方法纹因,會拋出一個錯誤,表示不存在該方法琳拨。
注意瞭恰,如果靜態(tài)方法包含this關(guān)鍵字,這個this指的是類狱庇,而不是實例惊畏。
class Foo{
static bar() {
this.baz();
? }
static baz() {
console.log('hello');
? }
baz() {
console.log('world');
? }
}
Foo.bar()// hello
上面代碼中,靜態(tài)方法bar調(diào)用了this.baz密任,這里的this指的是Foo類颜启,而不是Foo的實例,等同于調(diào)用Foo.baz浪讳。另外缰盏,從這個例子還可以看出,靜態(tài)方法可以與非靜態(tài)方法重名淹遵。
父類的靜態(tài)方法口猜,可以被子類繼承。
class Foo{
static classMethod() {
return'hello';
? }
}
class Barextends Foo{
}
Bar.classMethod()// 'hello'
上面代碼中合呐,父類Foo有一個靜態(tài)方法暮的,子類Bar可以調(diào)用這個方法笙以。
靜態(tài)方法也是可以從super對象上調(diào)用的淌实。
class Foo{
static classMethod() {
return'hello';
? }
}
class Bar extends Foo{
static classMethod() {
returnsuper.classMethod() + ', too';
? }
}
Bar.classMethod()// "hello, too"
2.屬性的新寫法
實例屬性除了定義在constructor()方法里面的this上面,也可以定義在類的最頂層猖腕。
class IncreasingCounter{
constructor() {
this._count = 0;
? }
get value()? {
console.log('Getting the current value!');
returnthis._count;
? }
increment() {
this._count++;
? }
}
上面代碼中拆祈,實例屬性this._count定義在constructor()方法里面。另一種寫法是倘感,這個屬性也可以定義在類的最頂層放坏,其他都不變。
class IncreasingCounter{
_count=0;
get value() {
console.log('Getting the current value!');
return this._count;
? }
increment() {
this._count++;
? }
}
上面代碼中老玛,實例屬性_count與取值函數(shù)value()和increment()方法淤年,處于同一個層級。這時蜡豹,不需要在實例屬性前面加上this麸粮。
這種新寫法的好處是,所有實例對象自身的屬性都定義在類的頭部镜廉,看上去比較整齊弄诲,一眼就能看出這個類有哪些實例屬性。
class foo{
bar = 'hello';
baz = 'world';
constructor() {
// ...
? }
}
上面的代碼娇唯,一眼就能看出齐遵,foo類有兩個實例屬性寂玲,一目了然。另外梗摇,寫起來也比較簡潔拓哟。
3.靜態(tài)屬性
靜態(tài)屬性指的是 Class 本身的屬性,即Class.propName伶授,而不是定義在實例對象(this)上的屬性彰檬。
class Foo{
}
Foo.prop=1;
Foo.prop// 1
上面的寫法為Foo類定義了一個靜態(tài)屬性prop。
目前谎砾,只有這種寫法可行逢倍,因為 ES6 明確規(guī)定,Class 內(nèi)部只有靜態(tài)方法景图,沒有靜態(tài)屬性〗系瘢現(xiàn)在有一個提案提供了類的靜態(tài)屬性,寫法是在實例屬性的前面挚币,加上static關(guān)鍵字亮蒋。
class MyClass{
static myStaticProp = 42;
constructor() {
console.log(MyClass.myStaticProp);// 42
? }
}
這個新寫法大大方便了靜態(tài)屬性的表達。
// 老寫法
class Foo{
// ...
}
Foo.prop = 1;
// 新寫法
class Foo{
staticprop = 1;
}
上面代碼中妆毕,老寫法的靜態(tài)屬性定義在類的外部慎玖。整個類生成以后,再生成靜態(tài)屬性笛粘。這樣讓人很容易忽略這個靜態(tài)屬性趁怔,也不符合相關(guān)代碼應(yīng)該放在一起的代碼組織原則。另外薪前,新寫法是顯式聲明(declarative)润努,而不是賦值處理,語義更好示括。
4.私有方法和私有屬性
現(xiàn)有的解決方案
私有方法和私有屬性铺浇,是只能在類的內(nèi)部訪問的方法和屬性,外部不能訪問垛膝。這是常見需求鳍侣,有利于代碼的封裝,但 ES6 不提供吼拥,只能通過變通方法模擬實現(xiàn)倚聚。
一種做法是在命名上加以區(qū)別。
class Widget{
// 公有方法
foo(baz) {
this._bar(baz);
? }
// 私有方法
_bar(baz) {
returnthis.snaf=baz;
? }
// ...
}
上面代碼中扔罪,_bar()方法前面的下劃線秉沼,表示這是一個只限于內(nèi)部使用的私有方法。但是,這種命名是不保險的唬复,在類的外部矗积,還是可以調(diào)用到這個方法。
另一種方法就是索性將私有方法移出類敞咧,因為類內(nèi)部的所有方法都是對外可見的棘捣。
class Widget{
foo(baz) {
bar.call(this,baz);
? }
// ...
}
function bar(baz) {
return this.snaf=baz;
}
上面代碼中,foo是公開方法休建,內(nèi)部調(diào)用了bar.call(this, baz)乍恐。這使得bar()實際上成為了當(dāng)前類的私有方法。
還有一種方法是利用Symbol值的唯一性测砂,將私有方法的名字命名為一個Symbol值茵烈。
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
? }
// 私有方法
[bar](baz) {
returnthis[snaf] = baz;
? }
// ...
};
上面代碼中,bar和snaf都是Symbol值砌些,一般情況下無法獲取到它們呜投,因此達到了私有方法和私有屬性的效果。但是也不是絕對不行存璃,Reflect.ownKeys()依然可以拿到它們仑荐。
const inst = newmyClass();
Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]
上面代碼中,Symbol 值的屬性名依然可以從類的外部拿到纵东。
私有屬性的提案
目前粘招,有一個提案,為class加了私有屬性偎球。方法是在屬性名之前洒扎,使用#表示。
class IncreasingCounter{
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
? }
increment() {
this.#count++;
? }
}
上面代碼中甜橱,#count就是私有屬性逊笆,只能在類的內(nèi)部使用(this.#count)栈戳。如果在類的外部使用岂傲,就會報錯。
const counter = new IncreasingCounter();
counter.#count // 報錯
counter.#count = 42 // 報錯
上面代碼在類的外部子檀,讀取私有屬性镊掖,就會報錯。
下面是另一個例子褂痰。
class Point{
#x;
constructor(x=0) {
this.#x = +x;
? }
get x() {
returnthis.#x;
? }
set x(value) {
this.#x = +value;
? }
}
上面代碼中亩进,#x就是私有屬性,在Point類之外是讀取不到這個屬性的缩歪。由于井號#是屬性名的一部分归薛,使用時必須帶有#一起使用,所以#x和x是兩個不同的屬性。
之所以要引入一個新的前綴#表示私有屬性主籍,而沒有采用private關(guān)鍵字习贫,是因為 JavaScript 是一門動態(tài)語言,沒有類型聲明千元,使用獨立的符號似乎是唯一的比較方便可靠的方法苫昌,能夠準(zhǔn)確地區(qū)分一種屬性是否為私有屬性。另外幸海,Ruby 語言使用@表示私有屬性祟身,ES6 沒有用這個符號而使用#,是因為@已經(jīng)被留給了 Decorator物独。
這種寫法不僅可以寫私有屬性袜硫,還可以用來寫私有方法。
class Foo{
#a;
#b;
const ructor(a,b) {
this.#a = a;
this.#b = b;
? }
#sum() {
return this.#a + this.#b;
? }
printSum() {
console.log(this.#sum());
? }
}
上面代碼中挡篓,#sum()就是一個私有方法父款。
另外,私有屬性也可以設(shè)置 getter 和 setter 方法瞻凤。
class Counter{
#xValue = 0;
const ructor() {
super();
// ...
? }
get #x() { return #xValue; }
set #x(value) {
this.#xValue = value;
? }
}
上面代碼中憨攒,#x是一個私有屬性,它的讀寫都通過get #x()和set #x()來完成阀参。
私有屬性不限于從this引用肝集,只要是在類的內(nèi)部,實例也可以引用私有屬性蛛壳。
class Foo{
#privateValue = 42;
staticgetPrivateValue(foo) {
return foo.#privateValue;
? }
}
Foo.getPrivateValue(newFoo());// 42
上面代碼允許從實例foo上面引用私有屬性杏瞻。
私有屬性和私有方法前面,也可以加上static關(guān)鍵字衙荐,表示這是一個靜態(tài)的私有屬性或私有方法捞挥。
class FakeMath{
staticPI = 22/7;
static #totallyRandomNumber = 4;
static #computeRandomNumber() {
return FakeMath.#totallyRandomNumber;
? }
staticrandom() {
console.log('I heard you like random numbers…')
returnFakeMath.#computeRandomNumber();
? }
}
FakeMath.PI// 3.142857142857143
FakeMath.random()
// I heard you like random numbers…
// 4
FakeMath.#totallyRandomNumber // 報錯
FakeMath.#computeRandomNumber() // 報錯
上面代碼中,#totallyRandomNumber是私有屬性忧吟,#computeRandomNumber()是私有方法砌函,只能在FakeMath這個類的內(nèi)部調(diào)用,外部調(diào)用就會報錯溜族。
in 運算符
try...catch結(jié)構(gòu)可以用來判斷是否存在某個私有屬性讹俊。
class A{
use(obj) {
try{
obj.#foo;
}catch{
// 私有屬性 #foo 不存在
?? }
? }
}
consta=newA();
a.use(a);// 報錯
上面示例中,類A并不存在私有屬性#foo煌抒,所以try...catch報錯了仍劈。
這樣的寫法很麻煩,可讀性很差寡壮,V8 引擎改進了in運算符贩疙,使它也可以用來判斷私有屬性讹弯。
class A{
use(obj) {
if(#foo in obj) {
// 私有屬性 #foo 存在
}else{
// 私有屬性 #foo 不存在
?? }
? }
}
上面示例中,in運算符判斷當(dāng)前類A的實例这溅,是否有私有屬性#foo闸婴,如果有返回true,否則返回false芍躏。
in也可以跟this一起配合使用邪乍。
class A{
#foo = 0;
m() {
console.log(#foo in this); // true
console.log(#bar in this); // false
? }
}
注意,判斷私有屬性時对竣,in只能用在定義該私有屬性的類的內(nèi)部庇楞。
class A{
#foo = 0;
statictest(obj) {
console.log(#foo in obj);
? }
}
A.test(newA())// true
A.test({})// false
class B{
#foo = 0;
}
A.test(newB())// false
上面示例中,類A的私有屬性#foo否纬,只能在類A內(nèi)部使用in運算符判斷吕晌,而且只對A的實例返回true,對于其他對象都返回false临燃。
子類從父類繼承的私有屬性睛驳,也可以使用in運算符來判斷。
class A{
#foo = 0;
statictest(obj) {
console.log(#foo in obj);
? }
}
classSubAextendA{};
A.test(newSubA())// true
上面示例中膜廊,SubA從父類繼承了私有屬性#foo乏沸,in運算符也有效。
注意爪瓜,in運算符對于Object.create()蹬跃、Object.setPrototypeOf形成的繼承,是無效的铆铆,因為這種繼承不會傳遞私有屬性蝶缀。
class A{
#foo = 0;
statictest(obj) {
console.log(#foo in obj);
? }
}
consta= new A();
consto1=Object.create(a);
A.test(o1)// false
A.test(o1.__proto__)// true
const o2={};
Object.setPrototypeOf(o2,A);
A.test(o2)// false
A.test(o2.__proto__)// true
上面示例中,對于修改原型鏈形成的繼承薄货,子類都取不到父類的私有屬性翁都,所以in運算符無效。
5.new.target 屬性
new是從構(gòu)造函數(shù)生成實例對象的命令谅猾。ES6 為new命令引入了一個new.target屬性柄慰,該屬性一般用在構(gòu)造函數(shù)之中,返回new命令作用于的那個構(gòu)造函數(shù)赊瞬。如果構(gòu)造函數(shù)不是通過new命令或Reflect.construct()調(diào)用的先煎,new.target會返回undefined,因此這個屬性可以用來確定構(gòu)造函數(shù)是怎么調(diào)用的巧涧。
function Person(name) {
if(new.target!==undefined) {
this.name=name;
}else{
thrownewError('必須使用 new 命令生成實例');
? }
}
// 另一種寫法
function Person(name) {
if(new.target===Person) {
this.name=name;
}else{
thrownewError('必須使用 new 命令生成實例');
? }
}
varperson=newPerson('張三');// 正確
varnotAPerson=Person.call(person,'張三');// 報錯
上面代碼確保構(gòu)造函數(shù)只能通過new命令調(diào)用。
Class 內(nèi)部調(diào)用new.target遥倦,返回當(dāng)前 Class谤绳。
class Rectangle{
const ructor(length,width) {
console.log(new.target===Rectangle);
this.length=length;
this.width=width;
? }
}
varobj=newRectangle(3,4);// 輸出 true
需要注意的是占锯,子類繼承父類時,new.target會返回子類缩筛。
class Rectangle{
const ructor(length,width) {
console.log(new.target===Rectangle);
// ...
? }
}
class SquareextendsRectangle{
const ructor(length,width) {
super(length,width);
? }
}
varobj=newSquare(3);// 輸出 false
上面代碼中消略,new.target會返回子類。
利用這個特點瞎抛,可以寫出不能獨立使用艺演、必須繼承后才能使用的類。
class Shape{
const ructor() {
if(new.target===Shape) {
thrownewError('本類不能實例化');
?? }
? }
}
class RectangleextendsShape{
const ructor(length,width) {
super();
// ...
? }
}
varx=newShape();// 報錯
vary=newRectangle(3,4);// 正確
上面代碼中桐臊,Shape類不能被實例化胎撤,只能用于繼承。
注意断凶,在函數(shù)外部伤提,使用new.target會報錯。