raichu raichu - 3 months ago 5x
Javascript Question

new.target property behaves unexpected in bound functions

I was reading about the ECMAScript 2015 new.target property. I thought it was interesting, I could configure a function using the new operator, and then call it by a normal call. But for some reason, this doesn't seem to work. This is my test code.

function Test(newConfig) {
if(Test2 !== undefined){
console.log(new.target === Test2);// why does this give false
console.log(new.target === Test);// why does this give true
let mergedConfig = {};
Object.assign(mergedConfig, newConfig || {}, new.target.config || {})
let fn = Test.bind({config: mergedConfig});
fn.config = mergedConfig;
return fn;
} else {
// do something with this.config
return this.config.a + this.config.b;

// initial configuration
// the first time I'm calling the Test function, so new.target === Test
var Test2 = new Test({
a: 'a'
// further configuration
// the second time I'm calling the Test2 function.
// Now, new.target !== Test2, but new.target === Test.
// This is what seems weird to me.
let Test3 = new Test2({b: 'b'});

// normal call
// new.target === undefined
let result = Test3();


new.target does not always refer to the called function. Rather, it refers to the function whose .prototype property should be used as the prototype for the new instance (so that superclasses can create the right thing).

The .bind() method does create special "bound function" objects whose only purpose it is to call the original function with the provided this value and partially applied arguments list. They don't even have a .prototype property - which should make clear that they are not an appropriate value for new.target. And indeed, their [[construct]] method is specified to use the original function in place of the bound function.

So what should you do here? Of course, "using new to configure functions" should be avoided in production code, but it's a nice experiment. I would suggest to avoid bind here and instead make use of closures:

function makeTest(config) {
    return function ConfiguredTest(newConfig) {
        if (new.target) {
            let mergedConfig = {};
            Object.assign(mergedConfig, newConfig || {}, config) // I presume you want to swap the latter two
            return makeTest(mergedConfig);
        } else {
            // use `config` here
            return config.a + config.b;
const Test = makeTest({});

// like before
let Test2 = new Test(…)
let Test3 = new Test2(…)