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 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.
[[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.
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:
- They have one implicit method (which is never specified in a closure definition) called doCall()
- 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.
- 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.
- 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.
- 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).
- A closure may reference any variables defined within its enclosing lexical scope. Any such variable is said to be bound to the closure
- Any variables bound to a closure are available to the closure even when the closure is returned outside of the enclosing scope.
- 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.
- The body of a closure is not executed until it is explicitly invoked e.g. a closure is not invoked at its definition time
- 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
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,我們將在另外一篇文章中介紹。