88
所所所所 所所所 [email protected] function infiniteSequence() { var i = 0; return function() { return i++; } } var increment = infiniteSequence(); console.log(increment()); console.log(increment()); console.log(increment()); // …

About closure

Embed Size (px)

DESCRIPTION

Talk about javascript closure in detail.

Citation preview

Page 1: About closure

所谓闭包张立理[email protected]

function infiniteSequence() { var i = 0; return function() { return i++; }}

var increment = infiniteSequence();console.log(increment()); console.log(increment());console.log(increment());// …

Page 2: About closure

WARNING!!

Page 3: About closure

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

只谈函数,不提 eval ,不提 new Function

Page 4: About closure

Summary 引言 什么是变量 闭包之表象 闭包之内在 关于垃圾回收

Page 5: About closure
Page 6: About closure
Page 7: About closure

作用域的历史

ECMAScript v3

Scope Chain

Variable Object

Identifier Resolution

ECMAScript v5

Lexical Environment

Variable Environment

GetIdentifierReference

Page 8: About closure

什么是变量

变量是一种关联关系( association ) 关联的目标:

符号名( symbolic name) 值( value )

关联是单向的 – 永远不可能根据值找到变量

A symbolic name associated with a value and whose associated value may be changed.

-- Wikipedia

Identifiername

Value‘GrayZhang’

Page 9: About closure

什么是变量 Variable Statement

var Identifier = AssignmentExpression FunctionDeclaration

function Identifier(FormalParameterList) { FunctionBody}

FormalParameterList Identifier, Identifier[, …]

Page 10: About closure

什么是变量

var name = ‘GrayZhang’;

function add(x, y) {

return x + y;

}

Keyword

Identifier

Value (String Literal)

Page 11: About closure

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

function outer() { var name = ‘GrayZhang’; function inner() { console.log(‘Hello ‘ + name); } inner();}

外层

内层

Page 12: About closure

闭包之内在 Q:为什么javascript会有闭包? A:因为ECMAScript中变量解析是一个查找过程,而非绑定过程。

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

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

Q:如何查找变量? A:自内向外。

Page 13: About closure

Page 14: About closure

可执行代码( Executable Code )

Global Code Function Code

Eval Code

<script>var name = ‘GrayZhang’;var prefix = ‘Hello‘;var phrases = [prefix, name];console.log(phrases.join(‘ ‘));</script>

var source = ‘var x = 3;’ + ‘console.log(x);’eval(source);

function sayHello(name) { var prefix = ‘Hello’; var phrases = [prefix, name]; console.log(phrases.join(‘ ‘));}

