答案(请在下面阅读,各自的作者提供了宝贵的见解):
问题:
有关该物业的MDN文件"可写"描述符状态:
可写 当且仅当可以使用赋值运算符更改与属性关联的值时,才返回true。 默认为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中,并且在某些情况下,实例上的重载会抛出一个关于属性的readonly性质的TypeError。
请你告诉我这是预期的行为吗?如果是的话,解释是什么?
答案 0 :(得分:3)
是的,这是预期的行为。
它无声地失败。
不完全是。或者:只在草率模式下。如果你"use strict"
mode,你会得到一个
Error { message: "Invalid assignment in strict mode", … }
在test4.answer = function() { return 0; };
它可以在子类(test2)的原型上重载,但不是子类的实例(test4)
这与实例与原型无关。您没有注意到的是您使用不同的方法来创建重载属性:
Object.defineProperty
调用只会创建一个新属性,除非该对象是不可扩展的您可以为您的实例执行相同的操作:
Object.defineProperty(test4, "answer", {
value: function() { return 42; }
});
答案 1 :(得分:2)
如果它的原型将该属性定义为不可写(并且对象实例没有描述符),则无法写入实例属性,因为set操作会转到父(原型)以检查它是否可以写入,甚至虽然它会写给孩子(实例)。见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"
答案 2 :(得分: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上定义。如果它没有定义,那么它将定义它。