11 ms·
Partial Application in JavaScript using bind()
- ghostdiver 13y agoThis "pattern" is wrong, it makes debugging much harder. In some cases less code means more pain and this is such case.
- Vekz 13y agoThis is poor communication. Saying its 'wrong' and makes it 'harder' are your personal generalizations. Thats an empty statement. How does it do either of those? This pattern encourages naming of anonymous functions that are used in callbacks. Which creates simpler stack traces. The new piece here about passing additional params to bind is also helpful, in that it will allow you to carry params across a callback stack. For me this will make carrying an express apps request and response objects across a callback chain much easier.
- ghostdiver 13y agoIt forces programmer to make few more steps during debugging process, using conditional breakpoints in particular. not a big deal to put conditional breakpoint in handleStreamEvent whereever it is, but in long run it just a waste of time. this.setup = function () { this.on('tweet', this.handleStreamEvent.bind(this, 'tweet')); this.on('retweet', this.handleStreamEvent.bind(this, 'retweet')); }; This pattern will be obsolote as soon as arrow function gets implemented by all major browsers: this.setup = function () { this.on('tweet', () => this.handleStreamEvent('tweet')); this.on('retweet', () => this.handleStreamEvent('retweet')); };
- deleted 13y ago[deleted]
- badman_ting 13y agoAh, but what is the value of 'this' when those handler functions are called?
- epidemian 13y agoArrow functions preserve the 'this' of their enclosing context: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/arrow_functions#Lexical_this https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... :)
- ch0wn 13y agoThose are not equivalent. You still have to pass on the parameters: this.setup = function () { this.on('tweet', (e, data) => this.handleStreamEvent('tweet', e, data)); this.on('retweet', (e, data) => this.handleStreamEvent('retweet', e, data)); };
- ianstormtaylor 13y agoGood catch. Once they are added though, it feels a lot more readable than the "bind" alternative.
- mike-cardwell 13y agoNote: Function.prototype.bind doesn't exist in IE8 and below. There is a Polyfill at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... which adds it when it is missing.
- jbaudanza 13y agoAlso the es5-shim library is quite nice, and includes a bind polyfill. https://github.com/es-shims/es5-shim https://github.com/es-shims/es5-shim
- geuis 13y agoIt's not entirely clear to me how bind is different than apply.
- shadowfiend 13y agoThe key difference is that bind returns a function with the updated this/argument bindings, while apply actually runs the function.
- deleted 13y ago[deleted]
- Touche 13y agoBind creates a function, apply calls a function.
- timothya 13y agoThe big difference is that unlike `apply` (and `call`), `bind` does not call the function, it just gives you a new function that you have to call later (or use as an event handler). `bind` returns a new function, which has it's context `this` set to whatever you pass in, along with arguments partially applied. `apply` calls the function, using the context object you provide and the arguments provided in an array. (`apply` also has a sister function `call`, which also calls the function with the context object you provide and the arguments you provide individually).
- wging 13y agoBind creates a function, which you can pass around as a value, and does not execute the function you give it immediately. Both call and apply call the function it's given immediately. That's why bind is useful in the context of event handlers--you want code to run at some later time. If you do this with 'apply', you still need a wrapper function. Moreover, there is some manual manipulation of the `arguments` special object you need to do in order to get this to work with apply. Without bind you'd have to do something like function tweetWithType(tweetType) { return function() { var newArgs = Array.prototype.slice.call(arguments); newArgs.unshift(tweetType); this.handleStreamEvent.apply(this, newArgs); }; } to be able to call it like this.on('tweet', tweetWithType('tweet')); and you STILL wouldn't be right, because `this` within tweetWithType is not the `this` you expect. You'd have to do the whole var self = this; thing.
- timothya 13y agoI've used bind to do partial function application for quite a while, and it really is a convenient pattern to use. It does have the downside that it's a bit harder to quickly understand what's going on, though, since the arguments being passed into a function can come from different places. My common use case is when I'm setting up event handlers and I want the event handler to know about a companion object I have to the element in the DOM. For example: MyObject.prototype.buildDom_ = function() { for (var i = 0; i < buttons.length; i++) { var button = new MyButton(...); this.myButtons_.push(button); var element = document.createElement('div'); element.addEventListener('click', this.handleClick_.bind(this, button)); this.rootElement_.appendChild(element); } }; MyObject.prototype.handleClick_ = function(button, clickEvent) { // I have access to the MyButton instance passed in. }; In this case, I use the partial application to pass in the MyButton instance so that when I'm in the handler for a button click, I have access to the original JavaScript object that was created when the event handler was set up.
- cocoflunchy 13y agoMy issue is how to remove the event listener once you've done document.addEventListener('click', handleEvent.bind(this)); Doing document.removeEventListener('click', handleEvent); doesn't work, so how can you get rid of it?
- Vekz 13y agoYou would need a reference to the function that you used bind on before binding it to the event var eventHandler = handleEvent.bind(this); document.addEventListener('click', eventHandler); document.removeEventListener('click', eventHandler); May seem redundant but if you are trying to remove a listener without using bind you still need a reference.
- timothya 13y agoYou could do: var boundEvent = handleEvent.bind(this); document.addEventListener('click', boundEvent); and later: document.removeEventListener('click', boundEvent); This situation is a bit messier, though, because you have to keep track of both `handleEvent` and `boundEvent`.
- cocoflunchy 13y agoNow it seems obvious... thank you ;)
- cjfont 13y agoSince bind() creates a new function just before it's added as a listener, you'll have to save that function somewhere and use that as the listener you want to remove.
- natecavanaugh 13y agoTwo useful things YUI provides around this (and I'm sure other libraries have similar), are: 1. Y.rbind, which does the same thing as bind, but appends the arguments (to me, this is more useful, as often, I'm binding a single function thats defined somewhere that expects at least the event obj as the first arg, but may be able to handle optional arguments that I could bind to an instance of that fn) 2. The ability to create a bound function using only the name of the function and the object it exists on. Using something like Y.bind('myFn', obj);, the function that will be executed will be looked up at runtime rather than at the time the bound fn is created. This is extremely useful for cases where you may be using AOP or monkey patching to displace a function before it's executed. It also allows you to bind a function before it exists. Passing in a reference to the function at bind time protects against cases like that, but I find it far more useful than just binding the reference.
- epx 13y agoI just prefer the explicit self, like Python's
- taylodl 13y agoBind can also be used to obtain a reference to a function expression in javascript. I exploited that ability in creating an implementation of trampoline in javascript: http://taylodl.wordpress.com/2013/06/07/functional-javascript-tail-call-optimization-and-trampolines/ http://taylodl.wordpress.com/2013/06/07/functional-javascrip...
- benihana 13y agoThis is also possible with jQuery's proxy function if you can't target only modern browsers. http://api.jquery.com/jquery.proxy/ http://api.jquery.com/jquery.proxy/
- pencilcode 13y agoI've heard bind is slow. Is this still true?
- spion 13y agoyes
- vectorjohn 13y agoSlow in what way? If you think about what it's doing, it can only be so slow. Assuming the browser's engine doesn't do any sort of optimization (bind is a native method, so it might), it is simply creating an anonymous function that calls the original. So it's an extra function call: var boundFn = function(){ return originalFunction.apply( this, [boundArg1, boundArg2].concat( arguments ) ); } or something like that.
- kevingadd 13y agoIt's significantly slower than doing it by hand because of how .bind() is implemented in v8 and spidermonkey. It messes with the type information the engines use to optimize and it often sends you through C++ to do the function call.
- cjfont 13y agoSo is it .bind() itself that is slow, or the function that it returns? Assuming the bind operation itself is done during initialization, its slowness is pretty irrelevant.
- kevingadd 13y agoBind itself is slower than doing it by hand with a closure, and the return value is an order of magnitude slower than the hand-written closure and pollutes type information for your entire application, making all your code slower.
- 13y ago
- jasonlotito 13y agoA bit more comprehensive article can be found here: http://coding.smashingmagazine.com/2014/01/23/understanding-javascript-function-prototype-bind/ http://coding.smashingmagazine.com/2014/01/23/understanding-...
- lennel 13y agothe use of bind with event listeners will lead to memory leaks since you have no way of removing the event listener, given that you don't have access to the function bind returns (at least in the code examples the writer created).
- mcgwiz 13y agoFor partial argument binding, I find myself using an implementation that doesn't close 'this' much more often. E.g. Function.prototype.partial = function () { var fn = this, args = Array.prototype.slice.call(arguments) ; return function() { var newArgs = Array.prototype.slice.call(arguments); return fn.apply(this,args.concat(newArgs)); } }