function getName() { var input = $(‘#name’); return input.val();}

getName();

Page 15: About closure

执行环境( Execution Context ) 当进入(开始执行)一段可执行代码时,生成

一个执行环境对象。 执行环境对象通过栈( Stack )维护。 新建的执行环境对象称为“当前运行的执行环

境对象”。function enterCode(code) { var ec = new ExecutionContext(); control.ecStack.push(ec); control.runningEC = ec; control.execute(code);}

Page 16: About closure

执行环境( Execution Context ) 一个执行环境对象包括:

词法环境 – LexicalEnvironment 变量环境 – VariableEnvironment This 绑定 - ThisBinding

ExecutionContext: { LexicalEnvironment, VariableEnvironment, ThisBinding}

Page 17: About closure

词法环境( LexicalEnvironment ) 既是一个属性,又是一个类型。 每个执行环境对象都有且仅有一个关联的词法

环境对象。 在代码执行过程中,需要解析变量时,通过词

法环境对象进行解析,从其环境数据中得到值。 一个词法环境对象包括:

环境数据 – environement records 外层环境 – outer environment

Page 18: About closure

词法环境( LexicalEnvironment ) 存在 2 种词法环境的实现类型

DeclarativeEnvironment ObjectEnvironment

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

?什么情况会出现ObjectEnvironment

Page 19: About closure

变量环境( VariableEnvironment ) 每个执行环境对象都有且仅有一个关联的变量

环境对象。 变量环境仅仅是一个名字,变量环境对象的类

型是词法环境( LexicalEnvironment )。 在进入(开始执行)代码时,所有的变量标识

符( Identifier )会存放在当前的变量环境对象中。

变量环境中有环境数据属性,但不使用外层环境属性。

Page 20: About closure

环境数据( environment records ) 存在于词法环境或变量环境中。 包含一个 binding object ,简单地认为 binding

object 是一个 Map 对象,保存变量标签符( Identifier )和变量值( Value )的关系。

常用方法: hadBinding(name) – 查看是否有变量绑定 createBinding(name) – 创建一个变量绑定 setBinding(name, value) – 修改变量绑定的值 getValue(name) – 获取变量绑定的值 deleteBinding(name) – 删除一个变量绑定

Page 21: About closure

环境数据( 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; }, // …}

Page 22: About closure

环境数据( environment records ) 存在 2 种环境数据的实现类型

DeclarativeEnvironmentRecords ObjectEnvironmentRecords

LexicalEnvironment EnvironmentRecords

ObjectEnvironment

DeclaractiveEnvironment

ObjectEnvironmentRecords

DeclaractiveEnvironmentRecords

Page 23: About closure

创建词法环境 NewDeclarativeEnvironment(e)

用于创建一个 DeclarativeEnvironment 进入函数时 执行 catch 表达式时

NewObjectEnvironment(o, e) 用于创建一个 ObjectEnvironment 执行 with 表达式时

Page 24: About closure

创建词法环境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;}

Page 25: About closure

消化一下• 名词解释完了吗?

NO

Page 26: About closure

总结ExecutionContext: { LexicalEnvironment, VariableEnvironment, ThisBinding}

EnvironmentRecords: { hasBinding(name), createBinding(name), setBinding(name, value), getValue(name), deleteBinding(name)}

LexicalEnvironment: { environmentRecords, outerEnvironment}

Page 27: About closure

函数( Function ) 是一个对象( Object )。 包含几个特殊的属性

[[Construct]] – new SomeFunction() [[Call]] – someFunction() [[HasInstance]] – o instanceof SomeFunction [[Scope]] – 闭包 [[FormalParameters]] – 参数列表 [[Code]] – 可执行代码

包含可执行代码( Executable Code )和执行状态( State )。

Page 28: About closure

创建函数( Create Function Object ) 新建一个普通对象( new Object() ) 将 [[Class]] 设为” function” 将 [[Prototype]] 指向 Function.prototype 根据默认的规则,设置 [[Call]] 、 [[Contruct]]

及 [[HasInstance]] 属性 将 [[Scope]] 设置为当前的 LexicalEnvironment

或 VariableEnvironment 对象 设置 [[Code]] 、 [[FormalParameterList]] 及

name 、 length 、 prototype 属性

Page 29: About closure

创建函数( 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 };

Page 30: About closure

创建函数( Create Function Object ) 作用域( [[Scope]] )是函数对象的一个属性function hello() { var o = {}; o.name = ‘GrayZhang’; return o;}var person = hello();console.log(person.name);

function outer() { var name = ‘GrayZhang’; function say() { alert(name); } return say;}var inner = outer();// inner.[[Scope]]inner();

Page 31: About closure

进入函数( Entering Function Code ) 新建一个执行环境。 根据规则设置 this 绑定。

如果 thisArg 是 null或 undefined,设置为 global。 如果 thisArg 不是 Object,设置为ToObject(thisArg) 。

以函数的 [[Scope]] 属性为参数, NewDeclarativeEnvironment 创建一个LexicalEnvironment 对象。

将当前 LexicalEnvironment 设置为该值。 将当前 VariableEnvironment 设置为该值。 开始初始化参数及函数内声明的变量。

同一对象

Page 32: About closure

进入函数( 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]]);

Page 33: About closure

进入函数( Entering Function Code ) 执行函数时,在作用域( [[Scope]] )的基础

上添加词法环境( LexicalEnvironment )

Global Environment

Outer Environment

Current Environment

// Global Environmentfunction outer() { // Outer Environment function inner() { // Current Environment }}var inner = outer();// [[Scope]] === Outer Environmentinner();

