Using `super()` when extending `Object` - javascript

I'm creating a class that extends Object in JavaScript and expect super() to initialise the keys/values when constructing a new instance of this class.
class ExtObject extends Object {
constructor(...args) {
super(...args);
}
}
const obj = new Object({foo:'bar'});
console.log(obj); // { foo: 'bar' }
const ext = new ExtObject({foo:'bar'});
console.log(ext); // ExtObject {}
console.log(ext.foo); // undefined
Why isn't foo defined as 'bar' on ext in this example?
EDIT
Explanation: Using `super()` when extending `Object`
Solution: Using `super()` when extending `Object`

Nobody has actually explained why it doesn't work. If we look at the latest spec, the Object function is defined as follows:
19.1.1.1 Object ( [ value ] )
When Object function is called with optional argument value, the following steps are taken:
If NewTarget is neither undefined nor the active function, then
Return ? OrdinaryCreateFromConstructor(NewTarget, "%ObjectPrototype%").
If value is null, undefined or not supplied, return ObjectCreate(%ObjectPrototype%).
Return ! ToObject(value).
The first step is the important one here: NewTarget refers to the function that new was called upon. So if you do new Object, it will be Object. If you call new ExtObject it will ExtObject.
Because ExtObject is not Object ("nor the active function"), the condition matches and OrdinaryCreateFromConstructor is evaluated and its result returned. As you can see, nothing is done with the value passed to the function.
value is only used if neither 1. nor 2. are fulfilled. And if value is an object, it is simply returned as is, no new object is created. So, new Object(objectValue) is actually the same as Object(objectValue):
var foo = {bar: 42};
console.log(new Object(foo) === foo);
console.log(Object(foo) === foo);
In other words: Object does not copy the properties of the passed in object, it simply returns the object as is. So extending Object wouldn't copy the properties either.

You are missing the Object.assign
class ExtObject extends Object {
constructor(...args) {
super(...args);
Object.assign(this, ...args);
}
}
const obj = new Object({foo:'bar'});
console.log(obj); // { foo: 'bar' }
const ext = new ExtObject({foo:'bar'});
console.log(ext); // { foo: 'bar' }
console.log(ext.foo); // bar

This answer works only when using the Babel transpiler.
Because Object's constructor returns value. See spec
15.2.2.1 new Object ( [ value ] )
When the Object constructor is called with no arguments or with one argument value, the following steps are taken:
...
8. Returns obj.
class ExtObject extends Object {
constructor(...args) {
return super(...args);
}
}
const obj = new Object({foo:'bar'});
console.log(obj); // { foo: 'bar' }
const ext = new ExtObject({foo:'bar'});
console.log(ext); // { foo: 'bar' }
console.log(ext.foo); // bar

Related

What different between assigning property on Object and Object.prototype?

What different between assigning property on Object and Object.prototype?
for example
Object.test =function(){};
and
Object.prototype.test =function(){}
The first gives Object a static method that can be invoked directly from the class, without an instance. For example:
Object.test =function(){
console.log('Object test running');
};
Object.test();
Assigning a function to the prototype, on the other hand, allows for instances to run the method:
Object.prototype.test = function() {
console.log('test running on object ', this);
};
// don't use the object constructor, this is just an example:
const obj = new Object();
obj.test();
It might make a bit more sense if you didn't use the built-in Object, which everything inherits from:
function Foo() {}
Foo.checkIfFoo = function(arg) {
return arg instanceof Foo;
};
const f = new Foo();
console.log(Foo.checkIfFoo(f));
Here, foo.checkIfFoo is a helper function on Foo that checks if a passed object is an instance of Foo or not - no instance is required to run checkIfFoo. Functions on the prototype, on the other hand, require an instance to run:
function Foo() {
this.info = 'A Foo instance';
}
Foo.prototype.checkInfo = function() {
console.log(this.info);
};
const f = new Foo();
f.checkInfo();
Note that in ES6+, you can put a function directly on the class with the static keyword:
// roughly equivalent to the snippet with checkIfFoo above
class Foo {
static checkIfFoo(arg) {
return arg instanceof Foo;
}
}
const f = new Foo();
console.log(Foo.checkIfFoo(f));
Whereas a standard method lacks the static keyword:
// roughly equivalent to the snippet with checkInfo above
class Foo {
constructor() {
this.info = 'A Foo instance';
}
checkInfo() {
console.log(this.info);
}
}
const f = new Foo();
f.checkInfo();

JS composition/inheritance prototype

