Mais conteúdo relacionado
Semelhante a Closures in Javascript (20)
Closures in Javascript
- 1. 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
- 2. Core Concepts
Although Javscript has block syntax { }, only
functions can create a new scope within a global
scope.
© 2010 David Semeria Page 2
- 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.
© 2010 David Semeria Page 3
- 4. 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
- 5. 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
- 6. 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
- 7. 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
- 8. 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
- 9. 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
- 10. 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
- 11. 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
- 12. 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
- 13. 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
- 14. 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
- 15. 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
- 16. Using Closures
● Information hiding (encapsulation)
● Queued functions (timers)
● Event Handlers
● Callbacks
© 2010 David Semeria Page 16
- 17. 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
- 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
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
- 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
Let's start with showPublic()
© 2010 David Semeria Page 19
- 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
We can see that showPublic() references a()
© 2010 David Semeria Page 20
- 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 a() references pub
© 2010 David Semeria Page 21
- 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 now showPrivate(), which directly references b().
© 2010 David Semeria Page 22
- 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 b() references c()
© 2010 David Semeria Page 23
- 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
And c() references priv
© 2010 David Semeria Page 24
- 25. 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
- 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 }
}
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
- 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 }
}
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
- 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 }
}
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
- 29. 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
- 30. Using Closures
● Information hiding (encapsulation)
● Queued functions (timers)
● Event Handlers
● Callbacks
© 2010 David Semeria Page 30
- 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();
}
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
- 32. 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
- 33. Using Closures
● Information hiding (encapsulation)
● Queued functions (timers)
● Event Handlers
● Callbacks
© 2010 David Semeria Page 33
- 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]
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
- 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
1
© 2010 David Semeria Page 35
- 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]
Onclick alert
3
© 2010 David Semeria Page 36
- 37. 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
- 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 38
- 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 39
- 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]
Onclick alert
4
© 2010 David Semeria Page 40
- 41. 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
- 42. 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
- 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]
Onclick alert
hello from 3
© 2010 David Semeria Page 43
- 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]
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
- 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);
[1] [2] [3]
By returning an access function for msg, we can change it's value from the
outside...
© 2010 David Semeria Page 45
- 46. 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
- 47. Using Closures
● Information hiding (encapsulation)
● Queued functions (timers)
● Event Handlers
● Callbacks
© 2010 David Semeria Page 47
- 48. 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
- 49. 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
- 50. 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
- 51. 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
- 52. 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
- 53. 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
- 54. 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
- 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
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
- 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
}
}
}
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
- 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
}
} 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
- 58. 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
- 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
}
}
}
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
- 60. 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
- 61. Comments are welcome, please leave them here
David Semeria http://lmframework.com
February 2010 http://twitter.com/hymanroth
© 2010 David Semeria Page 61