Page 34: About closure

定义绑定初始化 ( Declaration Binding Instantiation ) 从 Hosting Behavior 说起……function sayHello(name) { if (!name) { throw new Error(); } else { var prefix = 'Hello '; alert(prefix + name); }}

function sayHello(name) { var prefix; if (!name) { throw new Error(); } else { prefix = 'Hello '; alert(prefix + name); }}

Page 35: About closure

定义绑定初始化 ( Declaration Binding Instantiation ) 遍历 FormalParameterList (参数列表),对每一项

(参数),如果 VariableEnvironment 中不存在,则添加并赋值。

依次遍历源码中每个 FunctionDeclaration (函数声明),对每一项(函数),如果 VariableEnvironment中不存在,则添加,随后赋值。

如果 VariableEnvironment 中不存在 arguments ,则添加并赋值。

依次遍历源码中每个 VariableDeclaration (变量声明),对每一项(变量),如果 VariableEnvironment 中不存在,则添加并赋值为 undefined 。

Page 36: About closure

定义绑定初始化 ( Declaration Binding Instantiation )function format(template, data) { var regex = /\{(\w+)\:(\w+)\}/g; function replacer(match, name, type) { var value = data[name]; switch (type) { case 'boolean': value = !!value; break; case 'html': value = encodeHTML(value); break; } return value; } var html = template.replace(regex, replacer); return html;}

Variable Environment

arguments

Page 37: About closure

消化一下• 还有完没完了!

NO

Page 38: About closure

变量查找 GetIdentifierReference(lex, name) 从给定的 LexicalEnvironment 中查找是否存

在该变量 如果不存在,则从 LexicalEnvironment 的

Outer Environment 中查找 依次进行,直到 Outer Environment 为

null ,则返回 undefined 返回一个 Reference 对象,通过 GetValue

进一步获取变量的值

Page 39: About closure

变量查找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);}

Page 40: About closure

大串烧 进入全局环境

创建全局执行环境并入栈 创建全局环境对象 全局的词法环境对象指向该对象 全局的变量环境对象指向该对象 在变量环境中添加 outer 绑定并赋值 在变量环境中添加 prefix 绑定 在变量环境中添加 inner 绑定

// 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();

Page 41: About closure

大串烧 创建 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();

Page 42: About closure

大串烧Global Environment

outer: { [[Scope]] }inner: undefinedprefix: undefined

Global Execution Context

VariableEnvironment

LexicalEnvironment

Page 43: About closure

大串烧 为 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();

Page 44: About closure

大串烧Global Environment

outer: { [[Scope]] }inner: undefinedprefix: ‘Hello ‘

Global Execution Context

VariableEnvironment

LexicalEnvironment

Page 45: About closure

大串烧 执行 outer 函数

创建执行环境并入栈 创建一个词法环境 – DeclarativeEnvironment outer 的词法环境对象指向该对象 outer 的变量环境对象指向该对象 在变量环境中添加 say 绑定并赋值 在变量环境中添加 name 绑定 // 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();

Page 46: About closure

大串烧 创建 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();

Page 47: About closure

大串烧

Global Execution Context

Global Environment

outer: { [[Scope]] }inner: undefinedprefix: ‘Hello ‘

VariableEnvironment

LexicalEnvironment

Outer Environmentsay: { [[Scope]] }name: undefined

Outer Execution Context

VariableEnvironment

LexicalEnvironment

Page 48: About closure

大串烧 为 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();

Page 49: About closure

大串烧Global Environment

outer: { [[Scope]] }inner: { [[Scope]] }prefix: ‘Hello ‘

Outer Environmentsay: { [[Scope]] }name: ‘Gray Zhang’

Global Execution Context

VariableEnvironment

LexicalEnvironment

Outer Execution Context

VariableEnvironment

LexicalEnvironment

Page 50: About closure

大串烧 返回并赋值给 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();

Page 51: About closure

大串烧Global Environment

outer: { [[Scope]] }inner: { [[Scope]] }prefix: ‘Hello ‘

Outer Environmentsay: { [[Scope]] }name: ‘GrayZhang’

