[譯] 繼承 JavaScript 類中的靜態屬性

自 ES6 發佈以來,JavaScript 對類和靜態函數的支持相似其餘面嚮對象語言中的靜態函數。不幸的是,JavaScript 缺少對靜態屬性的支持,並且谷歌上的推薦方案沒有考慮到繼承問題。在實現一個 Mongoose 特性的時候,我陷入了一個須要更健壯的靜態屬性概念的困難。尤爲是我須要經過設置 prototype 或者 extends 來支持繼承靜態屬性。在本文,我將介紹在 ES6 中實現靜態屬性的模式。javascript

靜態方法和繼承

假設你有一個帶有靜態方法的簡單的符合 ES6 語法的類。html

class Base {
  static foo() {
    return 42;
  }
}
複製代碼

你可使用 extends 建立一個子類而且可以繼續使用 foo() 函數。前端

class Sub extends Base {}

Sub.foo(); // 42
複製代碼

你可使用靜態的 getter 和 setterBase 類中設置一個靜態的屬性。java

let foo = 42;

class Base {
  static get foo() { return foo; }
  static set foo(v) { foo = v; }
}
複製代碼

不幸的是,在繼承 Base 的時候,這個模式就行不通了。若是你設置子類 foo 的值,它將會覆蓋 Base 和全部其餘的子類的 fooandroid

class Sub extends Base {}

console.log(Base.foo, Sub.foo);

Sub.foo = 43;

// 打印 "43, 43"。在上面會覆蓋 「Base.foo」 和 「Sub.foo」 的值
console.log(Base.foo, Sub.foo);
複製代碼

若是屬性是一個數組或者是對象這個問題會變得更糟。由於典型的繼承,若是 foo 是一個數組,每個子類都會有一個數組副本的引用,以下所示。ios

class Base {
  static get foo() { return this._foo; }
  static set foo(v) { this._foo = v; }
}

Base.foo = [];

class Sub extends Base {}

console.log(Base.foo, Sub.foo);

Sub.foo.push('foo');

// 如今這兩個數組都包含 「foo」,由於它們都是同一個數組!
console.log(Base.foo, Sub.foo);
console.log(Base.foo === Sub.foo); // true
複製代碼

因此 JavaScript 支持靜態的 getter 和 setter,可是在數組和對象的狀況下使用它們將會是搬起石頭砸本身腳。事實證實,你能夠在 JavaScript 內置的 hasOwnProperty() 函數的幫助實現它。git

繼承靜態屬性

關鍵思想是 JavaScript 類只是另外一個對象,因此你能夠區分 自己的屬性 和繼承的屬性。es6

class Base {
  static get foo() {
    // 若是 「_foo」 被繼承了,或者不存在的時候將它當作 「undefined」
    return this.hasOwnProperty('_foo') ? this._foo : void 0;
  }
  static set foo(v) { this._foo = v; }
}

Base.foo = [];

class Sub extends Base {}

// 打印 "[] undefined"
console.log(Base.foo, Sub.foo);
console.log(Base.foo === Sub.foo); // false

Base.foo.push('foo');

// 打印 "['foo'] undefined"
console.log(Base.foo, Sub.foo);
console.log(Base.foo === Sub.foo); // false
複製代碼

這個模式在類中的實現是很簡潔,它也能夠被用於 ES6 以前的 JavaScript 標準的繼承。這一點很重要,由於 Mongoose 仍然使用 ES6 風格以前的繼承。過後看來,咱們本應該儘早使用這個方法,這個特性是咱們第一次看到使用 ES6 類和繼承比只設置函數的 prototype 有明顯優點。github

function Base() {}

Object.defineProperty(Base, 'foo', {
  get: function() { return this.hasOwnProperty('_foo') ? this._foo : void 0; },
  set: function(v) { this._foo = v; }
});

Base.foo = [];

// ES6 以前版本的繼承
function Sub1() {}
Sub1.prototype = Object.create(Base.prototype);
// Static properties were annoying pre-ES6
Object.defineProperty(Sub1, 'foo', Object.getOwnPropertyDescriptor(Base, 'foo'));

// ES6 的繼承
class Sub2 extends Base {}

// 打印 "[] undefined"
console.log(Base.foo, Sub1.foo);
// 打印 "[] undefined"
console.log(Base.foo, Sub2.foo);

Base.foo.push('foo');

// 打印 "['foo'] undefined"
console.log(Base.foo, Sub1.foo);
// 打印 "['foo'] undefined"
console.log(Base.foo, Sub2.foo);
複製代碼

繼續前進

ES6 類相對於老的 Sub.prototype = Object.create(Base.prototype) 有一個主要的優點,由於它 extends 了靜態屬性和函數的副本。使用 Object.hasOwnProperty() 作一些額外的工做,就能夠建立正確處理繼承的靜態 getter 和 setter。在 JavaScript 中使用靜態屬性要很是地當心:extends 在底層仍然使用典型的繼承。這意味着,除非你使用本篇文章提到的 hasOwnProperty() 模式,不然靜態的對象和數組在全部的子類中被共享。web

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索