function infiniteSequence() {    var i = 0;    return function() {        return i++;    }}var increment = infiniteSequenc...
WARNING!!            2
WARNING� 非常非常的学术性� 大量术语词汇出没� 也许永远都用不上� 内容并非标准,有所删减� 逻辑不是那么清晰� 只谈函数,不提eval,不提new   Function
Summary� 引言� 什么是变量� 闭包之表象� 闭包之内在� 关于垃圾回收
作用域的历史                      Scope Chain  ECMAScript                    Variable Object     v3                  Identifier ...
什么是变量A symbolic name associated with a value and whoseassociated value may be changed.                                    ...
什么是变量� Variable   Statement � var   Identifier = AssignmentExpression� FunctionDeclaration � function   Identifier(FormalP...
什么是变量                Value (String Literal)          var name = ‘GrayZhang’;                                     Identifie...
11闭包之表象� 内层函数可以使用外层函数作用域内的变量function outer() {                                        外层    var name = ‘GrayZhang’;    fun...
闭包之内在�   Q:为什么javascript 会有闭包?�   A:因为ECMAScript 中变量解析是一个查找过程,而非绑定    过程。�   Q:变量存放在哪里?�   A:Execution Context      Execut...
?
可执行代码(Executable Code) Global Code                         Function Code<script>                          function sayHell...
15执行环境(Execution Context)� 当进入(开始执行)一段可执行代码时,生成  一个执行环境对象。� 执行环境对象通过栈(Stack)维护。� 新建的执行环境对象称为“当前运行的执行环  境对象”。function enter...
执行环境(Execution Context)� 一个执行环境对象包括: � 词法环境   – LexicalEnvironment � 变量环境 – VariableEnvironment � This绑定 - ThisBindingExec...
词法环境(LexicalEnvironment)� 既是一个属性,又是一个类型。� 每个执行环境对象都有且仅有一个关联的词法  环境对象。� 在代码执行过程中,需要解析变量时,通过词  法环境对象进行解析,从其环境数据中得到值。� 一个词法环境...
词法环境(LexicalEnvironment)� 存在2种词法环境的实现类型 � DeclarativeEnvironment � ObjectEnvironment� 区别是ObjectEnvironment可接受指定的对 象作为环境数据属...
变量环境(VariableEnvironment)� 每个执行环境对象都有且仅有一个关联的变量  环境对象。� 变量环境仅仅是一个名字,变量环境对象的类  型是词法环境(LexicalEnvironment)。� 在进入(开始执行)代码时,所有...
环境数据(environment                      records)�   存在于词法环境或变量环境中。�   包含一个binding object,简单地认为binding object    是一个Map对象,保存变...
环境数据(environment                                 records)EnvironmentRecords: {    bindingObject: {},    hasBinding: functi...
环境数据(environment                        records)� 存在2种环境数据的实现类型 � DeclarativeEnvironmentRecords � ObjectEnvironmentRecords...
创建词法环境� NewDeclarativeEnvironment(e) � 用于创建一个DeclarativeEnvironment � 进入函数时 � 执行catch表达式时� NewObjectEnvironment(o,   e) � ...
创建词法环境function NewDeclarativeEnvironment(e) {    var env = new LexicalEnvironment();    var envRec = new EnvironmentRecord...
消化一下• 名词解释完了吗?        NO
总结ExecutionContext: {    LexicalEnvironment,    VariableEnvironment,   EnvironmentRecords: {    ThisBinding              h...
函数(Function)�   是一个对象(Object)。�   包含几个特殊的属性    �   [[Construct]] – new SomeFunction()    �   [[Call]] – someFunction()    ...
创建函数(Create Function Object)� 新建一个普通对象(new        Object())� 将[[Class]]设为”function”� 将[[Prototype]]指向Function.prototype� 根...
创建函数(Create Function Object)var fn = new Object();// [[DefaultValue]], [[HasProperty]], etc...initializeAsObject(fn);fn.[[...
创建函数(Create Function Object)� 作用域([[Scope]])是函数对象的一个属性function hello() {          function outer() {    var o = {};       ...
进入函数(Entering Function Code)�   新建一个执行环境。�   根据规则设置this绑定。    �   如果thisArg是null或undefined,设置为global。    �   如果thisArg不是Ob...
进入函数(Entering Function Code)var ec = new ExecutionContext();if (thisArg == null) {    thisArg = global;}if (typeof thisArg...
进入函数(Entering Function Code)� 执行函数时,在作用域([[Scope]])的基础 上添加词法环境(LexicalEnvironment)// Global Environmentfunction outer() { ...
定义绑定初始化                          34 (Declaration Binding Instantiation)� 从Hosting       Behavior说起……function sayHello(name...
定义绑定初始化    (Declaration   Binding Instantiation )�   遍历FormalParameterList(参数列表),对每一项    (参数),如果VariableEnvironment中不存在,则 ...
定义绑定初始化   (Declaration Binding             Instantiation)function format(template, data) {                      arguments ...
消化一下• 还有完没完了!        NO
变量查找� GetIdentifierReference(lex,                        name)� 从给定的LexicalEnvironment中查找是否存  在该变量� 如果不存在,则从LexicalEnviron...
变量查找function GetIdentifierReference(lex, name) {    if (lex == null) {        return new Reference(name, undefined);    } ...
大串烧�   进入全局环境    � 创建全局执行环境并入栈    � 创建全局环境对象    � 全局的词法环境对象指向该对象    � 全局的变量环境对象指向该对象    � 在变量环境中添加outer绑定并赋值              ...
大串烧�   创建outer函数    � 创建一个对象    � 设置[[Call]]、[[Construct]]、[[HasInstance]]等    � 设置[[Scope]]为当前词法环境 – 全局环境    � 设置[[Code]]...
大串烧                              Global Environment                           outer: { [[Scope]] }                        ...
大串烧�   为prefix变量赋值    � 在全局环境中寻找name绑定 – 找到    � 得到上一步返回的Reference的base – 即全局环境      的环境数据对象    � 调用其setBinding(‘prefix’, ...
大串烧                              Global Environment                           outer: { [[Scope]] }                        ...
大串烧�   执行outer函数    � 创建执行环境并入栈    � 创建一个词法环境 – DeclarativeEnvironment    � outer的词法环境对象指向该对象    � outer的变量环境对象指向该对象    � ...
大串烧�   创建say函数    � 创建一个对象    � 设置[[Call]]、[[Construct]]、[[HasInstance]]等    � 设置[[Scope]]为当前词法环境 – outer的词法环境    � 设置[[Co...
大串烧                              Global Environment                           outer: { [[Scope]] }                        ...
大串烧�   为name变量赋值    � 在outer的词法环境中寻找name绑定 – 找到    � 得到上一步返回的Reference的base – 即outer的词      法环境的环境数据对象    � 调用其setBinding(...
大串烧                              Global Environment                           outer: { [[Scope]] }                        ...
大串烧�   返回并赋值给inner变量    �   将outer的ExecutionContext出栈    �   在全局环境下寻找inner绑定 – 找到    �   得到上一步返回的Reference的base – 即全局环境   ...
大串烧                              Global Environment                           outer: { [[Scope]] }                        ...
大串烧�   执行inner函数    � 创建执行环境并入栈    � 创建一个词法环境 – DeclarativeEnvironment    � inner的词法环境对象指向该对象    � inner的变量环境对象指向该对象    � ...
大串烧                              Global Environment                           outer: { [[Scope]] }                        ...
大串烧�   为message变量赋值    � 查找prefix变量的值        � 在inner的词法环境中寻找prefix绑定 – 没有        � 在outer的词法环境中寻找prefix绑定 – 没有        � 在...
大串烧                              Global Environment                           outer: { [[Scope]] }                        ...
大串烧�   获取inner的值    �   在inner的词法环境中寻找message绑定 – 找到    �   得到上一步返回的Reference的base – 即inner的词法环        境的环境数据对象    �   调用该...
大串烧function born() {    var name = unknown;    var age = 1;    return {        setName: function(value) { name = value; },...
总结� 相关概念 � 可执行代码    – Executable Code � 执行环境   – Execution Context � 词法环境   – LexicalEnvironment � 变量环境   – VariableEnviro...
总结� 过程 � 创建函数   – [[Scope]]  � [[Scope]]在创建时决定且不会变化 � 进入函数   – 执行环境 + 词法环境 + 变量环境  � 执行时在最内层增加词法环境 � 定义绑定初始化     – 参数 + 函数...
继续消化• 我以为我懂了,直到…… – How with works – How catch works – How let works – When code meets eval – When code meets new Function...
从代码说起function outer() {    var o = LargetObject.fromSize(400MB);    return function() {        console.log(inner);    };}v...
但是事实上……function outer() {    var i = 3;    return function() {        debugger;    };}var inner = outer();inner();javascri...
如果你是计算机……function outer() {    var i = 3;             i:         不可回收    var j = 4;    var k = 5;             j:         不...
~          ~          ~          ~     大大      大      大   好好    好    好 力力  力  力压压压压
测试方法� 用断点! � Chrome    / Firefox� 看内存! � IE   / Opera
一些基本结果� IE6 – 8没有回收闭包内变量的机制� Opera没有回收闭包内变量的机制� Chrome回收闭包内变量后,再次访问该变量  将抛出ReferenceError       ReferenceError� Firefox回收闭...
试问!� 有哪些因素可能导致变量无法回收? � 变量被返回的函数直接引用。 � 变量被返回的函数间接引用(通过嵌套函数)。 � 返回的函数中有eval          eval          eval。 � 返回的函数在with     ...
直接引用Engine                    CollectableChrome – V8               NOFirefox – SpiderMonkey    NOIE9 - Chakra             ...
间接引用Engine                    CollectableChrome – V8               NOFirefox – SpiderMonkey    NOIE9 - Chakra             ...
嵌套函数的平衡function outer() {                function outer() {    var i = 0;                        var i = 0;    function he...
71嵌套函数的平衡Engine                    CollectableChrome – V8               NOFirefox – SpiderMonkey    NOIE9 - Chakra        ...
大恶魔evalfunction outer() {    var i = 3;                     ?    return function() {        return eval(‘i’);    }}var inn...
大恶魔evalvar reference = eval(‘someObject’);                                             字符串分析var reference = eval(‘some’ + ...
大恶魔evalfunction outer() {    var i = 3;    return function(variableName) {        return eval(variableName);    }}var inne...
囧
..                             .                             .                          ve                          ve    ...
大恶魔evalEngine                       CollectableChrome – V8                  NOFirefox – SpiderMonkey       NOIE9 - Chakra ...
间接eval和new Function� 间接eval �   window.eval(coe) | (1, eval)(code) | (true && eval)(code) �   In Edition 5, indirect calls...
间接eval和new Functionvar i = 3;function outer() {    var i = 4;    return function() {                       X        return...
间接eval和new FunctionEngine                      CollectableChrome – V8                 YESFirefox – SpiderMonkey      YESIE...
关于with的分歧function outer() {                              ?    var scope = { i: 3, j: 4 };    var m = 4;    var n = 5;     ...
关于with的分歧Engine                            CollectableChrome – V8                       NOFirefox – SpiderMonkey          ...
不被重视的catchEngine                          CollectableChrome – V8                     回收i,不回收ex                            ...
你能骗过引擎吗?function outer() {    var i = 3;    var j = 4;           ?   return function( i) {       var j = 5;       console....
总结�   outer声明的 – c1 = (i, j, k, m)�   inner声明的 – c2 = (i, j)�   inner用到的 – c3 = (i, j, k)                                 ...
谢   谢
知识要点• 变量声明在变量环境中,从词法环境中获  取,通常2者是同一个对象。• 作用域在函数创建时生成,是函数对象的  不变的属性 – 静。• 执行函数时,在作用域上增加一个词法环  境对象 – 动。• 动静结合即闭包的本质。• 闭包对垃圾回...
参考资料• Annotated ES5  – http://es5.github.com/    http://es5.github.com/• ECMA-262-5 in detail  – http://dmitrysoshnikov.co...
所谓闭包
所谓闭包
Upcoming SlideShare
Loading in...5
×

所谓闭包

1,127

Published on

Published in: Spiritual, Technology, Business
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
1,127
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
4
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

所谓闭包

  1. 1. function infiniteSequence() { var i = 0; return function() { return i++; }}var increment = infiniteSequence();console.log(increment());console.log(increment()); 所谓闭包console.log(increment()); 张立理// … otaksutay@gmail.com
  2. 2. WARNING!! 2
  3. 3. WARNING� 非常非常的学术性� 大量术语词汇出没� 也许永远都用不上� 内容并非标准,有所删减� 逻辑不是那么清晰� 只谈函数,不提eval,不提new Function
  4. 4. Summary� 引言� 什么是变量� 闭包之表象� 闭包之内在� 关于垃圾回收
  5. 5. 作用域的历史 Scope Chain ECMAScript Variable Object v3 Identifier Resolution Lexical Environment ECMAScript v5 Variable Environment GetIdentifierReference
  6. 6. 什么是变量A symbolic name associated with a value and whoseassociated value may be changed. -- Wikipedia� 变量是一种关联关系(association)� 关联的目标: � 符号名(symbolic name) � 值(value)� 关联是单向的 – 永远不可能根据值找到变量 Identifier Value name ‘GrayZhang’
  7. 7. 什么是变量� Variable Statement � var Identifier = AssignmentExpression� FunctionDeclaration � function Identifier(FormalParameterList) { FunctionBody } � FormalParameterList � Identifier, Identifier[, …]
  8. 8. 什么是变量 Value (String Literal) var name = ‘GrayZhang’; IdentifierKeyword function add(x, y) { return x + y; }
  9. 9. 11闭包之表象� 内层函数可以使用外层函数作用域内的变量function outer() { 外层 var name = ‘GrayZhang’; function inner() { console.log(‘Hello ‘ + name); 内层 } inner();}
  10. 10. 闭包之内在� Q:为什么javascript 会有闭包?� A:因为ECMAScript 中变量解析是一个查找过程,而非绑定 过程。� Q:变量存放在哪里?� A:Execution Context Execution Context中的VariableEnvironment VariableEnvironment VariableEnvironment。� Q:从哪里查找变量?� A:Execution Context Execution Context中的LexicalEnvironment LexicalEnvironment LexicalEnvironment。� Q:如何查找变量?� A:自内向外。
  11. 11.
  12. 12. 可执行代码(Executable Code) Global Code Function Code<script> function sayHello(name) {var name = ‘GrayZhang’; var prefix = ‘Hello’;var prefix = ‘Hello‘; var phrases = [prefix, name];var phrases = [prefix, name]; console.log(phrases.join(‘ ‘));console.log(phrases.join(‘ ‘)); }</script> function getName() { var input = $(‘#name’); Eval Code return input.val(); var source = } ‘var x = 3;’ + ‘console.log(x);’ getName(); eval(source);
  13. 13. 15执行环境(Execution Context)� 当进入(开始执行)一段可执行代码时,生成 一个执行环境对象。� 执行环境对象通过栈(Stack)维护。� 新建的执行环境对象称为“当前运行的执行环 境对象”。function enterCode(code) { var ec = new ExecutionContext(); control.ecStack.push(ec); control.runningEC = ec; control.execute(code);}
  14. 14. 执行环境(Execution Context)� 一个执行环境对象包括: � 词法环境 – LexicalEnvironment � 变量环境 – VariableEnvironment � This绑定 - ThisBindingExecutionContext: { LexicalEnvironment, VariableEnvironment, ThisBinding}
  15. 15. 词法环境(LexicalEnvironment)� 既是一个属性,又是一个类型。� 每个执行环境对象都有且仅有一个关联的词法 环境对象。� 在代码执行过程中,需要解析变量时,通过词 法环境对象进行解析,从其环境数据中得到值。� 一个词法环境对象包括: � 环境数据 – environement records � 外层环境 – outer environment
  16. 16. 词法环境(LexicalEnvironment)� 存在2种词法环境的实现类型 � DeclarativeEnvironment � ObjectEnvironment� 区别是ObjectEnvironment可接受指定的对 象作为环境数据属性的值 ?什么情况会出现 ObjectEnvironment
  17. 17. 变量环境(VariableEnvironment)� 每个执行环境对象都有且仅有一个关联的变量 环境对象。� 变量环境仅仅是一个名字,变量环境对象的类 型是词法环境(LexicalEnvironment)。� 在进入(开始执行)代码时,所有的变量标识 符(Identifier)会存放在当前的变量环境对 象中。� 变量环境中有环境数据属性,但不使用外层环 境属性。
  18. 18. 环境数据(environment records)� 存在于词法环境或变量环境中。� 包含一个binding object,简单地认为binding object 是一个Map对象,保存变量标签符(Identifier)和变 量值(Value)的关系。� 常用方法: � hadBinding(name) – 查看是否有变量绑定 � createBinding(name) – 创建一个变量绑定 � setBinding(name, value) – 修改变量绑定的值 � getValue(name) – 获取变量绑定的值 � deleteBinding(name) – 删除一个变量绑定
  19. 19. 环境数据(environment records)EnvironmentRecords: { bindingObject: {}, hasBinding: function(name) { return (name in this.bindingObject); }, createBinding: function(name) { this.bindingObject[name] = undefined; }, setBinding: function(name, value) { this.bindingObject[name] = value; }, // …}
  20. 20. 环境数据(environment records)� 存在2种环境数据的实现类型 � DeclarativeEnvironmentRecords � ObjectEnvironmentRecordsLexicalEnvironment EnvironmentRecords DeclaractiveEnvironmen DeclaractiveEnvironmentRecord t s ObjectEnvironment ObjectEnvironmentRecords
  21. 21. 创建词法环境� NewDeclarativeEnvironment(e) � 用于创建一个DeclarativeEnvironment � 进入函数时 � 执行catch表达式时� NewObjectEnvironment(o, e) � 用于创建一个ObjectEnvironment � 执行with表达式时
  22. 22. 创建词法环境function NewDeclarativeEnvironment(e) { var env = new LexicalEnvironment(); var envRec = new EnvironmentRecords(); envRec.bindingObject = {}; env.environmentRecords = envRec; env.outerEnvironment = e; return env;}function NewObjectEnvironment(o, e) { var env = new LexicalEnvironment(); var envRec = new EnvironmentRecords(); envRec.bindingObject = o; env.environmentRecords = envRec; env.outerEnvironment = e; return env;}
  23. 23. 消化一下• 名词解释完了吗? NO
  24. 24. 总结ExecutionContext: { LexicalEnvironment, VariableEnvironment, EnvironmentRecords: { ThisBinding hasBinding(name),} createBinding(name), setBinding(name, value), getValue(name),LexicalEnvironment: { deleteBinding(name) environmentRecords, } outerEnvironment}
  25. 25. 函数(Function)� 是一个对象(Object)。� 包含几个特殊的属性 � [[Construct]] – new SomeFunction() � [[Call]] – someFunction() � [[HasInstance]] – o instanceof SomeFunction � [[Scope]] – 闭包 � [[FormalParameters]] – 参数列表 � [[Code]] – 可执行代码� 包含可执行代码(Executable Code)和执行状态 (State)。
  26. 26. 创建函数(Create Function Object)� 新建一个普通对象(new Object())� 将[[Class]]设为”function”� 将[[Prototype]]指向Function.prototype� 根据默认的规则,设置[[Call]]、[[Contruct]] 及[[HasInstance]]属性� 将[[Scope]]设置为当前的 LexicalEnvironment对象� 设置[[Code]]、[[FormalParameterList]] 及name、length、prototype属性
  27. 27. 创建函数(Create Function Object)var fn = new Object();// [[DefaultValue]], [[HasProperty]], etc...initializeAsObject(fn);fn.[[Class]] = function;fn.[[Prototype]] = Function.prototype;fn.[[Call]] = function() { /* ... */ };fn.[[Construct]] = function() { /* ... */ };fn.[[HasInstance]] = function() { /* ... */ };fn.[[Scope]] = control.runningEC.lexicalEnvironment;fn.[[Code]] = functionBody;fn.[[FormalParameterList]] = parameterList;fn.name = functionName;fn.length = parameterList.length;fn.prototype = { constructor: fn };
  28. 28. 创建函数(Create Function Object)� 作用域([[Scope]])是函数对象的一个属性function hello() { function outer() { var o = {}; var name = ‘GrayZhang’; o.name = ‘GrayZhang’; function say() { return o; alert(name);} }var person = hello(); return say;console.log(person.name); } var inner = outer(); // inner.[[Scope]] inner();
  29. 29. 进入函数(Entering Function Code)� 新建一个执行环境。� 根据规则设置this绑定。 � 如果thisArg是null或undefined,设置为global。 � 如果thisArg不是Object,设置为ToObject(thisArg)。� 以函数的[[Scope]]属性为参数, NewDeclarativeEnvironment创建一个 LexicalEnvironment对象。� 将当前LexicalEnvironment设置为该值。 同一对象� 将当前VariableEnvironment设置为该值。� 开始初始化参数及函数内声明的变量。
  30. 30. 进入函数(Entering Function Code)var ec = new ExecutionContext();if (thisArg == null) { thisArg = global;}if (typeof thisArg !== object) { thisArg = ToObject(thisArg);}ec.thisBinding = thisArg;var localEnv = NewDeclarativeEnvironment(fn.[[Scope]]);ec.lexicalEnvironment = localEnv;ec.variableEnvironment = localEnv;initializeBinding(fn.[[Code]], fn.[[FormalParameterList]]);
  31. 31. 进入函数(Entering Function Code)� 执行函数时,在作用域([[Scope]])的基础 上添加词法环境(LexicalEnvironment)// Global Environmentfunction outer() { Global Environment // Outer Environment function inner() { Outer Environment // Current Environment } Current Environment}var inner = outer();// [[Scope]] === Outer Environmentinner();
  32. 32. 定义绑定初始化 34 (Declaration Binding Instantiation)� 从Hosting Behavior说起……function sayHello(name) { function sayHello(name) { if (!name) { var prefix; throw new Error(); if (!name) { } throw new Error(); else { } var prefix = Hello ; else { alert(prefix + name); prefix = Hello ; } alert(prefix + name);} } }
  33. 33. 定义绑定初始化 (Declaration Binding Instantiation )� 遍历FormalParameterList(参数列表),对每一项 (参数),如果VariableEnvironment中不存在,则 添加并赋值。� 依次遍历源码中每个FunctionDeclaration(函数声 明),对每一项(函数),如果 VariableEnvironment中不存在,则添加并赋值。� 如果VariableEnvironment中不存在arguments arguments arguments,则 添加并赋值。� 依次遍历源码中每个VariableDeclaration(变量声 明),对每一项(变量),如果 VariableEnvironment中不存在,则添加并赋值为 undefined undefined。
  34. 34. 定义绑定初始化 (Declaration Binding Instantiation)function format(template, data) { arguments var regex = /{(w+):(w+)}/g; function replacer(match, name, type) { var value = data[name]; switch (type) { case boolean: Variable Environment value = !!value; break; case html: value = encodeHTML(value); break; } return value; } var html = template.replace(regex, replacer); return html;}
  35. 35. 消化一下• 还有完没完了! NO
  36. 36. 变量查找� GetIdentifierReference(lex, name)� 从给定的LexicalEnvironment中查找是否存 在该变量� 如果不存在,则从LexicalEnvironment的 Outer Environment中查找� 依次进行,直到Outer Environment为 null,则返回undefined� 返回一个Reference对象,通过GetValue进一 步获取变量的值
  37. 37. 变量查找function GetIdentifierReference(lex, name) { if (lex == null) { return new Reference(name, undefined); } var envRec = lex.environmentRecords; if (envRec.hasBinding(name)) { return new Reference(name /* name */, envRec /* base */); } return GetIdentifierReference(lex.outerEnvironment, name);}function GetValue(reference) { var envRec = reference.base; return envRec.getValue(reference.name);}
  38. 38. 大串烧� 进入全局环境 � 创建全局执行环境并入栈 � 创建全局环境对象 � 全局的词法环境对象指向该对象 � 全局的变量环境对象指向该对象 � 在变量环境中添加outer绑定并赋值 // script… � 在变量环境中添加prefix绑定 var prefix = ‘Hello ‘; function outer() { � 在变量环境中添加inner绑定 var name = ‘GrayZhang’; function say() { var message = prefix + name; alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  39. 39. 大串烧� 创建outer函数 � 创建一个对象 � 设置[[Call]]、[[Construct]]、[[HasInstance]]等 � 设置[[Scope]]为当前词法环境 – 全局环境 � 设置[[Code]]、[[FormalParameterList]]等 � 设置length、prototype等 // script… var prefix = ‘Hello ‘; function outer() { var name = ‘GrayZhang’; function say() { var message = prefix + name; alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  40. 40. 大串烧 Global Environment outer: { [[Scope]] } inner: undefined prefix: undefinedGlobal Execution Context VariableEnvironmen t LexicalEnvironment
  41. 41. 大串烧� 为prefix变量赋值 � 在全局环境中寻找name绑定 – 找到 � 得到上一步返回的Reference的base – 即全局环境 的环境数据对象 � 调用其setBinding(‘prefix’, ‘Hello ’) // script… var prefix = ‘Hello ‘; function outer() { var name = ‘GrayZhang’; function say() { var message = prefix + name; alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  42. 42. 大串烧 Global Environment outer: { [[Scope]] } inner: undefined prefix: ‘Hello ‘Global Execution Context VariableEnvironmen t LexicalEnvironment
  43. 43. 大串烧� 执行outer函数 � 创建执行环境并入栈 � 创建一个词法环境 – DeclarativeEnvironment � outer的词法环境对象指向该对象 � outer的变量环境对象指向该对象 � 在变量环境中添加say绑定并赋值 // script… � 在变量环境中添加name绑定 var prefix = ‘Hello ‘; function outer() { var name = ‘GrayZhang’; function say() { var message = prefix + name; alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  44. 44. 大串烧� 创建say函数 � 创建一个对象 � 设置[[Call]]、[[Construct]]、[[HasInstance]]等 � 设置[[Scope]]为当前词法环境 – outer的词法环境 � 设置[[Code]]、[[FormalParameterList]]等 � 设置length、prototype等 // script… var prefix = ‘Hello ‘; function outer() { var name = ‘GrayZhang’; function say() { var message = prefix + name; alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  45. 45. 大串烧 Global Environment outer: { [[Scope]] } inner: undefined prefix: ‘Hello ‘ Outer EnvironmentOuter Execution Context say: { [[Scope]] } name: undefined VariableEnvironment LexicalEnvironmentGlobal Execution Context VariableEnvironmen t LexicalEnvironment
  46. 46. 大串烧� 为name变量赋值 � 在outer的词法环境中寻找name绑定 – 找到 � 得到上一步返回的Reference的base – 即outer的词 法环境的环境数据对象 � 调用其setBinding(‘name’, ‘GrayZhang’) // script… var prefix = ‘Hello ‘; function outer() { var name = ‘GrayZhang’; function say() { var message = prefix + name; alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  47. 47. 大串烧 Global Environment outer: { [[Scope]] } inner: { [[Scope]] } prefix: ‘Hello ‘ Outer EnvironmentOuter Execution Context say: { [[Scope]] } name: ‘Gray Zhang’ VariableEnvironment LexicalEnvironmentGlobal Execution Context VariableEnvironmen t LexicalEnvironment
  48. 48. 大串烧� 返回并赋值给inner变量 � 将outer的ExecutionContext出栈 � 在全局环境下寻找inner绑定 – 找到 � 得到上一步返回的Reference的base – 即全局环境 的环境数据对象 � 调用其setBinding(‘inner’, &say); // script… var prefix = ‘Hello ‘; function outer() { var name = ‘GrayZhang’; function say() { var message = prefix + name; alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  49. 49. 大串烧 Global Environment outer: { [[Scope]] } inner: { [[Scope]] } prefix: ‘Hello ‘ Outer Environment say: { [[Scope]] } name: ‘GrayZhang’Global Execution Context VariableEnvironmen t LexicalEnvironment
  50. 50. 大串烧� 执行inner函数 � 创建执行环境并入栈 � 创建一个词法环境 – DeclarativeEnvironment � inner的词法环境对象指向该对象 � inner的变量环境对象指向该对象 � 在变量环境中添加message绑定 // script… var prefix = ‘Hello ‘; function outer() { var name = ‘GrayZhang’; function say() { var message = prefix + name; alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  51. 51. 大串烧 Global Environment outer: { [[Scope]] } inner: { [[Scope]] } prefix: ‘Hello ‘ Outer EnvironmentInner Execution Context say: { [[Scope]] } name: ‘GrayZhang’ VariableEnvironment LexicalEnvironment Inner Environment message: undefinedGlobal Execution Context VariableEnvironmen t LexicalEnvironment
  52. 52. 大串烧� 为message变量赋值 � 查找prefix变量的值 � 在inner的词法环境中寻找prefix绑定 – 没有 � 在outer的词法环境中寻找prefix绑定 – 没有 � 在全局环境中寻找prefix绑定 – 找到 � 取得prefix的值 � 查找name变量的值 // script… �… var prefix = ‘Hello ‘; function outer() { � 在inner的词法环境中寻找message var name = ‘GrayZhang’; function say() { var message = prefix + name; � 给message绑定赋值 alert(message); } return say; } var inner = outer(); // inner.[[Scope]] inner();
  53. 53. 大串烧 Global Environment outer: { [[Scope]] } inner: { [[Scope]] } prefix: ‘Hello ‘ Outer EnvironmentInner Execution Context say: { [[Scope]] } name: ‘GrayZhang’ VariableEnvironment LexicalEnvironment Inner Environment message: ‘Hello GrayZhang’Global Execution Context VariableEnvironmen t LexicalEnvironment
  54. 54. 大串烧� 获取inner的值 � 在inner的词法环境中寻找message绑定 – 找到 � 得到上一步返回的Reference的base – 即inner的词法环 境的环境数据对象 � 调用该对象的getValue(‘message’)� 获取alert的值 � … // script… var prefix = ‘Hello ‘;� 将inner作为参数,调用alert函数 function outer() { var name = ‘GrayZhang’; function say() { var message = prefix + name; alert alert从何而来? } alert(message); return say; } var inner = outer(); // inner.[[Scope]] inner();
  55. 55. 大串烧function born() { var name = unknown; var age = 1; return { setName: function(value) { name = value; }, grow: function() { age++; }, print: function() { var parts = [name, age]; var joint = is now ; alert( parts.join(joint)); } };}var god = born();god.setName(‘leeight’);god.grow();god.grow();god.print();
  56. 56. 总结� 相关概念 � 可执行代码 – Executable Code � 执行环境 – Execution Context � 词法环境 – LexicalEnvironment � 变量环境 – VariableEnvironment � 环境数据 – Environment Records
  57. 57. 总结� 过程 � 创建函数 – [[Scope]] � [[Scope]]在创建时决定且不会变化 � 进入函数 – 执行环境 + 词法环境 + 变量环境 � 执行时在最内层增加词法环境 � 定义绑定初始化 – 参数 + 函数声明 + 变量声 明 � 变量环境和词法环境是同一个对象 � 变量查找 – GetIdentifierReference � 延词法环境自内向外查找
  58. 58. 继续消化• 我以为我懂了,直到…… – How with works – How catch works – How let works – When code meets eval – When code meets new Function – When there is strict mode
  59. 59. 从代码说起function outer() { var o = LargetObject.fromSize(400MB); return function() { console.log(inner); };}var inner = outer();// 对象图 此时对象之间的引用关 系? Lexical Global Function inner [[Scope]] Environment Global o有间接引用,无法回收o Global和o o environmentRecords Large o Binding bindingObject Environment Object Object Records
  60. 60. 但是事实上……function outer() { var i = 3; return function() { debugger; };}var inner = outer();inner();javascript引擎有能力回收i
  61. 61. 如果你是计算机……function outer() { var i = 3; i: 不可回收 var j = 4; var k = 5; j: 不可回收 function prepare() { k: 可回收 } i = i + k; prepare: 可回收 function help() { help: 不可回收 i = i + j; } prepare(); 人类的智商 return function() { help(); console.log(i); }; 计算机的智商}var inner = outer();inner();
  62. 62. ~ ~ ~ ~ 大大 大 大 好好 好 好 力力 力 力压压压压
  63. 63. 测试方法� 用断点! � Chrome / Firefox� 看内存! � IE / Opera
  64. 64. 一些基本结果� IE6 – 8没有回收闭包内变量的机制� Opera没有回收闭包内变量的机制� Chrome回收闭包内变量后,再次访问该变量 将抛出ReferenceError ReferenceError� Firefox回收闭包内变量后,再次访问该变量会 得到undefined undefined� Chrome、Firefox和IE9回收闭包内变量的策 略基本相同
  65. 65. 试问!� 有哪些因素可能导致变量无法回收? � 变量被返回的函数直接引用。 � 变量被返回的函数间接引用(通过嵌套函数)。 � 返回的函数中有eval eval eval。 � 返回的函数在with with with表达式建立的作用域中。 � 返回的函数在catch catch catch表达式中。� 只谈结果,不谈过程!
  66. 66. 直接引用Engine CollectableChrome – V8 NOFirefox – SpiderMonkey NOIE9 - Chakra NOfunction outer() { var i = 3; return function() { i; };}var inner = outer();
  67. 67. 间接引用Engine CollectableChrome – V8 NOFirefox – SpiderMonkey NOIE9 - Chakra NOfunction outer() { var i = 3; function help() { i; } return function() { help(); };}var inner = outer();
  68. 68. 嵌套函数的平衡function outer() { function outer() { var i = 0; var i = 0; function help() { function help() { i++; i++; } return inner(); } help(); function inner() { return function() { return i > 3 ? i : help(); console.log(nothing); } }} return inner(); }var inner = outer(); var inner = outer();需要图的遍历 需要处理环引用 高成本 + 低效
  69. 69. 71嵌套函数的平衡Engine CollectableChrome – V8 NOFirefox – SpiderMonkey NOIE9 - Chakra NOfunction outer() { var i = 3; function help() { i; } return function() { };}var inner = outer();
  70. 70. 大恶魔evalfunction outer() { var i = 3; ? return function() { return eval(‘i’); }}var inner = outer();var result = inner();console.log(result); // 3由字符串从词法环境中获取对象的唯一途径 可变性 特殊性
  71. 71. 大恶魔evalvar reference = eval(‘someObject’); 字符串分析var reference = eval(‘some’ + ‘Object’); 常量预计算var s = ‘some’;var reference = eval(s + ‘Object’); 变量->常量替换var array = [‘some’, ‘ject’];var reference = eval(array.join(‘Ob’));
  72. 72. 大恶魔evalfunction outer() { var i = 3; return function(variableName) { return eval(variableName); }}var inner = outer();var input = document.getElementById(‘variable_name’);var name = input.value.trim();var result = inner(name);console.log(result); // 3
  73. 73.
  74. 74. .. . . ve ve ve ve ti ti ti ti , s Na , s Na , , Na Na le le le le me s s mp time mp time mp mp ti ti me Si Si Si Si me me me me oo o o So So So SoToToToTo
  75. 75. 大恶魔evalEngine CollectableChrome – V8 NOFirefox – SpiderMonkey NOIE9 - Chakra NOfunction outer() { var i = 3; return function() { eval(‘’); // 无论eval的内容是什么 };}var inner = outer();
  76. 76. 间接eval和new Function� 间接eval � window.eval(coe) | (1, eval)(code) | (true && eval)(code) � In Edition 5, indirect calls to the eval function use the global environment as both the variable environment and lexical environment for the eval code.� new Function � Return a new Function object created as specified in 13.2 passing P as the FormalParameterList and body as the FunctionBody. Pass in the Global Environment as the Scope parameter and strict as the Strict flag.
  77. 77. 间接eval和new Functionvar i = 3;function outer() { var i = 4; return function() { X return window.eval(i); /* * var fn = new Function(return i;); * return fn(); */ }}var inner = outer();var result = inner();console.log(result); // 3
  78. 78. 间接eval和new FunctionEngine CollectableChrome – V8 YESFirefox – SpiderMonkey YESIE9 - Chakra YESfunction outer() { var i = 3; return function() { window.eval(‘i’); };}var inner = outer();
  79. 79. 关于with的分歧function outer() { ? var scope = { i: 3, j: 4 }; var m = 4; var n = 5; ? with (scope) { return function() { i++; m++; }; };}var inner = outer();inner();
  80. 80. 关于with的分歧Engine CollectableChrome – V8 NOFirefox – SpiderMonkey 回收k, scope k, scope,不回收i, j iIE9 - Chakra 回收k,不回收i, j scope k i j,scope scope未知function outer() { var scope = { i: 3, j: 4 }; var k = 4; with (scope) { return function() { i; }; }}var inner = outer();
  81. 81. 不被重视的catchEngine CollectableChrome – V8 回收i,不回收ex i exFirefox – SpiderMonkey 回收i,不回收ex i exIE9 - Chakra 回收i和ex i exfunction outer() { var i = 3; try { throw { j: 4 }; } catch (ex) { return function() {}; }}var inner = outer();
  82. 82. 你能骗过引擎吗?function outer() { var i = 3; var j = 4; ? return function( i) { var j = 5; console.log( i + j); };}var inner = outer();inner(6);Engine CollectableChrome – V8 YESFirefox – SpiderMonkey YESIE9 - Chakra YES
  83. 83. 总结� outer声明的 – c1 = (i, j, k, m)� inner声明的 – c2 = (i, j)� inner用到的 – c3 = (i, j, k) function outer() {� help声明的 – c4 = () var i = 3; var j = 4;� help用到的 – c5 = (j, k) var k = 5; var m = 6;� 可回收的 = c1- (c3 – c2) – (c5 – c4) function help() { console.log(j + k);� 遇上eval eval eval则不回收任何变量 }� 注意with catch with catch的影响 with和catch return function( i) { var j = 5; console.log( i + j + k); }; } var inner = outer(); inner(6);
  84. 84. 谢 谢
  85. 85. 知识要点• 变量声明在变量环境中,从词法环境中获 取,通常2者是同一个对象。• 作用域在函数创建时生成,是函数对象的 不变的属性 – 静。• 执行函数时,在作用域上增加一个词法环 境对象 – 动。• 动静结合即闭包的本质。• 闭包对垃圾回收会有一定的影响。
  86. 86. 参考资料• Annotated ES5 – http://es5.github.com/ http://es5.github.com/• ECMA-262-5 in detail – http://dmitrysoshnikov.com/tag/es-5/• 关于闭包及变量回收问题 – http://www.otakustay.com/about-closure-and-gc/• Discussion on reference & scope - digipedia? – http://www.digipedia.pl/usenet/thread/14438/704/• Discussion on V8 variable allocation - twitter – http://twitter.com/#!/erikcorry/status/53901976865476608
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×