(Mark)JS中关于闭包

闭包(Closures)

在ECMAScript中,函数是“第一类”对象。这个名词意味着函数可以作为参数被传递给其他函数使用 (在这种情况下,函数被称为“funargs”——“functional arguments”的缩写[译注:这里不知翻译为泛函参数是否恰当])。接收“funargs”的函数被称之为 高阶函数(higher-order functions) ,或者更接近数学概念的话,被称为 运算符(operators) 。其他函数的运行时也会返回函数,这些返回的函数被称为 function valued 函数 (有 functional value 的函数)。

“funargs”与“functional values”有两个概念上的问题,这两个子问题被称为“Funarg problem” (“泛函参数问题”)。要准确解决泛函参数问题,需要引入 闭包(closures) 到的概念。让我们仔细描述这两个问题(我们可以见到,在ECMAScript中使用了函数的[[Scope]]属性来解决这个问题)。

“funarg problem”的一个子问题是“upward funarg problem”[译注:或许可以翻译为:向上查找的函数参数问题]。当一个函数从其他函数返回到外部的时候,这个问题将会出现。要能够在外部上下文结束时,进入外部上下文的变量,内部函数 在创建的时候(at creation moment) 需要将之存储进[[Scope]]属性的父元素的作用域中。然后当函数被激活时,上下文的作用域链表现为激活对象与[[Scope]]属性的组合:

Scope chain = Activation object + [[Scope]]
作用域链 = 活动对象 + [[Scope]]

请注意,最主要的事情是——函数在被创建时保存外部作用域,是因为这个 被保存的作用域链(saved scope chain) 将会在未来的函数调用中用于变量查找。

function foo() {
  var x = 10;
  return function bar() {
    console.log(x);
  };
}

// "foo"返回的也是一个function
// 并且这个返回的function可以随意使用内部的变量x

var returnedFunction = foo();

// 全局变量 "x"
var x = 20;

// 支持返回的function
returnedFunction(); // 结果是10而不是20

这种形式的作用域称为静态作用域[static/lexical scope]。上面的x变量就是在函数bar的[[Scope]]中搜寻到的。理论上来说,也会有动态作用域[dynamic scope], 也就是上述的x被解释为20,而不是10. 但是EMCAScript不使用动态作用域。

“funarg problem”的另一个类型就是自上而下[”downward funarg problem”].在这种情况下,父级的上下会存在,但是在判断一个变量值的时候会有多义性。也就是,这个变量究竟应该使用哪个作用域。是在函数创建时的作用域呢,还是在执行时的作用域呢?为了避免这种多义性,可以采用闭包,也就是使用静态作用域。

请看下面的例子:

// 全局变量 "x"
var x = 10;

// 全局function
function foo() {
  console.log(x);
}

(function (funArg) {

  // 局部变量 "x"
  var x = 20;

  // 这不会有歧义
  // 因为我们使用"foo"函数的[[Scope]]里保存的全局变量"x",
  // 并不是caller作用域的"x"

  funArg(); // 10, 而不是20

})(foo); // 将foo作为一个"funarg"传递下去

从上述的情况,我们似乎可以断定,在语言中,使用静态作用域是闭包的一个强制性要求。不过,在某些语言中,会提供动态和静态作用域的结合,可以允许开发员选择哪一种作用域。但是在ECMAScript中,只采用了静态作用域。所以ECMAScript完全支持使用[[Scope]]的属性。我们可以给闭包得出如下定义:

A closure is a combination of a code block (in ECMAScript this is a function) and statically/lexically saved all parent scopes.Thus, via these saved scopes a function may easily refer free variables.

闭包是一系列代码块(在ECMAScript中是函数),并且静态保存所有父级的作用域。通过这些保存的作用域来搜寻到函数中的自由变量。

请注意,因为每一个普通函数在创建时保存了[[Scope]],理论上,ECMAScript中所有函数都是闭包。

还有一个很重要的点,几个函数可能含有相同的父级作用域(这是一个很普遍的情况,例如有好几个内部或者全局的函数)。在这种情况下,在[[Scope]]中存在的变量是会共享的。一个闭包中变量的变化,也会影响另一个闭包的。

function baz() {
  var x = 1;
  return {
    foo: function foo() { return ++x; },
    bar: function bar() { return --x; }
  };
}

var closures = baz();

console.log(
  closures.foo(), // 2
  closures.bar()  // 1
);

上述代码可以用这张图来表示:

                      

共享的[[Scope]]

在某个循环中创建多个函数时,上图会引发一个困惑。如果在创建的函数中使用循环变量(如”k”),那么所有的函数都使用同样的循环变量,导致一些程序员经常会得不到预期值。现在清楚为什么会产生如此问题了——因为所有函数共享同一个[[Scope]],其中循环变量为最后一次复赋值。

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

