如果我希望我的课程是不可变的,我知道我可以使用Object.freeze()
。现在如果我希望我的对象在构造之后是不可变的,我会将Object.freeze(this)
作为最后一行放入我的构造函数中。但是现在,如果我想要将其子类化,我无法添加更多参数,因为在调用this
之前我无法调用super
并且在调用super
之后调用{39}}&#39} ; s不可变:
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调用的,以便跳过冻结并将冻结留给子类但是这会让它继续冻结到子类。我怎么能最好地接近这个?有工厂吗?
答案 0 :(得分:5)
首先,你想要混合类似乎很奇怪,它们的基本原则基本上是有状态的可变性,具有不变性。我认为,具有组合的可组合工厂函数可能更具惯用性(可能有比下面更好的方法):
<new columns as key>
或者,如果您想坚持使用类语法,可以使用 new.target 来检查 A 中的构造函数调用是否为 super < / em>是否打电话:
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 });
答案 1 :(得分:0)
您可以使用proxy's construct trap添加此功能,基本上不是调用您调用中间函数的每个类的构造函数,该函数应该添加您需要的功能并创建实例
让我解释一下如何在ES5中完成,然后跳转到ES6
function A() {
this.name = 'a'
}
A.prototype.getName = function () {
return this.name
}
var instance = new A()
instance.getName() // 'a'
魔术关键字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'
&#13;
在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注意到Proxies cannot be transpiled/polyfilled due to limitations of ES5,请参阅compatibility table以获取有关支持此技术的平台的更多信息
答案 2 :(得分:0)
我有一个建议的答案可能具有某种简单性的优点。
它是这样的:不要在构造函数中冻结,只是在实例化中冻结。 E.g:
class A {
constructor(…) { … }
}
…
class B extends A {
constructor(…) { super(…); … }
}
…
let my_b = Object.freeze(new B(…));
这种方法具有额外的优点,它强烈宣传对象(例如my_b
)被冻结(并且可能是不可变的),并且不仅提供了从A
派生类的灵活性,而且还提供了灵活性。偶尔在物体上进行顽皮的突变。
事实上,提供一个显式使类的对象不可变的成员函数可能是明智的。 E.g:
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
。
为方便起见,您可以添加一个为您构造实例的静态成员函数。 E.g:
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(…);