One known thing
about javascript closures

#js #closure #iife #bind

Sometimes we need to handle specific event for many DOM nodes:

function foo1() { alert(a.href) }
function foo2() { alert(b.href) }
function foo3() { alert(c.href) }
a.onclick = foo1
b.onclick = foo2
c.onclick = foo3

e.target

If handlers are mostly identical and depend on element only, we can rewrite this as shown below:

function foo(e) {
  alert(e.target.href)
}
a.onclick = foo
b.onclick = foo
c.onclick = foo

(NB: to fully support IE you should write

var e = event ? event : e
e.target ? e.target : e.srcElement

instead of just e.target.)

Without e.target

But what if we not depend on target element itself but on some other things like variables in this snippet:

for (var i = 0; i < n; i++) {
  els[i].onsomeevent = function(e) {
    console.log(i)
  }
}

If you trigger all the event handlers you'll get this output for n = 3:

2
2
2

Duplicate values with total length of n. In this case we've got so called closure and our function is linked by reference to variable i. And this variable stops at n-1. So this value will be used for all the functions. And the functions will be efficiently identical. To create n variants of the function with different values of i we need something like this:

for (var i = 0; i < n; i++) {
  (function(i){
    els[i] = function() {
      console.log(i)
    }
  })(i)
}

Lookes lispy] But now the output is right:

0
1
2

So, this way we "declosure" variable i to make it used by it value at the moment of function definition. This is called IIFE — immediately invoked function expression.

There're shorter ways to do IIFE:

!function(...){...}(i)

or

-function(...){...}(i)

or

~function(...){...}(i)

or even

+function(...){...}(i)

But ! and ~ are better because they're always unary.

The trick here is interesting. Althrough

var f = function(...){...}
!f

is false, the priority of ! is less than priority of call, so the call executes first in this case (pretty obvious):

!f('hello')

And in this case (not so obvious):

!function(){}()

The brackets and unary operators switches the parser to "expression mode", in other words forces it to parse code as expression, not statement.

The correct js operators precedence table is available on MDN.

Read also this article for more information about IIFE.

String methods

Also because js is interpretated language, you can define function as text:

els[i].onclick = new Function("console.log('"+i+"')")

Or even using eval:

els[i].onclick = eval('(function(){console.log("'+i+'")})')

These methods are bad for obvious reasons.

Bind

Another approach without definitions of additional anonymous functions and eval hacks is to use bind:

f=(function(s){console.log(s)}).bind(null,'hi')
f()

This code outputs hi. The null here is needed because we call anonymous function and not funciton of object.

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

So, to not write this null you could just use this inside of the function:

f=(function(){console.log(this)}).bind('hi')
f()

But be carefull, in this case typeof(s) is object rather than string. In most (all?) cases this object converts to string automatically without prior convertion or casting.

For example, if

f=(function(){ return this }).bind('hi')
s=f()

then

typeof(s) == "object"
typeof(s.toString()) == "string"
s == 'hi'
s.trim() == 'hi'

So you can call String methods on this object and pass it everywhere you want.  

bind approach is better because it's less cryptic and allows us reuse a function even if it was declared somewhere elsewhere:

// somewhere elsewhere
function foo() { ... }

// attach events with bind
a.onclick = foo.bind(0)
a.onclick = foo.bind(1)
a.onclick = foo.bind(2)

Read also this article.

width hack

And finally, there's a little-known hack that uses with:

for (var i = 0; i < n; i++) {
  with({i:i}) {
    els[i] = function() {
      console.log(i)
    }
  }
}

 

Cheers, and know your javascript.

 

shitpoet@gmail.com

 



 

free hit counters