簡體   English   中英

帶有子類的構造函數中的 Object.freeze

[英]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執行以下操作

  1. 創建一個空對象,其[[Prototype]]指向構造函數的原型,即Object.getPrototype(emptyObject) === A.prototypeemptyObject.__proto__ === A.prototype
  2. 調用函數A ,將空對象設置為其上下文,即在函數內部, this是空對象
  3. 為我們返回對象(我們不必寫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

代理的好處在於您不必更改實現,只需將實現包裝在其他內容中即可

ES6演示

編輯:正如@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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM