Javascript可写描述符是否阻止实例的更改?

答案 (请在下面阅读,他们各自的作者提供了有价值的见解):

  • “writable:false”可以防止分配一个新的值, Object.defineProperty不是一个分配操作,因此忽略“可写”
  • 属性属性是inheritance的,因此一个属性在每个子类/实例上将保持不可写,直到一个子类(或子类的实例)将“writable”的值更改为true

问题

有关属性“可写”描述符的MDN文档声明:

当且仅当与属性相关联的值可能被赋值运算符改变时才为真。 默认为false。

官方的ECMA-262第6版或多或less陈述相同。 意思很清楚,但就我的理解而言,它仅限于原始财产(即该特定对象的财产)

不过,请考虑下面的例子( 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 

在这个例子之后,我们看到,尽pipe属性是不可写的,但是可以在子类的原型(test2)上重载,正如我所期望的那样。

但是,当试图在一个子类的实例(test4)上重载方法时,它会自动失败。 我会期望它的工作就像test2一样。 尝试重载父实例的属性时会发生同样的情况。

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

你能否向我确认这是预期的行为? 如果是这样,那么解释是什么?

是的,这是预期的行为。

它默默地失败。

不完全是。 或者:只有在马虎的模式下。 如果你"use strict"模式 ,你会得到一个

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

就行了test4.answer = function() { return 0; }; test4.answer = function() { return 0; };

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

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

  • 对inheritance和不可写入属性的分配失败
  • Object.defineProperty调用只是创build一个新的属性,除非对象是不可扩展的

你可以为你的实例做同样的事情:

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

如果它的原型将该属性定义为不可写(而且对象实例没有描述符),则不能写入实例属性,因为set操作继续到父(原型)以检查它是否可以写入,即使它写给孩子(实例)。 参见EcmaScript-262第9.1.9节1.ci

 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" 

我已经采取了你的示例代码,并构build了所有可能的方法来改变可能的结果: 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, ...)它不会检查原型链。 它将testing它是否在Child.prototype上定义。 如果没有定义,则会定义它。