SlideShare une entreprise Scribd logo
1  sur  88
Télécharger pour lire hors ligne
function infiniteSequence() {
    var i = 0;
    return function() {
        return i++;
    }
}

var increment = infiniteSequence();
console.log(increment());
console.log(increment());
                                      所谓闭包
console.log(increment());             张立理
// …
                                      otaksutay@gmail.com
WARNING!!

            2
WARNING
� 非常非常的学术性
� 大量术语词汇出没
� 也许永远都用不上
� 内容并非标准,有所删减
� 逻辑不是那么清晰


� 只谈函数,不提eval,不提new   Function
Summary
� 引言
� 什么是变量
� 闭包之表象
� 闭包之内在
� 关于垃圾回收
作用域的历史
                      Scope Chain

  ECMAScript
                    Variable Object
     v3

                  Identifier Resolution


                  Lexical Environment


  ECMAScript v5   Variable Environment


                  GetIdentifierReference
什么是变量
A symbolic name associated with a value and whose
associated value may be changed.
                                          -- Wikipedia
� 变量是一种关联关系(association)
� 关联的目标:
  � 符号名(symbolic         name)
  � 值(value)

� 关联是单向的          – 永远不可能根据值找到变量
            Identifier    Value
               name       ‘GrayZhang’
什么是变量
� Variable   Statement
 � var   Identifier = AssignmentExpression
� FunctionDeclaration
 � function
   Identifier(FormalParameterList) {
       FunctionBody
   }
 � FormalParameterList
 � Identifier, Identifier[, …]
什么是变量
                Value (String Literal)

          var name = ‘GrayZhang’;

                                     Identifier
Keyword   function add(x, y) {
              return x + y;
          }
11




闭包之表象
� 内层函数可以使用外层函数作用域内的变量



function outer() {
                                        外层
    var name = ‘GrayZhang’;
    function inner() {
        console.log(‘Hello ‘ + name);   内层
    }
    inner();
}
闭包之内在
�   Q:为什么javascript 会有闭包?
�   A:因为ECMAScript 中变量解析是一个查找过程,而非绑定
    过程。

�   Q:变量存放在哪里?
�   A:Execution Context
      Execution Context中的VariableEnvironment
                         VariableEnvironment
                         VariableEnvironment。

�   Q:从哪里查找变量?
�   A:Execution Context
      Execution Context中的LexicalEnvironment
                         LexicalEnvironment
                         LexicalEnvironment。

�   Q:如何查找变量?
�   A:自内向外。
?
可执行代码(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);
15




执行环境(Execution Context)
� 当进入(开始执行)一段可执行代码时,生成
  一个执行环境对象。
� 执行环境对象通过栈(Stack)维护。
� 新建的执行环境对象称为“当前运行的执行环
  境对象”。
function enterCode(code) {
    var ec = new ExecutionContext();
    control.ecStack.push(ec);
    control.runningEC = ec;
    control.execute(code);
}
执行环境(Execution Context)
� 一个执行环境对象包括:
 � 词法环境   – LexicalEnvironment
 � 变量环境 – VariableEnvironment
 � This绑定 - ThisBinding


ExecutionContext: {
    LexicalEnvironment,
    VariableEnvironment,
    ThisBinding
}
词法环境(LexicalEnvironment)
� 既是一个属性,又是一个类型。
� 每个执行环境对象都有且仅有一个关联的词法
  环境对象。
� 在代码执行过程中,需要解析变量时,通过词
  法环境对象进行解析,从其环境数据中得到值。
� 一个词法环境对象包括:
 � 环境数据 – environement records
 � 外层环境 – outer environment
词法环境(LexicalEnvironment)
� 存在2种词法环境的实现类型
 � DeclarativeEnvironment
 � ObjectEnvironment

� 区别是ObjectEnvironment可接受指定的对
 象作为环境数据属性的值

  ?什么情况会出现
  ObjectEnvironment
变量环境(VariableEnvironment)
� 每个执行环境对象都有且仅有一个关联的变量
  环境对象。
� 变量环境仅仅是一个名字,变量环境对象的类
  型是词法环境(LexicalEnvironment)。
� 在进入(开始执行)代码时,所有的变量标识
  符(Identifier)会存放在当前的变量环境对
  象中。
� 变量环境中有环境数据属性,但不使用外层环
  境属性。
环境数据(environment                      records)
�   存在于词法环境或变量环境中。
�   包含一个binding object,简单地认为binding object
    是一个Map对象,保存变量标签符(Identifier)和变
    量值(Value)的关系。
�   常用方法:
    �   hadBinding(name) – 查看是否有变量绑定
    �   createBinding(name) – 创建一个变量绑定
    �   setBinding(name, value) – 修改变量绑定的值
    �   getValue(name) – 获取变量绑定的值
    �   deleteBinding(name) – 删除一个变量绑定
环境数据(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;
    },
    // …
}
环境数据(environment                        records)
� 存在2种环境数据的实现类型
 � DeclarativeEnvironmentRecords
 � ObjectEnvironmentRecords

LexicalEnvironment                   EnvironmentRecords




 DeclaractiveEnvironmen   DeclaractiveEnvironmentRecord
             t                           s



   ObjectEnvironment        ObjectEnvironmentRecords
创建词法环境
� NewDeclarativeEnvironment(e)
 � 用于创建一个DeclarativeEnvironment
 � 进入函数时
 � 执行catch表达式时


� NewObjectEnvironment(o,   e)
 � 用于创建一个ObjectEnvironment
 � 执行with表达式时
创建词法环境
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;
}
消化一下
• 名词解释完了吗?




        NO
总结
ExecutionContext: {
    LexicalEnvironment,
    VariableEnvironment,   EnvironmentRecords: {
    ThisBinding              hasBinding(name),
}                            createBinding(name),
                             setBinding(name, value),
                             getValue(name),
LexicalEnvironment: {        deleteBinding(name)
    environmentRecords,    }
    outerEnvironment
}
函数(Function)
�   是一个对象(Object)。
�   包含几个特殊的属性
    �   [[Construct]] – new SomeFunction()
    �   [[Call]] – someFunction()
    �   [[HasInstance]] – o instanceof SomeFunction
    �   [[Scope]] – 闭包
    �   [[FormalParameters]] – 参数列表
    �   [[Code]] – 可执行代码
�   包含可执行代码(Executable Code)和执行状态
    (State)。
创建函数(Create Function Object)
� 新建一个普通对象(new        Object())
� 将[[Class]]设为”function”
� 将[[Prototype]]指向Function.prototype
� 根据默认的规则,设置[[Call]]、[[Contruct]]
  及[[HasInstance]]属性
� 将[[Scope]]设置为当前的
  LexicalEnvironment对象
� 设置[[Code]]、[[FormalParameterList]]
  及name、length、prototype属性
创建函数(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 };
创建函数(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();
进入函数(Entering Function Code)

�   新建一个执行环境。
�   根据规则设置this绑定。
    �   如果thisArg是null或undefined,设置为global。
    �   如果thisArg不是Object,设置为ToObject(thisArg)。
�   以函数的[[Scope]]属性为参数,
    NewDeclarativeEnvironment创建一个
    LexicalEnvironment对象。
�   将当前LexicalEnvironment设置为该值。
                                           同一对象
�   将当前VariableEnvironment设置为该值。
�   开始初始化参数及函数内声明的变量。
进入函数(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]]);
进入函数(Entering Function Code)
� 执行函数时,在作用域([[Scope]])的基础
 上添加词法环境(LexicalEnvironment)

// Global Environment
function outer() {                   Global Environment
    // Outer Environment
    function inner() {               Outer Environment
         // Current Environment
    }                                Current Environment
}
var inner = outer();
// [[Scope]] === Outer Environment
inner();
定义绑定初始化                          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);
}                                    }
                                 }
定义绑定初始化
    (Declaration   Binding Instantiation )


�   遍历FormalParameterList(参数列表),对每一项
    (参数),如果VariableEnvironment中不存在,则
    添加并赋值。
�   依次遍历源码中每个FunctionDeclaration(函数声
    明),对每一项(函数),如果
    VariableEnvironment中不存在,则添加并赋值。
�   如果VariableEnvironment中不存在arguments
                             arguments
                             arguments,则
    添加并赋值。
�   依次遍历源码中每个VariableDeclaration(变量声
    明),对每一项(变量),如果
    VariableEnvironment中不存在,则添加并赋值为
    undefined
    undefined。
定义绑定初始化
   (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;
}
消化一下
• 还有完没完了!




        NO
变量查找
� GetIdentifierReference(lex,
                        name)
� 从给定的LexicalEnvironment中查找是否存
  在该变量
� 如果不存在,则从LexicalEnvironment的
  Outer Environment中查找
� 依次进行,直到Outer Environment为
  null,则返回undefined
� 返回一个Reference对象,通过GetValue进一
  步获取变量的值
变量查找
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);
}
大串烧
�   进入全局环境
    � 创建全局执行环境并入栈
    � 创建全局环境对象
    � 全局的词法环境对象指向该对象
    � 全局的变量环境对象指向该对象
    � 在变量环境中添加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();
大串烧
�   创建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();
大串烧
                              Global Environment
                           outer: { [[Scope]] }
                           inner: undefined
                           prefix: undefined




Global Execution Context

 VariableEnvironmen
          t
 LexicalEnvironment
大串烧
�   为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();
大串烧
                              Global Environment
                           outer: { [[Scope]] }
                           inner: undefined
                           prefix: ‘Hello ‘




Global Execution Context

 VariableEnvironmen
          t
 LexicalEnvironment
大串烧
�   执行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();
大串烧
�   创建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();
大串烧
                              Global Environment
                           outer: { [[Scope]] }
                           inner: undefined
                           prefix: ‘Hello ‘


                              Outer Environment
Outer Execution Context      say: { [[Scope]] }
                             name: undefined
 VariableEnvironment
 LexicalEnvironment

Global Execution Context

 VariableEnvironmen
          t
 LexicalEnvironment
大串烧
�   为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();
大串烧
                              Global Environment
                           outer: { [[Scope]] }
                           inner: { [[Scope]] }
                           prefix: ‘Hello ‘


                              Outer Environment
Outer Execution Context      say: { [[Scope]] }
                             name: ‘Gray Zhang’
 VariableEnvironment
 LexicalEnvironment

Global Execution Context

 VariableEnvironmen
          t
 LexicalEnvironment
大串烧
�   返回并赋值给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();
大串烧
                              Global Environment
                           outer: { [[Scope]] }
                           inner: { [[Scope]] }
                           prefix: ‘Hello ‘


                              Outer Environment
                             say: { [[Scope]] }
                             name: ‘GrayZhang’




Global Execution Context

 VariableEnvironmen
          t
 LexicalEnvironment
大串烧
�   执行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();
大串烧
                              Global Environment
                           outer: { [[Scope]] }
                           inner: { [[Scope]] }
                           prefix: ‘Hello ‘


                              Outer Environment
Inner Execution Context      say: { [[Scope]] }
                             name: ‘GrayZhang’
 VariableEnvironment
 LexicalEnvironment           Inner Environment
                            message: undefined
