Answers (please read them below, their respective authors provided valuable insights):
Yes, this is expected behaviour.
it fails silently.
Not exactly. Or: Only in sloppy mode. If you "use strict" mode, you'll get an
Error { message: "Invalid assignment in strict mode", … }
on the line test4.answer = function() { return 0; };
it can be overloaded on the prototype of a subclass (test2), but not an instance of a subclass (test4)
This has nothing to do with instances vs. prototypes. What you didn't notice is that you're using different ways to create the overloading property:
Object.defineProperty
call just creates a new property, unless the object is non-extensibleYou can do the same for your instance:
Object.defineProperty(test4, "answer", {
value: function() { return 42; }
});
I've taken your example code and structured all the possible ways to change the possible outcome: 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());
When you use Object.create(...) to clone the prototype, the original descriptors are still attached higher up the prototype chain.
When assigning something to child.answer = 10
it will use Child.prototype.answer.writable
. If that doesn't exist it will try Child.prototype.constructor.prototype.answer.writable
if it does.
However, if you try Object.defineProperty(Child.prototype, ...)
it won't check the prototype chain. It will test if it is defined on Child.prototype. if it's not defined, it will define it then.
You cannot write to an instance property if its prototype defines that property as unwritable (and the object instance doesn't have a descriptor) because the set operation goes up to the parent (prototype) to check if it can write, even though it would write to the child (instance). See EcmaScript-262 Section 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).
However, if you are trying to get around that, you can set the descriptor of the instance itself.
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"