有一些用以解决这类问题的技术。其中一种技巧是在作用域链中提供一个额外的对象,比如增加一个函数:

var data = [];

for (var k = 0; k < 3; k++) {
  data[k] = (function (x) {
    return function () {
      alert(x);
    };
  })(k); // 将k当做参数传递进去
}

// 结果正确
data[0](); // 0
data[1](); // 1
data[2](); // 2
时间: 2024-10-08 03:34:35

(Mark)JS中关于闭包的相关文章

js中的闭包之我理解

闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的理解以及js内部解释器的运作方式的描述,都是可以看出你js实际水平的.即使你没答对,也能让考官对你的水平有个评估.那么我先来说说我对js中的闭包的理解. 闭包是很多语言都具备的特性,在js中,闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等. 在理解闭包以前.最好能

(转)js中的闭包问题

闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的理解以及js内部解释器的运作方式的描述,都是可以看出你js实际水平的.即使你没答对,也能让考官对你的水平有个评估.那么我先来说说我对js中的闭包的理解. 闭包是很多语言都具备的特性,在js中,闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等. 在理解闭包以前.最好能

js中的闭包理解

闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的理解以及js内部解释器的运作方式的描述,都是可以看出你js实际水平的.即使你没答对,也能让考官对你的水平有个评估.那么我先来说说我对js中的闭包的理解. 闭包是很多语言都具备的特性,在js中,闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等. 在理解闭包以前.最好能

js中的闭包理解一

闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的理解以及js内部解释器的运作方式的描述,都是可以看出你js实际水平的.即使你没答对,也能让考官对你的水平有个评估.那么我先来说说我对js中的闭包的理解. 闭包是很多语言都具备的特性,在js中,闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等. 在理解闭包以前.最好能

转载 - 读博客 - 笔记 - JS中的闭包(closure)

原博客标题:JS中的闭包(closure)原博客地址:http://www.cnblogs.com/jingwhale/p/4574792.html 笔记如下: 1. 函数内部声明变量的时候,一定要使用var命令.如果不用的话,实际上是声明了一个全局变量. function outer(){ localVal = 30; return localVal; } outer(); alert(localVal);//30 2. Javascript语言特有的“链式作用域”结构(chain scope

js中的闭包和c#中的闭包

我们 关于闭包,一个老僧长谈的话题:js的闭包俺将的比较多了,而且很详细,俺就不说了,可以看看之前的文章: 我们来对比一下c#中的闭包和js中的闭包: 先看我们的c#代码: static List<Action> fn0() { int result = 0; List<Action> list = new List<Action>(); for (int i = 0; i < 10; i++) { result = i + 1; //这样result相当于一个全

浅谈JS中的闭包

今天 大年初一,祝各位小伙伴们狗年旺旺啊,闲来也没事,只能钻研一下自己的分内之事,也就是作为一个前端码农的身份,得时刻保持学习的态度,温故而知新,每天都给自己一个小目标去完成,日积月累,所想达到的状态,都会有所见长. JS中的闭包,想必,做web开发的程序猿们都有一定的认识吧,不仅仅是js里有这种特性,而且弱语言类型的譬如python里也是有闭包这么一个强大的特性的,对于老司机们来说,闭包可真的是一个很好的东西,但是对于新入门的菜鸟们来说,闭包却是一个难以搞得清楚的特性,很多情况下,会用,但是却

通俗易懂地解释JS中的闭包

1. "闭包就是跨作用域访问变量." [示例一] ? 1 2 3 4 5 6 7 8 9 var name = 'wangxi' function user () {  // var name = 'wangxi'  function getName () {  console.log(name)  }  getName() } user() // wangxi 在 getName 函数中获取 name,首先在 getName 函数的作用域中查找 name,未找到,进而在 user 函

详解js中的闭包

前言 在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以<javascript高级程序设计>里面的理论为基础.用拆分的方式,深入讲解一下对于闭包的理解,如果有不对请指正. 写在闭包之前 闭包的内部细节,依赖于函数被调用过程所发生的一系列事件为基础,所以有必要先弄清楚以下几个概念: 1. 执行环境和活动对象 ** - 执行环境(execution context)定义了变量或者函数有权访问的其他数据,每个执行环境都有一个与之关联的

js中的闭包

对于没有接触过闭包的初学者来说,很难见名知义马上理解什么是闭包.所以首先我们要明确一下什么是闭包.我们先来看一下官方对于闭包的解释:所谓"闭包",指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.看了这个官方的解释,大家对于闭包理解了吗?反正当我看到这个解释的时候是一脸蒙逼,不知道这说的是些什么鬼.因为看到这个解释我很难把闭包和我平时在js中进行的一些操作联系到一起更谈不上学会应用闭包了.可以说闭包是js中的一个难点,难就难在它很