Global Execution Context

 VariableEnvironmen
          t
 LexicalEnvironment
大串烧
�   为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();
大串烧
                              Global Environment
                           outer: { [[Scope]] }
                           inner: { [[Scope]] }
                           prefix: ‘Hello ‘


                              Outer Environment
Inner Execution Context      say: { [[Scope]] }
                             name: ‘GrayZhang’
 VariableEnvironment
 LexicalEnvironment           Inner Environment
                            message: ‘Hello GrayZhang’
Global Execution Context

 VariableEnvironmen
          t
 LexicalEnvironment
大串烧
�   获取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();
大串烧
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();
总结
� 相关概念
 � 可执行代码    – Executable Code
 � 执行环境   – Execution Context
 � 词法环境   – LexicalEnvironment
 � 变量环境   – VariableEnvironment
 � 环境数据   – Environment Records
总结
� 过程
 � 创建函数   – [[Scope]]
  � [[Scope]]在创建时决定且不会变化
 � 进入函数   – 执行环境 + 词法环境 + 变量环境
  � 执行时在最内层增加词法环境
 � 定义绑定初始化     – 参数 + 函数声明 + 变量声
  明
  � 变量环境和词法环境是同一个对象
 � 变量查找   – GetIdentifierReference
  � 延词法环境自内向外查找
继续消化
• 我以为我懂了,直到……
 – How with works
 – How catch works
 – How let works

 – When code meets eval
 – When code meets new Function

 – When there is strict mode
从代码说起
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
但是事实上……
function outer() {
    var i = 3;

    return function() {
        debugger;
    };
}

var inner = outer();
inner();




javascript引擎有能力回收i
如果你是计算机……
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();
~
          ~
          ~
          ~
     大大
      大
      大
   好好
    好
    好
 力力
  力
  力
压
压
压
压
测试方法
� 用断点!
 � Chrome    / Firefox


� 看内存!
 � IE   / Opera
一些基本结果
� IE6 – 8没有回收闭包内变量的机制
� Opera没有回收闭包内变量的机制
� Chrome回收闭包内变量后,再次访问该变量
  将抛出ReferenceError
       ReferenceError
� Firefox回收闭包内变量后,再次访问该变量会
 得到undefined
   undefined
� Chrome、Firefox和IE9回收闭包内变量的策
 略基本相同
试问!
� 有哪些因素可能导致变量无法回收?
 � 变量被返回的函数直接引用。
 � 变量被返回的函数间接引用(通过嵌套函数)。
 � 返回的函数中有eval
          eval
          eval。
 � 返回的函数在with
         with
         with表达式建立的作用域中。
 � 返回的函数在catch
         catch
         catch表达式中。


� 只谈结果,不谈过程!
直接引用
Engine                    Collectable
Chrome – V8               NO
Firefox – SpiderMonkey    NO
IE9 - Chakra              NO
function outer() {
    var i = 3;
    return function() {
        i;
    };
}
var inner = outer();
间接引用
Engine                    Collectable
Chrome – V8               NO
Firefox – SpiderMonkey    NO
IE9 - Chakra              NO
function outer() {
    var i = 3;
    function help() {
        i;
    }
    return function() {
        help();
    };
}
var inner = outer();
嵌套函数的平衡
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();
需要图的遍历
                                  需要处理环引用
                 高成本 + 低效
71




嵌套函数的平衡
Engine                    Collectable
Chrome – V8               NO
Firefox – SpiderMonkey    NO
IE9 - Chakra              NO
function outer() {
    var i = 3;
    function help() {
        i;
    }

    return function() {
    };
}
var inner = outer();
大恶魔eval
function outer() {
    var i = 3;
                     ?
    return function() {
        return eval(‘i’);
    }
}

var inner = outer();
var result = inner();
console.log(result); // 3

由字符串从词法环境中获取对象的唯一途径

          可变性            特殊性
大恶魔eval
var 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’));
大恶魔eval
function 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
囧
..
                             .
                             .
                          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
        So
To
To
To
To
大恶魔eval
Engine                       Collectable
Chrome – V8                  NO
Firefox – SpiderMonkey       NO
IE9 - Chakra                NO
function outer() {
    var i = 3;

    return function() {
        eval(‘’); // 无论eval的内容是什么
    };
}
var inner = outer();
间接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.
间接eval和new Function
var 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
间接eval和new Function
Engine                      Collectable
Chrome – V8                 YES
Firefox – SpiderMonkey      YES
IE9 - Chakra                YES
function outer() {
    var i = 3;

    return function() {
        window.eval(‘i’);
    };
}
var inner = outer();
关于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();
关于with的分歧
Engine                            Collectable
Chrome – V8                       NO
Firefox – SpiderMonkey            回收k, scope
                                    k, scope,不回收i, j
                                                i
IE9 - 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();
不被重视的catch
Engine                          Collectable
Chrome – V8                     回收i,不回收ex
                                  i    ex
Firefox – SpiderMonkey          回收i,不回收ex
                                  i    ex
IE9 - Chakra                    回收i和ex
                                  i ex
function outer() {
    var i = 3;
    try {
        throw { j: 4 };
    }
    catch (ex) {
        return function() {};
    }
}
var inner = outer();
你能骗过引擎吗?
function outer() {
    var i = 3;
    var j = 4;           ?
   return function( i) {
       var j = 5;
       console.log( i + j);
   };
}
var inner = outer();
inner(6);

Engine                        Collectable
Chrome – V8                   YES
Firefox – SpiderMonkey        YES
IE9 - Chakra                  YES
总结
�   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);
谢   谢
知识要点
• 变量声明在变量环境中,从词法环境中获
  取,通常2者是同一个对象。
