Javascript 靜態(tài)屬性和靜態(tài)方法

2023-02-17 10:52 更新

我們可以把一個(gè)方法作為一個(gè)整體賦值給類(lèi)。這樣的方法被稱(chēng)為 靜態(tài)的(static)。

在一個(gè)類(lèi)的聲明中,它們以 static 關(guān)鍵字開(kāi)頭,如下所示:

class User {
  static staticMethod() {
    alert(this === User);
  }
}

User.staticMethod(); // true

這實(shí)際上跟直接將其作為屬性賦值的作用相同:

class User { }

User.staticMethod = function() {
  alert(this === User);
};

User.staticMethod(); // true

在 User.staticMethod() 調(diào)用中的 this 的值是類(lèi)構(gòu)造器 User 自身(“點(diǎn)符號(hào)前面的對(duì)象”規(guī)則)。

通常,靜態(tài)方法用于實(shí)現(xiàn)屬于整個(gè)類(lèi),但不屬于該類(lèi)任何特定對(duì)象的函數(shù)。

例如,我們有對(duì)象 Article,并且需要一個(gè)方法來(lái)比較它們。

通常的解決方案就是添加 Article.compare 靜態(tài)方法:

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static compare(articleA, articleB) {
    return articleA.date - articleB.date;
  }
}

// 用法
let articles = [
  new Article("HTML", new Date(2019, 1, 1)),
  new Article("CSS", new Date(2019, 0, 1)),
  new Article("JavaScript", new Date(2019, 11, 1))
];

articles.sort(Article.compare);

alert( articles[0].title ); // CSS

這里 Article.compare 方法代表“上面的”文章,意思是比較它們。它不是文章的方法,而是整個(gè) class 的方法。

另一個(gè)例子是所謂的“工廠(chǎng)”方法。

比如說(shuō),我們需要通過(guò)多種方式來(lái)創(chuàng)建一篇文章:

  1. 通過(guò)用給定的參數(shù)來(lái)創(chuàng)建(?title?,?date? 等)。
  2. 使用今天的日期來(lái)創(chuàng)建一個(gè)空的文章。
  3. ……其它方法。

第一種方法我們可以通過(guò) constructor 來(lái)實(shí)現(xiàn)。對(duì)于第二種方式,我們可以創(chuàng)建類(lèi)的一個(gè)靜態(tài)方法來(lái)實(shí)現(xiàn)。

例如這里的 Article.createTodays()

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static createTodays() {
    // 記住 this = Article
    return new this("Today's digest", new Date());
  }
}

let article = Article.createTodays();

alert( article.title ); // Today's digest

現(xiàn)在,每當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)今天的文章時(shí),我們就可以調(diào)用 Article.createTodays()。再說(shuō)明一次,它不是一個(gè)文章的方法,而是整個(gè) class 的方法。

靜態(tài)方法也被用于與數(shù)據(jù)庫(kù)相關(guān)的公共類(lèi),可以用于搜索/保存/刪除數(shù)據(jù)庫(kù)中的條目, 就像這樣:

// 假定 Article 是一個(gè)用來(lái)管理文章的特殊類(lèi)
// 通過(guò) id 來(lái)移除文章的靜態(tài)方法:
Article.remove({id: 12345});

靜態(tài)方法不適用于單個(gè)對(duì)象

靜態(tài)方法可以在類(lèi)上調(diào)用,而不是在單個(gè)對(duì)象上。

例如,這樣的代碼無(wú)法正常工作:

// ...
article.createTodays(); /// Error: article.createTodays is not a function

靜態(tài)屬性

最近新增的特性

這是一個(gè)最近添加到 JavaScript 的特性。 示例可以在最近的 Chrome 工作。

靜態(tài)的屬性也是可能的,它們看起來(lái)就像常規(guī)的類(lèi)屬性,但前面加有 static

class Article {
  static publisher = "Levi Ding";
}

alert( Article.publisher ); // Levi Ding

這等同于直接給 Article 賦值:

Article.publisher = "Levi Ding";

繼承靜態(tài)屬性和方法

靜態(tài)屬性和方法是可被繼承的。

例如,下面這段代碼中的 Animal.compare 和 Animal.planet 是可被繼承的,可以通過(guò) Rabbit.compare 和 Rabbit.planet 來(lái)訪(fǎng)問(wèn):

class Animal {
  static planet = "Earth";

  constructor(name, speed) {
    this.speed = speed;
    this.name = name;
  }

  run(speed = 0) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }

}

// 繼承于 Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbits = [
  new Rabbit("White Rabbit", 10),
  new Rabbit("Black Rabbit", 5)
];

rabbits.sort(Rabbit.compare);

rabbits[0].run(); // Black Rabbit runs with speed 5.

alert(Rabbit.planet); // Earth

現(xiàn)在我們調(diào)用 Rabbit.compare 時(shí),繼承的 Animal.compare 將會(huì)被調(diào)用。

它是如何工作的?再次,使用原型。你可能已經(jīng)猜到了,extends 讓 Rabbit 的 [[Prototype]] 指向了 Animal。


所以,Rabbit extends Animal 創(chuàng)建了兩個(gè) [[Prototype]] 引用:

  1. ?Rabbit? 函數(shù)原型繼承自 ?Animal? 函數(shù)。
  2. ?Rabbit.prototype? 原型繼承自 ?Animal.prototype?。

