19

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?

Community
  • 1
  • 1
XY L
  • 25,431
  • 14
  • 84
  • 143
  • 1
    MDN is a wiki. The answer you quoted was written in 2014. The phrase in question [was edited in 2016](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind$compare?to=1068336&from=1068176) – Bergi Apr 26 '17 at 15:12
  • 1
    Why has it been removed? – XY L Apr 26 '17 at 15:14
  • 1
    That you'll have to ask the editor, not me. The current description is no less correct, but just doesn't focus on this edge case any more. It simply is implied that when a bound function is called, any passed in thisValue will be ignored. – Bergi Apr 26 '17 at 15:15
  • in fact: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Function/bind it says that function that was wrapped with bind is not accessible: `When bound function is called, it calls internal method [[Call]] with following arguments Call(target, boundThis, args). Where, target is [[BoundTargetFunction]], boundThis is [[BoundThis]], args is [[BoundArguments]].` Your function becomes inaccessible to be changed so http://joxi.ru/EA4aaWswMDELrb – num8er Apr 26 '17 at 15:15
  • possible duplicate of [Chaining `bind` calls](https://stackoverflow.com/questions/26545549/chaining-bind-calls-in-javascript-unexpected-result) – Bergi Oct 11 '17 at 15:36
  • Does this answer your question? [Bind more arguments of an already bound function in Javascript](https://stackoverflow.com/questions/20925138/bind-more-arguments-of-an-already-bound-function-in-javascript) – marciowb Aug 25 '20 at 14:28

3 Answers3

7

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.

Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
1

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 ;)

Max
  • 286
  • 2
  • 5
  • It gives an error in Chrome Canary and in Chrome 56 it gives an error on the last line. – Abhinav Galodha Apr 26 '17 at 19:57
  • You getting something like a ReferenceError ? That's because most browsers throw this error if you access properties of non existing objects. And because the this-argument was set to null in the last line it throws this error, because the function tries to access this.value where this is null. – Max Apr 27 '17 at 00:39
  • I was using nodejs 7^ – Max Apr 27 '17 at 00:40
0

The bind method wraps the original function and creates a new Bounded Function. Actually, a function which wraps the original function keeping the same body of the original function. This is the definition in the MDN website:

The bind() function creates a new bound function (BF). A BF is an exotic function object (a term from ECMAScript 2015) that wraps the original function object. Calling a BF generally results in the execution of its wrapped function.

So, every time you call .bind, you create a new function passing the context as first parameter and the args as rest of parameters, but keeping the body of the first definition. You can also override the initial body, and bind the function again. At the same time, you can also take a previously bounded function and bind it again to a new function.
In the following example, you should see the expected behavior:

var printer = function(a) { 
    console.log(a); 
};

var printer1 = printer.bind(null, 1);
var printer2 = printer.bind(null, 2);
var printer3 = printer.bind(null, 3);

printer1();
printer2();
printer3();

printer = function(a) {
    console.log("I am a new " + a); 
};

var printer4 = printer.bind(null, 4);
printer4();

var newPrinter = function() {
    console.log('I am a new printer!');
}
printer4 = newPrinter.bind(null);
printer4();
quirimmo
  • 9,800
  • 3
  • 30
  • 45