(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(), //
closures.bar() //
);
上述代码可以用这张图来表示:

共享的[[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](); //
data[1](); //
data[2](); //
(Mark)JS中关于闭包的更多相关文章
- 详解js中的闭包
前言 在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以<javascript高级程序设计>里面的理论为基础.用拆分的方式 ...
- js中的闭包之我理解
闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...
- 浅谈JS中的闭包
浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...
- js中的“闭包”
js中的“闭包” 姓名:闭包 官方概念:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ( ⊙o⊙ )!!!这个也太尼玛官方了撒,作为菜鸟的 ...
- JS中的闭包(closure)
JS中的闭包(closure) 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面就是我的学习笔记,对于Javascript初学者应该是很有用 ...
- Js中的闭包原理
要了解清楚js中的闭包制机,那么得先了解全局执行环境.块级执行环境.函数执行环境.变量对象.环境栈.作用域链.摧毁执行环境. 全局执行环境 全局执行环境指的是最外层的执行环境.在web中全局执行环境被 ...
- js中的闭包理解一
闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...
- js中的闭包理解
闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...
- 初识js中的闭包
今天看了关于js闭包方面的文章,还是有些云里雾里,对于一个菜鸟来说,学习闭包确实有一定的难度,不说别的,能够在网上找到一篇优秀的是那样的不易. 当然之所以闭包难理解,个人觉得是基础知识掌握的不牢,因为 ...
- 理解js中的闭包
闭包 我的理解是 能够有权访问另一个函数作用域中变量的函数 通常我们知道 普通的函数在调用完成之后,活动对象不会从内存中销毁,其执行环境的作用域链会被销毁,造成资源的浪费 而闭包的好处就在于执行完就会 ...
随机推荐
- Linux从入门到放弃
Ch.0 几点Linux常识 Linux严格区分大小写,不像windows中命令是不区分大小写的 Linux中所有内容以文件形式保存,包括硬件 Linux不靠扩展名区分文件类型,所有扩展名只是为了方便 ...
- 【Android XML】Android XML 转 Java Code 系列之 Selector(2)
今天我们要把drawable下的selector的XML文件转换成Java代码.(打包进jar,不依赖apk) 在转换工具中的代码为: https://github.com/SickWorm/Andr ...
- gradle问题总结与理解(一篇文章带你理解android studio 与gradle 的关系)
前言:近日在网上找了个很不错的安卓二维码美化,由于下载的项目经常出问题,且不方便依赖使用,因此我想把它写个demo,并把源码发布到jcenter中,修改还是很顺利的,运行项目到手机也没问题,发布遇到了 ...
- WiderFace标注格式转PASCAL VOC2007标注格式
#coding=utf-8 import os import cv2 from xml.dom.minidom import Document def create_xml(boxes_dict,ta ...
- Oracle简述
Oracle是甲骨文公司推出的一款大型数据库管理系统.甲骨文公司成立于1977年,总部位于美国加利福尼亚州的红木滩.1989年,Oracle正式进入中国市场:2013年,甲骨文超越 IBM ,成为继 ...
- FineReport——JS二次开发(分页预览)
BS访问某个cpt模板,报表servlet将会将cpt文件解析成对应的html,报表内容最终转换为一个table,位于id=content-container的div中. 在模板和html页面中,他们 ...
- Hive 体系学习
Hive简介 Hive是一个基于Hadoop的数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并使用HQL作为查询接口.HDFS作为存储底层.MapReduce作为执行层,将HQL语句转换成M ...
- C#取出字符串中的数字或字母
string str20 = "ABC123"; string strSplit1,strSplit2; //取出字符串中所有的英文字母 strSplit1 = Regex.Rep ...
- mysql部署后无法远程连接的原因(错误代码10061),服务监听127.0.0.1和0.0.0.0的区别
在Ubuntu上部署mysql服务并添加了一个非root用户后,发现无法远程连接, Navicat连接mysql出现2003——can't connect to mysql server on loc ...
- .net core 2.0学习记录(一):搭建一个.Net Core网站项目
.Net Core开发可以使用Visual Studio 2017或者Visual Studio Code,下面使用Visual Studio 2017搭建一个.net Core MVC网站项目. 一 ...