Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you rebind a rebound function using `bind`

Tags:

javascript

bind method creates a new function that when called has its this keyword set to the provided value.

var obj = {
  a: 0,
  b() {
    console.log(this.a);
  }
}

obj.b() // -> 0

var functionBound = obj.b.bind(obj)
functionBound() // -> 0
functionBound.bind(null)() // -> 0 AND I expect an error here

Clearly, I cannot rebind a function has already been rebound. However, I could not find any documentation on this behavior.

Quote from "Bind more arguments of an already bound function in Javascript"

Once you bound an object to a function with bind, you cannot override it. It's clearly written in the specs, as you can see in MDN documentation:

The bind() function creates a new function (a bound function) with the same function body (internal call property in ECMAScript 5 terms) as the function it is being called on (the bound function's target function) with the this value bound to the first argument of bind(), which cannot be overridden.

I could not find these in MDN documentation. I did an exact full-text search on the quote above on Google and seems the SO answer above is the only source for this behavior. I also try to find an answer in the language spec with no luck.

My question is do you know this behavior and where can I find any official documentation on these?

like image 439
X.Creates Avatar asked Apr 26 '17 14:04

X.Creates


2 Answers

This may not directly answer the question about getting a officially documented specification validating this behavior, but we can base our conclusions on the source code provided in MDN, specifically in the documentation for Function.prototype.bind(), under section Polyfill, where they provide an example of how a polyfill bind function would look like.

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
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
}

We can see that the oThis parameter is used in the closure fBound which is the one ultimately returned by the bind function.

This means that when you invoke the bind function, you get a closure function in return which, when invoked, accesses the oThis free variable provided as parameter in the original invocation of bind.

As such, it doesn't matter how many more times you rebind the bound fBound function, this function is already bound forever to the original context oThis within its closure.

The MDN documentation also points to Raynos bind implementation for further reference, which seems to correspond to this example as well.

like image 133
Edwin Dalorzo Avatar answered Sep 23 '22 07:09

Edwin Dalorzo


The problem is that Function.prototype.bind returns a NEW function instead of the same. Calling a bound function with a different this-argument has no effect, because the bound function already knows which value to use as the this-argument.

You could use this for binding your functions:

Function.boundOriginProp = Symbol()
Function.prototype.bindDynamic = thisArg => {
    let origin = this[Function.bindOriginProp] || this
    let bound = (...args) => origin.call(thisArg, ...args)
    bound[Function.bindOriginProp] = origin
    return bound
}

So you can rebind functions that have already been bound like this:

let obj1 = { value: 1 }
let obj2 = { value: 2 }

function example() {
    console.log(this.value)
}

let fn1 = example.bindDynamic(obj1)
fn1() // -> 1

let fn2 = fn1.bindDynamic(obj2)
fn2() // -> 2

let fn3 = fn1.bindDynamic(null)
fn3() // -> undefined

I hope this can help you ;)

like image 35
Max Avatar answered Sep 25 '22 07:09

Max