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

JavaScript Scopes/Closures 2015

adbreind
October 15, 2015

JavaScript Scopes/Closures 2015

As presented at the South Bay JS Meetup

adbreind

October 15, 2015
Tweet

More Decks by adbreind

Other Decks in Technology

Transcript

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

    No More Waffling Explanations v  Examples Are Not Definitions v  … All Mysteries Will Be Revealed! Adam Breindel [email protected]
  2. why? •  Scope chains and closures are not op4onal – you

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

    has a global scope –  iden4cal 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 func4on they are declared within •  Func4on declara4ons (but not expression-assignments) produce local scope variables •  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 resolu4on
  4. the big scope rules summary (2) •  "Hois4ng": before a

    func4on is executed, its body is parsed/scanned for declara4ons (i.e., var & func4on declara4on) –  resul4ng local alloca4ons are created in a local scope object before any code in the func4on body is run •  var declara4ons create a local scope variable but do not assign a value •  func4on declara4ons create a local variable and a func4on object and bind the variable to the func4on (value) at hois4ng 4me •  Hois4ng disregards program flow logic; logically unreachable declara4ons can s4ll be "hoisted" and affect your program! •  Notes: –  Redeclara4ons of the same name in same scope are no-ops –  We are ignoring the complica4on of the deprecated "with" feature –  We will talk about ES6/2015 const, let, class, and func4on* scoping later –  func4on declara4ons 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
  5. 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>
  6. 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"
  7. 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"
  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 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"
  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 Prepping to enter "f" … set up scope for this execution of "f" lambda "f" Local scope for execution of f (#1) Key Value
  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 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"
  11. 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
  12. 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
  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 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
  14. •  Where does the concept of closures come from? – 

    Mo4vated by a ques4on: What should happen (i.e., what shall we define our programming language to do) when a func:on 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 resolu4on 3.  "You can do that and…" the original scope chain resolu4on will s4ll work! closure answer closures – whence?
  15. What is a closure? •  Closure is an implicit, permanent

    link between a func:on and its scope chain What does it mean? Why is this powerful? •  Any:me you execute a func:on, it runs with a scope chain based on where it was defined (not where it is run) PreBy slick… how? •  The func)on's hidden [[scope]] reference –  holds the scope chain (preven1ng garbage collec1on) –  is copied as the new scope's "outer environment reference" any1me the func1on is run, recrea1ng the original chain closures – what? and how?
  16. 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" [[scope]] << COPY << One easy way to implement closures: just copy the [[scope]] reference from a function, to create the "outer environment reference" on the new scope [[scope]]
  17. 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" [[scope]] [[scope]]
  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 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 [[scope]] [[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> 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 [[scope]] [[scope]]
  20. •  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 s4ll around to be used later (line 4): •  Closures: what are they good for? –  "Implicit": Retain state from async invoca4on to response –  "Explicit": Strongest encapsula4on 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);
  21. closures and the module paBern •  Aside from the implicit

    everyday necessity of closures to make async programming work the way we expect, the most useful "applica4on" of closures is the module paBern •  JS doesn't have strong object, module, or file encapsula4on –  The one strong encapsula4on we have is func4on scopes –  So we create a func4on and use its scope as our mechanism of encapsula4on – the scope contains effec4vely private locals –  To define and expose an interface, we return an object which has – via scope rules – access to these internal locals
  22. 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 paBern: separa4ng the interface from the implementa4on!
  23. addi4onal links on closures •  Explana4on of JavaScript closures as

    with reference to the ECMAScript spec –  hBp://lostechies.com/derekgreer/2012/02/17/javascript- closures-explained/ •  A brief descrip4on and history of closures (in general, not just JavaScript) –  hBp://www.jdl.co.uk/briefings/closures.html •  More general theory –  Structure and Interpreta1on of Computer Programs, especially •  hBp://mitpress.mit.edu/sicp/full-text/book/book-Z-H-21.html# %_sec_3.2.1
  24. why is this useful? •  Scope chains and closures underlie…

    – many constructor-based paBerns – object defini4on paBerns in many libraries – events and callbacks – more advanced async control flow paBerns – modules and dependency management – func4onal programming paBerns – promises and other monads – and much more…
  25. scope and ECMAScript 6 •  ES6 introduces addi4onal declara4on paBerns

    –  let, const –  func4on* (generator) –  class •  What about everything we’ve just seen? –  Everything we’ve seen so far is s4ll true –  ES6 does not require strict mode everywhere, but strict mode usage is more prevalent with ES6 •  Strict mode modifies a few of the rules we’ve talked about •  See MDN: hBps://developer.mozilla.org/en-US/docs/Web/ JavaScript/Reference/Strict_mode
  26. let and const •  let & const declare block-scoped (as

    opposed to func4on-scoped) variables/constants •  Blocks are generally delimited by { } –  (there is an excep4on but it is not relevant here) •  How would this work, in a naïve implementa4on? Key Value this * arguments myVar Execu4ng demo() creates a scope…
  27. let and const •  let & const declare block-scoped (as

    opposed to func4on-scoped) variables/constants •  Blocks are generally delimited by { } –  (there is an excep4on but it is not relevant here) •  How would this work, in a naïve implementa4on? Key Value this * arguments myVar “foo” Assigning “foo” associates a value with myVar
  28. let and const •  let & const declare block-scoped (as

    opposed to func4on-scoped) variables/constants •  Blocks are generally delimited by { } –  (there is an excep4on but it is not relevant here) •  How would this work, in a naïve implementa4on? Key Value this * arguments myVar “foo” Block adds a new scope object to the chain… Key Value myVar Outer env ref to current scope
  29. but... does it blend? er, hoist? •  Technically let and

    const do hoist… – “The variables are created when their containing Lexical Environment is instan4ated…” •  BUT (this is a huge one) – “…but may not be accessed in any way un4l the variable’s LexicalBinding is evaluated.” •  What does this mean? – ReferenceError referring to these vars before the let/const declara4on
  30. temporal dead zone •  Between… – the beginning of the scope

    (block) – and the declara4on (let/const) •  You can’t use the variables in any way – Or you get a ReferenceError – This is known as the Temporal Dead Zone Block start Declara4on Ok! FAIL! TDZ!
  31. what about class declara4ons? •  ES6 has class declara4ons – (but

    no “actual” classes) – (huh? class actually generates a func4on) – (s4ll quite useful) •  Scope rules?? – Treated like let/const – i.e., technically hoisted but unusable in the TDZ