• 作用域在函数创建时生成,是函数对象的
  不变的属性 – 静。
• 执行函数时,在作用域上增加一个词法环
  境对象 – 动。
• 动静结合即闭包的本质。
• 闭包对垃圾回收会有一定的影响。
参考资料
• 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

Contenu connexe

Tendances

Ecma script edition5-小试
Ecma script edition5-小试Ecma script edition5-小试
Ecma script edition5-小试lydiafly
 
Ecmascript
EcmascriptEcmascript
Ecmascriptjay li
 
Inside.java.concurrency 35.thread pool.part8_future.scheduledthreadpoolexecutor
Inside.java.concurrency 35.thread pool.part8_future.scheduledthreadpoolexecutorInside.java.concurrency 35.thread pool.part8_future.scheduledthreadpoolexecutor
Inside.java.concurrency 35.thread pool.part8_future.scheduledthreadpoolexecutorAdy Liu
 
Java8 lambda
Java8 lambdaJava8 lambda
Java8 lambdakoji lin
 
Java程序员面试之葵花宝典
Java程序员面试之葵花宝典Java程序员面试之葵花宝典
Java程序员面试之葵花宝典yiditushe
 
lambda/closure – JavaScript、Python、Scala 到 Java SE 7
lambda/closure – JavaScript、Python、Scala 到 Java SE 7lambda/closure – JavaScript、Python、Scala 到 Java SE 7
lambda/closure – JavaScript、Python、Scala 到 Java SE 7Justin Lin
 
潜力无限的编程语言Javascript
潜力无限的编程语言Javascript潜力无限的编程语言Javascript
潜力无限的编程语言Javascriptjay li
 
J2ee面试知识
J2ee面试知识J2ee面试知识
J2ee面试知识yiditushe
 
JavaScript 快速複習 2017Q1
JavaScript 快速複習 2017Q1JavaScript 快速複習 2017Q1
JavaScript 快速複習 2017Q1Sheng-Han Su
 
OpenEJB - 另一個選擇
OpenEJB - 另一個選擇OpenEJB - 另一個選擇
OpenEJB - 另一個選擇Justin Lin
 
Ejb工作原理学习笔记
Ejb工作原理学习笔记Ejb工作原理学习笔记
Ejb工作原理学习笔记yiditushe
 
jQuery介绍@disandu.com
jQuery介绍@disandu.comjQuery介绍@disandu.com
jQuery介绍@disandu.comThink hy
 
Coding guideline
Coding guidelineCoding guideline
Coding guideline斯理 衛
 
Jsp面试知识
Jsp面试知识Jsp面试知识
Jsp面试知识yiditushe
 

Tendances (18)

Sun java
Sun javaSun java
Sun java
 
Ecma script edition5-小试
Ecma script edition5-小试Ecma script edition5-小试
Ecma script edition5-小试
 
Ecmascript
EcmascriptEcmascript
Ecmascript
 
Inside.java.concurrency 35.thread pool.part8_future.scheduledthreadpoolexecutor
Inside.java.concurrency 35.thread pool.part8_future.scheduledthreadpoolexecutorInside.java.concurrency 35.thread pool.part8_future.scheduledthreadpoolexecutor
Inside.java.concurrency 35.thread pool.part8_future.scheduledthreadpoolexecutor
 
前端测试
前端测试前端测试
前端测试
 
Ooredis
OoredisOoredis
Ooredis
 
Java8 lambda
Java8 lambdaJava8 lambda
Java8 lambda
 
Java程序员面试之葵花宝典
Java程序员面试之葵花宝典Java程序员面试之葵花宝典
Java程序员面试之葵花宝典
 
lambda/closure – JavaScript、Python、Scala 到 Java SE 7
lambda/closure – JavaScript、Python、Scala 到 Java SE 7lambda/closure – JavaScript、Python、Scala 到 Java SE 7
lambda/closure – JavaScript、Python、Scala 到 Java SE 7
 
潜力无限的编程语言Javascript
潜力无限的编程语言Javascript潜力无限的编程语言Javascript
潜力无限的编程语言Javascript
 
J2ee面试知识
J2ee面试知识J2ee面试知识
J2ee面试知识
 
JavaScript 快速複習 2017Q1
JavaScript 快速複習 2017Q1JavaScript 快速複習 2017Q1
JavaScript 快速複習 2017Q1
 
OpenEJB - 另一個選擇
OpenEJB - 另一個選擇OpenEJB - 另一個選擇
OpenEJB - 另一個選擇
 
Ejb工作原理学习笔记
Ejb工作原理学习笔记Ejb工作原理学习笔记
Ejb工作原理学习笔记
 
jQuery介绍@disandu.com
jQuery介绍@disandu.comjQuery介绍@disandu.com
jQuery介绍@disandu.com
 
Coding guideline
Coding guidelineCoding guideline
Coding guideline
 
Jsp面试知识
Jsp面试知识Jsp面试知识
Jsp面试知识
 
泛型总结
泛型总结泛型总结
泛型总结
 

En vedette

Memcached浅析 韩建华
Memcached浅析 韩建华Memcached浅析 韩建华
Memcached浅析 韩建华youzitang
 
Qcon multi team sprint planning
Qcon multi team sprint planningQcon multi team sprint planning
Qcon multi team sprint planningyouzitang
 
Qcon flex体系架构深度剖析
Qcon flex体系架构深度剖析Qcon flex体系架构深度剖析
Qcon flex体系架构深度剖析youzitang
 
Qcon ria与geo web连横共进
Qcon ria与geo web连横共进Qcon ria与geo web连横共进
Qcon ria与geo web连横共进youzitang
 