結(jié)果就是,繼承對(duì)常規(guī)方法和靜態(tài)方法都有效。

這里,讓我們通過(guò)代碼來(lái)檢驗(yàn)一下:

class Animal {}
class Rabbit extends Animal {}

// 對(duì)于靜態(tài)的
alert(Rabbit.__proto__ === Animal); // true

// 對(duì)于常規(guī)方法
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true

總結(jié)

靜態(tài)方法被用于實(shí)現(xiàn)屬于整個(gè)類(lèi)的功能。它與具體的類(lèi)實(shí)例無(wú)關(guān)。

舉個(gè)例子, 一個(gè)用于進(jìn)行比較的方法 Article.compare(article1, article2) 或一個(gè)工廠(chǎng)(factory)方法 Article.createTodays()

在類(lèi)聲明中,它們都被用關(guān)鍵字 static 進(jìn)行了標(biāo)記。

靜態(tài)屬性被用于當(dāng)我們想要存儲(chǔ)類(lèi)級(jí)別的數(shù)據(jù)時(shí),而不是綁定到實(shí)例。

語(yǔ)法如下所示:

class MyClass {
  static property = ...;

  static method() {
    ...
  }
}

從技術(shù)上講,靜態(tài)聲明與直接給類(lèi)本身賦值相同:

MyClass.property = ...
MyClass.method = ...

靜態(tài)屬性和方法是可被繼承的。

對(duì)于 class B extends A,類(lèi) B 的 prototype 指向了 AB.[[Prototype]] = A。因此,如果一個(gè)字段在 B 中沒(méi)有找到,會(huì)繼續(xù)在 A 中查找。

任務(wù)


類(lèi)擴(kuò)展自對(duì)象?

重要程度: 3

正如我們所知道的,所有的對(duì)象通常都繼承自 Object.prototype,并且可以訪(fǎng)問(wèn)“通用”對(duì)象方法,例如 hasOwnProperty 等。

例如:

class Rabbit {
  constructor(name) {
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

// hasOwnProperty 方法來(lái)自于 Object.prototype
alert( rabbit.hasOwnProperty('name') ); // true

但是,如果我們像這樣 "class Rabbit extends Object" 把它明確地寫(xiě)出來(lái),那么結(jié)果會(huì)與簡(jiǎn)單的 "class Rabbit" 有所不同么?

不同之處在哪里?

下面是此類(lèi)的示例代碼(它無(wú)法正常運(yùn)行 —— 為什么?修復(fù)它?):

class Rabbit extends Object {
  constructor(name) {
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

alert( rabbit.hasOwnProperty('name') ); // Error

解決方案

首先,讓我們看看為什么之前的代碼無(wú)法運(yùn)行。

如果我們嘗試運(yùn)行它,就會(huì)發(fā)現(xiàn)原因其實(shí)很明顯。派生類(lèi)的 constructor 必須調(diào)用 super()。否則 "this" 不會(huì)被定義。

下面是修復(fù)后的代碼:

class Rabbit extends Object {
  constructor(name) {
    super(); // 需要在繼承時(shí)調(diào)用父類(lèi)的 constructor
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

alert( rabbit.hasOwnProperty('name') ); // true

但這還不是全部原因。

即便修復(fù)了它,"class Rabbit extends Object" 和 class Rabbit 之間仍存在著一個(gè)重要的差異。

我們知道,“extends” 語(yǔ)法會(huì)設(shè)置兩個(gè)原型:

  1. 在構(gòu)造函數(shù)的 ?"prototype"? 之間設(shè)置原型(為了獲取實(shí)例方法)。
  2. 在構(gòu)造函數(shù)之間會(huì)設(shè)置原型(為了獲取靜態(tài)方法)。

在 class Rabbit extends Object 的例子中,意味著:

class Rabbit extends Object {}

alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) true

所以,現(xiàn)在 Rabbit 可以通過(guò) Rabbit 訪(fǎng)問(wèn) Object 的靜態(tài)方法,像這樣:

class Rabbit extends Object {}

// 通常我們調(diào)用 Object.getOwnPropertyNames
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b

但是如果我們沒(méi)有 extends Object,那么 Rabbit.__proto__ 將不會(huì)被設(shè)置為 Object。

下面是示例:

class Rabbit {}

alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) false (!)
alert( Rabbit.__proto__ === Function.prototype ); // true,所有函數(shù)都是默認(rèn)如此

// error,Rabbit 中沒(méi)有這樣的函數(shù)
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error

所以,在這種情況下,Rabbit 沒(méi)有提供對(duì) Object 的靜態(tài)方法的訪(fǎng)問(wèn)。

順便說(shuō)一下,Function.prototype 也有一些“通用”函數(shù)方法,例如 call 和 bind 等。在上述的兩種情況下它們都是可用的,因?yàn)閷?duì)于內(nèi)建的 Object 構(gòu)造函數(shù)而言,Object.__proto__ === Function.prototype。

我們用一張圖來(lái)解釋?zhuān)?


所以,簡(jiǎn)而言之,這里有兩點(diǎn)區(qū)別:

class Rabbit class Rabbit extends Object
需要在 constructor 中調(diào)用 super()
Rabbit.__proto__ === Function.prototype Rabbit.__proto__ === Object


以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)