0

In a codebase I see bind used to make bound copies of functions on the prototype, used as callbacks for DOM events.

Why might this idiom be used, rather than, for example, using the methods on the prototype directly?

Does this offer some benefits in terms of memory consumption/the ability to free memory use when events are unbound from DOM events?

function F() {
  var onFoo = this._onFoo.bind(this); // Why?

  document.getElementById('foo').onClick(onFoo);
}

F.prototype._onFoo = function () { /*...*/ }
Ben Aston
  • 53,718
  • 65
  • 205
  • 331
  • 1
    This should explain it: [How to access the correct `this` / context inside a callback?](http://stackoverflow.com/a/20279485/218196) – Felix Kling Jan 25 '15 at 20:15
  • But I don't think this is the reason for the idiom in this instance. I know what bind does. Perhaps I should have phrased the question, does binding a bound copy of a function to a DOM callback offer any benefits beyond binding the receiver? – Ben Aston Jan 25 '15 at 20:15
  • Additionally to Felix: `.bind` is a cheap way to create partially applied functions in JS – zerkms Jan 25 '15 at 20:16
  • @Ben: Yes it is. Otherwise `this` would refer to the DOM element, not the instance of `F`. – Felix Kling Jan 25 '15 at 20:16
  • @Ben: it is. Please show us how you would invoke/pass it otherwise. – zerkms Jan 25 '15 at 20:16
  • No need to use bind method as it access the `this` context as it is directly inside function F. – Bhojendra Rauniyar Jan 25 '15 at 20:18
  • If you use the methods on the prototype directly, `this` won't work. – SLaks Jan 25 '15 at 20:18
  • *"does binding a bound copy of a function to a DOM callback offer any benefits beyond binding the receiver"* Not in this example. – Felix Kling Jan 25 '15 at 20:19
  • @BhojendraNepal not true. But my question remains. Are there any benefits to using bound copies of functions over and above the setting of the receiver in the callback? The callback might not need the receiver to be set to the instance of `F`, for example. – Ben Aston Jan 25 '15 at 20:19
  • *"The callback might not need the receiver to be set to F, for example"* Unlikely if you are using a prototype method. Of course if it's not needed then don't bind it. – Felix Kling Jan 25 '15 at 20:20
  • For example, say you define a bunch of callback methods on the prototype. This means that there is only one callback method per definition of the original constructor function. c.f. "flyweight pattern". If I then use bind to create the actual callbacks as needed for binding to DOM events, then when I unbind from DOM events the bound (via bind) instance of the method can be garbage collected. Does this make sense? – Ben Aston Jan 25 '15 at 20:24
  • @Ben What makes you think that people use `Function.prototype.bind()` for memory related issues? If you know what **bind** does, then you've answered your question. – istos Jan 25 '15 at 20:34

1 Answers1

1

The issue is that event handlers set their own value for this when they call the callback. That value will typically be related to the event handler, not to the object that the method is bound to. For example, in your example:

document.getElementById('foo').onClick(myObj.myFunc);

The this pointer in myFunc will be set to the DOM element that had the event handler attached (in this case, the foo element). But that isn't myObj so myFunc in that case could not access any of it's own instance variables via the this pointer (the normal way that methods access their instance data).

So, if you have a method that wants to access it's own instance data when it is called directly by an event handler, you have to do something other than just pass the method to the event handler. There are a couple ways to work around this issue.

One way of doing so it so use .bind() which returns a new stub function who's function is to set this before calling your function like this:

document.getElementById('foo').addEventListener('click', myObj.myFunc.bind(myObj));

In this case .bind() actually returns a new stub function who's function is to set the value of this to myObj before it calls myFunc.

You could also do that manually yourself like this:

document.getElementById('foo').addEventListener('click', function(e) {
    myObj.myFunc();
});

But, as you can see, .bind() provides a shortcut that takes less code (which is why it was invented).


A potential disadvantage to using .bind() in some cases is that you may no longer have access to the value of this that the caller of your callback would have set itself because .bind() threw that value away and replaced it with your own. In the event handler example above, this is not an issue because the original source of the event can be accesses via the e argument that is passed to the event handler so it is not lost if you need it.


I am aware of no meaningful difference in memory consumption or garbage collection between the two methods above. Both create a new function that is used to call the original and control the value of this when calling the original function. Both will have the same garbage collection lifetime.


It appears that one thing that is confusing you is that objects in Javascript are assigned or passed by pointer (some call it by reference, but that has some connotations that don't apply here so I'll use the phrase by pointer).

var x = {};
x.myFunc = function() {console.log("hello");};
x.myFunc();        // generates "hello" in the console

var t = x.myFunc;  // save reference to the function that x.myFunc currently points to
delete x.myFunc;   // remove property myfunc from the x object
t();               // generates "hello" in the console

t() still works event after x.myFunc has been removed because both t and x.myFunc had a reference (or pointer) to the same function. Doing a delete x.myFunc simply removed the myFunc property from the x object. The function that x.myFunc points to will only be "freed" by the GC when there are no other references to it. But, there is another reference to that function in t, so it is not freed and t() can use it for as long as t exists.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Please can you comment on whether using bind to create instances of functions on the prototype gives any benefit/additional control over memory usage/garbage collection of functions? – Ben Aston Jan 25 '15 at 20:29
  • @Ben - what is "using bind to create instances of functions on the prototype". I don't understand what that phrase means. Your code illustrates using `.bind()` in the constructor to create a temporary stub function that always uses this object when it is called. That new "bound" function is a local variable used only inside the constructor. – jfriend00 Jan 25 '15 at 20:30
  • `bind` will *copy* a function setting the receiver (and partially applying args). Is this copy semantic used to give finer control over memory allocation/deallocation? For example, I could stick the functions directly on each instance of F, but this would require more memory because there would be an instance of each callback function, *per instance* of F for the lifetime of each of those instances. But if a bound copy of a function on the prototype is used, then if I want to unbind a function, then that bound copy of the callback can be garbage collected. Does that make sense? – Ben Aston Jan 25 '15 at 20:34
  • @Ben - bind does NOT copy a function. It creates a new stub function that calls the original. When calling the original function, it uses `.apply()` to set the value of `this` when the original function is called. You can see exactly what `.bind()` does by looking [here on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) at the polyfill for `.bind()` (scroll near the bottom of the page). Creating this new function does use some memory (a Function object) each time you create one. Normal GC applies. – jfriend00 Jan 25 '15 at 20:35
  • @Ben`- in my two examples above, I'm aware of no meaningful differences in memory consumption or garbage collection lifetime. In your original question, you only provided one implementation so I don't know what you want to compare to. – jfriend00 Jan 25 '15 at 20:42
  • That can't be right. If so, why does this work then? http://jsfiddle.net/4m5e5tL3/1/ In summary, deleting the original function does not prevent the bound function from working. – Ben Aston Jan 25 '15 at 20:42
  • @Ben - sorry, but multiline code is simply not readable in comments. If you want to clarify what you're asking about, please use the edit link to add a new section to your original question and then add a comment to notify people that you have edited your question and added some new material. – jfriend00 Jan 25 '15 at 20:44
  • @Ben - because `.bind()` creates a new function that contains a reference to the function pointed to by `F.prototype.foo1`. Just because you then delete `F.prototype.foo1` does not mean that the function that it points to is gone if other things also have a reference to it. This is how Javascript works. I will add a simpler example to my answer to illustrate. – jfriend00 Jan 25 '15 at 20:47
  • @Ben - I've added a new section to the end of my answer that explains about references (or pointers) to functions. – jfriend00 Jan 25 '15 at 20:52
  • OK, but the MDN page is ambiguous as written. It talks about creating a new function with the same body. The polyfill is not sufficient to win the argument, because by its nature it is only an approximation of the ES5 implementation. Please can you give me a reference to confirm your statement about pointing to the original function, that is not a polyfill? – Ben Aston Jan 25 '15 at 20:54
  • @ jfriend00 I fully understand pass by ref and value. I did have a misunderstanding about `delete`. Thank you for clearing that up. I would be grateful if you could point me to a non-polyfill reference that confirms your comments that bind does not have a copy semantic. – Ben Aston Jan 25 '15 at 21:02
  • @Ben - yeah, `delete` in JS is often assumed to be something different than it is - probably because of people's prior understanding of what `delete` means in something like C++ which is obviously quite different. Anyway, glad things are settled now. – jfriend00 Jan 25 '15 at 21:30