Qcon java在企业级开发中的应用
Qcon java在企业级开发中的应用Qcon java在企业级开发中的应用
Qcon java在企业级开发中的应用youzitang
 
Qcon best practices for scaling websites
Qcon best practices for scaling websitesQcon best practices for scaling websites
Qcon best practices for scaling websitesyouzitang
 
Qcon ria的技术趋势和应用趋势
Qcon ria的技术趋势和应用趋势Qcon ria的技术趋势和应用趋势
Qcon ria的技术趋势和应用趋势youzitang
 
Qcon sun的云计算平台和技术实现
Qcon sun的云计算平台和技术实现Qcon sun的云计算平台和技术实现
Qcon sun的云计算平台和技术实现youzitang
 
Qcon 10个提高架构质量的观点
Qcon 10个提高架构质量的观点Qcon 10个提高架构质量的观点
Qcon 10个提高架构质量的观点youzitang
 

En vedette (9)

Memcached浅析 韩建华
Memcached浅析 韩建华Memcached浅析 韩建华
Memcached浅析 韩建华
 
Qcon multi team sprint planning
Qcon multi team sprint planningQcon multi team sprint planning
Qcon multi team sprint planning
 
Qcon flex体系架构深度剖析
Qcon flex体系架构深度剖析Qcon flex体系架构深度剖析
Qcon flex体系架构深度剖析
 
Qcon ria与geo web连横共进
Qcon ria与geo web连横共进Qcon ria与geo web连横共进
Qcon ria与geo web连横共进
 
Qcon java在企业级开发中的应用
Qcon java在企业级开发中的应用Qcon java在企业级开发中的应用
Qcon java在企业级开发中的应用
 
Qcon best practices for scaling websites
Qcon best practices for scaling websitesQcon best practices for scaling websites
Qcon best practices for scaling websites
 
Qcon ria的技术趋势和应用趋势
Qcon ria的技术趋势和应用趋势Qcon ria的技术趋势和应用趋势
Qcon ria的技术趋势和应用趋势
 
Qcon sun的云计算平台和技术实现
Qcon sun的云计算平台和技术实现Qcon sun的云计算平台和技术实现
Qcon sun的云计算平台和技术实现
 
Qcon 10个提高架构质量的观点
Qcon 10个提高架构质量的观点Qcon 10个提高架构质量的观点
Qcon 10个提高架构质量的观点
 

Similaire à 所谓闭包

Java script closures
Java script closuresJava script closures
Java script closuresskywalker1114
 
Ecma script3
Ecma script3 Ecma script3
Ecma script3 gniavaj
 
Java Script 引擎技术
Java Script 引擎技术Java Script 引擎技术
Java Script 引擎技术bigqiang zou
 
ES5 introduction
ES5 introductionES5 introduction
ES5 introductionotakustay
 
JavaScript 教程
JavaScript 教程JavaScript 教程
JavaScript 教程Bobby Zhou
 
Js的国(转载)
Js的国(转载)Js的国(转载)
Js的国(转载)Leo Hui
 
