mjmitche
mjmitche

Reputation: 2067

JavaScript Binding

thank you if you can help. Source of code http://ejohn.org/apps/learn/#84

1) in line 3 of the program below, where it says return context[name] what does this mean? Im guessing that it means name is bound to the context as a result of the apply function? Is that correct?

2)If my guess in 1 is correct, why does it use the [] brackets? Is that just the syntax. When I look at it, it makes me think array or object?

3) When it says apply(context, arguments) is arguments not the same as name or is arguments both context and name together? to put it another way, in the language of the call bind(Button, "click") is arguments only "click" or is it both button and click?

4) I tried to rewrite line 3 by substituting name for arguments like this

return context[name].apply(context, name); 

but it didn`t work anymore, which raises the questions

a)if it is returning name bound to context (i.e. context[name]), why isn`t it sufficient to just have apply(context,name)?

b) if arguments includes both name and context, is the third line of the function essentially

return context[name].apply(context, [context, name]); 

c) if my assumption in 4(b) is correct, why would we effectively have to have context passed twice in order to bind name to context? which is to say, I dont understand why line 3 doesnt work if you just write apply(context, name) instead of apply(context,arguments)

function bind(context, name){ 
  return function(){ 
    return context[name].apply(context, arguments); 
  }; 
} 

var Button = { 
  click: function(){ 
    this.clicked = true; 
  } 
}; 

var elem = document.createElement("li"); 
elem.innerHTML = "Click me!"; 
elem.onclick = bind(Button, "click"); 
document.getElementById("results").appendChild(elem); 

elem.onclick(); 
assert( Button.clicked, "The clicked property was correctly set on the object" );

Click me!

Upvotes: 1

Views: 729

Answers (2)

Mark Bessey
Mark Bessey

Reputation: 19782

1) context[name] just means the property of the "context" object with that name. In the case of:

bind(Button, "click");

that works out to Button["click"], which is the click() function inside the Button object

2) All objects in Javascript are a collection of properties, which can be accessed by their names. Given the definition:

var Button = { 
  click: function(){ 
    this.clicked = true; 
  } 
};

both Button.click and Button["click"] would refer to the same thing - the function click() inside the Button object.

3) The arguments keyword refers to an array-like object containing all of the arguments passed to a function. In the example, bind() is returning a newly-created function. The "arguments" referred to in that function are whatever arguments that function gets called with. In this case, it's neither context nor name, it's whatever the onclick mechanism passes to the event handler.

Here's a slightly different way to write the code that sets up the event handler:

var elem = document.createElement("li"); 
elem.innerHTML = "Click me!"; 
var boundFunction=bind(Button, "click");
elem.onclick=boundFunction;
document.getElementById("results").appendChild(elem); 

Maybe this makes it more clear that when you call bind(), it returns a new function. If you were to call the boundFunction like this:

boundFunction("these", "are", "arguments")

The use of arguments is inside the returned function, so arguments would be ["these", "are", "arguments"] in this case. The arguments that were passed to "bind" are used to construct the function that bind returns, so they're no longer relevant when the bound function gets called.

4) Until you understand the basics of how returning a function from another function works, this'll be pretty confusing. The purpose of apply() is to set the "this" keyword for a particular function invocation. Given the definition of Button, you might expect to be able to do this to set up the event handler:

elem.onclick = Button.click;

This doesn't work correctly, because when the event handling code calls the Button.click function, "this" is set to the global context, rather than to the Button instance. The purpose of the bind() function is to make a function that sets "this" appropriately, then calls the function you originally passed to bind().

I have a half-completed blog entry on this which might be a simpler example: http://codemines.blogspot.com/2010/01/javascript-by-example-functions-and.html

Upvotes: 1

Anurag
Anurag

Reputation: 141879

It may be helpful to understand the basics of JavaScript objects before diving into the specifics. Any JavaScript property can be accessed with the bracket notation, or the dot notation (if it is a valid identifier). It can be confusing since arrays also use this notation. Say there is an object of cars and their makes,

var cars = { Ford: 2007, Honda: 2010, BMW: 2011 };

Then we can access their keys using the dot notation or the bracket notation

cars.Ford     // 2007
cars["Honda"] // 2010

Next, remember that functions are first class citizens in JavaScript. So you could use them as ordinary variables including storing them as object property values, in arrays, etc. Let's replace the years in the previous cars example with actual functions,

var cars = {
    Ford: function() { alert("2007"); },
    Honda: function() { alert("2010"); },
    BMW: function() { alert("2011"); }
};

The keys Ford, Honda, and BMW can still be accessed as in the previous example with the dot or bracket notation, with the only difference that this time a function will be returned instead of the integer year.

cars["BMW"] now returns a function which can be directly invoked as

cars["BMW"](); // or
cars.BMW();    // or

var name = "BMW"; 
cars[name]();    

That's not all. There are still two more ways to execute a function - through apply and call. The difference between apply and call is subtle but you should read up more about them.

Finally, arguments represents an array-like object that contains the arguments passed in to a function. This is best demonstrated by an example,

function whatever() {
    console.log(arguments);
}

whatever(1, 2);                        // [1, 2]
whatever("foo", "bar", 23, [4, 6, 8]); // ["foo", "bar", 23, [4, 6, 8]]
whatever();                            // undefined

Without giving any names to the function parameters, we were able to log all the arguments passed to the function. Since it is an array like object, access each argument individually as arguments[0], arguments[1], etc.

And now to answer your questions,

1) in line 3 of the program below, where it says return context[name] what does this mean? Im guessing that it means name is bound to the context as a result of the apply function? Is that correct?

context[name] is similar to the cars['Ford'] example above. It is supposed to give a function which is then invoked by calling apply on it. When that function is called, inside the function this will refer to the object - context.

2) If my guess in 1 is correct, why does it use the [] brackets? Is that just the syntax. When I look at it, it makes me think array or object?

Hopefully this was answered above.

3) When it says apply(context, arguments) is arguments not the same as name or is arguments both context and name together? to put it another way, in the language of the call bind(Button, "click") is arguments only "click" or is it both button and click?

arguments has nothing to do with either context or name. It is simply a list of the arguments/parameters that the function was called with. Hopefully the above description cleared this as well.

4) I tried to rewrite line 3 by substituting name for arguments like this

return context[name].apply(context, name);

but it didn`t work anymore

It didn't work because apply expects the second argument to be an Array, and you passed it a String. Try return context[name].apply(context, [name]); instead.

which raises the questions

a) if it is returning name bound to context (i.e. context[name]), why isn`t it sufficient to just have apply(context,name)?

b) if arguments includes both name and context, is the third line of the function essentially

return context[name].apply(context, [context, name]);

arguments has nothing to do with the context, or name. Hopefully this was cleared up in the above examples.

c) if my assumption in 4(b) is correct, why would we effectively have to have context passed twice in order to bind name to context? which is to say, I dont understand why line 3 doesnt work if you just write apply(context, name) instead of apply(context,arguments)

The above answers already answer this part.

Upvotes: 3

Related Questions