Home >Web Front-end >JS Tutorial >JavaScript Closures Part 1: Introduction to Closures

JavaScript Closures Part 1: Introduction to Closures

黄舟
黄舟Original
2016-12-20 16:03:21971browse

We introduced scope chains and variable objects earlier, and now it’s easy to understand when we talk about closures. In fact, everyone has already talked about closures. Nonetheless, here we will try to discuss closures from a theoretical perspective and see how closures in ECMAScript actually work internally.

Before discussing ECMAScript closures directly, it is still necessary to look at some basic definitions in functional programming.

As we all know, in functional languages ​​(ECMAScript also supports this style), functions are data. For example, functions can be assigned to variables, passed as parameters to other functions, returned from functions, etc. Such functions have special names and structures.

Definition

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

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

Example:

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

The actual parameter of funarg in the above example is actually the anonymous function passed to exampleFunc.

In turn, a function that accepts functional parameters is called a high-order function (HOF for short). It can also be called: functional function or partial mathematical theory or operator. In the above example, exampleFunc is such a function.

As mentioned before, functions can not only be used as parameters, but also as return values. Such functions that return a function are called functions with functional value or function valued functions.

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

Functions that can exist in the form of normal data (for example: when parameters are passed, accept functional parameters or return function values) are called first-class functions (generally first-class objects). In ECMAScript, all functions are first-class objects.

Functions that can exist as normal data (for example: when parameters are passed, accept functional parameters or return function values) are called first-class functions (generally first-class objects).

In ECMAScript, all functions are first-class objects.

A function that accepts itself as a parameter is called an auto-applicative function or self-applicative function:

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

A function that takes itself as a return value is called an auto-replicative function or self-replicative function function). Often, the term "self-replicating" is used in literature:

(function selfReplicative() {
  return selfReplicative;
})();

One of the more interesting patterns for self-replicating functions is to accept only one item of a collection as an argument instead of accepting the collection itself.

// 接受集合的函数
function registerModes(modes) {
  modes.forEach(registerMode, modes);
}
 
// 用法
registerModes(['roster', 'accounts', 'groups']);
 
// 自复制函数的声明
function modes(mode) {
  registerMode(mode); // 注册一个mode
  return modes; // 返回函数自身
}
 
// 用法,modes链式调用
modes('roster')('accounts')('groups')
 
//有点类似:jQueryObject.addClass("a").toggle().removClass("b")

But passing the collection directly is relatively effective and intuitive.

Variables defined in function parameters are accessible when "funarg" is activated (because the variable object storing the context data is created each time the context is entered):

function testFn(funArg) {
  // funarg激活时, 局部变量localVar可以访问了
  funArg(10); // 20
  funArg(20); // 30
 
}
 
testFn(function (arg) {
  var localVar = 10;
  alert(arg + localVar);
});

However, in ECMAScript , the function can be encapsulated in the parent function and can use the variables of the parent function context. This feature can cause funarg problems.

Funarg problem

In a stack-oriented programming language, the local variables of a function are stored on the stack. Whenever the function is activated, these variables and function parameters will be pushed onto the stack.

When the function returns, these parameters will be removed from the stack. This model places significant restrictions on using functions as functional values ​​(for example, as return values ​​from parent functions). Most of the time, problems arise when functions have free variables.

Free variables refer to variables used in functions, but are neither function parameters nor local variables of the function

Example:

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

In the above example, for the innerFn function, localVar is a free variable.

For systems that use a stack-oriented model to store local variables, this means that when the testFn function call ends, its local variables will be removed from the stack. In this way, when a function call to innerFn is made from the outside, an error will occur (because the localVar variable no longer exists).

Moreover, in the above example, in the stack-oriented implementation model, it is simply impossible to return innerFn as a return value. Because it is also a local variable of the testFn function, it will also be removed when testFn returns.

Another problem is when the system uses dynamic scope and functions are used as function parameters.

Look at the following example (pseudocode):

var z = 10;
 
function foo() {
  alert(z);
}
 
foo(); // 10 – 使用静态和动态作用域的时候
 
(function () {
 
  var z = 20;
  foo(); // 10 – 使用静态作用域, 20 – 使用动态作用域
 
})();
 
// 将foo作为参数的时候是一样的
(function (funArg) {
 
  var z = 30;
  funArg(); // 10 – 静态作用域, 30 – 动态作用域
 
})(foo);

We see that using dynamic scope, the system of variables (identifiers) is managed through the dynamic stack of variables. Therefore, free variables are queried in the currently active dynamic chain, rather than in the static scope chain that was saved when the function was created.

这样就会产生冲突。比方说,即使Z仍然存在(与之前从栈中移除变量的例子相反),还是会有这样一个问题: 在不同的函数调用中,Z的值到底取哪个呢(从哪个上下文,哪个作用域中查询)?

上述描述的就是两类funarg问题 —— 取决于是否将函数以返回值返回(第一类问题)以及是否将函数当函数参数使用(第二类问题)。

为了解决上述问题,就引入了 闭包的概念。

闭包

闭包是代码块和创建该代码块的上下文中数据的结合。

让我们来看下面这个例子(伪代码):

var x = 20;
 
function foo() {
  alert(x); // 自由变量"x" == 20
}
 
// 为foo闭包
fooClosure = {
  call: foo // 引用到function
  lexicalEnvironment: {x: 20} // 搜索上下文的上下文
};

上述例子中,“fooClosure”部分是伪代码。对应的,在ECMAScript中,“foo”函数已经有了一个内部属性——创建该函数上下文的作用域链。

“lexical”通常是省略的。上述例子中是为了强调在闭包创建的同时,上下文的数据就会保存起来。当下次调用该函数的时候,自由变量就可以在保存的(闭包)上下文中找到了,正如上述代码所示,变量“z”的值总是10。

定义中我们使用的比较广义的词 —— “代码块”,然而,通常(在ECMAScript中)会使用我们经常用到的函数。当然了,并不是所有对闭包的实现都会将闭包和函数绑在一起,比方说,在Ruby语言中,闭包就有可能是: 一个过程对象(procedure object), 一个lambda表达式或者是代码块。

对于要实现将局部变量在上下文销毁后仍然保存下来,基于栈的实现显然是不适用的(因为与基于栈的结构相矛盾)。因此在这种情况下,上层作用域的闭包数据是通过 动态分配内存的方式来实现的(基于“堆”的实现),配合使用垃圾回收器(garbage collector简称GC)和 引用计数(reference counting)。这种实现方式比基于栈的实现性能要低,然而,任何一种实现总是可以优化的: 可以分析函数是否使用了自由变量,函数式参数或者函数式值,然后根据情况来决定 —— 是将数据存放在堆栈中还是堆中。

 以上就是JavaScript闭包其一:闭包概论的内容,更多相关内容请关注PHP中文网(www.php.cn)! 


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn