Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript Scope Chains and Closures

adbreind
June 11, 2014

JavaScript Scope Chains and Closures

adbreind

June 11, 2014
Tweet

More Decks by adbreind

Other Decks in Technology

Transcript

  1. JS Scope Chains and Closures  End the Handwaving 

    No More Waffling Explanations  Examples Are Not Definitions  … All Mysteries Will Be Revealed! Adam Breindel [email protected] SFJS Meetup #44 10 June 2014
  2. goal • At the end of this talk, my goal

    is for you to – understand scopes, scope chains, and closures – and be able easily and quickly to explain them to other developers – without resorting to any "hand-waving" • http://c2.com/cgi/wiki?HandWaving
  3. why? • Scope chains and closures are not optional –

    you don't get to "opt in" – there is no syntax in JavaScript for these features – these features are in effect all of the time • Without them, lots of "plain" JS wouldn’t work • Without a working knowledge, most advanced JavaScript patterns are hard or impossible to understand • Spec is not designed as a teaching document
  4. the big scope rules summary (1) • Every JS runtime

    has a global scope – identical with the global object – window in browsers, global in node, self in a worker… • Local variables are declared with "var" and are scoped to the function they are declared within • Function declarations (but not expressions) produce local bindings, and have priority over var in the same scope • When a variable is accessed (declared or not), JS resolves it by looking in the current scope for a variable with that name – If it fails to find one, it looks through the enclosing scopes in order from innermost to global – As soon as it finds a variable with that name it stops and uses that one • If it doesn't find any variable by the end, it produces a ReferenceError (for reads) or "accidentally" (implicitly) creates a global variable (for writes) • As a consequence, if an outer scope variable is "shadowed" by a closer (inner) variable of the same name, the outer will not be accessible by scope resolution
  5. the big scope rules summary (2) • "Hoisting": before a

    function is executed, its body is parsed/scanned for declarations (i.e., var & function declaration) – resulting local allocations are created in a local scope object before any code in the function body is run • var declarations create a local scope variable but do not assign a value • function declarations create a local variable and a function object and bind the variable to the function (value) at hoisting time • Hoisting disregards program flow logic; logically unreachable declarations can still be "hoisted" and affect your program! • Notes: – Redeclarations of the same name in same scope are no-ops – We are ignoring the complication of the deprecated "with" feature – ES6 allows block scoped variables with new "let" keyword – function declarations in a block have varied treatment in browsers (e.g., as of June 2014, Chrome/Firefox treat them differently! Recommend avoiding…) – For more, see Kyle Simpson's most excellent YDKJS: Scopes & Closures
  6. Key Value Global Scope (Window) Scope Chains and Closures This

    will make sense … … if we go through it step by step! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script>
  7. Key Value b f Global Scope (Window) Scope Chains and

    Closures Initial hoisting… 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script> lambda "f"
  8. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b f Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script> Scope Chains and Closures Running global scope code (starting at line 2) lambda "f"
  9. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 f a 1 Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script> Scope Chains and Closures Running global scope code Ready to run line 21 (execute "f") lambda "f"
  10. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 f a 1 Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script> Scope Chains and Closures Prepping to enter "f" … set up scope for this execution of "f" lambda "f" Local scope for execution of f (#1) Key Value
  11. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 f a 1 Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script> Scope Chains and Closures Ready to enter "f" (line 6) lambda "f" Key Value z 1 this * arguments d e g Local scope for execution of f (#1) pseudoArr [1] lambda "g"
  12. Key Value this arguments e 1 2 3 4 5

    6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 3 f a 1 c 4 Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script> Scope Chains and Closures Prepping to execute "g" (line 17) lambda "f" Key Value z 1 this * arguments d 5 e 6 g Local scope for execution of f (#1) pseudoArr [1] lambda "g" Local scope for execution of g (#1) pseudoArr inaccessible in g
  13. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 3 f a 1 c 4 Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script> Scope Chains and Closures Ready to return from "g" (and "f") line 14 lambda "f" Key Value z 1 this * arguments d 5 10 e 6 g Local scope for execution of f (#1) pseudoArr [1] lambda "g" Key Value this arguments e 0 Local scope for execution of g (#1) pseudoArr inaccessible in g
  14. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 3 f a 1 c 4 Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); var e; } f(1); // returns 10; </script> Scope Chains and Closures Script is completed… So what could (theoretically) get garbage collected at this point? lambda "f" Key Value z 1 this * arguments d 5 10 e 6 g Local scope for execution of f (#1) pseudoArr [1] lambda "g" Key Value this arguments e 0 Local scope for execution of g (#1) pseudoArr inaccessible in g
  15. • Where does the concept of closures come from? –

    Motivated by a question: What should happen (i.e., what shall we define our programming language to do) when a function is executed outside of its original scope chain? • Many possible answers! 1. "You can't do that!" or we'll throw an error 2. "You can do that but…" you must provide bindings for any free variables, in place of the scope chain resolution 3. "You can do that and…" the original scope chain resolution will still work! closure answer closures – whence?
  16. What is a closure? • Closure is an implicit, permanent

    link between a function and its scope chain What does it mean? Why is this powerful? • Anytime you execute a function, it runs with a scope chain based on where it was defined (not where it is run) Pretty slick… how? • The function's hidden [[scope]] reference – holds the scope chain (preventing garbage collection) – is copied as the new scope's "outer environment reference" anytime the function is run, recreating the original chain closures – what? and how?
  17. • Without closures, we would not be able to leverage

    scope chain rules with asynchronous callbacks! – e.g., this could fail because without a closure "rule" there's no guarantee data (line 2) is still around to be used later (line 4): • Closures: what are they good for? – "Implicit": Retain state from async invocation to response – "Explicit": Strongest encapsulation mechanism in JS (see modules) • Scopes persist as long as anything has a handle to them • So scope chains can be a source of memory leaks if – We hold them unnecessarily – Or we hold data in them which we don’t need later • How to fix? "unhook" (set to null) handles to unneeded data closures – why? 1 2 3 4 5 //assume we are not in the global scope... var data = "some info"; setTimeout(function() { console.log(data); }, 1000);
  18. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 3 f a 1 c 4 Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); // no call operator var e; } myG = f(1); // returns a function ("g") myG(); // returns 10; </script> Let's change things a little bit… add our new knowledge, run through line 19 and then prep a local scope for executing f: lambda "f" Key Value z 1 this * arguments d e g Local scope for execution of f (#1) pseudoArr [1] lambda "g" One easy way to implement closures: just copy the [[scope]] reference from a function, to create the "outer environment reference" on the new scope
  19. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 3 f a 1 c 4 myG Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); // no call operator var e; } myG = f(1); // returns a function ("g") myG(); // returns 10; </script> Run through line 20… Now we know "f" and "g" are both holding [[scope]] handles… so nothing can get garbage collected lambda "f" Key Value z 1 this * arguments d 5 e 6 g Local scope for execution of f (#1) pseudoArr [1] lambda "g"
  20. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 3 f a 1 c 4 myG Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); // no call operator var e; } myG = f(1); // returns a function ("g") myG(); // returns 10; </script> Now we can run line 21 … and copy "g.[[scope]]" to create proper scope chain for a new local scope (for executing "myG"/"g") lambda "f" Key Value z 1 this * arguments d 5 10 e 6 g Local scope for execution of f (#1) pseudoArr [1] lambda "g" Key Value this arguments e 0 Local scope for execution of g (#1) pseudoArr inaccessible in g
  21. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 21 22 Key Value b 2 3 f a 1 c 4 myG Global Scope (Window) <script> a = 1; var b = 2; function f(z) { b = 3; c = 4; var d = 5; e = 6; function g() { var e = 0; d = 2*d; return d; } return g(); // no call operator var e; } myG = f(1); // returns a function ("g") myG(); // returns 10; </script> Scope Chains and Closures And now when the script is finished, what could get collected? lambda "f" Key Value z 1 this * arguments d 5 10 e 6 g Local scope for execution of f (#1) pseudoArr [1] lambda "g" Key Value this arguments e 0 Local scope for execution of g (#1) pseudoArr inaccessible in g
  22. closures and the module pattern • Aside from the implicit

    everyday necessity of closures to make async programming work the way we expect, the most useful "application" of closures is the module pattern • JS doesn't have strong object, module, or file encapsulation – The one strong encapsulation we have is function scopes – So we create a function and use its scope as our mechanism of encapsulation – the scope contains effectively private locals – To define and expose an interface, we return an object which has – via scope rules – access to these internal locals
  23. myAccessors = (function() { var data = "interesting data"; //

    effectively private //put in any other private data, helper functions, etc! var getData = function() { console.log(data); // data resolves to outer scope }; var setData = function(val) { data = val; // data resolves to outer scope }; return { getData : getData, setData : setData }; })(); // "IIFE": just a func expr that we call right away myAccessors.getData(); // returns "interesting data" myAccessors.setData("new info"); myAccessors.getData(); // returns "new info" module pattern: separating the interface from the implementation!
  24. additional links on closures • Explanation of JavaScript closures as

    with reference to the ECMAScript spec – http://lostechies.com/derekgreer/2012/02/17/jav ascript-closures-explained/ • A brief description and history of closures (in general, not just JavaScript) – http://www.jdl.co.uk/briefings/closures.html
  25. why is this useful? • Scope chains and closures underlie…

    – many constructor-based patterns – object definition patterns in many libraries – events and callbacks – more advanced async control flow patterns – modules and dependency management – functional programming patterns – promises and other monads – and much more…
  26. what next? • Spread the word! • If this presentation

    is clear and helpful… – spend a little time to get more familiar – present it at a brown-bag lunch at your company – present it at a user group or school • It is hard to take a language seriously if it seems full of mysteries which no one can easily explain • The more we explain and de-mystify JavaScript, the better it is for the whole community!