bind is faster
https://github.com/Mr0grog/newless/blob/3fe059f3f8ac26d945afb01c880dd68d2e61d631/newless.js#L50-L61
Right there, I visited that link, and it seems nowadays bind is a lot faster (in Chrome):

Almost twice as fast.
That’s great! It should be obvious I haven’t worked on this library since before that was the case, though, and I don’t think it has any serious consumers today. If you’d like to change this, I’ll happily accept a PR.
Not sure I know enough to make the change yet. So even if a constructor function is bound, it can still be constructed and the binding doesn't affect the construction?
// seems to work:
class Foo {}
new (Foo.bind.apply(Foo, [null, 1, 2, 3]))
But what's the point? What does that achieve over just new (Foo.bind(null, 1, 2, 3)), or even just new Foo(1, 2, 3)?
Oh, I see, because unknown number of args. Doh.
On this part,
https://github.com/Mr0grog/newless/blob/3fe059f3f8ac26d945afb01c880dd68d2e61d631/newless.js#L41-L48
is there an advantage of using class there? Or would using spread with new constructor(...([].slice.call(args))) work just as fine?
How about something like this one?
return Function("constructor, args, target",
"'use strict';" +
// spec: TypeError if third arg is explicitily undefined
"if (arguments.length === 3 && typeof target === 'undefined') throw new TypeError('undefined is not a constructor');" +
"target = target || constructor;" +
(supportsSpread ?
"var value = new constructor(...([].slice.call(args)));"
:
"args = Array.prototype.slice.call(args);" +
"args = [null].concat(args);" +
"var value = new constructor.bind.apply(constructor, args);"
) +
// fix up the prototype so it matches the intended one, not one who's
// prototype is the intended one :P
"Object.setPrototypeOf(value, target.prototype);" +
"return value;"
);
Am I missing something critical by not using class syntax there?
Am I missing something critical by not using class syntax there?
Yes. That’s what makes the third argument (target) work. Here’s an example:
// An example class hierarchy
class Z { constructor() { console.log('Z'); } }
class Y extends Z { constructor() { super(); console.log('Y'); } }
class X extends Y { constructor() { super(); console.log('X'); } }
// For reference, let’s see what Reflect.construct actually does:
var v1 = Reflect.construct(Y, [], X); // Logs: Z, Y
console.log(v1 instanceof X); // true
// Here’s how we simulate that with the ad-hoc `class`
var TempClass = class instantiator extends X {};
Object.setPrototypeOf(TempClass, Y);
var v2 = new TempClass(); // Logs: Z, Y
console.log(v2 instanceof X); // true
// Here is what you are proposing:
var v3 = new Y(); // Logs: Z, Y
console.log(v3 instanceof X); // false (not what we want)
If supportsSpread is false, then it calls constructor directly, which is the same problem.
Plus, later on, you have the lines
// fix up the prototype so it matches the intended one, not one who's
// prototype is the intended one :P
"Object.setPrototypeOf(value, target.prototype);" +
"return value;"
Isn't that what takes care of making sure value is isntanceof target? If so, then isn't the ad-hoc class just redundant?
That's why I was copying the non-supportsSpread version (instantiating constructor directly), figuring it worked the same. With the class instantiator, those last two lines just remove the instantiator prototype, but in either case the instance will have the same constructor logic ran on it, and will be instanceof target.
Seems to work, try this in Chrome console:
var supportsSpread = true;
var construct = Function("constructor, args, newTarget",
"'use strict';" +
"if (arguments.length === 3 && typeof newTarget === undefined)" +
"throw new TypeError('undefined is not a constructor');" +
"newTarget = newTarget || constructor;" +
(supportsSpread ?
"var value = new constructor(...([].slice.call(args)));"
:
"args = Array.prototype.slice.call(args);" +
"args = [null].concat(args);" +
"var value = new constructor.bind.apply(constructor, args);"
) +
"Object.setPrototypeOf(value, newTarget.prototype);" +
"return value;"
);
// An example class hierarchy
class Z { constructor() { console.log('Z'); } }
class Y extends Z { constructor() { super(); console.log('Y'); } }
class X extends Y { constructor() { super(); console.log('X'); } }
// let’s see what the polyfill does:
var v1 = construct(Y, [], X); // Logs: Z, Y
console.log(v1 instanceof X); // true
Oops, I had Reflect.construct in that last example. I updated it to just construct which works.
I have a question about your example on ES Discuss. How does that magic work?
It still matters during construction. Consider this case:
class Z { constructor() { console.log('Z! this instanceof X?', this instanceof X); } }
class Y extends Z { constructor() { super(); console.log('Y! this instanceof X?', this instanceof X); } }
class X extends Y { constructor() { super(); console.log('X'); } }
// let’s see what the polyfill does:
var v1 = Reflect.construct(Y, [], X); // Logs: Z! this instanceof X? true, Y! this instanceof X? true
console.log(v1 instanceof X); // true
// Here’s how we simulate that with the ad-hoc `class`
var TempClass = class instantiator extends X {};
Object.setPrototypeOf(TempClass, Y);
var v2 = new TempClass(); // Logs: Z! this instanceof X? true, Y! this instanceof X? true
Object.setPrototypeOf(v2, X.prototype);
console.log(v2 instanceof X); // true
// Here is what you are proposing:
var v3 = new Y(); // Logs: Z! this instanceof X? false, Y! this instanceof X? false (not what we want)
Object.setPrototypeOf(v3, X.prototype);
console.log(v3 instanceof X); // true
That’s especially important if you have method overrides that change behavior used during instantiation:
class Z {
constructor() {
console.log('Z! this instanceof X?', this instanceof X);
console.log(`My name is ${this.name}. Watch me dance: ${this.dance()}`);
}
get name() { return 'Ziggy'; }
dance() { return '💃'; }
}
class Y extends Z {
constructor() { super(); console.log('Y! this instanceof X?', this instanceof X); }
}
class X extends Y {
constructor() { super(); console.log('X'); }
get name() { return 'Xavier'; }
dance() { return '🎷'; }
}
I can imagine this being especially critical for custom elements, where some parsing logic or event logic or something might be different on a subclass.
The reason Object.setPrototypeOf() is there is to remove instantiator from the prototype chain, because it should just be a hidden implementation detail. It’s changing the resulting prototype chain from:
value → instantiator.prototype → X.prototype → Y.prototype → Z.prototype → Object.prototype
to:
value → X.prototype → Y.prototype → Z.prototype → Object.prototype
Apologies if the comment above that line in the code was a little too obscure/jokey to make that clear.
Got it! What it boils down to is simple ES5-like behavior,
const self = Object.create(X.prototype)
Y.apply(self)
return self
, except not. I wish ES6 didn't introduce classes that make ES5-style extension so difficult to work with.
In the part of the polyfill when spread is not supported, there's no instantiator pattern in the snippet:
"var argList = '';" +
"for (var i = 0, len = args.length; i < len; i++) {" +
"if (i > 0) argList += ',';" +
"argList += 'args[' + i + ']';" +
"}" +
"var constructCall = Function('constructor, args'," +
"'return new constructor(' + argList + ');'" +
");" +
"var value = constructCall(constructor, args);"
Doesn't it need it there too?