Nested .bind not working as expected

197 views Asked by At

Unfortunately .bind has been giving me grief when creating more complex closures.

I am quite interested in why .bind seems to work differently once you nest functions.

For example :

function t(){
    t = t.bind({}); //correctly assigns *this* to t  
    function nested_t(){
        nested_t = nested_t.bind({}); // fails to assign *this* to nested_t
        return nested_t; 
    }
    return nested_t(); 
}
//CASE ONE
alert(t()); 
          // alerts the whole function t instead of nested_t

//CASE TWO
aleft(t.call(t)); 
         // alerts the global object (window) 

In both cases I was expecting a behavior like this:

function t(){
    var nested_t = function nested_t(){
        return this;
    };
    return nested_t.call(nested_t);
}

alert(t.call(t));

If someone could explain the behavior of .bind in the first (and/or) second case it would be much appreciated!

3

There are 3 answers

4
Scimonster On BEST ANSWER

So, i'm not entirely reproducing your issue here (both cases return the global object), but i'll try and explain the code as i see it.

function t(){
    t = t.bind({}); //correctly assigns *this* to t  
    function nested_t(){
        nested_t = nested_t.bind({}); // fails to assign *this* to nested_t
        return this; 
    }
    return nested_t(); 
}
//CASE ONE
alert(t()); 

Let's take it step by step.

First, the function t() is defined. Then, upon call, it gets overwritten with a clean context. However, i don't see any usage of this context.

Now, a nested function (nested_t) is defined. Upon call, it is overwritten with a clean context. It then returns the context it had when it was called.

Back to t(). You then return the result of nested_t(), not nested_t itself. In the original function call, nested_t is still being called with global context.

Therefore, when you run t(), it returns the global object.

0
hon2a On

How your code works

It's very unclear, what your code is trying to do. You can find the documentation for .bind() here. It looks like you might be somehow misunderstanding what this is and how to use it. Anyway, what happens when you run your code is this:

  1. A t function is created in global scope.
  2. [case one] The t function is called.
    1. Global scope t is replaced with a new value (original t bound to a specific context - anonymous empty object), which doesn't affect the current call in any way. Also, while the global t is overwritten, the local t is behaving as read-only. You can check it out by trying the following code: (function foo () { return (function bar () { bar = window.bar = 'baz'; return bar; })(); })() and comparing return value with window.bar.
    2. The same thing happens with nested_t in the nested context (instead of global context).
    3. Result of nested_t call is returned. nested_t returns the context it was called with, which was window, as no context was specified. Specifically, it was not called with an empty object context, because the .bind() inside didn't affect the call itself.
  3. [case two] The exact same thing happens once again. Now you're just calling t with itself as context. Since t doesn't use its context (this) anywhere in its code, nothing really changes.

What your misconceptions might be

Basically, you're mixing up two things - function instance and function call context. A function is a "first-class citizen" in JavaScript - it's an object and you can assign values to its properties.

function foo () {
    foo.property = 'value';
}
foo();    // foo.property is assigned a value

This has nothing to do with function call context. When you call a function a context is assigned to that call, which can be accessed using this (inside function body)

function foo () {
    this.property = 'value';
}
var object = {};
foo.call(object);    // object.property is assigned a value

When you use .bind(), you just create a new function with the same code, that is locked to a specific context.

function foo () {
    this.property = 'value';
}
var fixedContext = {},
    object = {};
bar = foo.bind(fixedContext);
foo.call(object);    // fixedContext.property is set instead of object.property

But in this case, there are also function instances foo and bar, which can also be assigned properties, and which have nothing to do with contexts of calls of those functions.

2
candu On

Let's look at how bind works. First, one level of nesting:

var foo = function() { return this.x; };
alert(foo());  // undefined
alert(foo.bind({x: 42})()); // 42

Now we can add the next level of nesting:

var bar = function() { return foo.bind(this)(); };
alert(bar());  // undefined
alert(bar.bind({x: 42})());

We pass our this context to foo with - guess what? - bind. There is nothing different about the way bind works between scopes. The only difference is that we've already bound this within bar, and so the body of bar is free to re-bind this within foo.

As a couple of commenters have noted, functions that overwrite themselves are a huge code smell. There is no reason to do this; you can bind context to your functions when you call them.

I highly, highly recommend reading the documentation on bind, and trying to understand it to the point where you can write a basic version of Function.prototype.bind from scratch.