通过示例学习JavaScript闭包
译者按: 在上一篇博客,我们通过实现一个计数器,了解了如何使用闭包(Closure),这篇博客将提供一些代码示例,帮助大家理解闭包。
原文: JavaScript Closures for Dummies
译者: Fundebug
为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。
闭包并不神奇
其实,只要你领会了闭包的关键概念,一切就非常简单了。作为JavaScript开发者,你应该可以理解以下代码:
Example 1
|
function sayHello(name)
{
var text = 'Hello ' + name;
var sayAlert = function() { console.log(text); }
sayAlert();
}
sayHello("Bob") // 输出"Hello Bob"
|
在sayHello()函数中定义并调用了sayAlert()函数;sayAlert()作为内层函数,可以访问外层函数sayHello()中的text变量。理解这一点,你就可以继续阅读这篇博客了。
一个闭包示例
两句话总结闭包(注意,这个定义并不规范,但是有助于理解):
- 闭包就是函数的局部变量,这些变量在函数return之后仍然可以访问
- 闭包就是函数的内存堆栈,这个内存堆栈在函数return之后并没有被收回
Example 2
|
function sayHello2(name)
{
var text = 'Hello ' + name; // 局部变量
var sayAlert = function() { console.log(text); }
return sayAlert;
}
var say2 = sayHello2("Jane");
say2(); // 输出"Hello Jane"
|
调用sayHello2()函数返回了sayAlert,它是一个引用变量,指向一个函数。相信大多数JavaScript程序员能够理解什么是引用变量,而C程序员则可以把sayAlert以及say2理解为指向函数的指针。
C指针与JavaScript引用变量并无实质区分。在JavaScript中,不妨这样理解,指向函数的引用变量不仅指向函数本身,还隐含地指向了一个闭包。
代码中匿名函数function() { alert(text); }是在另一个函数,即sayHello2()中定义的。在JavaScript中,如果你在函数中定义了一个函数,则创建了闭包。
对于C语言,以及其他绝大多数语言:函数return之后,其局部变量将无法访问,因为内存中的堆栈会被销毁。
对于JavaScript,如果你在函数中定义函数的话,当外层函数return之后,其局部变量仍然可以访问。代码中已经证明了这一点:当sayHello2()函数return之后,我们调用了say2()函数,成功打印了text变量,而text变量正是sayHello2()函数的局部变量。
更多示例
如果只是从定义的角度去理解闭包,显然是非常困难。然而,如果通过代码示例去理解闭包,则简单很多。因此,强烈建议你认真地理解每一个示例,弄清楚它们是如何运行的,这样你会避免很多奇怪的BUG。
Example 3
Example 3中,say667()函数return后,num变量将仍然保留在内存中。并且,sayNumba函数中的num变量并非复制而是引用,因此它输出的是667而非666。
|
function say667() {
var num = 666; // say667()函数return后,num变量将仍然保留在内存中
var sayAlert = function() { console.log(num); }
num++;
return sayAlert;
}
var sayNumba = say667();
sayNumba(); // 输出667
|
Example 4
Example 4中,3个全局函数gAlertNumber,gIncreaseNumber,gSetNumber指向了同一个闭包,因为它们是在同一次setupSomeGlobals()调用中声明的。它们所指向的闭包就是setupSomeGlobals()函数的局部变量,包括了num变量。也就是说,它们操作的是同一个num变量。
|
function setupSomeGlobals() {
var num = 666;
gAlertNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gAlertNumber(); // 输出666
gIncreaseNumber();
gAlertNumber(); // 输出667
gSetNumber(5);
gAlertNumber(); // 输出5
|
Example 5
Example 5的代码比较难,不少人都会犯同样的错误,因为它的执行结果很可能违背了你的直觉。
|
function buildList(list)
{
var result = [];
for (var i = 0; i < list.length; i++)
{
var item = 'item' + list[i];
result.push( function() { console.log(item + ' ' + list[i])} );
}
return result;
}
var fnlist = buildList([1,2,3]);
for (var j = 0; j < fnlist.length; j++)
{
fnlist[j](); // 连续输出3个"item3 undefined"
}
|
result.push( function() {alert(item + ‘ ‘ + list[i])}将指向匿名函数function() {alert(item + ‘ ‘ + list[i])}的引用变量加入了数组,其效果等价于:
|
pointer = function() {alert(item + ' ' + list[i])};
result.push(pointer);
|
代码执行后,连续输出了3个”item3 undefined”,明显与直觉不同。
调用buildList()函数之后,我们得到了一个数组,数组中有3个函数,而这3个函数指向了同一个闭包。而闭包中的item变量值为“item3”,i变量值为3。如果理解了3个函数指向的是同一个闭包,则输出结果就不难理解了。
Example 6
Example 6中,alice变量在sayAlert函数之后定义,这并未影响代码执行。因为返回函数sayAlice2所指向的闭包会包含sayAlice()函数中的所有局部变量,这自然包括了alice变量,因此可以正常打印”Hello Alice”。
|
function sayAlice()
{
var sayAlert = function() { console.log(alice); }
var alice = 'Hello Alice';
return sayAlert;
}
var sayAlice2 = sayAlice();
sayAlice2(); // 输出"Hello Alice"
|
Example 7
由Example 7可知,每次调用newClosure()都会创建独立的闭包,它们的局部变量num与ref的值并不相同。
|
function newClosure(someNum, someRef)
{
var anArray = [1,2,3];
var num = someNum;
var ref = someRef;
return function(x)
{
num += x;
anArray.push(num);
console.log('num: ' + num + "; " + 'anArray ' + anArray.toString() + "; " + 'ref.someVar ' + ref.someVar);
}
}
closure1 = newClosure(40, {someVar: "closure 1"});
closure2 = newClosure(1000, {someVar: "closure 2"});
closure1(5); // 打印"num: 45; anArray 1,2,3,45; ref.someVar closure 1"
closure2(-10); // 打印"num: 990; anArray 1,2,3,990; ref.someVar closure 2"
|
总结
严格来讲,我对闭包的解释并不准确。不过,将闭包简单地看做局部变量,理解起来会更加简单。
参考链接
关于Fundebug
Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了7亿+错误事件,得到了Google、360、金山软件、百姓网等众多知名用户的认可。欢迎免费试用!
版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/08/07/javascript-closure-examples/
通过示例学习JavaScript闭包的更多相关文章
- [转载]学习Javascript闭包(Closure)
学习Javascript闭包(Closure) 源地址: http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures ...
- 学习JavaScript闭包
作者: 阮一峰 日期: 2009年8月30日 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 下面就是我的学习笔记,对于Javascript初 ...
- [JS]学习Javascript闭包(Closure)
转自:阮一峰 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 下面就是我的学习笔记,对于Javascript初学者应该是很有用的. 一.变量的 ...
- 学习Javascript闭包(Closure)及几个经典面试题理解
今天遇到一个面试题,结果让我百思不得其解.后来在查阅了各种文档后,理清了来龙去脉.让我们先来看看这道题: function Foo( ){ var i = 0; return function( ){ ...
- 学习Javascript闭包(Closure)
闭包作用 1.让变量驻留在内存中 2.函数外部可以读取函数内部的私有变量 <!DOCTYPE html> <html lang="en"> <head ...
- 学习Javascript闭包(Closure) by 阮一峰
闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域 ...
- (译)学习JavaScript闭包
原文地址:https://medium.freecodecamp.org/lets-learn-javascript-closures-66feb44f6a44 闭包是JavaScript中一个基 ...
- 深入学习javaScript闭包(闭包的原理,闭包的作用,闭包与内存管理)
前言 虽然JavaScript是一门完整的面向对象的编程语言,但这门语言同时也拥有许多函数式语言的特性. 函数式语言的鼻祖是LISP,JavaScript在设计之初参考了LISP两大方言之一的Sche ...
- 【转】学习JavaScript闭包
原文: http://www.cnblogs.com/Lau7/p/7942100.html#undefined ------------------------------------------- ...
随机推荐
- 编程语言吐槽之Java与C
包含各种偏见和武断,请谨慎阅读. 为什么在学校学习的C,拿到企业生产中不起作用? 而为什么企业级的程序员,依然对C/C++无法掌控?在算法各方面不够精湛? 根本原因还是应用场景的不同.企业级的开发,主 ...
- Mybatis框架六:关联查询
这里搞一个测试场景: 用户和订单,一位用户可以有多个订单,而每个订单只属于一位用户 以用户为中心,相对于订单:一对多 以订单为中心,当对于用户:一对一 两张表结构: 订单表: 用户表: 对应的POJO ...
- Dash by Plotly 学习笔记
一.介绍 1.dash 是什么 dash 是一个基于 Flask (Python) + React 的 web 框架. 入门指南:https://dash.plot.ly/getting-starte ...
- GIT的初级使用
安装git [root@localhost ~]# yum -y install git [root@localhost ~]# git --version git version 1.8.3.1 初 ...
- PHP之ThinkPHP框架(验证码、文件上传、图片处理)
验证码 验证码是框架自带有的,比之前使用GD库简单方便许多,其实现原理基本相似,都是生成图片,保存验证码值到Session中,表单提交验证码,然后进行值的对比验证. 简单的显示: <form ...
- while true 死循环判断端口按顺序启动应用
需求:spring微服务应用启动较慢并且要求一个应用启完才能启第二个应用. 思路:加了个while true 死循环判断端口启动了才启下一个应用. 执行方式:/appupgrade/spring_cl ...
- 微信小程序onLaunch异步,首页onLoad先执行?
本来按照事件顺序,小程序初始化时触发App里的onLaunch,后面再执行页面Page里的onLoad,但是在onLaunch里请求获取是否有权限,等待返回值的时候Page里的onLoad事件就已经执 ...
- jvm详情——4、分代垃圾回收详述
虚拟机中的共划分为三个代: 年轻代(Young Generation) 年老点(Old Generation) 持久代(Permanent Generation) 其中持久代主要存放的是Java类的类 ...
- SpringCloud学习6-如何创建一个服务消费者consumer
上一节如何创建一个服务提供者provider已经启动了一个provider的server,提供用户信息查询接口.接下来,我们启动另一个provider,由于是同一台机器本地测试,我们换一个端口 --s ...
- python 加密算法及其相关模块的学习(hashlib,random,string,math)
加密算法介绍 一,HASH Hash,一般翻译做“散列”,也有直接音译为”哈希”的,就是把任意长度的输入(又叫做预映射,pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值.这种 ...