4

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

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

问题

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

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

官方的 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。

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

4

3 回答 3

3

是的,这是预期的行为。

它默默地失败了。

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

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

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

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

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

  • 对继承且不可写的属性的分配失败
  • 调用Object.defineProperty只是创建一个新属性,除非该对象是不可扩展的

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

Object.defineProperty(test4, "answer", {
    value: function() { return 42; }
});
于 2016-02-03T15:01:11.580 回答
3

如果实例属性的原型将该属性定义为不可写(并且对象实例没有描述符),则您不能写入该属性,因为设置操作会上升到父级(原型)以检查它是否可以写入,即使它会写给孩子(实例)。请参阅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"
于 2016-02-03T14:56:50.987 回答
0

我已经采用了您的示例代码并构建了所有可能的方法来改变可能的结果: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 上定义。如果它没有定义,那么它将定义它。

于 2016-02-03T15:01:14.347 回答