[英]Object.freeze in constructor with subclasses
如果我希望我的類是不可變的,我知道我可以使用Object.freeze()
。 現在,如果我希望我的對象在構造后不可變,我會將Object.freeze(this)
作為最后一行放入我的構造函數中。 但現在,如果我要繼承的是,我不能添加更多的參數,因為我不能把this
調用之前super
和調用后super
是一成不變的:
class A {
constructor(x) {
this.x = x
Object.freeze(this)
}
}
class B extends A {
constructor(x, y) {
this.y = y // nope. No "this" before "super"
super(x)
this.y = y // nope. "this" is already frozen
Object.freeze(this)
}
}
我正在考慮弄清楚是否通過 super 調用構造函數以跳過凍結並將凍結留給子類,但這會讓子類凍結或不凍結。 我將如何最好地解決這個問題? 有工廠嗎?
首先,你想要混合類似乎很奇怪,其基本原則基本上是有狀態的可變性,具有不變性。 我認為,具有組合的可組合工廠函數可能更慣用(可能有比下面更好的方法):
function compose(funcs...) {
return function(...args) {
const obj = funcs.reduce((obj, func) = >{
return func(obj, args);
}, {});
}
return Object.freeze(obj);
}
function a(obj, { x }) {
obj.x = x;
return obj;
}
function b(obj, { y }) {
obj.y = y;
return obj;
}
const ab = compose(a, b);
ab({ x: 1, y: 2 });
或者,如果您想堅持使用類語法,可以使用new.target檢查A中的構造函數調用是否是超級調用:
class A {
constructor(x) {
this.x = x;
// Not a super call, thus freeze the object
if (new.target === A) {
Object.freeze(this);
}
}
}
class B extends A {
constructor(x, y) {
super(x)
this.y = y
if (new.target === B) {
Object.freeze(this)
}
}
}
您可以使用代理的構造陷阱添加此功能,基本上不是調用您調用中間函數的每個類的構造函數,該函數應該添加您需要的功能並創建實例
讓我解釋一下如何在ES5中完成,然后跳轉到ES6
function A() {
this.name = 'a'
}
A.prototype.getName = function () {
return this.name
}
var instance = new A()
instance.getName() // 'a'
magic關鍵字new
執行以下操作
[[Prototype]]
指向構造函數的原型,即Object.getPrototype(emptyObject) === A.prototype
或emptyObject.__proto__ === A.prototype
A
,將空對象設置為其上下文,即在函數內部, this
是空對象 return this
) 我們可以通過以下方式模仿這種行為
function freezeInstance(T) {
function proxy () {
// 1.
var instance = Object.create(T.prototype)
// 2.
T.apply(instance, arguments)
// this check is added so that constructors up
// in the constructor chain don't freeze the object
if (this instanceof proxy) {
Object.freeze(instance)
}
// 3.
return instance
}
// mimic prototype
proxy.prototype = T.prototype
return proxy
}
如果我們用函數調用這個函數並將內部函數賦值給參數T
我們已經有效地創建了與new相同的行為,我們也可以在代理中添加我們的自定義函數,即
A = freezeInstance(A)
var instance = new A()
instance.getName() // 'a'
a.name = 'b'
instance.getName() // 'a'
如果你有一個子類,凍結行為不會影響超類,因為我們只對初始調用執行特殊功能
function freezeInstance(T) { function proxy() { var instance = Object.create(T.prototype) T.apply(instance, arguments) if (this instanceof proxy) { Object.freeze(instance) } return instance } // mimic prototype proxy.prototype = T.prototype return proxy } function A() { this.name = 'a' } A.prototype.getName = function() { return this.name } A = freezeInstance(A) function B() { A.call(this) this.name = 'b' } B.prototype = Object.create(A.prototype) B = freezeInstance(B) var instance = new B() document.write(instance.getName()) // 'b' instance.name = 'a' document.write(instance.getName()) // 'b'
在ES6中你也可以這樣做
function freezeInstance(T) {
return new Proxy(T, {
construct: function (target, argumentLists, newTarget) {
var instance = new target(...argumentLists)
Object.freeze(instance)
return instance
}
})
}
class A {
constructor() {
this.name = 'a'
}
getName() {
return this.name
}
}
A = freezeInstance(A)
class B extends A {
constructor() {
super()
this.name = 'b'
}
}
B = freezeInstance(B)
var a = new A()
console.log(a.getName()) // a
a.name = 'b'
console.log(a.getName()) // a
var b = new B()
console.log(b.getName()) // b
b.name = 'a'
console.log(b.getName()) // b
代理的好處在於您不必更改實現,只需將實現包裝在其他內容中即可
編輯:正如@nils所說, 由於ES5的限制,無法對代理進行轉換/填充 ,請參閱兼容性表以獲取有關支持此技術的平台的更多信息
我有一個建議的答案,可能具有某種簡單性的優點。
它是這樣的:不要在構造函數中凍結,只是在實例化中凍結。 例如:
class A {
constructor(…) { … }
}
…
class B extends A {
constructor(…) { super(…); … }
}
…
let my_b = Object.freeze(new B(…));
這種方法具有額外的優點,它強烈地宣稱對象(例如my_b
)被凍結(並且可能是不可變的),並且提供靈活性,不僅從A
派生類,而且在有利時偶爾對對象執行頑皮的突變。 。
實際上,提供一個顯式使類的對象不可變的成員函數可能是明智的。 例如:
class A {
constructor(…) { this.m1 = new X(…); … }
deepFreeze() { Object.freeze(this); this.m1.deepFreeze(); return this; }
}
…
class B extends A {
constructor(…) { super(…); this.m2 = new Y(…); … }
deepFreeze() { super.deepFreeze(); this.m2.deepFreeze(); return this; }
}
…
let my_b = (new B(…)).deepFreeze();
這里的想法是, A
類知道如何深度凍結其成員對象(例如m1
)以及凍結自身,以便正確地強制其實例的不變性。 子類可以以可組合的方式覆蓋deepFreeze
。
為方便起見,您可以添加一個靜態成員函數來為您構造實例。 例如:
class A {
constructor(…) { this.m1 = new X(…); … }
deepFreeze() { Object.freeze(this); this.m1.deepFreeze(); return this; }
static deepFrozen(…) { return new this(…).deepFreeze(); }
}
…
class B extends A {
constructor(…) { super(…); this.m2 = new Y(…); … }
deepFreeze() { super.deepFreeze(); this.m2.deepFreeze(); return this; }
}
…
let my_b = B.deepFrozen(…);
另一個非常簡單的選擇是將層次結構中每個類的凍結版本分開。
因此,如果您有類 A、B 擴展 A 和 C 擴展 B 只需添加類 FrozenA、FrozenB、FrozenC,它們分別繼承自 A、B 和 C,並且它們本身沒有子類。
如果您使用純 javascript 並且沒有對元編程做任何真正瘋狂的事情(例如,為它們的類互加對象,然后詢問另一個對象是否是該類的實例),那么 FrozenB 在技術上不是一個子類並不重要FrozenA,因為該語言不是靜態類型的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.