文档章节

通过示例学习Javascript闭包

Fundebug
 Fundebug
发布于 2018/11/21 15:35
字数 1569
阅读 6
收藏 0

译者按: 上一篇博客,我们通过实现一个计数器,了解了如何使用闭包(Closure),这篇博客将提供一些代码示例,帮助大家理解闭包。

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

闭包并不神奇

其实,只要你领会了闭包的关键概念,一切就非常简单了。作为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()都会创建独立的闭包,它们的局部变量numref的值并不相同。

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/

© 著作权归作者所有

Fundebug
粉丝 12
博文 301
码字总数 313608
作品 0
厦门
私信 提问
javascript 设计模式之单体(Singleton)模式

单体模式介绍 单体模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。” javascript是一个没有类的程序语言,当然也可以去...

hlxiong
2014/04/13
229
0
【译】理解JavaScript闭包——新手指南

闭包是JavaScript中一个基本的概念,每个JavaScript开发者都应该知道和理解的。然而,很多新手JavaScript开发者对这个概念还是很困惑的。 正确理解闭包可以帮助你写出更好、更高效、简洁的代...

LINJIAJUN
2018/11/28
0
0
JavaScript世界的一等公民 - 函数

简介 在很多传统语言(C/C++/Java/C#等)中,函数都是作为一个二等公民存在,你只能用语言的关键字声明一个函数然后调用它,如果需要把函数作为参数传给另一个函数,或是赋值给一个本地变量,...

i33
2013/03/14
164
0
前端开发JavaScript干货知识点汇总

很多初学的朋友经常问我,前端JavaScript都需要学习哪些东西呀?哪些是JavaScript的重点知识啊? 其实做前端开发工程师,所有的知识点都是我们学习必备的东西,只有扎实的技术基础才是高薪的...

WEB开发阿靖
06/26
0
0
[译]玩转 JavaScript 面试:何为闭包?

原文地址 Medium - Master the JavaScript Interview: What is a Closure? 坦白的讲,不掌握闭包这个知识点的话你是不会在 JavaScript 这条路上走太远的。你不仅要掌握闭包的机制是什么,还要...

zhaofeihao
06/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

矩阵中的路径

判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向上下左右移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路...

Garphy
7分钟前
2
0
Hibernate 5 Maven 仓库的 Artifacts

Hibernate artifacts 官方发布的仓库在 JBoss Maven repository 中。Hibernate 发布的 artifacts 也会同时同步到 Maven Central 仓库中,这是一个自动同步进程(可能会有一些延迟)。 Hibern...

honeymoose
55分钟前
4
0
如何学习uni-app?

uni-app 是一个使用 Vue.js 开发跨平台应用的前端框架。 开发者通过编写 Vue.js 代码,uni-app 将其编译到iOS、Android、微信小程序、H5等多个平台,保证其正确运行并达到优秀体验。 <templ...

达叔小生
58分钟前
5
0
OSChina 周一乱弹 —— 后来马云就一心想挣钱了

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 小小编辑:《空帆船》- 朴树 《空帆船》- 朴树 手机党少年们想听歌,请使劲儿戳(这里) @webw :第二次被锁在电梯里了 上次你忘带电梯卡, ...

小小编辑
今天
775
13
关于does not give a valid preprocessing token

#define VFUNC(self) ((##self##)->_vptr) 这样在gcc下会编译失败, VC不会 报pasting ) does not give a valid preprocessing token 据说是因为版本问题 解法:去掉## define VFUNC(self) (......

shzwork
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部