ECMA-262-3 in detail. Chapter 6. Closures.

转载地址:http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/

Introduction

In this article we will talk about one of the most discussed topics related with JavaScript — aboutclosures. The topic, as a matter of fact, is not new and was discussed many times. However we will try to discuss and understand it more from theoretical point of view, and also will look at how closures are made in ECMAScript from within.

Two previous chapters devoted to scope chain and variable object can be good to consider first, since in this chapter we will use material discussed earlier.

General theory

Before the discussion of closures directly in ECMAScript, it is necessary to specify a number of definitions from the general theory of functional programming.

As is known, in functional languages (and ECMAScript supports this paradigm and stylistics), functions are data, i.e. they can be assigned to variables, passed as arguments to other functions,returned from functions etc. Such functions have special names and structure.

Definitions

functional argument (“Funarg”) — is an argument which value is a function.

Example:

function exampleFunc(funArg) {
   funArg();
}
 
exampleFunc( function () {
   alert( 'funArg' );
});

The actual parameter related with the “funarg” in this case is the anonymous function passed to theexampleFunc function.

In turn, the function which receives another function as the argument is called a higher-order function (HOF).

Another name of a HOF is a functional or, closer to mathematics, an operator. In the example above,exampleFunc function is a higher-order function.

As it was noted, a function can be not only passed as an argument, but also returned as a valuefrom another function.

The functions which return other functions are called functions with functional value (or function valued functions).

( function functionValued() {
   return function () {
     alert( 'returned function is called' );
   };
})()();

Functions which can participate as normal data, i.e. be passed as arguments, receive functional arguments or be returned as functional values, are called first-class functions.

In ECMAScript all functions are first-class.

A function which receives itself as an argument, is called an auto-applicative (or self-applicative) function:

( function selfApplicative(funArg) {
 
   if (funArg && funArg === selfApplicative) {
     alert( 'self-applicative' );
     return ;
   }
 
   selfApplicative(selfApplicative);
 
})();

A function which returns itself is called an auto-replicative (or self-replicative) function. Sometimes, the name self-reproducing is used in a literature:

( function selfReplicative() {
   return selfReplicative;
})();
One of interesting patterns of  self-replicative functions is a  declarative form of working with a single argument of a collection instead of accepting the collection itself:

// imperative function
// which accepts collection
 
function registerModes(modes) {
   modes.forEach(registerMode, modes);
}
 
// usage
registerModes([ 'roster' , 'accounts' , 'groups' ]);
 
// declarative form using
// self-replicating function
 
function modes(mode) {
   registerMode(mode); // register one mode
   return modes; // and return the function itself
}
 
// usage: we just *declare* modes
 
modes
   ( 'roster' )
   ( 'accounts' )
   ( 'groups' )

However, in practice working with the collection itself can be more efficient and intuitive.

Local variables which are defined in the passed functional argument are of course accessible atactivation of this function, since the variable object which stores the data of the context is created every time on entering the context:

function testFn(funArg) {
 
   // activation of the funarg, local
   // variable "localVar" is available
 
   funArg(10); // 20
   funArg(20); // 30
 
}
 
testFn( function (arg) {
 
   var localVar = 10;
   alert(arg + localVar);
 
});

However, as we know from the chapter 4, functions in ECMAScript may be enclosed with parent functions and use variables from parent contexts. With this feature so-called a funarg problem is related.

Funarg problem

In stack-oriented programming languages local variables of functions are stored on a stack which ispushed with these variables and function arguments every time when the function is called.

On return from the function the variables are removed from the stack. This model is a big restrictionfor using functions as functional values (i.e. returning them from parent functions). Mostly this problem appears when a function uses free variables.

free variable is a variable which is used by a function, but is neither a parameter, nor a local variable of the function.

Example:

function testFn() {
 
   var localVar = 10;
 
   function innerFn(innerParam) {
     alert(innerParam + localVar);
   }
 
   return innerFn;
}
 
var someFn = testFn();
someFn(20); // 30

In this example localVar variable is free for the innerFn function.

If this system had use a stack-oriented model for storing local variables, it would mean that on return from testFn function all its local variables would be removed from the stack. And this would cause an error at innerFn function activation from the outside.

Moreover, in this particular case, in the stack-oriented implementation, returning of the innerFnfunction would not be possible at all, since innerFn is also local for testFn and therefore is also removed on returning from the testFn.

Another problem of functional objects is related with passing a function as an argument in a system with dynamic scope implementation.

Example (pseudo-code):

var z = 10;
 
function foo() {
   alert(z);
}
 
foo(); // 10 – with using both static and dynamic scope
 
( function () {
 
   var z = 20;
   foo(); // 10 – with static scope, 20 – with dynamic scope
 
})();
 
// the same with passing foo
// as an arguments
 
( function (funArg) {
 
   var z = 30;
   funArg(); // 10 – with static scope, 30 – with dynamic scope
 
})(foo);

We see that in systems with dynamic scope, variable resolution is managed with a dynamic (active) stack of variables. Thus, free variables are searched in the dynamic chain of the current activation — in the place where the function is called, but not in the static (lexical) scope chain which is saved atfunction creation.

And this can lead to ambiguity. Thus, even if z exists (in contrast with the previous example where local variables would be removed from a stack), there is a question: which value of z (i.e. z from which context, from which scope) should be used in various calls of foo function?