Global Execution Context

VariableEnvironment

LexicalEnvironment

Page 52: About closure

大串烧 执行 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();

Page 53: About closure

大串烧Global Environment

outer: { [[Scope]] }inner: { [[Scope]] }prefix: ‘Hello ‘

Outer Environmentsay: { [[Scope]] }name: ‘GrayZhang’

Inner Environment

message: undefined

Global Execution Context

VariableEnvironment

LexicalEnvironment

Inner Execution Context

VariableEnvironment

LexicalEnvironment

Page 54: About closure

大串烧 为 message 变量赋值

查找 prefix 变量的值 在 inner 的词法环境中寻找 prefix 绑定 – 没有 在 outer 的词法环境中寻找 prefix 绑定 – 没有 在全局环境中寻找 prefix 绑定 – 找到 取得 prefix 的值

查找 name 变量的值 …

在 inner 的词法环境中寻找 message 给 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();

Page 55: About closure

大串烧Global Environment

outer: { [[Scope]] }inner: { [[Scope]] }prefix: ‘Hello ‘

Outer Environmentsay: { [[Scope]] }name: ‘GrayZhang’

Inner Environment

message: ‘Hello GrayZhang’

Global Execution Context

VariableEnvironment

LexicalEnvironment

Inner Execution Context

VariableEnvironment

LexicalEnvironment

Page 56: About closure

大串烧 获取 inner 的值

在 inner 的词法环境中寻找 message 绑定 – 找到 得到上一步返回的 Reference 的 base – 即 inner 的词法

环境的环境数据对象 调用该对象的 getValue(‘message’)

获取 alert 的值 …

将 inner 作为参数,调用 alert 函数// 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();

alert 从何而来?

Page 57: About closure

大串烧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();

Page 58: About closure

总结 相关概念

可执行代码 – Executable Code 执行环境 – Execution Context 词法环境 – LexicalEnvironment 变量环境 – VariableEnvironment 环境数据 – Environment Records

Page 59: About closure

总结 过程

创建函数 – [[Scope]] [[Scope]] 在创建时决定且不会变化

进入函数 – 执行环境 + 词法环境 + 变量环境 执行时在最内层增加词法环境

定义绑定初始化 – 参数 + 函数声明 + 变量声明 变量环境和词法环境是同一个对象

变量查找 – GetIdentifierReference 延词法环境自内向外查找

Page 60: About closure

继续消化• 我以为我懂了,直到……– How with works– How catch works– How let works

– When code meets eval– When code meets new Function

– When there is strict mode

Page 61: About closure

从代码说起function outer() { var o = LargetObject.fromSize('400MB');

return function() { console.log('inner'); };}var inner = outer();// 对象图 此时对象之间的引用关系?

Global

Function

Lexical Environmen

t

Environment Records

Binding Object

Large Object

inner [[Scope]]

environmentRecords

bindingObject

o

Global 和 o 有间接引用,无法回收 o

Page 62: About closure

但是事实上……function outer() { var i = 3; return function() { debugger; };}

var inner = outer();inner();

javascript 引擎有能力回收 i

Page 63: About closure

如果你是计算机……function outer() { var i = 3; var j = 4; var k = 5;

function prepare() { i = i + k; }

function help() { i = i + j; } prepare();

return function() { help(); console.log(i); };}

var inner = outer();inner();

i: 不可回收j: 不可回收k: 可回收prepare: 可回收help: 不可回收

人类的智商

计算机的智商

Page 64: About closure

压力

好大

~

Page 65: About closure

测试方法 用断点!

Chrome / Firefox

看内存! IE / Opera

Page 66: About closure

一些基本结果 IE6 – 8 没有回收闭包内变量的机制 Opera 没有回收闭包内变量的机制 Chrome 回收闭包内变量后,再次访问该变量

将抛出 ReferenceError Firefox 回收闭包内变量后,再次访问该变量

会得到 undefined Chrome 、 Firefox 和 IE9 回收闭包内变量的策略基本相同

Page 67: About closure

试问! 有哪些因素可能导致变量无法回收?

