Top Banner
所谓闭包 张立理 [email protected] function infiniteSequence() { var i = 0; return function() { return i++; } } var increment = infiniteSequence(); console.log(increment()); console.log(increment()); console.log(increment()); // …
88

所谓闭包

Jun 14, 2015

Download

Spiritual

youzitang
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: 所谓闭包

所谓闭包张立理

[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: 所谓闭包

2

WARNING!!

Page 3: 所谓闭包

WARNING�非常非常的学术性

�大量术语词汇出没

�也许永远都用不上

�内容并非标准,有所删减

�逻辑不是那么清晰

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

Page 4: 所谓闭包

Summary�引言

�什么是变量

�闭包之表象

�闭包之内在

�关于垃圾回收

Page 5: 所谓闭包
Page 6: 所谓闭包
Page 7: 所谓闭包

作用域的历史

ECMAScript v3

Scope Chain

Variable Object

Identifier Resolution

ECMAScript v5

Lexical Environment

Variable Environment

GetIdentifierReference

Page 8: 所谓闭包

什么是变量

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

�符号名(symbolic name)�值(value)

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

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

-- Wikipedia

Identifiername

Value‘GrayZhang’

Page 9: 所谓闭包

什么是变量�Variable StatementVariable StatementVariable StatementVariable Statement� var Identifier = AssignmentExpression

�FFFFunctionDeclarationunctionDeclarationunctionDeclarationunctionDeclaration� function Identifier(FormalParameterList) { FunctionBody}

� FormalParameterListFormalParameterListFormalParameterListFormalParameterList� Identifier, Identifier[, …]

Page 10: 所谓闭包

什么是变量

var name = ‘GrayZhang’;

function add(x, y) {

return x + y;

}

Keyword

Identifier

Value (String Literal)

Page 11: 所谓闭包

11

闭包之表象

�内层函数可以使用外层函数作用域内的变量

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

外层

内层

Page 12: 所谓闭包

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

过程。

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

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

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

Page 13: 所谓闭包

Page 14: 所谓闭包

可执行代码(Executable Code)

Global CodeGlobal CodeGlobal CodeGlobal Code Function CodeFunction CodeFunction CodeFunction 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: 所谓闭包

15

执行环境(Execution Context)

�当进入(开始执行)一段可执行代码时,生成一个执行环境对象。

�执行环境对象通过栈(Stack)维护。

�新建的执行环境对象称为“当前运行的执行环境对象”。function enterCode(code) { var ec = new ExecutionContext(); control.ecStack.push(ec); control.runningEC = ec; control.execute(code);}

Page 16: 所谓闭包

执行环境(Execution Context)

�一个执行环境对象包括:

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

ExecutionContext: { LexicalEnvironment, VariableEnvironment, ThisBinding}

Page 17: 所谓闭包

词法环境(LexicalEnvironment)

�既是一个属性,又是一个类型。

�每个执行环境对象都有且仅有一个关联的词法环境对象。

�在代码执行过程中,需要解析变量时,通过词法环境对象进行解析,从其环境数据中得到值。

�一个词法环境对象包括:

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

Page 18: 所谓闭包

词法环境(LexicalEnvironment)

�存在2种词法环境的实现类型

�DeclarativeEnvironment�ObjectEnvironment

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

?什么情况会出现ObjectEnvironment

Page 19: 所谓闭包

变量环境(VariableEnvironment)

�每个执行环境对象都有且仅有一个关联的变量环境对象。

�变量环境仅仅是一个名字,变量环境对象的类型是词法环境(LexicalEnvironment)。

�在进入(开始执行)代码时,所有的变量标识符(Identifier)会存放在当前的变量环境对象中。

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

Page 20: 所谓闭包

环境数据(environment records)� 存在于词法环境或变量环境中。

� 包含一个binding object,简单地认为binding object是一个Map对象,保存变量标签符(Identifier)和变量值(Value)的关系。

� 常用方法:

� hadBinding(name) – 查看是否有变量绑定

� createBinding(name) – 创建一个变量绑定

� setBinding(name, value) – 修改变量绑定的值

� getValue(name) – 获取变量绑定的值

� deleteBinding(name) – 删除一个变量绑定

Page 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; }, // …}

Page 22: 所谓闭包

环境数据(environment records)

�存在2种环境数据的实现类型

�DeclarativeEnvironmentRecords�ObjectEnvironmentRecords

LexicalEnvironment EnvironmentRecords

ObjectEnvironment

DeclaractiveEnvironment

ObjectEnvironmentRecords

DeclaractiveEnvironmentRecords

Page 23: 所谓闭包

创建词法环境�NewDeclarativeEnvironment(e)�用于创建一个DeclarativeEnvironment�进入函数时

�执行catch表达式时

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

Page 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;}

Page 25: 所谓闭包

消化一下

• 名词解释完了吗?

NONONONO

Page 26: 所谓闭包

总结ExecutionContext: { LexicalEnvironment, VariableEnvironment, ThisBinding}

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

LexicalEnvironment: { environmentRecords, outerEnvironment}

Page 27: 所谓闭包