The described cases are two types of the funarg problem — depending on whether we deal with the functional value returned from a function (upward funarg), or with the functional argumentpassed to the function (downward funarg).

For solving this problem (and its subtypes) the concept of a closure was proposed.

Closure

closure is a combination of a code block and data of a context in which this code block is created.

Let’s see an example in a pseudo-code:

var x = 20;
 
function foo() {
   alert(x); // free variable "x" == 20
}
 
// Closure for foo
fooClosure = {
   call: foo // reference to function
   lexicalEnvironment: {x: 20} // context for searching free variables
};

In the example above, fooClosure of course is a pseudo-code whereas in ECMAScript foo function already contains as one of its internal property a scope chain of a context in which it has been created.

The word “lexical” is often omitted, since goes without saying, and in this case it focuses attention that a closure saves its parent variables in the lexical place of the source code, that is — where the function is defined. At next activations of the function, free variables are searched in this saved(closured) context, that we can see in examples above where variable z always should be resolved as 10 in ECMAScript.

In definition we used a generalized concept — “the code block”, however usually the term “function” is used. Though, not in all implementations closures are associated only with functions: for example, in Ruby programming language, as a closure may be: a procedure object, a lambda-expression or a code block.

As to implementations, for storing local variables after the context is destroyed, the stack-basedimplementation is not fit any more (because it contradicts the definition of stack-based structure). Therefore in this case closured data of the parent context are saved in the dynamic memoryallocation (in the “heap”, i.e. heap-based implementations), with using a garbage collector (GC) andreferences counting. Such systems are less effective by speed than stack-based systems. However, implementations may always optimize it: at parsing stage to find out, whether free variables are used in function, and depending on this decide — to place the data in the stack or in the “heap”.

ECMAScript closures implementation

Having discussed the theory, we at last have reached closures regarding directly ECMAScript. Here it is necessary to notice that ECMAScript uses only static (lexical) scope (whereas in some languages, for example in Perl, variables can be declared using both static or dynamic scope).

var x = 10;
 
function foo() {
   alert(x);
}
 
( function (funArg) {
 
   var x = 20;
 
   // variable "x" for funArg is saved statically
   // from the (lexical) context, in which it was created
   // therefore:
 
   funArg(); // 10, but not 20
 
})(foo);

Technically, the variables of a parent context are saved in the internal [[Scope]] property of the function. So if you completely understand the [[Scope]] and a scope chain topics, which in detail where discussed in the chapter 4, the question on understanding closures in ECMAScript will disappear by itself.

Referencing to algorithm of functions creation, we see that all functions in ECMAScript are closures, since all of them at creation save scope chain of a parent context. The important moment here is that, regardless — whether a function will be activated later or not — the parent scope is already saved to it at creation moment:

var x = 10;
 
function foo() {
   alert(x);
}
 
// foo is a closure
foo: <FunctionObject> = {
   [[Call]]: <code block of foo>,
   [[Scope]]: [
     global: {
       x: 10
     }
   ],
   ... // other properties
};

As we mentioned, for optimization purpose, when a function does not use free variables, implementations may not to save a parent scope chain. However, in ECMA-262-3 specification nothing is said about it; therefore, formally (and by the technical algorithm) — all functions save scope chain in the [[Scope]] property at creation moment.

Some implementations allow access to the closured scope directly. For example in Rhino, for the[[Scope]] property of a function, corresponds a non-standard property __parent__ which wediscussed in the chapter about variable object:

var global = this ;
var x = 10;
 
var foo = ( function () {
 
   var y = 20;
 
   return function () {
     alert(y);
   };
 
})();
 
foo(); // 20
alert(foo.__parent__.y); // 20
 
foo.__parent__.y = 30;
foo(); // 30
 
// we can move through the scope chain further to the top
alert(foo.__parent__.__parent__ === global); // true
alert(foo.__parent__.__parent__.x); // 10

One [[Scope]] value for “them all”

It is necessary to notice that closured [[Scope]] in ECMAScript is the same object for the several inner functions created in this parent context. It means that modifying the closured variable from one closure, reflects on reading this variable in another closure.

That is, all inner functions share the same parent scope.

var firstClosure;
var secondClosure;
 
function foo() {
 
   var x = 1;
 
   firstClosure = function () { return ++x; };
   secondClosure = function () { return --x; };
 
   x = 2; // affection on AO["x"], which is in [[Scope]] of both closures
 
   alert(firstClosure()); // 3, via firstClosure.[[Scope]]
}
 
foo();
 
alert(firstClosure()); // 4
alert(secondClosure()); // 3

There is a widespread mistake related with this feature. Often programmers get unexpected result, when create functions in a loop, trying to associate with every function the loop’s counter variable, expecting that every function will keep its “own” needed value.

var data = [];
 
for ( var k = 0; k < 3; k++) {
   data[k] = function () {
     alert(k);
   };
}
 
data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2

The previous example explains this behavior — a scope of a context which creates functions is thesame for all three functions. Every function refers it through the [[Scope]] property, and the variablek in this parent scope can be easily changed.

Schematically:

activeContext.Scope = [
   ... // higher variable objects
   {data: [...], k: 3} // activation object
];
 
data[0].[[Scope]] === Scope;
data[1].[[Scope]] === Scope;

你可能感兴趣的:(closure)