JavaScript inheritance with Object.create()?
or
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
suggests Object.create() is designed in ES5 to proivde a simple manner to inherit JavaScript Object.
This works as intended as below:
const A = function() {};
A.prototype.x = 10;
A.prototype.say = function() {
console.log(this.x);
};
const a = new A();
a.say(); //10
const b = Object.create(a);
b.say(); //10
However, now I want to inherit an Object of primitives such as
Object(3)
.
For some reason, people (in most of the cases, who feels hard to resolve the question with a certain question) tends to ask "Why do you need to this?" which really does not matter to the specification, so, probably it's better to specify.
I try to create a way to provide a type to any JavaScript Object on demand basis.
Fortunately, every primitive values like 3
can be lift to an object by Object(3)
.
Now, I tried how Object.create
works in this scenario, but it does not work.
const a = Object(3);
console.log(
a
);//[Number: 3]
console.log(
a.toString()
);// 3
const b = Object.create(a);
console.log(
b
);//{}
console.log(
Object.getPrototypeOf(b)
);
console.log(
b.toString()
);
//TypeError: Number.prototype.toString requires that
// 'this' be a Number
What do I miss? Is there any workaround? or is it impossible for some reason?
Thanks.
EDIT: I really do not want to expand a specific topic or specification issue here, but unfortunately, I can't avoid people asking me "Why do you need this?" thing that is completely out of topic I believe.
Those objects known as "boxed primitives" in JavaScript definitely seem weird. Let's see what you've discovered, and how you can use derivation via prototypes to do what you want to do.
First, remember what y = Object.create(x)
does: It makes a new object y whose prototype is x.
x = {name: "Rex"};
x.constructor; // [Function: Object]
typeof x; // "object"
y = Object.create(x);
Object.getPrototypeOf(y) === x; // true
x.isPrototypeOf(y); // true
y.name; // "Rex"
Nice: x
refers to an object, and y
is a new object whose prototype is x
. The object referred to by x has an enumerable, configurable, writable, value property called name
, which is inherited by y. In x, name
is an own property; in y
, it is an inherited property. But in both cases, the property is pretty "normal."
Now let's use a Number
object:
x = Object(3); // [Number: 3]
x.constructor; // [Function: Number]
typeof x; // "object"
x.valueOf(); // 3
x + 0 // 3
y = Object.create(x);
Object.getPrototypeOf(y) === x; // true
x.isPrototypeOf(y); // true
// So far so good, but now
y.valueOf()
// TypeError: Number.prototype.valueOf requires that
// 'this' be a Number
y + 0
// TypeError: Number.prototype.valueOf requires that
// 'this' be a Number
Woah what just happened? Is this saying that y
is not a Number? Let's check that:
y.constructor // [Function: Number]
Well it sure looks like a number. Since the prototype of y
is x
, and the prototype of y
is Number.prototype
, y
certainly has access to all the functions in Number.prototype
. But it seems that no matter which of these we call, e.g.:
y.toFixed(2)
y.toLocaleString()
and so on, we get that error! What is happening here is that all of these functions in Number.prototype
are inspecting an internal property of the object, where they expect to see a primitive. This internal slot of the number object is NOT inherited, so when you did y = Object.create(x)
that slot of x that contained 3 was not inherited and so in y
, that slot does not contain a primitive number! All of the methods in Number.prototype
expect that internal slot (it is called [[NumberData]]
... see the official ECMAScript Specification Section on Number Objects to have a primitive value.
Now scroll down a little bit to Section 20.1.3 and you can see how the number operations all try to extract, via the abstract operation thisNumberValue
the value in the [[NumberData]]
slot and will throw a TypeError
if it doesn't check out. This is what you are seeing!
So what does this mean for you?
If you want to use prototypal inheritance to create a new numeric object whose prototype is an existing numeric object, and do that in a way that the new object's numeric value is the same as the original object, you cannot do this directly in JavaScript. This is not how the Number
object works! You can, however, create your own number type where the primitive value is stored in an inheritable property.
Another thing you can try: create your own function for each of the primitives. For example:
function createNewNumber(original) {
// n is expected to be a Number object
const derived = new Number(original.valueOf());
Object.setPrototypeOf(derived, original);
return derived;
}
Now
x = Object(5) // [Number: 5]
y = createNewNumber(x) // [Number: 5]
x.isPrototypeOf(y) // true
This probably does what you want, but remember you can not directly JUST use Object.create
! Using Object.create on a number simply does not inherit the [[NumberData]]
property as we have seen with our own eyes. You need to implement your own derivation function and set the prototype yourself. It is a hack, but I hope it helps!
ADDENDUM
As to why the [[NumberData]]
slot is not inherited, here is a quote from the ES9 Spec:
Internal slots correspond to internal state that is associated with objects and used by various ECMAScript specification algorithms. Internal slots are not object properties and they are not inherited. Depending upon the specific internal slot specification, such state may consist of values of any ECMAScript language type or of specific ECMAScript specification type values. Unless explicitly specified otherwise, internal slots are allocated as part of the process of creating an object and may not be dynamically added to an object. Unless specified otherwise, the initial value of an internal slot is the value undefined. Various algorithms within this specification create objects that have internal slots. However, the ECMAScript language provides no direct way to associate internal slots with an object.
While this language makes clear there is no way to create or set a slot on an object, it also appears we can't even check for one. So grabbing a numeric value from an object will need to use the programmer-accessible valueOf
property from Object.prototype
. Unless one does some crazy thing like Object.create(null)
. :)