1

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(new.target){
    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();
raichu
  • 688
  • 2
  • 8
  • 15
  • I'm not sure what you were reading, or what you mean by "*configure a function using the new operator, and then call it by a normal call*", but it looks like you're confusing it with the `this` keyword. Please explain why you expected to get `Test2` there in the first place. – Bergi Jun 25 '16 at 22:48
  • `ReferenceError: can't access lexical declaration 'Test2' before initialization`. Did you mean to declare `Test2` with `var`? `if(Test2 !== undefined)` is not a reliable way to check if `Test2` has been initialized or is in the TDZ. – Oriol Jun 27 '16 at 03:27
  • You're right. It should be var. I changed the code a bit from my real test code, to make the issue clearer and ECMAScript 6-like. – raichu Jun 27 '16 at 11:47

1 Answers1

2

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(…)
Test3()
Bergi
  • 630,263
  • 148
  • 957
  • 1,375