变量被返回的函数直接引用。 变量被返回的函数间接引用(通过嵌套函数)。 返回的函数中有 eval 。 返回的函数在 with 表达式建立的作用域中。 返回的函数在 catch 表达式中。

只谈结果,不谈过程!

Page 68: About closure

直接引用Engine Collectable

Chrome – V8 NO

Firefox – SpiderMonkey NO

IE9 - Chakra NO

function outer() { var i = 3; return function() { i; };}var inner = outer();

Page 69: About closure

间接引用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();

Page 70: About closure

嵌套函数的平衡function outer() { var i = 0;

function help() { i++; }

help();

return function() { console.log('nothing'); }}

var inner = outer();

function outer() { var i = 0;

function help() { i++; return inner(); }

function inner() { return i > 3 ? i : help(); }

return inner();}

var inner = outer();需要图的遍历需要处理环引用高成本 + 低

Page 71: About closure

嵌套函数的平衡Engine Collectable

Chrome – V8 NO

Firefox – SpiderMonkey NO

IE9 - Chakra NO

function outer() { var i = 3; function help() { i; }

return function() { };}var inner = outer();

Page 72: About closure

大恶魔 evalfunction outer() { var i = 3;

return function() { return eval(‘i’); }}

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

?

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

可变性 特殊性

Page 73: About closure

大恶魔 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’));

Page 74: About closure

大恶魔 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

Page 75: About closure

Page 76: About closure

Too Simple,

Sometimes Native.

Page 77: About closure

大恶魔 evalEngine Collectable

Chrome – V8 NO

Firefox – SpiderMonkey NO

IE9 - Chakra NO

function outer() { var i = 3;

return function() { eval(‘’); // 无论 eval 的内容是什么 };}var inner = outer();

Page 78: About closure

间接 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.

Page 79: About closure

间接 eval 和 new Functionvar i = 3;

function outer() { var i = 4; return function() { return window.eval('i'); /* * var fn = new Function('return i;'); * return fn(); */ }}

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

X

Page 80: About closure

间接 eval 和 new FunctionEngine Collectable

Chrome – V8 YES

Firefox – SpiderMonkey YES

IE9 - Chakra YES

function outer() { var i = 3;

return function() { window.eval(‘i’); };}var inner = outer();

Page 81: About closure

关于 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();

??

Page 82: About closure

关于 with 的分歧Engine Collectable

Chrome – V8 NO

Firefox – SpiderMonkey 回收 k, scope ,不回收 i, j

IE9 - Chakra 回收 k ,不回收 i, j , scope未知function outer() {

var scope = { i: 3, j: 4 }; var k = 4; with (scope) { return function() { i; }; }}var inner = outer();

Page 83: About closure

不被重视的 catchEngine Collectable

Chrome – V8 回收 i ,不回收 ex

Firefox – SpiderMonkey 回收 i ,不回收 ex

IE9 - Chakra 回收 i 和 exfunction outer() { var i = 3; try { throw { j: 4 }; } catch (ex) { return function() {}; }}var inner = outer();

Page 84: About closure

你能骗过引擎吗?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

Page 85: About closure

总结 outer 声明的 – c1 = (i, j, k, m) inner 声明的 – c2 = (i, j) inner 用到的 – c3 = (i, j, k) help 声明的 – c4 = () help 用到的 – c5 = (j, k) 可回收的 = c1- (c3 – c2) – (c5 – c4)

遇上 eval 则不回收任何变量 注意 with 和 catch 的影响

function outer() { var i = 3; var j = 4; var k = 5; var m = 6;

function help() { console.log(j + k); }

return function(i) { var j = 5; console.log(i + j + k); };}var inner = outer();inner(6);

Page 86: About closure

谢谢

Page 87: About closure

知识要点• 变量声明在变量环境中,从词法环境中获

取,通常 2者是同一个对象。• 作用域在函数创建时生成,是函数对象的

不变的属性 – 静。• 执行函数时,在作用域上增加一个词法环

境对象 – 动。• 动静结合即闭包的本质。• 闭包对垃圾回收会有一定的影响。

Page 88: About closure

参考资料• Annotated ES5

– 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