具有子类的构造函数中的Object.freeze
Object.freeze in constructor with subclasses
如果我希望我的类是不可变的,我知道我可以使用Object.freeze()
。现在,如果我希望我的对象在构造后是不可变的,我会把Object.freeze(this)
作为最后一行放入我的构造函数中。但现在,如果我想将其子类化,我就不能添加更多的参数,因为我不能在调用super
之前调用this
,而在调用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中的构造函数调用是否是super调用:
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'
魔术关键字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
代理的好处是,您不必更改实现,只需将实现封装在其他中即可
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的类A、B和扩展B的C,只需添加分别继承自A、B、C的类FrozenA、FrozenB、Frozen C,它们本身没有子类。
如果你使用纯javascript,并且没有在元编程方面做任何非常疯狂的事情(例如,为它们的类互操作对象,然后询问另一个对象是否是该类的实例),那么FrozenB在技术上不是FrozenA的子类就无关紧要了,因为该语言不是静态类型的。
我不知道这是否是您的情况,但您可以执行以下操作:
class A{
constructor(){}
}
class B extends A{
constructor(){
super();
Object.freeze(this.__proto__);
}
}
这是最简单的解决方案,它阻止了类属性的添加,注意你不能使用B.prototype;等等;如果你这样做。
- 函数未在Object.keys或Object.getOwnPropertyNames下列出,但可以调用
- delete在Object上效率低下,但在DOM Element's的数据属性,与null out相比
- 有没有一种方法可以列出Ember.Object的所有绑定
- 如何取消object.prototypes javascript的一个函数
- 为什么是文档.旧版应用程序中的DOM-object.properties为null
- 将*.js文件的内容放入Object中
- Object.prototype using 'this'
- 使用Object.create()的角度服务继承
- 如何使用object.assign()从其他对象引用基本对象属性
- 循环的数组推入在Object容器中具有不同的值
- 具有子类的构造函数中的Object.freeze
- 为什么我应该在object.freeze上使用immutablejs
- 使用object.freeze()扩展函数对象-can't添加字段
- 与 JavaScript 中的 Object.freeze 或 Object.seal 相反
- 有没有办法 Object.freeze() 一个 JavaScript Date
- Object.freeze不'不要真的僵住了
- Object.freeze() vs const
- 在ES5中Object.freeze的效果可以被逆转吗?
- Object.freeze()上的typearray和ArrayBuffers (node / chrome)没有按预期
- 是否有任何理由对函数进行Object.freeze