函数(Function)� 是一个对象(Object)。

� 包含几个特殊的属性

� [[Construct]] – new SomeFunction()� [[Call]] – someFunction()� [[HasInstance]] – o instanceof SomeFunction� [[Scope]] – 闭包

� [[FormalParameters]] – 参数列表

� [[Code]] – 可执行代码

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

Page 28: 所谓闭包

创建函数(Create Function Object)

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

�将[[Scope]]设置为当前的LexicalEnvironment对象

�设置[[Code]]、[[FormalParameterList]]及name、length、prototype属性

Page 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 };

Page 30: 所谓闭包

创建函数(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: 所谓闭包

进入函数(Entering Function Code)

� 新建一个执行环境。

� 根据规则设置this绑定。

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

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

� 将当前LexicalEnvironment设置为该值。

� 将当前VariableEnvironment设置为该值。

� 开始初始化参数及函数内声明的变量。

同一对象

Page 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]]);

Page 33: 所谓闭包

进入函数(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: 所谓闭包

34定义绑定初始化 (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: 所谓闭包

定义绑定初始化 (Declaration Binding Instantiation)

� 遍历FormalParameterList(参数列表),对每一项(参数),如果VariableEnvironment中不存在,则添加并赋值。

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

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

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

Page 36: 所谓闭包

定义绑定初始化 (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: 所谓闭包

消化一下

• 还有完没完了!

NONONONO

Page 38: 所谓闭包

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

�如果不存在,则从LexicalEnvironment的Outer Environment中查找

�依次进行,直到Outer Environment为null,则返回undefined

�返回一个Reference对象,通过GetValue进一步获取变量的值

Page 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);}

Page 40: 所谓闭包

大串烧� 进入全局环境

� 创建全局执行环境并入栈

� 创建全局环境对象

� 全局的词法环境对象指向该对象

� 全局的变量环境对象指向该对象

� 在变量环境中添加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: 所谓闭包

大串烧� 创建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: 所谓闭包

大串烧Global Environment

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

Global Execution Context

VariableEnvironment

LexicalEnvironment

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

Page 44: 所谓闭包

大串烧Global Environment

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

Global Execution Context

VariableEnvironment

LexicalEnvironment

Page 45: 所谓闭包

大串烧� 执行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: 所谓闭包

大串烧� 创建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: 所谓闭包

大串烧

Global Execution Context

Global Environmentouter: { [[Scope]] }inner: undefinedprefix: ‘Hello ‘

VariableEnvironment

LexicalEnvironment

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

Outer Execution Context

VariableEnvironment

LexicalEnvironment

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

Page 49: 所谓闭包

大串烧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: 所谓闭包

大串烧� 返回并赋值给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: 所谓闭包

大串烧Global Environment

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

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

Global Execution Context

VariableEnvironment

LexicalEnvironment

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

Page 53: 所谓闭包

大串烧Global Environment

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

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

Inner Environmentmessage: undefined

Global Execution Context

VariableEnvironment

LexicalEnvironment

Inner Execution Context

VariableEnvironment

LexicalEnvironment

Page 54: 所谓闭包

大串烧� 为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: 所谓闭包

大串烧Global Environment

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

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

Inner Environmentmessage: ‘Hello GrayZhang’

Global Execution Context

VariableEnvironment

LexicalEnvironment

Inner Execution Context

VariableEnvironment

LexicalEnvironment

Page 56: 所谓闭包

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

alertalertalertalert从何而来?

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

Page 58: 所谓闭包

总结

�相关概念

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

Page 59: 所谓闭包

总结�过程�创建函数 – [[Scope]]� [[Scope]]在创建时决定且不会变化

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

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

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

Page 60: 所谓闭包

继续消化

• 我以为我懂了,直到……– How withwithwithwith works– How catchcatchcatchcatch works– How letletletlet works

– When code meets evalevalevaleval– When code meets new Functionnew Functionnew Functionnew Function

– When there is strict modestrict modestrict modestrict mode

Page 61: 所谓闭包

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

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

系?Global Function Lexical

Environment

Environment Records

Binding Object

Large Object

inner [[Scope]]

environmentRecords

bindingObjecto

GlobalGlobalGlobalGlobal和oooo有间接引用,无法回收oooo

Page 62: 所谓闭包

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

var inner = outer();inner();

javascriptjavascriptjavascriptjavascript引擎有能力回收iiii

Page 63: 所谓闭包

如果你是计算机……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: 所谓闭包

压压压压力

好大

力好

大力

好大

力好

大 ~ ~ ~ ~

Page 65: 所谓闭包

测试方法

�用断点!

� Chrome / Firefox

�看内存!

� IE / Opera

Page 66: 所谓闭包

一些基本结果� IE6 – 8没有回收闭包内变量的机制

�Opera没有回收闭包内变量的机制

�Chrome回收闭包内变量后,再次访问该变量将抛出ReferenceErrorReferenceErrorReferenceErrorReferenceError

�Firefox回收闭包内变量后,再次访问该变量会得到undefinedundefinedundefinedundefined

�Chrome、Firefox和IE9回收闭包内变量的策略基本相同

Page 67: 所谓闭包

试问!

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

�变量被返回的函数直接引用。

�变量被返回的函数间接引用(通过嵌套函数)。

�返回的函数中有evalevalevaleval。�返回的函数在withwithwithwith表达式建立的作用域中。

�返回的函数在catchcatchcatchcatch表达式中。

�只谈结果,不谈过程!

Page 68: 所谓闭包

直接引用Engine CollectableChrome – V8 NONONONOFirefox – SpiderMonkey NONONONOIE9 - Chakra NONONONOfunction outer() { var i = 3; return function() { i; };}var inner = outer();

Page 69: 所谓闭包

间接引用Engine CollectableChrome – V8 NONONONOFirefox – SpiderMonkey NONONONOIE9 - Chakra NONONONOfunction outer() { var i = 3; function help() { i; } return function() { help(); };}var inner = outer();

Page 70: 所谓闭包

嵌套函数的平衡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: 所谓闭包

71

嵌套函数的平衡Engine CollectableChrome – V8 NONONONOFirefox – SpiderMonkey NONONONOIE9 - Chakra NONONONOfunction outer() { var i = 3; function help() { i; }

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

Page 72: 所谓闭包

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

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

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

????

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

可变性 特殊性

Page 73: 所谓闭包

大恶魔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: 所谓闭包

大恶魔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: 所谓闭包

Page 76: 所谓闭包

Too Simple,

Too Simple,

Too Simple,

Too Simple,

Sometimes Native.

Sometimes Native.

Sometimes Native.

Sometimes Native.

Page 77: 所谓闭包

大恶魔evalEngine CollectableChrome – V8 NONONONOFirefox – SpiderMonkey NONONONOIE9 - Chakra NONONONOfunction outer() { var i = 3;

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

Page 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 global global global

environment environment environment environment as both the variable environmentvariable environmentvariable environmentvariable environment and lexical lexical lexical lexical environment environment environment 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 Global Environment Global Environment Global Environment as the ScopeScopeScopeScope parameter and strict as the Strict flag.

Page 79: 所谓闭包

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

XXXX

Page 80: 所谓闭包

间接eval和new FunctionEngine CollectableChrome – V8 YESYESYESYESFirefox – SpiderMonkey YESYESYESYESIE9 - Chakra YESYESYESYESfunction outer() { var i = 3;

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

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

??

Page 82: 所谓闭包

关于with的分歧Engine CollectableChrome – V8 NONONONOFirefox – SpiderMonkey 回收k, scopek, scopek, scopek, scope,不回收iiii, j, j, j, jIE9 - Chakra 回收kkkk,不回收iiii, j, j, j, j,scopescopescopescope未知

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

Page 83: 所谓闭包

不被重视的catchEngine CollectableChrome – V8 回收iiii,不回收exexexexFirefox – SpiderMonkey 回收iiii,不回收exexexexIE9 - Chakra 回收iiii和exexexexfunction outer() { var i = 3; try { throw { j: 4 }; } catch (ex) { return function() {}; }}var inner = outer();

Page 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 CollectableChrome – V8 YESYESYESYESFirefox – SpiderMonkey YESYESYESYESIE9 - Chakra YESYESYESYES

Page 85: 所谓闭包

总结� 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)

� 遇上evalevalevaleval则不回收任何变量

� 注意withwithwithwith和catchcatchcatchcatch的影响

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: 所谓闭包

谢 谢谢 谢谢 谢谢 谢

Page 87: 所谓闭包

知识要点

• 变量声明在变量环境中,从词法环境中获取,通常2者是同一个对象。

• 作用域在函数创建时生成,是函数对象的不变的属性 – 静。

• 执行函数时,在作用域上增加一个词法环境对象 – 动。

• 动静结合即闭包的本质。

• 闭包对垃圾回收会有一定的影响。

Page 88: 所谓闭包

参考资料

• Annotated ES5– http://es5.github.comhttp://es5.github.comhttp://es5.github.comhttp://es5.github.com////

• ECMA-262-5 in detail– http://dmitrysoshnikov.com/tag/es-5/http://dmitrysoshnikov.com/tag/es-5/http://dmitrysoshnikov.com/tag/es-5/http://dmitrysoshnikov.com/tag/es-5/

• 关于闭包及变量回收问题– http://www.otakustay.com/about-closure-and-gc/http://www.otakustay.com/about-closure-and-gc/http://www.otakustay.com/about-closure-and-gc/http://www.otakustay.com/about-closure-and-gc/

• Discussion on reference & scope - digipedia?– http://www.digipedia.pl/usenet/thread/14438/704/http://www.digipedia.pl/usenet/thread/14438/704/http://www.digipedia.pl/usenet/thread/14438/704/http://www.digipedia.pl/usenet/thread/14438/704/

• Discussion on V8 variable allocation - twitter– http://twitter.com/#!/erikcorry/status/53901976865476608http://twitter.com/#!/erikcorry/status/53901976865476608http://twitter.com/#!/erikcorry/status/53901976865476608http://twitter.com/#!/erikcorry/status/53901976865476608