2022 年 5 月 3 日

靜態屬性和方法

我們也可以將方法指定給整個類別。此類方法稱為靜態

在類別宣告中,它們會加上 static 關鍵字,如下所示

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

User.staticMethod(); // true

這實際上與直接將其指定為屬性相同

class User { }

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

User.staticMethod(); // true

User.staticMethod() 呼叫中,this 的值是類別建構函式 User 本身(「點之前的物件」規則)。

通常,靜態方法用於實作屬於整個類別的功能,但不屬於任何特定物件。

例如,我們有 Article 物件,需要一個函式來比較它們。

一個自然的解決方案是新增 Article.compare 靜態方法

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

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

// usage
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 方法位於文章「上方」,作為比較文章的方法。它不是文章的方法,而是整個類別的方法。

另一個範例是所謂的「工廠」方法。

假設我們需要多種方式來建立文章

  1. 透過給定的參數建立(標題日期等)。
  2. 建立一個空的,日期為今天的文章。
  3. …或其他方式。

第一種方式可以透過建構函式來實作。而第二種方式,我們可以建立類別的靜態方法。

例如此處的 Article.createTodays()

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

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

let article = Article.createTodays();

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

現在,每當我們需要建立今天的摘要時,我們可以呼叫 Article.createTodays()。再次強調,這不是文章的方法,而是整個類別的方法。

靜態方法也用於與資料庫相關的類別,用於搜尋/儲存/移除資料庫中的項目,如下所示

// assuming Article is a special class for managing articles
// static method to remove the article by id:
Article.remove({id: 12345});
靜態方法不適用於個別物件

靜態方法可以呼叫類別,但不能呼叫個別物件。

例如,以下程式碼將無法執行

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

靜態屬性

最近新增
這是最近新增的語言功能。範例在最近的 Chrome 中執行。

靜態屬性也是可行的,它們看起來像是常規類別屬性,但前面加上 static

class Article {
  static publisher = "Ilya Kantor";
}

alert( Article.publisher ); // Ilya Kantor

這與直接指定給 Article 相同

Article.publisher = "Ilya Kantor";

靜態屬性和方法的繼承

靜態屬性和方法會被繼承。

例如,以下程式碼中的 Animal.compareAnimal.planet 會被繼承,並可作為 Rabbit.compareRabbit.planet 存取

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;
  }

}

// Inherit from 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

現在,當我們呼叫 Rabbit.compare 時,會呼叫繼承的 Animal.compare

它是如何運作的?同樣地,使用原型。您可能已經猜到了,extends 會給予 RabbitAnimal[[Prototype]] 參考。

因此,Rabbit extends Animal 會建立兩個 [[Prototype]] 參考

  1. Rabbit 函式會從 Animal 函式原型繼承。
  2. Rabbit.prototype 會從 Animal.prototype 原型繼承。

因此,繼承適用於常規方法和靜態方法。

在此,讓我們透過程式碼來檢查

class Animal {}
class Rabbit extends Animal {}

// for statics
alert(Rabbit.__proto__ === Animal); // true

// for regular methods
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true

摘要

靜態方法用於屬於「整體」類別的功能。它與具體類別實例無關。

例如,用於比較的 Article.compare(article1, article2) 方法或工廠方法 Article.createTodays()

它們在類別宣告中標記為 static 字詞。

當我們想要儲存類別層級資料時,會使用靜態屬性,而且不繫結到執行個體。

語法為

class MyClass {
  static property = ...;

  static method() {
    ...
  }
}

技術上來說,靜態宣告與指派給類別本身相同

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

靜態屬性和方法會被繼承。

對於 class B extends A,類別 B 本身的原型指向 AB.[[Prototype]] = A。因此,如果在 B 中找不到欄位,搜尋會繼續在 A 中進行。

工作

重要性:3

我們知道,所有物件通常會繼承自 Object.prototype,並取得「一般」物件方法的存取權,例如 hasOwnProperty 等。

例如

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

let rabbit = new Rabbit("Rab");

// hasOwnProperty method is from Object.prototype
alert( rabbit.hasOwnProperty('name') ); // true

但如果我們明確地寫成 "class Rabbit extends Object",那麼結果會與單純的 "class Rabbit" 不同嗎?

差異在哪裡?

以下是此類程式碼的範例(它無法運作 - 為什麼?修正它)

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

let rabbit = new Rabbit("Rab");

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

首先,讓我們看看後面的程式碼為什麼無法運作。

如果我們嘗試執行它,原因就會很明顯。繼承類別建構函式必須呼叫 super()。否則「this」將不會「被定義」。

因此,修正方法如下

class Rabbit extends Object {
  constructor(name) {
    super(); // need to call the parent constructor when inheriting
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

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

但這還不是全部。

即使在修正之後,"class Rabbit extends Object"class Rabbit 之間仍然存在一個重要的差異。

我們知道,「extends」語法會設定兩個原型

  1. 在建構函式(用於方法)的「原型」之間。
  2. 在建構函式本身(用於靜態方法)之間。

class Rabbit extends Object 的情況下,這表示

class Rabbit extends Object {}

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

因此,Rabbit 現在可透過 Rabbit 存取 Object 的靜態方法,如下所示

class Rabbit extends Object {}

// normally we call Object.getOwnPropertyNames
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b

但如果我們沒有 extends Object,則 Rabbit.__proto__ 就不會設定為 Object

以下是示範

class Rabbit {}

alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) false (!)
alert( Rabbit.__proto__ === Function.prototype ); // as any function by default

// error, no such function in Rabbit
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error

因此,在這種情況下,Rabbit 無法存取 Object 的靜態方法。

順帶一提,Function.prototype 也有「一般」函式方法,例如 callbind 等。它們最終在兩種情況下都可用,因為對於內建的 Object 建構函式,Object.__proto__ === Function.prototype

以下是圖片

因此,簡而言之,有兩個差異

class Rabbit class Rabbit extends Object
需要在建構函式中呼叫 super()
Rabbit.__proto__ === Function.prototype Rabbit.__proto__ === Object
教學課程地圖

留言

留言前請先閱讀…
  • 如果你有建議要如何改進,請 提交 GitHub 問題 或提出拉取請求,而不是留言。
  • 如果你無法理解文章中的某些內容,請詳細說明。
  • 若要插入幾行程式碼,請使用 <code> 標籤,若要插入多行程式碼,請將它們包覆在 <pre> 標籤中,若要插入超過 10 行程式碼,請使用沙盒 (plnkrjsbincodepen…)