I wish to be able to extend any object given in an .extend(obj) function. So far I have it working except for when an object literal is being passed in.
example:
class myClass {
static extend(obj) {
Object.assign(Object.getPrototypeOf(obj), myClass.prototype);
myClass.call(obj);
}
sayHello() {
return 'hello!'
}
}
This works fine when the extend function is called from within a constructor like so:
function foo() {
myClass.extend(this);
}
const bar = new foo();
bar.sayHello();
However when I pass in an object literal which is already created the methods from myClass.prototype are not available.
const foo = {};
myClass.extend(foo);
foo.sayHello(); // this is not available.
Is there a way to check the last case and assign the prototype to the object itself instead of it's prototype so that the last scenario will also work?
static extend() {
if (/* obj is an object literal */) {
Object.assign(obj, myClass.prototype);
} else {
// first example
}
This works fine when the extend function is called from within a constructor like so:
It shouldn't work fine, and when I ran it I got the error Class constructor myClass cannot be invoked without 'new'. That error is because of the statement myClass.call(obj);. Only if I change the class to an ES5 constructor function does it work.
However when I pass in an object literal which is already created the methods from myClass.prototype are not available.
They were for me.
function myClass() {}
myClass.extend = function(obj) {
Object.assign(Object.getPrototypeOf(obj), myClass.prototype);
myClass.call(obj);
}
myClass.prototype.sayHello = function() {
return 'hello!'
}
const foo = {};
myClass.extend(foo);
foo.sayHello(); // "hello!"

ECMAScript - how to make an empty 'null' Object an instance of something

I create an empty Object in ECMAScript without a prototype and define some properties like this:
var obj = Object.create(null)
obj.name = 'John'
obj.age = 27
Here, obj is not an instance of Object.
obj.name // John
obj.age // 27
obj instanceof Object // false
obj.prototype // undefined
obj.constructor // undefined
Trying to make obj an instance of something using two methods to extend this empty Object.
Using Object.__proto__ does not work.
function EmptyObject() {
this.__proto__ = Object.create(null)
this.foo = function() { // could use defineProperty
return this.bar // obviously does not exist here
}
return this
}
var obj = new EmptyObject()
obj instanceof EmptyObject // false!
Extending EmptyObject using ES6 class extends does not work either.
class SpecialObject extends EmptyObject { ... }
let obj = new SpecialObject()
obj instanceof SpecialObject // also false
I tried faking the instance by giving obj a prototype and constructor name property like an ordinary Object but this does not change the return value of instanceof.
Is it somehow possible to make this type of Object an instance of something? Maybe by wrapping it in a function differently or using ES6 class extends? I do not mind iterating over the Object keys or manually clearing them in the function if you think that is the best method of implementation.
The purpose is to create an Object that is not instance of Object (no prototype, etc) but can be instance of something. Is this possible at all? Maybe in a sneaky hacky way.
var obj = new EmptyObject()
obj instanceof Object // false
obj instanceof EmptyObject // true
Thanks!
Assigning this.__proto__ = Object.create(null) inside the constructor will completely disable you from extending this class. Don't do that. I suspect what you are actually look for is
function EmptyObject() {…}
EmptyObject.prototype = Object.create(null);
or in ES6
class EmptyObject extends null {
…
}
which you can instantiate, on which instanceof will work, and which you can extend using class syntax. Using new EmptyObject will create an object that inherits from the EmptyObject.prototype object, which in turn does inherit from nothing (i.e. null) instead of Object.prototype as usual.

Overriden toString() not called when object inialized with object literal

I have the following problem: I have a class Foo which overrides the default implementation of toString().
class Foo {
bar: string;
toString(): string {
return this.bar;
}
}
When I initialize a fresh variable with an (typed) object literal and call toString() on this new object the standard Javascript implementation is called. So that "[object Object]" is printed out into the console.
let foo : Foo = {
bar: "baz"
}
console.log(foo.toString()) // does not invoke Foo.toString()
However, when I create the object with the new operator everything works like a charm:
let foo: Foo = new Foo();
foo.bar = "baz"
console.log(foo.toString()); // prints out "baz"
What am I doing wrong? I'd like to avoid the second alternative because in reality the Foo object has many (nested) attributes. So that it would lead to very verbose object initializations.
Your first example:
let foo : Foo = {
bar: "baz"
}
You're creating an object literal and claiming it has the same interface as Foo. It's not an actual instance of Foo so it uses the default implementation of toString().
In your second example:
let foo: Foo = new Foo();
You're creating an actual instance of Foo which has your override of toString() implemented.

What does Function.prototype.toMethod() do?

I noticed that the Function.prototype has a toMethod() method in experimental JavaScript, but what does that actually do? And how do I use it?
Update: the toMethod method was experimental only and did not make it into the standard. The home object is essentially static now, the only way to manipulate super is to change the [[prototype]]:
var base = {…}; // as below
var obj = Object.setPrototypeOf({
foo() { // needs to use method definition syntax
super.foo();
}
}, base);
obj.foo();
It's very similar to the bind method of function objects. However, instead of creating a new function with a bound this value, it creates a new function with a bound [[HomeObject]], which is the reference that is used for super calls:
[[HomeObject]] (Object): If the function uses super, this is the object whose [[GetPrototypeOf]] provides the object where super property lookups begin.
Consider this example (not using any class syntax):
var base = {
foo: function() {
console.log("base foo called on", this);
}
};
base.foo(); // base foo called on base
var obj = Object.create(base);
obj.foo(); // base foo called on obj
obj.foo = function() {
super.foo();
};
obj.foo(); // ReferenceError: this method has no home
obj.bar = obj.foo.toMethod(obj);
obj.bar(); // base foo called on obj
obj.baz = function() {
super();
};
obj.baz(); // ReferenceError: this constructor has no parent class
Reflect.setPrototypeOf(obj.baz, base.foo);
obj.baz(); // base foo called on obj
My understanding is that .toMethod is like cloning a function. Consider the example in the source I posted,
class P { }
class C extends P {
foo() {
console.log("f");
super();
}
}
P.prototype.foo=C.prototype.foo;
(new C).foo();
Here you reference a subclass method .foo in the superclass, so when you call .foo, it will reference P's .foo which is C's .foo and you have just created a loop.
It seems like to solve this issue, you can use .toMethod which "clones" the function and give it a different super/"home" that you specifed:
P.prototype.foo = C.prototype.foo.toMethod(P.prototype);
now calling (new C).foo() would not go on forever.

Categories

Resources