【问题标题】:Does Javascript writable descriptor prevent changes on instances?Javascript 可写描述符是否会阻止对实例的更改?
【发布时间】:2016-05-12 16:28:09
【问题描述】:

答案(请阅读下面的答案,他们各自的作者提供了宝贵的见解):

  • "writable: false" 阻止分配新值,但是 Object.defineProperty 不是赋值操作,因此 忽略“可写”的值
  • 属性属性 是继承的,因此一个属性将保持不可写 子类/实例,直到一个子类(或子类的实例)将“可写”的值更改回为真

问题

关于属性“可写”描述符的 MDN 文档指出:

可写 当且仅当与属性关联的值可以使用赋值运算符更改时才为真。 默认为 false。

官方的 ECMA-262 第 6 版或多或少地声明了相同的内容。 含义很清楚,但据我了解,它仅限于原始属性(即该特定对象上的属性)

但是,请考虑以下示例 (JSFiddle):

//works as expected, overloading complete       
var Parent = function() {};
Object.defineProperty(Parent.prototype, "answer", {
    value: function() { return 42; }
});

var Child = function() {};
Child.prototype = Object.create(Parent.prototype, {
    answer: {
        value: function() { return 0; }
    }
});

var test1 = new Parent();
console.log(test1.answer()); //42
var test2 = new Child();
console.log(test2.answer()); //0

//does not work as expected
var Parent2 = function() {};
Object.defineProperty(Parent2.prototype, "answer", {
    value: function() { return 42; }
});

var Child2 = function() {};
Child2.prototype = Object.create(Parent2.prototype);

test3 = new Parent2();
console.log(test3.answer()); //42
test4 = new Child2();
test4.answer = function() { return 0; };
console.log(test4.answer()); //42

按照这个例子,我们看到,虽然该属性不可写,但它可以在子类 (test2) 的原型上重载,正如我所期望的那样。

但是,当尝试在子类 (test4) 的实例上重载方法时,它会静默失败。我原以为它可以像 test2 一样工作。尝试重载 Parent 实例上的属性时也会发生同样的情况。

同样的事情发生在 NodeJS 和 JSFiddle 中,并且在某些情况下,实例上的重载会引发关于属性只读性质的 TypeError。

您能否向我确认这是预期的行为?如果有,说明什么?

【问题讨论】:

  • 你确实意识到你有一个错字,对吧?回答者
  • 谢谢。此处显示的示例不是我的原始代码,而是为清楚起见为该问题制作的示例。纠正错字后,我现在意识到它不能正确地证明手头的问题。我相应地更新了帖子
  • 我一直在想这个问题,你给了我做研究所需的推动力。
  • @JuanMendes 在这里也一样! @Gilgalas 在您的答案中,第一个答案的表述不正确。 child.prototype 上的defineProperties 是可能的,因为child.prototype 没有answer 的描述符。该描述符在child.prototype.constructor.prototype 中定义。所以它并没有忽略它,它只是还不存在:-) child.prototype.constructor.prototype 等于 parent.prototype

标签: javascript node.js inheritance writable


【解决方案1】:

如果实例属性的原型将属性定义为不可写(并且对象实例没有描述符),则您无法写入该属性,因为设置操作会上升到父级(原型)以检查它是否可以写入,甚至虽然它会写给孩子(实例)。请参阅EcmaScript-262 第 9.1.9 节 1.c.i

4. If ownDesc is undefined, then
  a. Let parent be O.[[GetPrototypeOf]]().
  b. ReturnIfAbrupt(parent).
  c. If parent is not null, then
      i. Return parent.[[Set]](P, V, Receiver).

但是,如果您想解决这个问题,您可以设置实例本身的描述符。

var proto = Object.defineProperties({}, {
  foo: {
    value: "a",
    writable: false,  // read-only
    configurable: true  // explained later
  }
});

var instance = Object.create(proto);
Object.defineProperty(instance, "foo", {writable: true});
instance.foo // "b"