The Evolution of Async Programming (GZ TechParty C#)
The Evolution of Async Programming (GZ TechParty C#)The Evolution of Async Programming (GZ TechParty C#)
The Evolution of Async Programming (GZ TechParty C#)jeffz
 
基于原型的JavaScript面向对象编程
基于原型的JavaScript面向对象编程基于原型的JavaScript面向对象编程
基于原型的JavaScript面向对象编程zhangdaiping
 
Javascript share
Javascript shareJavascript share
Javascript shareXu Mac
 
异步编程与浏览器执行模型
异步编程与浏览器执行模型异步编程与浏览器执行模型
异步编程与浏览器执行模型keelii
 
Js is js(程劭非) (1)
Js is js(程劭非) (1)Js is js(程劭非) (1)
Js is js(程劭非) (1)looneyren
 
Node.js开发体验
Node.js开发体验Node.js开发体验
Node.js开发体验QLeelulu
 
cppcheck源码分析
cppcheck源码分析cppcheck源码分析
cppcheck源码分析Wu Liang
 
180518 ntut js and node
180518 ntut js and node180518 ntut js and node
180518 ntut js and nodePeter Yi
 
Javascript之昨是今非
Javascript之昨是今非Javascript之昨是今非
Javascript之昨是今非Tony Deng
 
Hello Javascript
Hello JavascriptHello Javascript
Hello JavascriptBaidu, Inc.
 
Java面试32题
Java面试32题Java面试32题
Java面试32题yiditushe
 

Similaire à 所谓闭包 (20)

Java script closures
Java script closuresJava script closures
Java script closures
 
Ecma script3
Ecma script3 Ecma script3
Ecma script3
 
Java Script 引擎技术
Java Script 引擎技术Java Script 引擎技术
Java Script 引擎技术
 
Scala
ScalaScala
Scala
 
ES5 introduction
ES5 introductionES5 introduction
ES5 introduction
 
JavaScript 教程
JavaScript 教程JavaScript 教程
JavaScript 教程
 
Js的国(转载)
Js的国(转载)Js的国(转载)
Js的国(转载)
 
The Evolution of Async Programming (GZ TechParty C#)
The Evolution of Async Programming (GZ TechParty C#)The Evolution of Async Programming (GZ TechParty C#)
The Evolution of Async Programming (GZ TechParty C#)
 
基于原型的JavaScript面向对象编程
基于原型的JavaScript面向对象编程基于原型的JavaScript面向对象编程
基于原型的JavaScript面向对象编程
 
Javascript share
Javascript shareJavascript share
Javascript share
 
异步编程与浏览器执行模型
异步编程与浏览器执行模型异步编程与浏览器执行模型
异步编程与浏览器执行模型
 
Js is js(程劭非) (1)
Js is js(程劭非) (1)Js is js(程劭非) (1)
Js is js(程劭非) (1)
 
Node.js开发体验
Node.js开发体验Node.js开发体验
Node.js开发体验
 
Js培训
Js培训Js培训
Js培训
 
前端测试
前端测试前端测试
前端测试
 
cppcheck源码分析
cppcheck源码分析cppcheck源码分析
cppcheck源码分析
 
180518 ntut js and node
180518 ntut js and node180518 ntut js and node
180518 ntut js and node
 
Javascript之昨是今非
Javascript之昨是今非Javascript之昨是今非
Javascript之昨是今非
 
Hello Javascript
Hello JavascriptHello Javascript
Hello Javascript
 
Java面试32题
Java面试32题Java面试32题
Java面试32题
 

所谓闭包

  • 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
  • 3. WARNING � 非常非常的学术性 � 大量术语词汇出没 � 也许永远都用不上 � 内容并非标准,有所删减 � 逻辑不是那么清晰 � 只谈函数,不提eval,不提new Function
  • 4. Summary � 引言 � 什么是变量 � 闭包之表象 � 闭包之内在 � 关于垃圾回收
  • 5.
  • 6.
  • 7. 作用域的历史 Scope Chain ECMAScript Variable Object v3 Identifier Resolution Lexical Environment ECMAScript v5 Variable Environment GetIdentifierReference
  • 8. 什么是变量 A symbolic name associated with a value and whose associated value may be changed. -- Wikipedia � 变量是一种关联关系(association) � 关联的目标: � 符号名(symbolic name) � 值(value) � 关联是单向的 – 永远不可能根据值找到变量 Identifier Value name ‘GrayZhang’
  • 9. 什么是变量 � Variable Statement � var Identifier = AssignmentExpression � FunctionDeclaration � function Identifier(FormalParameterList) { FunctionBody } � FormalParameterList � Identifier, Identifier[, …]
  • 10. 什么是变量 Value (String Literal) var name = ‘GrayZhang’; Identifier Keyword function add(x, y) { return x + y; }
  • 11. 11 闭包之表象 � 内层函数可以使用外层函数作用域内的变量 function outer() { 外层 var name = ‘GrayZhang’; function inner() { console.log(‘Hello ‘ + name); 内层 } inner(); }
  • 12. 闭包之内在 � Q:为什么javascript 会有闭包? � A:因为ECMAScript 中变量解析是一个查找过程,而非绑定 过程。 � Q:变量存放在哪里? � A:Execution Context Execution Context中的VariableEnvironment VariableEnvironment VariableEnvironment。 � Q:从哪里查找变量? � A:Execution Context Execution Context中的LexicalEnvironment LexicalEnvironment LexicalEnvironment。 � Q:如何查找变量? � A:自内向外。
  • 13.
  • 14. 可执行代码(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);
  • 15. 15 执行环境(Execution Context) � 当进入(开始执行)一段可执行代码时,生成 一个执行环境对象。 � 执行环境对象通过栈(Stack)维护。 � 新建的执行环境对象称为“当前运行的执行环 境对象”。 function enterCode(code) { var ec = new ExecutionContext(); control.ecStack.push(ec); control.runningEC = ec; control.execute(code); }
  • 16. 执行环境(Execution Context) � 一个执行环境对象包括: � 词法环境 – LexicalEnvironment � 变量环境 – VariableEnvironment � This绑定 - ThisBinding ExecutionContext: { LexicalEnvironment, VariableEnvironment, ThisBinding }
  • 17. 词法环境(LexicalEnvironment) � 既是一个属性,又是一个类型。 � 每个执行环境对象都有且仅有一个关联的词法 环境对象。 � 在代码执行过程中,需要解析变量时,通过词 法环境对象进行解析,从其环境数据中得到值。 � 一个词法环境对象包括: � 环境数据 – environement records � 外层环境 – outer environment
  • 18. 词法环境(LexicalEnvironment) � 存在2种词法环境的实现类型 � DeclarativeEnvironment � ObjectEnvironment � 区别是ObjectEnvironment可接受指定的对 象作为环境数据属性的值 ?什么情况会出现 ObjectEnvironment
  • 19. 变量环境(VariableEnvironment) � 每个执行环境对象都有且仅有一个关联的变量 环境对象。 � 变量环境仅仅是一个名字,变量环境对象的类 型是词法环境(LexicalEnvironment)。 � 在进入(开始执行)代码时,所有的变量标识 符(Identifier)会存放在当前的变量环境对 象中。 � 变量环境中有环境数据属性,但不使用外层环 境属性。
  • 20. 环境数据(environment records) � 存在于词法环境或变量环境中。 � 包含一个binding object,简单地认为binding object 是一个Map对象,保存变量标签符(Identifier)和变 量值(Value)的关系。 � 常用方法: � hadBinding(name) – 查看是否有变量绑定 � createBinding(name) – 创建一个变量绑定 � setBinding(name, value) – 修改变量绑定的值 � getValue(name) – 获取变量绑定的值 � deleteBinding(name) – 删除一个变量绑定
  • 21. 环境数据(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; }, // … }
  • 22. 环境数据(environment records) � 存在2种环境数据的实现类型 � DeclarativeEnvironmentRecords � ObjectEnvironmentRecords LexicalEnvironment EnvironmentRecords DeclaractiveEnvironmen DeclaractiveEnvironmentRecord t s ObjectEnvironment ObjectEnvironmentRecords
  • 23. 创建词法环境 � NewDeclarativeEnvironment(e) � 用于创建一个DeclarativeEnvironment � 进入函数时 � 执行catch表达式时 � NewObjectEnvironment(o, e) � 用于创建一个ObjectEnvironment � 执行with表达式时
  • 24. 创建词法环境 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; }
  • 26. 总结 ExecutionContext: { LexicalEnvironment, VariableEnvironment, EnvironmentRecords: { ThisBinding hasBinding(name), } createBinding(name), setBinding(name, value), getValue(name), LexicalEnvironment: { deleteBinding(name) environmentRecords, } outerEnvironment }
  • 27. 函数(Function) � 是一个对象(Object)。 � 包含几个特殊的属性 � [[Construct]] – new SomeFunction() � [[Call]] – someFunction() � [[HasInstance]] – o instanceof SomeFunction � [[Scope]] – 闭包 � [[FormalParameters]] – 参数列表 � [[Code]] – 可执行代码 � 包含可执行代码(Executable Code)和执行状态 (State)。
  • 28. 创建函数(Create Function Object) � 新建一个普通对象(new Object()) � 将[[Class]]设为”function” � 将[[Prototype]]指向Function.prototype � 根据默认的规则,设置[[Call]]、[[Contruct]] 及[[HasInstance]]属性 � 将[[Scope]]设置为当前的 LexicalEnvironment对象 � 设置[[Code]]、[[FormalParameterList]] 及name、length、prototype属性
  • 29. 创建函数(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 };
  • 30. 创建函数(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();
  • 31. 进入函数(Entering Function Code) � 新建一个执行环境。 � 根据规则设置this绑定。 � 如果thisArg是null或undefined,设置为global。 � 如果thisArg不是Object,设置为ToObject(thisArg)。 � 以函数的[[Scope]]属性为参数, NewDeclarativeEnvironment创建一个 LexicalEnvironment对象。 � 将当前LexicalEnvironment设置为该值。 同一对象 � 将当前VariableEnvironment设置为该值。 � 开始初始化参数及函数内声明的变量。
  • 32. 进入函数(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]]);
  • 33. 进入函数(Entering Function Code) � 执行函数时,在作用域([[Scope]])的基础 上添加词法环境(LexicalEnvironment) // Global Environment function outer() { Global Environment // Outer Environment function inner() { Outer Environment // Current Environment } Current Environment } var inner = outer(); // [[Scope]] === Outer Environment inner();
  • 34. 定义绑定初始化 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); } } }
  • 35. 定义绑定初始化 (Declaration Binding Instantiation ) � 遍历FormalParameterList(参数列表),对每一项 (参数),如果VariableEnvironment中不存在,则 添加并赋值。 � 依次遍历源码中每个FunctionDeclaration(函数声 明),对每一项(函数),如果 VariableEnvironment中不存在,则添加并赋值。 � 如果VariableEnvironment中不存在arguments arguments arguments,则 添加并赋值。 � 依次遍历源码中每个VariableDeclaration(变量声 明),对每一项(变量),如果 VariableEnvironment中不存在,则添加并赋值为 undefined undefined。
  • 36. 定义绑定初始化 (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; }
  • 38. 变量查找 � GetIdentifierReference(lex, name) � 从给定的LexicalEnvironment中查找是否存 在该变量 � 如果不存在,则从LexicalEnvironment的 Outer Environment中查找 � 依次进行,直到Outer Environment为 null,则返回undefined � 返回一个Reference对象,通过GetValue进一 步获取变量的值
  • 39. 变量查找 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); }
  • 40. 大串烧 � 进入全局环境 � 创建全局执行环境并入栈 � 创建全局环境对象 � 全局的词法环境对象指向该对象 � 全局的变量环境对象指向该对象 � 在变量环境中添加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();
  • 41. 大串烧 � 创建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();
  • 42. 大串烧 Global Environment outer: { [[Scope]] } inner: undefined prefix: undefined Global Execution Context VariableEnvironmen t LexicalEnvironment
  • 43. 大串烧 � 为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();
  • 44. 大串烧 Global Environment outer: { [[Scope]] } inner: undefined prefix: ‘Hello ‘ Global Execution Context VariableEnvironmen t LexicalEnvironment
  • 45. 大串烧 � 执行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();
  • 46. 大串烧 � 创建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();
  • 47. 大串烧 Global Environment outer: { [[Scope]] } inner: undefined prefix: ‘Hello ‘ Outer Environment Outer Execution Context say: { [[Scope]] } name: undefined VariableEnvironment LexicalEnvironment Global Execution Context VariableEnvironmen t LexicalEnvironment
  • 48. 大串烧 � 为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();
  • 49. 大串烧 Global Environment outer: { [[Scope]] } inner: { [[Scope]] } prefix: ‘Hello ‘ Outer Environment Outer Execution Context say: { [[Scope]] } name: ‘Gray Zhang’ VariableEnvironment LexicalEnvironment Global Execution Context VariableEnvironmen t LexicalEnvironment
  • 50. 大串烧 � 返回并赋值给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();
  • 51. 大串烧 Global Environment outer: { [[Scope]] } inner: { [[Scope]] } prefix: ‘Hello ‘ Outer Environment say: { [[Scope]] } name: ‘GrayZhang’ Global Execution Context VariableEnvironmen t LexicalEnvironment
  • 52. 大串烧 � 执行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();
  • 53. 大串烧 Global Environment outer: { [[Scope]] } inner: { [[Scope]] } prefix: ‘Hello ‘ Outer Environment Inner Execution Context say: { [[Scope]] } name: ‘GrayZhang’ VariableEnvironment LexicalEnvironment Inner Environment message: undefined Global Execution Context VariableEnvironmen t LexicalEnvironment
  • 54. 大串烧 � 为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();
  • 55. 大串烧 Global Environment outer: { [[Scope]] } inner: { [[Scope]] } prefix: ‘Hello ‘ Outer Environment Inner Execution Context say: { [[Scope]] } name: ‘GrayZhang’ VariableEnvironment LexicalEnvironment Inner Environment message: ‘Hello GrayZhang’ Global Execution Context VariableEnvironmen t LexicalEnvironment
  • 56. 大串烧 � 获取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();
  • 57. 大串烧 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();
  • 58. 总结 � 相关概念 � 可执行代码 – Executable Code � 执行环境 – Execution Context � 词法环境 – LexicalEnvironment � 变量环境 – VariableEnvironment � 环境数据 – Environment Records
  • 59. 总结 � 过程 � 创建函数 – [[Scope]] � [[Scope]]在创建时决定且不会变化 � 进入函数 – 执行环境 + 词法环境 + 变量环境 � 执行时在最内层增加词法环境 � 定义绑定初始化 – 参数 + 函数声明 + 变量声 明 � 变量环境和词法环境是同一个对象 � 变量查找 – GetIdentifierReference � 延词法环境自内向外查找
  • 60. 继续消化 • 我以为我懂了,直到…… – How with works – How catch works – How let works – When code meets eval – When code meets new Function – When there is strict mode
  • 61. 从代码说起 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
  • 62. 但是事实上…… function outer() { var i = 3; return function() { debugger; }; } var inner = outer(); inner(); javascript引擎有能力回收i
  • 63. 如果你是计算机…… 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();
  • 64. ~ ~ ~ ~ 大大 大 大 好好 好 好 力力 力 力 压 压 压 压
  • 65. 测试方法 � 用断点! � Chrome / Firefox � 看内存! � IE / Opera
  • 66. 一些基本结果 � IE6 – 8没有回收闭包内变量的机制 � Opera没有回收闭包内变量的机制 � Chrome回收闭包内变量后,再次访问该变量 将抛出ReferenceError ReferenceError � Firefox回收闭包内变量后,再次访问该变量会 得到undefined undefined � Chrome、Firefox和IE9回收闭包内变量的策 略基本相同
  • 67. 试问! � 有哪些因素可能导致变量无法回收? � 变量被返回的函数直接引用。 � 变量被返回的函数间接引用(通过嵌套函数)。 � 返回的函数中有eval eval eval。 � 返回的函数在with with with表达式建立的作用域中。 � 返回的函数在catch catch catch表达式中。 � 只谈结果,不谈过程!
  • 68. 直接引用 Engine Collectable Chrome – V8 NO Firefox – SpiderMonkey NO IE9 - Chakra NO function outer() { var i = 3; return function() { i; }; } var inner = outer();
  • 69. 间接引用 Engine Collectable Chrome – V8 NO Firefox – SpiderMonkey NO IE9 - Chakra NO function outer() { var i = 3; function help() { i; } return function() { help(); }; } var inner = outer();
  • 70. 嵌套函数的平衡 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(); 需要图的遍历 需要处理环引用 高成本 + 低效
  • 71. 71 嵌套函数的平衡 Engine Collectable Chrome – V8 NO Firefox – SpiderMonkey NO IE9 - Chakra NO function outer() { var i = 3; function help() { i; } return function() { }; } var inner = outer();
  • 72. 大恶魔eval function outer() { var i = 3; ? return function() { return eval(‘i’); } } var inner = outer(); var result = inner(); console.log(result); // 3 由字符串从词法环境中获取对象的唯一途径 可变性 特殊性
  • 73. 大恶魔eval var 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’));
  • 74. 大恶魔eval function 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
  • 75.
  • 76. .. . . 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 So To To To To
  • 77. 大恶魔eval Engine Collectable Chrome – V8 NO Firefox – SpiderMonkey NO IE9 - Chakra NO function outer() { var i = 3; return function() { eval(‘’); // 无论eval的内容是什么 }; } var inner = outer();
  • 78. 间接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.
  • 79. 间接eval和new Function var 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
  • 80. 间接eval和new Function Engine Collectable Chrome – V8 YES Firefox – SpiderMonkey YES IE9 - Chakra YES function outer() { var i = 3; return function() { window.eval(‘i’); }; } var inner = outer();
  • 81. 关于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();
  • 82. 关于with的分歧 Engine Collectable Chrome – V8 NO Firefox – SpiderMonkey 回收k, scope k, scope,不回收i, j i IE9 - 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();
  • 83. 不被重视的catch Engine Collectable Chrome – V8 回收i,不回收ex i ex Firefox – SpiderMonkey 回收i,不回收ex i ex IE9 - Chakra 回收i和ex i ex function outer() { var i = 3; try { throw { j: 4 }; } catch (ex) { return function() {}; } } var inner = outer();
  • 84. 你能骗过引擎吗? function outer() { var i = 3; var j = 4; ? return function( i) { var j = 5; console.log( i + j); }; } var inner = outer(); inner(6); Engine Collectable Chrome – V8 YES Firefox – SpiderMonkey YES IE9 - Chakra YES
  • 85. 总结 � 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);
  • 86.
  • 87. 知识要点 • 变量声明在变量环境中,从词法环境中获 取,通常2者是同一个对象。 • 作用域在函数创建时生成,是函数对象的 不变的属性 – 静。 • 执行函数时,在作用域上增加一个词法环 境对象 – 动。 • 动静结合即闭包的本质。 • 闭包对垃圾回收会有一定的影响。
  • 88. 参考资料 • 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