3

It seems that instead of

{  onClick: function(event){  someHandler('clicked', event); }  }

you can write

{  onClick: someHandler.bind(null, 'clicked'); }

which looks more concise and feels more "functional".

Are there any drawbacks to this? Is there a performance gain by not having created a closure? Is there a way to keep the this that the closure would have received?

Thilo
  • 257,207
  • 101
  • 511
  • 656
  • What is the point of *bind* when you are passing *null*? Don't you mean to pass the element? – RobG Sep 11 '14 at 04:46
  • @RobG: I want to pass "clicked" as the first parameter (before the other ones that come in from the caller). I would like to pass `this` as well, but don't know how. – Thilo Sep 11 '14 at 04:53
  • related: http://stackoverflow.com/questions/6397096/function-bind-vs-closure-in-javascript-how-to-choose – Thilo Sep 11 '14 at 04:57
  • What are you trying to set at *this*? – RobG Sep 11 '14 at 08:38
  • @RobG: someone is calling `onClick` and probably setting `this`. I'd like to pass that along to `someHandler`. See torazaburo's answer about Underscore's `_.partial` that does exactly that. – Thilo Sep 12 '14 at 03:57

2 Answers2

1

It's not a "closure", it's just an anonymous function.

Personally I prefer the bind version because as you say, it's more concise. However, according to this jsperf (http://jsperf.com/anonymous-function-vs-bind), it's ten times slower, which surprises me greatly, especially since the bind used here seems to be the native one. One hypothesis is that bind, or rather the function it generates, needs to do work around looking at the arguments passed in and constructing an argument list to pass along to the function being called.

To maintain this, you need a variant of bind such as Underscore's _.partial, or you could write one yourself:

function partial(fn) {
    var slice = Array.prototype.slice,
        args = slice.call(arguments, 1);
    return function() {
        return fn.apply(this, args.concat(slice.call(arguments, 1)));
    };
}

Unfortunately, the variation using partial ({ onClick: partial(someHandler, 'clicked'); }) is still ten times slower than the anonymous function.

The hypothesis that argument list handling is causing the slowdown is supported by another test case in the jsperf, which defines a partial1 which predefines just the first of exactly two arguments to the underlying function:

function partial1(fn, a) {
    return function(b) {
        return fn.call(this, a, b);
    };
}

Using that one, which doesn't have to create and merge argument lists, results in a slowdown of only 25-35%, instead of 90%.

If we don't care about passing through this, which allows us to avoid using Function#call:

function partial2(fn, a) {
    return function(b) {
        return fn(a, b);
    };
}

Then the slowdown is just 10%.

However, if we really want to pass through this, then we need to write the anonymous function version as

{ onClick: function(event) { someHandler.call(this, 'clicked', event); }  }

which also results in a 20-25% slowdown from the original version, presumably due to the cost of invoking Function#call. So in that sense, asusming you do want to pass through this, the performance of the anonymous function and our home-grown partial1, customized for number of arguments, is roughly equivalent, which is not surprising since they're essentially doing identical work.

  • I suspect that it's not an issue of 10 times slower but the anonymous function is 10 times faster due to optimizations done to the javascript engine by the various vendors. – slebetman Sep 11 '14 at 04:40
  • Yes, it's possible that the entire call is in-lined. –  Sep 11 '14 at 04:54
  • 1
    "It's not a closure". Why not? Doesn't it capture `someHandler`? – Thilo Sep 11 '14 at 04:55
  • See the SO thread on the "what is a closure" topic. Yes, it's a closure in a trivial sense. –  Sep 11 '14 at 04:58
  • Anyway, the consensus seems to be that in current implementations, it is a lot slower than the hand-rolled "function", so I guess that decides it. Will consider `partial2`. – Thilo Sep 11 '14 at 05:00
  • 1
    Yes, it decides that it's slower, which is not the same, of course, as saying you shouldn't use it. –  Sep 11 '14 at 05:01
  • Speaking of performance: I suppose the slow-down comes when the function is actually invoked. With event handlers, I might set up a lot of them that are never used. Could this come out in favour of bind? – Thilo Sep 11 '14 at 05:09
  • 1
    According to this jsperf (http://jsperf.com/anonymous-function-vs-bind-compile-time), simply evaluating `foo.bind(0)` is more than 100x slower than `function() { foo(); }`, so no. –  Sep 11 '14 at 05:15
  • @torazaburo—those are two **entirely** different things, the results are irrelevant. The "inline" test is simply a function declaration, the "bind" test calls *foo.bind*, passing it a primitive (see [*Function.prototype.bind*](http://ecma-international.org/ecma-262/5.1/#sec-15.9.3.2). – RobG Sep 11 '14 at 08:28
  • @RobG What am I missing? I thought both were expressions with identical meaning, namely when "called" (for instance, by putting `()` after them) they end up executing the function `foo`. The test I did was essentially to compare the performance of the JS parser/evaluator when encountering these two (I thought semantically identical) constructs. –  Sep 11 '14 at 08:32
  • The "inline" test is simply a function declaration, it doesn't execute any code at all which is why Chrome can do 400,000,000 (400 million) of them per second. The second is calling the built–in bind method, which requires quite a bit of work. In any case, they would only be executed once so unless they take say 100ms, speed is irrelevant. The inline test also doesn't set *this*, it will default to the global object (or remain undefined in strict mode). – RobG Sep 11 '14 at 08:37
  • Yes, I agree. I was responding to @Thilo's question above in the comment thread, where he was wondering if the "compile-time" would be different enough to possibly affect the trade-off with much slower run times. The (obvious, I agree) answer is that `bind` is slower *both* to set up *and* to run. –  Sep 11 '14 at 08:43
  • @torazaburo—you're still missing the point that the inline version doesn't set *this*, it's not doing anything at all even if *x* is called, it just calls *foo*. Those "tests" should be the setup, then you should call the functions. The inline test setup should be something like `function x(thisArg){foo.call(thisArg)}` and the bind one `foo.bind(thisArg); x = foo;`. This will compare the speed of calling *x* so yo get the result of different methods of setting *this*. – RobG Sep 11 '14 at 08:48
  • @torazaburo—here's an [*updated test*](http://jsperf.com/anonymous-function-vs-bind-compile-time/2) where you can see that setting *this* using *bind* or *call* has virtually no effect on performance in Safari, but does have a big effect in other browsers (though I'm still not sure the difference is really significant unless you are calling the function thousands of times in a few seconds). – RobG Sep 11 '14 at 08:54
1
  1. Are there any drawbacks to this? Is there a performance gain by not having created a closure?

Yes, bind has a performance drawback you can find more details here

  1. Is there a way to keep the this that the closure would have received?

This depends on how you define this

It works well if you pass an object like my_klass in the example

function some(a,b){
   console.log("a is: " + a);
   console.log("b is: " + b);
   console.log("this.val: " + this.val)
}

function klass(val){
   this.val = val;
}

my_klass = new klass("test val");

var ab = {
  click: function(a){ some('clicked',a);},
  bclick: some.bind(my_klass,'clicked')
}

ab.bclick("param");

Output:

a is: clicked
b is: param
this.val: test val

It wont work if you do like

function some(a,b){
   console.log("a is: " + a);
   console.log("b is: " + b);
   console.log("this.val: " + this.val)
}

var ab = {
  val: 99,
  click: function(a){ some('clicked',a);},
  bclick: some.bind(this,'clicked')
}

ab.bclick("param"); // Expected to print 99

Output:

a is: clicked
b is: param
this.val: undefined  
Community
  • 1
  • 1
Siva
  • 7,780
  • 6
  • 47
  • 54