【讨论】:

  • 谢谢你,这解释了一切。我不能说我同意去见父母的原则,但是,它就是这样。我想它在我还没有遇到的其他用例中有它的用途。
  • @Gilgalas 我在另一边,我很高兴这是这种行为。我希望能够在原型中定义属性不可写。这样,您仍然可以选择更改行为。
【解决方案2】:

是的,这是预期的行为。

它默默地失败了。

不完全是。或者:仅在草率模式下。如果你"use strict" mode,你会得到一个

Error { message: "Invalid assignment in strict mode", … }

上线test4.answer = function() { return 0; };

它可以在子类的原型(test2)上重载,但不能在子类的实例(test4)上重载

这与实例与原型无关。您没有注意到的是您使用不同的方式来创建重载属性:

  • inherited and non-writable 属性的分配失败
  • Object.defineProperty 调用只会创建一个新属性,除非该对象是不可扩展的

您可以对您的实例执行相同的操作:

Object.defineProperty(test4, "answer", {
    value: function() { return 42; }
});

【讨论】:

  • 我一直在经历同样的问题,似乎为 parent 定义的属性被复制到 child,包括 可写 标志。但是,configurable 标志并没有被复制,因为我能够重新定义孩子的属性,即使它在父节点上是不可配置的。有什么线索吗?
  • @DoXicK "re-create" 不是 "re-define",即使两者都有覆盖的效果。
  • @DoXicK:不,实际上没有任何内容被复制——无论是属性还是它们的属性。访问属性时,值的继承开始发挥作用,而分配属性时,甚至属性也被继承。但是当你使用Object.defineProperty 时,它只是创建了一个新属性(具有默认属性)并完全忽略了继承。
  • 感谢所有 cmets。我以某种方式将 Object.defineProperty 与赋值操作合并
  • 感谢@Bergi 的解释,结合指向您其他答案的链接,这让我有了更好的看法。
【解决方案3】:

我采用了您的示例代码并构建了所有可能的方法来改变可能的结果:https://jsfiddle.net/s7wdmqdv/1/

var Parent = function() {};
Object.defineProperty(Parent.prototype,"type", {
    value: function() { return 'Parent'; }
});
var oParent = new Parent();
console.log('parent', oParent.type()); // Parent


var Child1 = function() {};
Child1.prototype = Object.create(Parent.prototype, {
    type: {
        value: function() { return 'Child1'; }
    }
});
var oChild1 = new Child1();
console.log('child1', oChild1.type()); // Child1


var Child2 = function() {};
Child2.prototype = Object.create(Parent.prototype);
Object.defineProperty(Child2.prototype, 'type', {
    value: function() { return 'Child2'; }
});
var oChild2 = new Child2();
console.log('child2', oChild2.type()); // Child2


var Child3 = function() {};
Child3.prototype = Object.create(Parent.prototype);
var oChild3 = new Child3();
oChild3.type = function() { return 'Child3'; };
console.log('child3', oChild3.type()); // Parent


var Child4 = function() {};
Child4.prototype = Object.create(Parent.prototype);
Child4.prototype.type = function() { return 'Child4'; };
var oChild4 = new Child4();
console.log('child4', oChild4.type()); // Parent


Object.defineProperty(Parent.prototype,"type", {
    value: function() { return 'Parent2'; }
});
var oParent2 = new Parent();
console.log('parent2',oParent2.type());

当您使用 Object.create(...) 克隆原型时,原始描述符仍然附加在原型链的较高位置。

当给child.answer = 10 赋值时,它将使用Child.prototype.answer.writable。如果不存在,它将尝试Child.prototype.constructor.prototype.answer.writable

但是,如果您尝试Object.defineProperty(Child.prototype, ...),它将不会检查原型链。它将测试它是否在 Child.prototype 上定义。如果没有定义,那么它会定义它。

【讨论】:

  • 有趣的发现。它们以后可能会派上用场。谢谢。
猜你喜欢
  • 1970-01-01
  • 2020-03-26
  • 2013-11-22
  • 1970-01-01
  • 2011-06-05
  • 2022-08-04
  • 2015-12-04
  • 1970-01-01
  • 2018-05-01
相关资源
最近更新 更多