- 原文地址: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 中實現靜態屬性的模式。javascript
假設你有一個帶有靜態方法的簡單的符合 ES6 語法的類。html
class Base {
static foo() {
return 42;
}
}
複製代碼
你可使用 extends
建立一個子類而且可以繼續使用 foo()
函數。前端
class Sub extends Base {}
Sub.foo(); // 42
複製代碼
你可使用靜態的 getter 和 setter 在 Base
類中設置一個靜態的屬性。java
let foo = 42;
class Base {
static get foo() { return foo; }
static set foo(v) { foo = v; }
}
複製代碼
不幸的是,在繼承 Base
的時候,這個模式就行不通了。若是你設置子類 foo
的值,它將會覆蓋 Base
和全部其餘的子類的 foo
。android
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 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。