15分钟帮你轻松理解 JS闭包

记得几年前刚工作,听到高大上的 JS闭包 一词时 让我一头雾水,很多初学者也许和我当时一样困惑,其实 闭包 也并没那么高深莫测。

今天我写了篇简单的学习笔记 帮助大家轻松理解 JS闭包。
参考资料:1.《你所不知道的JavaScript-上卷》闭包和作用域章节、2. 阮一峰老师的《学习JavaScript闭包》

要彻底弄懂 闭包,必须先理解 JS的 变量作用域,变量作用域分为: 全局变量 和 局部变量,JS的特殊之处在于:每个函数都会创建一个新的作用域,函数内部可以读取外部变量,相反 函数外部无法读取内部变量,则会报错。

var a = 123;
function foo1() {
  alert(a);
}
foo1(); // 123
function foo2() {
  var a = 123;
}
alert(a); // error,查找不到变量a的引用

为了更透彻理解作用域,看下面这个例子,请思考以下代码:

function foo(a) {
  var b = a * 2;

  function bar(c) {
    console.log( a, b, c );
  }

  bar( b * 3 );
}

foo(2); // 2, 4, 12

上面代码中有三个逐级嵌套的作用域,我们大可将它们想象成几个逐级包含的 作用域气泡:
这里写图片描述

作用域1:包含着整个全局作用域,其中只有一个标识符:foo
作用域2:包含着 foo 所创建的作用域,其中有三个标识符:a、b、bar
作用域3:包含着 bar 所创建的作用域,其中只有一个标识符:c

作用域气泡由其对应的作用域块代码写在哪里决定,它们是逐级包含的,最里面的 气泡 完全被包在 foo所创建的气泡 里。

上段代码中,当JS引擎执行 console.log(…) 方法时,开始查找 a、b、c 这3个变量的引用,它首先会从最内部的作用域 bar(…) 内部开始查找,引擎无法在这里找到 a,因此它会去上一级所嵌套的 foo(…) 作用域中继续查找,在这里找到了 a 因此引擎使用了这个引用,对于 b 和 c 也是一样,一层一层逐级向上查找,直到找到第一个匹配的标识符才停止。

到这里,我们知道作用域的规则是只能从内部向外部查找变量。

那如果从外部读取内部的变量?一般情况当然不行,我们可以在函数内部定义一个函数,再将它作为返回值,下面这个例子清晰的展示了 闭包:

function foo() {
  var a = 2;

  function bar() {
    console.log( a ); // 2
  }

  return bar;
}

var todo = foo();
todo(); // 2,快来淫阿,快来淫阿, 这就是闭包!

没明白?我来解释下,函数 bar() 的作用域能访问 foo() 的内部作用域,函数 bar() 被作为一个值返回,var todo =foo(); 执行 foo() 时,其实将函数 bar() 作为值引用类型传递给了 todo,当执行 todo() 时 等于执行了 bar(),并访问到了局部变量 a,所以我们看到的结果为2,神器的 闭包 让函数从外面作用域访问到了内部作用域的变量,原理其实就是 局部函数 bar() 在它自己的定义的作用域之外被执行了。

在 JS 中,通常一个函数执行完后,内部的整个作用域都会被销毁 被引擎的垃圾回收器回收,但闭包的出现阻止了这件事,上个例子中函数 的作用域就不会销毁,因为内部的作用域依然还存在,原来是 bar() 本身在使用,它在 foo() 作用域之外被执行,使得该作用域一直存在,当每次调用 todo() 便又访问到 foo() 函数内部的变量 a 。简而言之,这个例子中的函数 bar() 就是一个闭包。

各种专业文献上的”闭包“(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。

在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

使用闭包的注意点:
(1) 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,会造成网页的性能问题,在IE中可能导致内存泄露。可以在退出函数之前,将不使用的局部变量全部删除。
(2) 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

其实在你写的过的代码中 到处都有闭包的身影,如果将函数(访问它们各自的作用域)当作第一级的值类型并到处传递时,你就会看到闭包在这些函数中的应用,如:定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers 或任何其他异步或同步的任务中,只要使用了回调函数,那就是在使用闭包。

// 思考以下闭包示例:[/size]
// 闭包1
function wait(message) {
  setTimeout(function timer() {
    console.log(message);
  }, 1000);
}

wait("Hello, closure!");

// 闭包2
function setupBot(name, selector) {
  $(selector).click(function () {
    console.log("Activating" + name);
  });
}

setupBot("Closure Bot", "#bot");

// 等等……

ps:鉴于个人经验有限,所有观点,如有异议,请直接回复讨论(请勿发表攻击言论)。
加入QQ群209952809(需回答问题,答案为csdn);群聊更快解决问题,更happy。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值