• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Closures in Javascript
 

Closures in Javascript

on

  • 5,212 views

Scope, Garbage Collection and Closures in Javascript

Scope, Garbage Collection and Closures in Javascript

Statistics

Views

Total Views
5,212
Views on SlideShare
4,450
Embed Views
762

Actions

Likes
32
Downloads
0
Comments
5

13 Embeds 762

http://lmframework.com 708
http://www.slideshare.net 17
http://www.cnblogs.com 12
http://127.0.0.1 8
http://www.linkedin.com 7
http://web.archive.org 2
http://feeds2.feedburner.com 2
http://translate.googleusercontent.com 1
http://twitter.com 1
http://dev2dev.us 1
http://www.haogongju.net 1
http://com-lab.biz 1
https://www.linkedin.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

15 of 5 previous next Post a comment

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • Very good explanation.
    One question: In page 17, should the var obj = f('PUBLIC', 'PRIVATE'); be var obj = f('PRIVATE', 'PUBLIC');?
    Are you sure you want to
    Your message goes here
    Processing…
  • impressive and helpful, thanks
    Are you sure you want to
    Your message goes here
    Processing…
  • Brilliant explanation!
    Thank You very much, David .
    Are you sure you want to
    Your message goes here
    Processing…
  • How can I download this presentation?
    Are you sure you want to
    Your message goes here
    Processing…
  • good!
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Closures in Javascript Closures in Javascript Presentation Transcript

    • Scope, Garbage Collection & Closures In Javascript David Semeria lmframework.com This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License © 2010 David Semeria Page 1
    • Core Concepts Although Javscript has block syntax { }, only functions can create a new scope within a global scope. © 2010 David Semeria Page 2
    • Core Concepts Although Javscript has block syntax { }, only functions can create a new scope within a global scope. After a function exits, if none of the contents of its scope are referenced by other (active) scopes then the function's scope is destroyed. © 2010 David Semeria Page 3
    • Core Concepts Although Javscript has block syntax { }, only functions can create a new scope within a global scope. After a function exits, if none of the contents of its scope are referenced by other (active) scopes then the function's scope is destroyed. Alternatively, if any of the contents of an exited function's scope are referenced in other scopes, the function's scope will continue to live on until no such references exist. © 2010 David Semeria Page 4
    • In Pictures.. Global Scope var f = function() { ... Scope for f var g = function() { ... Scope for g var h = function() { .... Scope for h .... } } } The post-execution lifetimes of inner scopes depend solely on the existence of active external references to their contents. © 2010 David Semeria Page 5
    • Scope Persistence Global Scope var f = function() { var obj = { a: “I live in f's scope”, Scope for f b: “So do I” }; return obj; } External reference to obj from global scope var instance = f(); A scope persists when any of its contents persist. Above, f()'s scope persists given the external reference to obj. A closure is a special case of scope persistence, and is formed when an external reference to a function exists, and that function accesses variables from an outer function's scope. © 2010 David Semeria Page 6
    • Closures Global Scope var f = function() { var x = ... ; Scope for f var g = function() { var y = ... ; Scope for g var h = function() { var z = x + y; Scope for h .... x and y belong to outer scopes } } } External reference to h() from global scope A closure occurs when a function which was not defined in the global scope continues to be referenced (either directly or indirectly) from an object in an active scope after the function that created it has finished execution. In this case, the referenced function's scope, and those surrounding it, persist for as long as any external reference to it exists. © 2010 David Semeria Page 7
    • Closure ? Global Scope var f = function() { alert(“in f”); var g = function() { alert(“in g”); } g(); } f(); //two alerts Is this a closure? © 2010 David Semeria Page 8
    • Closure ? Global Scope var f = function() { alert(“in f”); var g = function() { alert(“in g”); } g(); } f(); //two alerts No; f() runs, it calls g(), then both functions exit. There are no active references from the global scope to g(). © 2010 David Semeria Page 9
    • A Simple Closure Global Scope var f = function(x) { var m = function(y) { return x * y; } return m; } var instance = f(z); We'll see examples of the function working in a moment. First let's look at the references which exist after f() has terminated.... © 2010 David Semeria Page 10
    • A Simple Closure Global Scope var f = function(x) { var m = function(y) { return x * y; } return m; } var instance = f(z); The only external reference we have is instance which points to m(). © 2010 David Semeria Page 11
    • A Simple Closure Global Scope var f = function(x) { var m = function(y) { return x * y; } return m; } var instance = f(z); Within the scope of m() there is a reference to x Given that x is the variable we want to “remember”, we can see that it continues to live because it is indirectly referenced from the global scope. Also, note that x is defined in m()'s parent scope, not m()'s © 2010 David Semeria Page 12
    • A Simple Closure Global Scope var f = function(x) { var m = function(y) { return x * y; } return m; } var myDouble = f(2); var myTreble = f(3); alert (myDouble(10)) // 20 alert (myTreble(10)) // 30 Each time f() runs, a new scope is created in which a new m() is also created. As long as a reference to a given m() exists in the global scope, then m()'s scope, and that of its parent, continue to exist. In this way, each m() has access to the correct value of x . © 2010 David Semeria Page 13
    • A Simple Closure (digging deeper) var f = function(x) { Global Scope var u; var m = function(y) { return x * y; } return m; } What about the variable u ? Does it still exist within f() 's scope, after f() exits ? © 2010 David Semeria Page 14
    • A Simple Closure (digging deeper) var f = function(x) { Global Scope var u; var m = function(y) { return x * y; } return m; } Probably not. But then again, who cares? The garbage collector will see there are no active references to u and will therefore destroy it. When we say that a scope persists we don't imply that all its contents also do. This is the whole point of garbage collection - it destroys only what we have no way of accessing. © 2010 David Semeria Page 15
    • Using Closures ● Information hiding (encapsulation) ● Queued functions (timers) ● Event Handlers ● Callbacks © 2010 David Semeria Page 16
    • Information Hiding Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE The parameter containing the private information priv is only accessible from the outside via the showPrivate() method. Similarly, the private function c() is only accessible via the privileged method b() - which is exposed as showPrivate(). © 2010 David Semeria Page 17
    • Information Hiding (back-tracing) Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE A useful exercise is to work backwards from the external references and check that all the contents we want to persist are either directly or indirectly referenced from an active scope. © 2010 David Semeria Page 18
    • Information Hiding (back-tracing) Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE Let's start with showPublic() © 2010 David Semeria Page 19
    • Information Hiding (back-tracing) Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE We can see that showPublic() references a() © 2010 David Semeria Page 20
    • Information Hiding (back-tracing) Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE And a() references pub © 2010 David Semeria Page 21
    • Information Hiding (back-tracing) Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE And now showPrivate(), which directly references b(). © 2010 David Semeria Page 22
    • Information Hiding (back-tracing) Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE And b() references c() © 2010 David Semeria Page 23
    • Information Hiding (back-tracing) Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE And c() references priv © 2010 David Semeria Page 24
    • Information Hiding (back-tracing) Global Scope var f = function(priv,pub) { var a = function() { alert(pub) } // public function var b = function() { c() } // privileged function var c = function() { alert(priv)} // private function return { showPublic : a, showPrivate: b } } var obj = f('PUBLIC','PRIVATE'); obj.showPublic(); // alert: PUBLIC obj.showPrivate(); // alert: PRIVATE We can see that all the information within the scope of f() (including its parameters) are referenced either directly or indirectly from the global scope. This is actually no surprise, because if a variable was not accessible from the outside, we wouldn't care whether it existed or not. © 2010 David Semeria Page 25
    • Information Hiding (modify once) var f = function(state) { Global Scope var mod = function(newState){ state = newState; mod = function(){} // redefine mod } var get = function(){ return state } return { modify: function(s){mod(s)}, getState: get } } This function uses a closure to store a state variable which can be modified only once. After the first time state is modified, the function which permits its modification (mod) redefines itself (perhaps to return an error). Further details in the next slide... © 2010 David Semeria Page 26
    • Information Hiding (modify once) var f = function(state) { Global Scope var mod = function(newState){ state = newState; mod = function(){} // redefine mod } var get = function(){ return state } return { modify: function(s){mod(s)}, getState: get } } var obj = f('state_1'); alert(obj.getState()) // state_1 obj.modify('state_2'); alert(obj.getState()) // state_2 obj.modify('state_3'); alert(obj.getState()) // state_2 © 2010 David Semeria Page 27
    • Information Hiding (modify once) var f = function(state) { Global Scope var mod = function(newState){ state = newState; mod = function(){} // redefine mod } var get = function(){ return state } return { modify: function(s){mod(s)}, getState: get } } Care must be taken not to leave dangling references to objects we wish to remove / redefine. This is the reason why mod is exposed as function(s){mod(s)} and not as a simple reference to mod. © 2010 David Semeria Page 28
    • Information Hiding (modify once) var f = function(state) { Global Scope var mod = function(newState){ state = newState; mod = function(){} // redefine mod } var get = function(){ return state } return { modify: function(s){mod(s)}, getState: get } } After mod is redefined, there are no longer any references to the original function, and so its is destroyed by the garbage collector. Hence, this pattern is also useful for removing module initialization code once it has run. © 2010 David Semeria Page 29
    • Using Closures ● Information hiding (encapsulation) ● Queued functions (timers) ● Event Handlers ● Callbacks © 2010 David Semeria Page 30
    • Queued Functions var fade = function(e,delay) { Global Scope e.style.opacity = 1; var proc = function() { if (e.style.opacity >= 0.1) { e.style.opacity -= 0.1; setTimeout(proc,delay); } else { e.style.opacity = 0; } } proc(); } The key point here is that the garbage collector (for very good reasons) considers queued functions (inserted via setTimeout and setInterval) to be part of the global scope. © 2010 David Semeria Page 31
    • Queued Functions var fade = function(e,delay) { Global Scope e.style.opacity = 1; var proc = function() { if (e.style.opacity >= 0.1) { e.style.opacity -= 0.1; setTimeout(proc,delay); } else { e.style.opacity = 0; } } proc(); } This means that the variables we need to remember (e and delay) continue to exist within fade() and proc()'s scopes given the queued reference to proc(). © 2010 David Semeria Page 32
    • Using Closures ● Information hiding (encapsulation) ● Queued functions (timers) ● Event Handlers ● Callbacks © 2010 David Semeria Page 33
    • Event Handlers ( good example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(x+1) } }(i); } } [1] [2] [3] This function takes an element and attaches to each of its child nodes an onclick function which alerts its position in the list (starting from 1). To demonstrate the function working, we create three spans ([1],[2],[3]) and pass their parent node to attach(), as shown next.. © 2010 David Semeria Page 34
    • Event Handlers ( good example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(x+1) } }(i); } } [1] [2] [3] Onclick alert 1 © 2010 David Semeria Page 35
    • Event Handlers ( good example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(x+1) } }(i); } } [1] [2] [3] Onclick alert 3 © 2010 David Semeria Page 36
    • Event Handlers ( good example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(x+1) } }(i); } } [1] [2] [3] This example is often used to show applications of closures in event handlers (we'll see how it works in a moment). In reality, it's not a closure - because the inner function contains no references to the scope of the outer one. The above function actually provides a workaround for an unwanted closure created by an incorrect implementation, which is shown next. © 2010 David Semeria Page 37
    • Event Handlers ( bad example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(){ alert(i+1) } } } [1] [2] [3] Onclick alert 4 © 2010 David Semeria Page 38
    • Event Handlers ( bad example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(){ alert(i+1) } } } [1] [2] [3] Onclick alert 4 © 2010 David Semeria Page 39
    • Event Handlers ( bad example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(){ alert(i+1) } } } [1] [2] [3] Onclick alert 4 © 2010 David Semeria Page 40
    • Event Handlers ( bad example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(){ alert(i+1) } } } [1] [2] [3] This incorrect behaviour stems from the accidental creation of a closure. The i used in each onclick function is a reference to the same i used in the for loop. This reference creates the unwanted closure, meaning the for loop's i continues to live on (with the value which caused the loop to terminate) in each of the created onclick functions. © 2010 David Semeria Page 41
    • Event Handlers ( good example ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(x+1) } }(i); } } [1] [2] [3] Back to the correct version. The unwanted effects of the closure are avoided by passing i to a function which is invoked immediately (note the (i) at the end). This means that the x used in the new onclick's function is bound to the correct value of i each time. And if we really want to use a closure, we can use the example which follows. © 2010 David Semeria Page 42
    • Event Handlers ( using a closure ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { var msg = 'hello from '; e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(msg+(x+1)) } }(i); } return function (m) {msg = m}; } var mod = attach(elem); [1] [2] [3] Onclick alert hello from 3 © 2010 David Semeria Page 43
    • Event Handlers ( using a closure ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { var msg = 'hello from '; e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(msg+(x+1)) } }(i); } return function (m) {msg = m}; } var mod = attach(elem); [1] [2] [3] Here, we create a variable - msg - within the scope of attach() . We then create a closure by referencing msg directly in the onclick function. © 2010 David Semeria Page 44
    • Event Handlers ( using a closure ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { var msg = 'hello from '; e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(msg+(x+1)) } }(i); } return function (m) {msg = m}; } var mod = attach(elem); [1] [2] [3] By returning an access function for msg, we can change it's value from the outside... © 2010 David Semeria Page 45
    • Event Handlers ( using a closure ) var attach = function(e) { Global Scope for (var i=0; i < e.ChildNodes.length; i++) { var msg = 'hello from '; e.ChildNodes.length[i].onclick = function(x){ return function(evt){ alert(msg+(x+1)) } }(i); } return function (m) {msg = m}; } var mod = attach(elem); mod('goodbye from '); [1] [2] [3] Onclick alert goodbye from 3 © 2010 David Semeria Page 46
    • Using Closures ● Information hiding (encapsulation) ● Queued functions (timers) ● Event Handlers ● Callbacks © 2010 David Semeria Page 47
    • Callbacks Global Scope var replace = function(e,ref) { var callback = function(t) { e.innerHTML = t; } makeXHR(callback,ref); // async } replace(elem,ref); © 2010 David Semeria Page 48
    • Callbacks Global Scope var replace = function(e,ref) { var callback = function(t) { e.innerHTML = t; } makeXHR(callback,ref); // async } External reference to callback from global scope replace(elem,ref); By now it should be clear what is happening. The makeXHR() call returns immediately and maintains a reference to callback(), which in turn retains a reference to e. Even though replace() exits immediately, the contents of the closure will persist until they are needed by the callback() function. © 2010 David Semeria Page 49
    • Callbacks Global Scope var replace = function(e,ref) { var callback = function(t) { e.innerHTML = t; } makeXHR(callback,ref); // async } replace(elem,ref); This simple example demonstrates the power of closures. Even though we pass one simple reference to makeXHR(), we can elegantly store as much context-specific information as we like in the closure, safe in the knowledge that it will available when we need it. © 2010 David Semeria Page 50
    • This & That ( overview ) ● Many discussions of closures frequently get bogged down with issues regarding the self-referential object pointer this. Consequently, we have avoided it so far. ● Under certain circumstances, the use of this can lead to unintended behaviour - examples of which are provided in the following sides. ● That said, sometimes this can be very useful (if not essential), and so we'll also present a few examples of how this can usefully be employed in closures. © 2010 David Semeria Page 51
    • This & That ( problems with 'this' ) Global Scope var F = function() { this.x = 1; if (this === window) alert(“'this' is bound to window”); if (this.x) alert(“'this' is bound to new object”); } var obj = new F(); // alert: 'this' is bound to new object var obj = F(); // alert: 'this' is bound to window ● It is important to bear in mind that this is only bound to the object being created by a constructor function when new is used. © 2010 David Semeria Page 52
    • This & That ( object construction ) Global Scope var f = function(a,b,c) { var obj = new function(){ this.a = a; this.b = b; } return { d: obj, c: c } } var newObj = f('a','b','c'); ● For this reason it is often better to enclose new within a general function, which no longer needs to be called using new. Also, objects can easily be created without using new, for example by using object literals {} ● The contrived example above employs both these techniques © 2010 David Semeria Page 53
    • This & That ( when 'this' is useful ) Global Scope var myOnclick = function(evt) { alert(“my id is “ + this.id); } elem.onclick = myOnclick; Sometimes, however, the use of this is either unavoidable or useful (or both). Examples are situations when functions are attached to objects, such as event handlers. But it is important to remember that each function may access a different this – a situation that can lead to unexpected behaviour, as shown next. © 2010 David Semeria Page 54
    • This & That ( bad example ) var multiply = function(list){ Global Scope for (var i=0; i<list.length; i++) { list[i].onclick = function(e){ var newE = document.createElement('div'); this.appendChild(newE); newE.onclick = function(e){ alert('created by '+this.id); //this is wrong } } } elem.onclick = multiply(lst); // lst references 3 blue divs The purpose of this function is to take an array of elements (list) and assign a new onlclick function to each. The onclick function will create a new div within the clicked element and assign to the new div an onclick function which will alert the id of the element which created it. © 2010 David Semeria Page 55
    • This & That ( bad example ) var multiply = function(list){ Global Scope for (var i=0; i<list.length; i++) { list[i].onclick = function(e){ var newE = document.createElement('div'); this.appendChild(newE); newE.onclick = function(e){ alert('created by '+this.id); //this is wrong } } } elem.onclick = multiply(lst); // lst references 3 blue divs Unfortunately, the function does not work as hoped. In the following slide, we create three blue divs and pass them (as the members of list) to the above function. We assume the rightmost blue div has already been clicked, thus creating a new (red) div - which does not report its creator correctly. © 2010 David Semeria Page 56
    • This & That ( bad example ) var multiply = function(list){ Global Scope for (var i=0; i<list.length; i++) { list[i].onclick = function(e){ var newE = document.createElement('div'); this.appendChild(newE); newE.onclick = function(e){ alert('created by '+this.id); //this is wrong } } Onclick alert } Created by undefined elem.onclick = multiply(lst); // lst references 3 blue divs id: blue_div_1 id: blue_div_2 id: blue_div_3 © 2010 David Semeria Page 57
    • This & That ( bad example ) var multiply = function(list){ Global Scope for (var i=0; i<list.length; i++) { list[i].onclick = function(e){ var newE = document.createElement('div'); this.appendChild(newE); newE.onclick = function(e){ alert('created by '+this.id); //this is wrong } } } elem.onclick = multiply(lst); // lst references 3 blue divs The reason for the incorrect behaviour stems from the fact that the innermost onclick function also creates its own this – thereby obscuring the this we were hoping to use from the outer scope. © 2010 David Semeria Page 58
    • This & That ( good example ) var multiply = function(list){ Global Scope for (var i=0; i<list.length; i++) { list[i].onclick = function(e){ var newE = document.createElement('div'); this.appendChild(newE); var that = this; newE.onclick = function(e){ alert('created by '+that.id); //this is right } } } elem.onclick = multiply(lst); // lst references 3 blue divs Fortunately, the solution is straightforward. We merely create a reference to the outer this – by convention named that – which will be available to the inner scope. © 2010 David Semeria Page 59
    • This & That ( good example ) var multiply = function(list){ Global Scope for (var i=0; i<list.length; i++) { list[i].onclick = function(e){ var newE = document.createElement('div'); this.appendChild(newE); var that = this; newE.onclick = function(e){ alert('created by '+that.id); //this is right } } Onclick alert } Created by blue_div_3 elem.onclick = multiply(lst); // lst references 3 blue divs id: blue_div_1 id: blue_div_2 id: blue_div_3 © 2010 David Semeria Page 60
    • Comments are welcome, please leave them here David Semeria http://lmframework.com February 2010 http://twitter.com/hymanroth © 2010 David Semeria Page 61