- 原文地址:Static Properties in JavaScript Classes with Inheritance
- 原文作者:Valeri Karpov
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:Augustwuli
- 校對者:RicardoCao-Biker, Mcskiller
自 ES6 釋出以來,JavaScript 對類和靜態函式的支援類似其他面嚮物件語言中的靜態函式。不幸的是,JavaScript 缺乏對靜態屬性的支援,而且谷歌上的推薦方案沒有考慮到繼承問題。在實現一個 Mongoose 特性的時候,我陷入了一個需要更健壯的靜態屬性概念的困難。尤其是我需要通過設定 prototype
或者 extends
來支援繼承靜態屬性。在本文,我將介紹在 ES6 中實現靜態屬性的模式。
靜態方法和繼承
假設你有一個帶有靜態方法的簡單的符合 ES6 語法的類。
class Base {
static foo() {
return 42;
}
}
複製程式碼
你可以使用 extends
建立一個子類並且能夠繼續使用 foo()
函式。
class Sub extends Base {}
Sub.foo(); // 42
複製程式碼
你可以使用靜態的 getter 和 setter 在 Base
類中設定一個靜態的屬性。
let foo = 42;
class Base {
static get foo() { return foo; }
static set foo(v) { foo = v; }
}
複製程式碼
不幸的是,在繼承 Base
的時候,這個模式就行不通了。如果你設定子類 foo
的值,它將會覆蓋 Base
和所有其他的子類的 foo
。
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
是一個陣列,每一個子類都會有一個陣列副本的引用,如下所示。
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()
函式的幫助實現它。
繼承靜態屬性
關鍵思想是 JavaScript 類只是另一個物件,所以你可以區分 本身的屬性 和繼承的屬性。
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
有明顯優勢。
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()
模式,否則靜態的物件和陣列在所有的子類中被共享。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。