Search code examples
javascriptnew-operator

How is `new` operator able to override hard-binding, in the Function.prototype.bind(..)


This is a purely theoretical question. I am learing javascript from 'you don't know js', and i was stuck on the implementation of the bind function in JS. consider the following code:

function foo(something) {
  this.a = something;
}

var obj1 = {};

var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3

In the above snippet, we bind foo() to obj1, so the this in foo() belongs to obj1 and that's why obj1.a becomes 2 when we call bar(2). but the new operator is able to take precedence and obj1.a does not change even when bar(3) is called with new.

Below is the polyfill provided by the MDN page for bind(..):

if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
    if (typeof this !== "function") {
        // closest thing possible to the ECMAScript 5
        // internal IsCallable function
        throw new TypeError( "Function.prototype.bind - what " +
            "is trying to be bound is not callable"
        );
    }

    var aArgs = Array.prototype.slice.call( arguments, 1 ),
        fToBind = this,
        fNOP = function(){},
        fBound = function(){
            return fToBind.apply(
                (
                    this instanceof fNOP &&
                    oThis ? this : oThis
                ),
                aArgs.concat( Array.prototype.slice.call( arguments ) )
            );
        }
    ;

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
};
}

The part that's allowing new overriding according to the book, is:

this instanceof fNOP &&
oThis ? this : oThis

// ... and:

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();

so , now the main point. according to the book: "We won't actually dive into explaining how this trickery works (it's complicated and beyond our scope here), but essentially the utility determines whether or not the hard-bound function has been called with new (resulting in a newly constructed object being its this), and if so, it uses that newly created this rather than the previously specified hard binding for this."

how is the logic in the bind() function allows new operator to override the hard binding?


Solution

  • First, it is important to understand the difference between an object's prototype (represented the spec as [[Prototype]] and accessible via the function Object.getPrototypeOf or the deprecated __proto__ property) and the property on a function whose name is prototype. Every function has a property named prototype which is used when the function is called with new.

    When you call a function with new, that function is supplied with a this value set to a newly-constructed object whose prototype (i.e., [[Prototype]]) is set to the prototype property of the function being called. That is, when you call new Foo(), then when the code inside Foo is run, the this value will be an object of the form

    { [[Prototype]]: Foo.prototype }
    

    Let's briefly meet the cast of variables:

    • fToBind is the function being bound: for foo.bind(...), foo is fToBind.
    • fBound is the bound version of fToBind; it is the returned value of the bind operation. fBound acts like a gatekeeper for the original fToBind function, and decides what this value fToBind gets when it is invoked.
    • oThis is the first argument supplied to bind, i.e., the object being bound to the function's this.
    • fNOP is a function whose prototype property is set to fToBind.prototype
    • fBound.prototype = new fNOP() causes these to be true:

      Object.getPrototypeOf(fBound.prototype) === fNOP.prototype
      Object.getPrototypeOf(fBound.prototype) === fToBind.prototype
      

    When fBound is called with new, then the this that is supplied to fBound is of the form

    { [[Prototype]]: fBound.prototype }
    

    and fBound.prototype is an object of the form

    { [[Prototype]]: fNOP.prototype }
    

    making the full form of this equivalent to

    { [[Prototype]]: { [[Prototype]]: fNOP.prototype } }
    

    So, fNOP.prototype is in the prototype chain of the newly-created this object when fBound is called with new. That's exactly what the object instanceof constructor operation tests for:

    The instanceof operator tests the presence of constructor.prototype in object's prototype chain.

    The order of operations between && and ternary here is:

    (this instanceof fNOP && oThis) ? this : oThis
    

    If this has fNOP.prototype in its prototype chain and the original bind call was given a truthy first argument to bind to the function, then use the natrually-created this supplied to fBound when it is called with new and supply that to fToBind instead of the bound this.