`
arganzheng
  • 浏览: 104040 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

闭包学习笔记

阅读更多

闭包学习笔记

2010-05-27 星期四 晴朗

最近由于比较空闲,所以找个时间研究了一下闭包。其实以前也学习过一段时间,但是都是知其然而不知其所以然。现在终于可以说真正理解闭包是什么了。

要研究闭包,网上最好的资料是《Javascript Closures——FAQ>FAQ Notes》。虽然它只是集中介绍了Javascript的闭包,但是其他语言也是一样的机制。
下面我们也是直接介绍Javascript的闭包概念。

关键概念

1. The Resolution of Property Names on Objects

结论:给定一个属性,会先在对象上查找该名字的属性,如果不存在,则查找原型链,再找不到的话就返回undef。

2. Identifier Resolution, Execution Contexts and scope chains

The Execution Context

All javascript code is executed in an execution context. Global code (code executed inline, normally as a JS file, or HTML page, loads) gets executed in global execution context, and each invocation of a function (possibly as a constructor) has an associated execution context. Code executed with the eval function also gets a distinct execution context but as eval is never normally used by javascript programmers it will not be discussed here. The specified details of execution contexts are to be found in section 10.2 of ECMA 262 (3rd edition). 

When a javascript function is called it enters an execution context, if another function is called (or the same function recursively) a new execution context is created and execution enters that context for the duration of the function call. Returning to the original execution context when that called function returns. Thus running javascript code forms a stack of execution contexts

 When an execution context is created a number of things happen in a defined order. First, in the execution context of a function, an "Activation" object is created. The activation object is another specification mechanism. It can be considered as an object because it ends up having accessible named properties, but it is not a normal object as it has no prototype (at least not a defined prototype) and it cannot be directly referenced by javascript code.

The next step in the creation of the execution context for a function call is the creation of an arguments object, which is an array-like object with integer indexed members corresponding with the arguments passed to the function call, in order. It also has length and callee properties (which are not relevant to this discussion, see the spec for details). A property of the Activation object is created with the name "arguments" and a reference to the arguments object is assigned to that property.

Next the execution context is assigned a scope. A scope consists of a list (or chain) of objects. Each function object has an internal [[scope]] property (which we will go into more detail about shortly) that also consists of a list (or chain) of objects. The scope that is assigned to the execution context of a function call consists of the list referred to by the [[scope]] property of the corresponding function object with the Activation object added at the front of the chain (or the top of the list).

Then the process of "variable instantiation" takes place using an object that ECMA 262 refers to as the "Variable" object. However, the Activation object is used as the Variable object (note this, it is important: they are the same object). Named properties of the Variable object are created for each of the function's formal parameters, and if arguments to the function call correspond with those parameters the values of those arguments are assigned to the properties (otherwise the assigned value is undefined). Inner function definitions are used to create function objects which are assigned to properties of the Variable object with names that correspond to the function name used in the function declaration. The last stage of variable instantiation is to create named properties of the Variable object that correspond with all the local variables declared within the function.

The properties created on the Variable object that correspond with declared local variables are initially assigned undefined values during variable instantiation, the actual initialisation of local variables does not happen until the evaluation of the corresponding assignment expressions during the execution of the function body code.

It is the fact that the Activation object, with its arguments property, and the Variable object, with named properties corresponding with function local variables, are the same object, that allows the identifier arguments to be treated as if it was a function local variable.

Finally a value is assigned for use with the this keyword. If the value assigned refers to an object then property accessors prefixed with the this keyword reference properties of that object. If the value assigned (internally) is null then the this keyword will refer to the global object.

The global execution context gets some slightly different handling as it does not have arguments so it does not need a defined Activation object to refer to them. The global execution context does need a scope and its scope chain consists of exactly one object, the global object. The global execution context does go through variable instantiation, its inner functions are the normal top level function declarations that make up the bulk of javascript code. The global object is used as the Variable object, which is why globally declared functions become properties of the global object. As do globally declared variables.

The global execution context also uses a reference to the global object for the this object. 

说明:每个Activation/Variable对象包含了本函数执行上下文(Execution Context)中属于自身的数据(参数,和局部变量),而通过函数对象中的scope可以得到其他相关函数的Activation/Variable对象,从而得到其他对象的数据。

scope chains and [[scope]]

The scope chain of the execution context for a function call is constructed by adding the execution context's Activation/Variable object to the front of the scope chain held in the function object's [[scope]] property.

 Function objects created with the Function constructor always have a [[scope]] property referring to a scope chain that only contains the global object.

Function objects created with function declarations or function expressions have the scope chain of the execution context in which they are created assigned to their internal [[scope]] property. 


说明:每个Activation/Variable对象可能包含函数对象(这经常就是闭包),这个函数对象通过作用域链可以得到外层函数的数据。
Identifier Resolution

Identifiers are resolved against the scope chain.

Identifier resolution starts with the first object in the scope chain. It is checked to see if it has a property with a name that corresponds with the identifier. Because the scope chain is a chain of objects this checking encompasses the prototype chain of that object (if it has one). If no corresponding value can be found on the first object in the scope chain the search progresses to the next object. And so on until one of the objects in the chain (or one of its prototypes) has a property with a name that corresponds with the identifier or the scope chain is exhausted. 

As execution contexts associated with function calls will have the Activation/Variable object at the front of the chain, identifiers used in function bodies are effectively first checked to see whether they correspond with formal parameters, inner function declaration names or local variables. Those would be resolved as named properties of the Activation/Variable object.

3. Closures

Automatic Garbage Collection

ECMAScript uses automatic garbage collection. The specification does not define the details, leaving that to the implementers to sort out, and some implementations are known to give a very low priority to their garbage collection operations. But the general idea is that if an object becomes un-referable (by having no remaining references to it left accessible to executing code) it becomes available for garbage collection and will at some future point be destroyed and any resources it is consuming freed and returned to the system for re-use.

This would normally be the case upon exiting an execution context. The scope chain structure, the Activation/Variable object and any objects created within the execution context, including function objects, would no longer be accessible and so would become available for garbage collection. 

Forming Closures

A closure is formed by returning a function object that was created within an execution context of a function call from that function call and assigning a reference to that inner function to a property of another object. Or by directly assigning a reference to such a function object to, for example, a global variable, a property of a globally accessible object or an object passed by reference as an argument to the outer function call. e.g:-

function exampleClosureForm(arg1, arg2){
    var localVar = 8;
    function exampleReturned(innerArg){
        return ((arg1 + arg2)/(innerArg + localVar));
    }
    /* return a reference to the inner function defined as -
       exampleReturned -:-
    */
    return exampleReturned;
}

var globalVar = exampleClosureForm(2, 4);

Now the function object created within the execution context of the call to exampleClosureForm cannot be garbage collected because it is referred to by a global variable and is still accessible, it can even be executed with globalVar(n).

But something a little more complicated has happened because the function object now referred to by globalVar was created with a [[scope]] property referring to a scope chain containing the Activation/Variable object belonging to the execution context in which it was created (and the global object). Now the Activation/Variable object cannot be garbage collected either as the execution of the function object referred to by globalVar will need to add the whole scope chain from its [[scope]] property to the scope of the execution context created for each call to it.

A closure is formed. The inner function object has the free variables and the Activation/Variable object on the function's scope chain is the environment that binds them.

The Activation/Variable object is trapped by being referred to in the scope chain assigned to the internal [[scope]] property of the function object now referred to by the globalVar variable. The Activation/Variable object is preserved along with its state; the values of its properties. Scope resolution in the execution context of calls to the inner function will resolve identifiers that correspond with named properties of that Activation/Variable object as properties of that object. The value of those properties can still be read and set even though the execution context for which it was created has exited.

In the example above that Activation/Variable object has a state that represents the values of formal parameters, inner function definitions and local variables, at the time when the outer function returned (exited its execution context). The arg1 property has the value 2,the arg2 property the value 4, localVar the value 8 and an exampleReturned property that is a reference to the inner function object that was returned form the outer function. (We will be referring to this Activation/Variable object as "ActOuter1" in later discussion, for convenience.)

If the exampleClosureForm function was called again as:-

var secondGlobalVar = exampleClosureForm(12, 3);

- a new execution context would be created, along with a new Activation object. And a new function object would be returned, with its own distinct [[scope]] property referring to a scope chain containing the Activation object form this second execution context, with arg1 being 12 and arg2 being 3. (We will be referring to this Activation/Variable object as "ActOuter2" in later discussion, for convenience.)

A second and distinct closure has been formed by the second execution of exampleClosureForm.

The two function objects created by the execution of exampleClosureForm to which references have been assigned to the global variable globalVar and secondGlobalVar respectively, return the expression ((arg1 + arg2)/(innerArg + localVar)). Which applies various operators to four identifiers. How these identifiers are resolved is critical to the use and value of closures.

Consider the execution of the function object referred to by globalVar, as globalVar(2). A new execution context is created and an Activation object (we will call it "ActInner1"), which is added to the head of the scope chain referred to the [[scope]] property of the executed function object. ActInner1 is given a property named innerArg, after its formal parameter and the argument value 2 assigned to it. The scope chain for this new execution context is: ActInner1-> ActOuter1-> global object.

Identifier resolution is done against the scope chain so in order to return the value of the expression ((arg1 + arg2)/(innerArg + localVar)) the values of the identifiers will be determined by looking for properties, with names corresponding with the identifiers, on each object in the scope chain in turn.

The first object in the chain is ActInner1 and it has a property named innerArg with the value 2. All of the other 3 identifiers correspond with named properties of ActOuter1; arg1 is 2, arg2 is 4 and localVar is 8. The function call returns ((2 + 4)/(2 + 8)).

Compare that with the execution of the otherwise identical function object referred to by secondGlobalVar, as secondGlobalVar(5). Calling the Activation object for this new execution context "ActInner2", the scope chain becomes: ActInner2-> ActOuter2-> global object. ActInner2 returns innerArg as 5 and ActOuter2 returns arg1, arg2 and localVar as 12, 3 and 8 respectively. The value returned is ((12 + 3)/(5 + 8)).

Execute secondGlobalVar again and a new Activation object will appear at the front of the scope chain but ActOuter2 will still be next object in the chain and the value of its named properties will again be used in the resolution of the identifiers arg1, arg2 and localVar.

This is how ECMAScript inner functions gain, and maintain, access to the formal parameters, declared inner functions and local variables of the execution context in which they were created. And it is how the forming of a closure allows such a function object to keep referring to those values, reading and writing to them, for as long as it continues to exist. The Activation/Variable object from the execution context in which the inner function was created remains on the scope chain referred to by the function object's [[scope]] property, until all references to the inner function are freed and the function object is made available for garbage collection (along with any now unneeded objects on its scope chain).

Inner function may themselves have inner functions, and the inner functions returned from the execution of functions to form closures may themselves return inner functions and form closures of their own. With each nesting the scope chain gains extra Activation objects originating with the execution contexts in which the inner function objects were created. The ECMAScript specification requires a scope chain to be finite, but imposes no limits on their length. Implementations probably do impose some practical limitation but no specific magnitude has yet been reported. The potential for nesting inner functions seems so far to have exceeded anyone's desire to code them. 

写道最后都懒散了,直接贴文档了。一言以蔽之:如果说类是封装是行为的数据,那么闭包就是封装着数据的行为。创建一个闭包(往往是通过执行一个返回一个内层函数对象的函数),就是创建了一个邦定了创建该闭包的执行上下文的函数对象,因为是对象,所以他往往是有实例状态的。

经典例子:
<html>
<head>
<script>
function closure(){
    for(i=0; i <10; i++){
        var btn = document.getElementById("button" + i);
function handler(i){
   return (function(){
              alert(i);
           });
}
btn.onclick = handler(i); 
    }
}
</script>

</head>


<body onLoad="closure()">
<input id="button0" type="button" value="0" />
<input id="button1" type="button" value="1" />
<input id="button2" type="button" value="2" />
<input id="button3" type="button" value="3" />
<input id="button4" type="button" value="4" />
<input id="button5" type="button" value="5" />
<input id="button6" type="button" value="6" />
<input id="button7" type="button" value="7" />
<input id="button8" type="button" value="8" />
<input id="button9" type="button" value="9" />
</body>

</html>

说明:
 当我们定义如下函数时:
function handler(i){
   return (function(){
              alert(i);
           });
}
就确定了其作用域链,这是一个Lexical Scope,也就是说scope chain是在函数定义时就已经确定了。但是这时候scope chain中的每个Activation/Variable对象都是空的,也就是说是无状态的。
当我们执行外层函数handler时,就创建并且进入了handler的执行上下文:
btn.onclick = handler(i); 
根据前面的介绍的:“When an execution context is created a number of things happen in a defined order.。。。”handler函数的整个完整执行上下文就创建好了,执行完成之后本来应该是被垃圾回收的,但是因为handler返回了一个內部函数,所以该上下文不会被回收,而是邦定到內部函数了,thus形成了闭包(一个封装着状态的行为,不就是对象么^_^):
Closure
A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).   


groovy的闭包

Closure semantics

Closures appear to be a convenient mechanism for defining something like an inner classs, but the semantics are in fact more powerful and subtle than what an inner class offers. In particular, the properties of closures can be summarized in this manner:

  1. They have one implicit method (which is never specified in a closure definition) called doCall()
  2. A closure may be invoked via the call() method, or with a special syntax of an unnamed () invocation. Either invocation will be translated by Groovy into a call to the Closure's doCall() method.
  3. Closures may have 1...N arguments, which may be statically typed or untyped. The first parameter is available via an implicit untyped argument named it if no explicit arguments are named. If the caller does not specify any arguments, the first parameter (and, by extension, it) will be null.
  4. The developer does not have to use it for the first parameter. If they wish to use a different name, they may specify it in the parameter list.
  5. Closures always return a value. This may occur via either an explicit return statement, or as the value of the last statement in the closure body (e.g. an explicit return statement is optional).
  6. A closure may reference any variables defined within its enclosing lexical scope. Any such variable is said to be bound to the closure
  7. Any variables bound to a closure are available to the closure even when the closure is returned outside of the enclosing scope.
  8. Closures are first class objects in Groovy, and are always derived from the class Closure. Code which uses closures may reference them via untyped variables or variables typed as Closure.
  9. The body of a closure is not executed until it is explicitly invoked e.g. a closure is not invoked at its definition time
  10. A closure may be curried so that one a copy the closure is made with one or more of its parameters fixed to a constant value
一言以蔽之:如果说类是封装是行为的数据,那么闭包就是封装着数据的行为!
其实闭包就是一个类,只是他的状态只有在运行时候才能确定(因为他的数据很多时候来自于外部(lexical scope),他只是保存着这些数据的引用,所以多个闭包可能共享同个状态(several closures can share the same state (or part of it))),因为他是一个对象,所以他可以作为参数和返回值到处传递,我们可以通过()或call()方法促发他的doCall()方法。

例子:变量引用——Closure邦定的是变量的引用
package alibaba.b2b.forrest;

import groovy.lang.Delegate;

class A {
private String member  = "private member";
private String method(){
return "this is a private method";
}
def publicMethod(String name){
def localVar = "localVar";
def clos = {println "before: ${member} ${name} ${localVar} ${method()}" 
member = "member changed in closure";
localVar = "localVar changed in closure";
println "after: ${member} ${name} ${localVar} ${method()}" 
};
return clos;
}
}


A sample = new A();
def closureVar = sample.publicMethod ("Forrest");
closureVar ();
println();
def closureVar2 = sample.publicMethod ("Gump");
closureVar2 ();

/////////////////output//////////////////////////
before: private member Forrest localVar this is a private method
after: member changed in closure Forrest localVar changed in closure this is a private method

before: member changed in closure Gump localVar this is a private method
after: member changed in closure Gump localVar changed in closure this is a private method

例2:this & owner & delegate

this, owner, and delegate

this : as in Java, this refers to the instance of the enclosing class where a Closure is defined
owner : the enclosing object (this or a surrounding Closure)
delegate : by default the same as owner, but changeable for example in a builder or ExpandoMetaClass

 

Example:

class Class1 {

  def closure = {

    println this.class.name

    println delegate.class.name

    def nestedClos = {

      println owner.class.name

    }

    nestedClos()

  }

}

 

def clos = new Class1().closure

clos.delegate = this

clos()

/*  prints:

 Class1

 Script1

 Class1$_closure1  */

 

因为闭包实际上就是一个对象,所以它是可以,并且经常作为参数传递。这点有点类似于C++中的函数对象。

def list = ['a','b','c','d']

def newList = []

 

list.collect( newList ) {

   it.toUpperCase()

}

println newList           //  ["A", "B", "C", "D"]

In the above example, the collect method accepts a List and a Closure argument. The same could be accomplished like so (although it is more verbose):

def list = ['a','b','c','d']

def newList = []

def clos = { it.toUpperCase() }

list.collect( newList, clos )

assert newList == ["A", "B", "C", "D"]

groovy方法调用中Closure可以并且经常作为参数传递,而为了方便编写代码,一般也是将Closure写在方法调用的最后,这个特性是Groovy的Builder构建DSL的核心(当然最核心的是方法调用invokeMethod方法)。关于Groovy的Builder构建DSL,我们将在另外一篇文章中介绍。


分享到:
评论

相关推荐

    JS页面获取 session 值,作用域和闭包学习笔记

    本文实例讲述了JS页面获取 session 值,作用域和闭包。分享给大家供大家参考,具体如下: Javascript获取session的值: var name= "${sessioScope.变量名}"; 注意这里面需要使用 “” 把 El 表达式给括起来,否则...

    Go 学习笔记 高清

    根据提供的文件内容,以下是对Go语言学习笔记的详细知识点阐述。 Go语言是Google开发的一种静态类型、编译型、并发型,并具有垃圾回收功能的编程语言。它由Robert Griesemer、Rob Pike和Ken Thompson于2007年9月...

    Go语言学习笔记.pdf 共174页

    Go语言学习笔记.pdf 共174页 Go语言学习笔记.pdf 共174页是一本关于Go语言的详细学习笔记,涵盖了Go语言的基础知识、函数、数组、Maps、Structs、接口、并发、程序结构、标准库等方面的内容。本笔记共分为三大部分...

    h5前端学习笔记

    学习笔记通常包括了基础概念、关键特性、实用技巧以及常见问题的解决方法。 【标签】"h5"、"前端"、"学习笔记"进一步明确了内容的重点。"h5"即HTML5,是前端开发的核心;"前端"意味着这些笔记涉及的是用户可见和...

    Rust学习笔记_78实用知识库分享

    7. Rust闭包:Rust学习笔记Day 23-24讲述了Rust的闭包,包括闭包的定义、闭包的使用场景、闭包的实现等。 8. Rust库和生态系统:Rust学习笔记Day 25讲述了Rust的库和生态系统,包括Rust的标准库、 THIRD-PARTY库和...

    Go语言学习笔记

    "Go语言学习笔记" Go语言学习笔记是关于Go语言基础入门篇的笔记,主要介绍Go语言基础语法、数据类型、逻辑语句等相关知识点。 语言概述 Go语言是一种开源的编程语言,能让构造简单、可靠且高效的软件变得容易。Go...

    Python语言学习笔记

    这份“Python语言学习笔记”是一份开源文档,最初发布在GitHub上,作者希望借助开放的精神将其分享出来。文档主要包含两大部分,第一部分是Python语言的基础知识,第二部分则是关于Python标准库的介绍。文档适合已经...

    王纯业的Python学习笔记

    《王纯业的Python学习笔记》是一份专为Python初学者和进阶者设计的学习资料,旨在帮助读者全面掌握这门强大的编程语言。Python作为一门高级编程语言,因其简洁、易读的语法特性,被广泛应用于数据分析、机器学习、...

    js 笔记 javascript 学习笔记

    本学习笔记将深入探讨JavaScript的核心概念,包括变量、数据类型、操作符、控制流程、函数、对象、数组、原型链、闭包等,并结合实际示例,如my.js、order.js、login.js等文件,来讲解其在实际项目中的应用。...

    javascript学习笔记(十三) js闭包介绍(转)

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现

    python学习笔记+源码练习

    "Python学习笔记+源码练习"是一个适合初学者的资源包,旨在帮助你从零基础开始掌握Python编程。这份资料包含了理论知识讲解和实际代码实践,使学习过程更为直观和实用。 在学习Python时,笔记是关键,它们可以帮助...

    Groovy学习笔记 PDF版

    在《Groovy学习笔记》这本书中,你可能会学到如何快速入门Groovy,理解其基本语法,包括变量、控制流、类和对象的创建,以及如何利用Groovy的特性来提高开发效率。此外,书中可能还会涵盖Groovy在实际项目中的应用,...

    python基础学习笔记

    本文档是一份详尽的Python基础学习笔记,适用于初学者,涵盖了Python编程语言的方方面面。笔记内容从基础的环境配置到面向对象的高级特性,详尽阐述了Python的核心概念和技术要点。 在Linux基础部分,笔记从终端...

    go学习笔记-文档-文档

    本文档集合了关于Go语言的学习笔记,旨在帮助读者全面理解并掌握Go语言的核心概念、语法特性以及实际应用。 一、基础语法 Go语言的语法简洁明了,易于上手。它采用了C风格的语法,但增加了诸如类型推断(Type ...

    JS学习笔记之闭包小案例分析

    本文实例讲述了JS学习笔记之闭包小案例。分享给大家供大家参考,具体如下: 直接上代码 &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="UTF-8" /&gt; &lt;title&gt;Document&lt;/...

    Go+学习笔记

    Go+学习笔记所包含的知识点涵盖了Go语言的基础知识、编程范式、数据结构、并发处理、标准库使用以及进阶话题等多个方面,详细介绍了Go语言的学习路径和应用实践。 首先,Go语言的基础部分包括变量声明、基本类型、...

    javascript入门学习笔记

    以下是对"javascript入门学习笔记"的详细解读: 一、JavaScript基础 1. 变量与数据类型:JavaScript支持六种基本数据类型(字符串、数字、布尔值、null、undefined和symbol)以及一种复杂数据类型(对象)。变量...

Global site tag (gtag.js) - Google Analytics