理解javascript中的闭包


Posted in Javascript onJanuary 11, 2017

阅读目录

  • 什么是闭包?
  • 闭包的特性
  • 闭包的作用:
  • 闭包的代码示例
  • 注意事项
  • 总结

闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术。下来对其进行一个小小的总结

什么是闭包?

官方说法:

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量------《javascript高级程序设计第三版》

下面就是一个简单的闭包:

function A(){
 var text="hello world";
 function B(){
 console.log(text);
 }
 return B;
}
var c=A();
c(); // hello world 

按照字面量的意思是:函数B有权访问函数A作用域中的变量(text),通过另一个函数C来访问这个函数的局部变量text。因此函数B形成了一个闭包。也可以说C是一个闭包,因为C执行的实际是函数B。

这个需要注意的是,直接执行A();是没有任何反应的。因为return B没有执行,除非是return B();

闭包的特性

闭包有三个特性:

 1.函数嵌套函数

 2.函数内部可以引用外部的参数和变量

 3.参数和变量不会被垃圾回收机制回收

解释一下第3点,为什么闭包的参数和变量不会被垃圾回收机制回收呢?

首先我们先了解一下javascript的垃圾回收原理:

(1)、在javascript中,如果一个对象不再被引用,那么这个对象就会被GC(garbage collection)回收;

(2)、如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

上面的示例代码中A是B的父函数,而B被赋给了一个全局变量C(全局变量的生命周期直至浏览器卸载页面才会结束),这导致B始终在内存中,而B的存在依赖于A,因此A也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

闭包的作用:

其实闭包的作用也是有闭包的特性决定的,根据上面的闭包特性,闭包的作用如下:

  1、可以读取函数内部的变量,而不是定义一起全局变量,避免污染环境

  2、让这些变量的值始终保持在内存中。

闭包的代码示例

下面主要介绍几种常见的闭包,并进行解析:

demo1 局部变量的累加。

function countFn(){
 var count=1;
 return function(){  //函数嵌套函数
 count++;
 console.log(count);
 }
}
var y = countFn(); //外部函数赋给变量y;
y(); //2 //y函数调用一次,结果为2,相当于countFn()()
y(); //3 //y函数调用第二次,结果为3,因为上一次调用的count还保存在内存中,没有被销毁,所以实现了累加
y=null; //垃圾回收,释放内存
y(); // y is not a function

由于第一次执行完,变量count还保存在内存中,所以不会被回收,以致于第二次执行的时候可以对上次的值就行累加。当引入y=null时,销毁引用,释放内存

demo2 循环中使用闭包

代码如下(下面的三个代码示例):我们的目的是想在每次循环中调用循环序号:

demo2-1

for (var i = 0; i < 10; i++) {
 var a = function(){
 console.log(i)
 }
 a() //依次为0--9
}

这个例子的结果是没有题的,我们依次打印出了0-9

每一层匿名函数和变量i都组成了一个闭包,但是这样在循环中并没有问题,因为函数在循环体中立即被执行了

demo2-2

但是在setTimeout中就不一样了

for(var i = 0; i < 10; i++) {
 setTimeout(function() {
 console.log(i); //10次10
 }, 1000);
}

我们期望的依次是打印出0--10,实际情况是打印出 10次10。即使吧setTimeout的时间改为0,也是打印出10个10。这是为什么呢?

这是因为setTimeout的一种机制,setTimeout是从任务队列结束的时候开始计时的,如果前面有进程没有结束,那么它就等到它结束再开始计时。在这里,任务队列就是它自己所在的循环。

循环结束setTimeout才开始计时,所以无论如何,setTimeout里面的i都是最后一次循环的 i。该代码中,最后的 i 为10,所以打印出了10个10.

这也就是为什么setTimeout的回调不是每次取循环时的值,而取最后一次的值

demo2-3

解决上面的setTimeout不能依次打印出循环的问题

for(var i=0;i<10;i++){
 var a=function(e){
 return function(){
  console.log(e); //依次输入0--9
 }
 }
 setTimeout(a(i),0);
}

因为setTimeout第一个参数需要一个函数,所以返回一个函数给它,返回的同时把 i 作为参数传进去,通过形参 e 缓存了i,也就是说e变量相当于是 i 的一个拷贝 ,并带进返回的函数里面。

当 setTimeout 的执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

也可以用下面的写法,和上面类似:

for(var i = 0; i < 10; i++) {
 (function(e) {
 setTimeout(function() {
  console.log(e); //依次打印出0-9
 }, 0);
 })(i);
}

demo3 循环中添加事件

看下面的一个典型的demo.

我们希望每次点击li的时候,alert出li的索引值,所以用下面的代码:

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes = document.getElementsByTagName("li");
for(i = 0,len=nodes.length;i<len;i++){
 nodes[i].onclick = function(){
 alert(i); //值全是4
 };
}

事与愿违,无论点击哪一个li,都是alert(4),也就是都是alert循环结束之后的索引值。这是为什么呢?

这是因为循环中为不同的元素绑定事件,事件回调函数里如果调用了跟循环相关的变量,则这个变量取循环的最后一个值。

由于绑定的回调函数是一个匿名函数,所以上面的代码中, 这个匿名函数是一个闭包,携带的作用域为外层作用域(也就是for里面的作用域),当事件触发的时候,作用域中的变量已经随着循环走到最后了。

还有一点就是,事件是需要触发的,而绝大多数情况下,触发的时候循环已经结束了,所以循环相关的变量就是最后一次的取值。

要实现点击li,alert出li的索引值,需要将上面的代码进行以下的修改:

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes=document.getElementsByTagName("li");
for(var i=0;i<nodes.length;i++){
 (function(e){
 nodes[i].onclick=function(){
  alert(e);
 };
 })(i)
}

解决思路: 增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标)。

当立即执行函数执行的时候,e 值不会被销毁,因为它的里面有个匿名函数(也可以说是因为闭包的存在,所以变量不会被销毁)。执行后,e 值 与全局变量 i 的联系就切断了,

也就是说,执行的时候,传进的 i 是多少,立即执行函数的 e 就是多少,但是 e 值不会消失,因为匿名函数的存在。

也可以用下面的解法,原理是一样的:

<ul id="test">
 <li>第一个</li>
 <li>第二个</li>
 <li>第三个</li>
 <li>第四个</li>
</ul>
var nodes=document.getElementsByTagName('li');
for(var i = 0; i<nodes.length;i++){
 (function(){
 var temp = i;
 nodes[i].onclick = function () {
  alert(temp);
 }
 })();
}

注意事项

1、造成内存泄露

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以只有在绝对必要时再考虑使用闭包。

2、在闭包中使用this也可能会导致一些问题。

代码示例:来源于《js高级程序设计3》;

其实我们的目的是想alert出object里面的name

var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  return function(){
  return this.name;
  }
 }
 }
 alert(object.getNameFunc()()); // The Window

因为在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。

每个函数在被调用时,都会自动取的两个特殊变量:this和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止。也就是说,里面的return function只会搜索

到全局的this就停止继续搜索了。因为它永远不可能直接访问外部函数中的这两个变量。

稍作修改,把外部作用域中的this对象保存在一个闭包能够访问的变量里。这样就可以让闭包访问该对象了。

var name="The Window";
 var object={
 name:"My Object",
 getNameFunc:function(){
  var that=this;
  return function(){
  return that.name;
  }
 }
 }
 alert(object.getNameFunc()()); // My Object

我们把this对象赋值给了that变量。定义了闭包之后闭包也可以访问这个变量。因此,即使在函数返回之后,that也仍引用这object,所以调用object.getNameFunc()()就返回 “My Object”了。

总结

当在函数内部定义了其他函数,就创建了闭包。闭包有权访问包含函数内部的所有变量。

闭包的作用域包含着它自己的作用域、包含函数的作用域和全局作用域。

当函数返回一个闭包时,这个函数的作用域会一直在内存中保存到闭包不存在为止。

使用闭包必须维护额外的作用域,所有过度使用它们可能会占用大量的内存

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Javascript 相关文章推荐
js 跨域和ajax 跨域问题小结
Jul 01 Javascript
javascript两段代码,两个小技巧
Feb 04 Javascript
jQuery Mobile的loading对话框显示/隐藏方法分享
Nov 26 Javascript
jQuery中animate()方法用法实例
Dec 24 Javascript
JavaScript中的object转换成number或string规则介绍
Dec 31 Javascript
JavaScript实现数组在指定位置插入若干元素的方法
Apr 06 Javascript
jQuery增加自定义函数的方法
Jul 18 Javascript
js针对ip地址、子网掩码、网关的逻辑性判断
Jan 06 Javascript
JS不用正则验证输入的字符串是否为空(包含空格)的实现代码
Jun 14 Javascript
详解Javascript中prototype属性(推荐)
Sep 03 Javascript
javascript读取文本节点方法小结
Dec 15 Javascript
浅谈在不使用ssr的情况下解决Vue单页面SEO问题(2)
Nov 08 Javascript
javascript常用经典算法详解
Jan 11 #Javascript
总结几道关于Node.js的面试问题
Jan 11 #Javascript
微信端开发--登录小程序步骤
Jan 11 #Javascript
Vue数据驱动模拟实现1
Jan 11 #Javascript
利用iscroll4实现轮播图效果实例代码
Jan 11 #Javascript
详解js前端代码异常监控
Jan 11 #Javascript
Vue数据驱动模拟实现3
Jan 11 #Javascript
You might like
PHP同时连接多个mysql数据库示例代码
2014/03/17 PHP
9段PHP实用功能的代码推荐
2014/10/14 PHP
PHP中abstract(抽象)、final(最终)和static(静态)原理与用法详解
2020/06/05 PHP
JavaScript性能陷阱小结(附实例说明)
2010/12/28 Javascript
一样的table?不一样的table(可编辑状态table)
2012/09/19 Javascript
用JS在浏览器中创建下载文件
2014/03/05 Javascript
用js将内容复制到剪贴板兼容浏览器
2014/03/18 Javascript
javascript常见用法总结
2014/05/22 Javascript
谷歌浏览器调试JavaScript小技巧
2014/12/29 Javascript
jquery制作多功能轮播图插件
2015/04/02 Javascript
JavaScript DOM元素尺寸和位置
2015/04/13 Javascript
纯js的右下角弹窗实例
2017/03/12 Javascript
JavaScript实现图片无缝滚动效果
2017/07/07 Javascript
bootstrap-table.js扩展分页工具栏(增加跳转到xx页)功能
2017/12/28 Javascript
React Native中NavigatorIOS组件的简单使用详解
2018/01/27 Javascript
vue实现局部刷新的实现示例
2019/04/16 Javascript
更强大的vue ssr实现预取数据的方式
2019/07/19 Javascript
[01:00:30]完美世界DOTA2联赛循环赛 Inki vs Matador BO2第二场 10.31
2020/11/02 DOTA
深入解析Python中的descriptor描述器的作用及用法
2016/06/27 Python
Collatz 序列、逗号代码、字符图网格实例
2017/06/22 Python
Python实现的栈(Stack)
2018/01/26 Python
python 将print输出的内容保存到txt文件中
2018/07/17 Python
Python爬虫之pandas基本安装与使用方法示例
2018/08/08 Python
python 3调用百度OCR API实现剪贴板文字识别
2018/09/04 Python
解决安装python3.7.4报错Can''t connect to HTTPS URL because the SSL module is not available
2019/07/31 Python
提升python处理速度原理及方法实例
2019/12/25 Python
Python使用进程Process模块管理资源
2020/03/05 Python
浅谈Keras参数 input_shape、input_dim和input_length用法
2020/06/29 Python
python批量修改交换机密码的示例
2020/09/22 Python
Python xlwings插入Excel图片的实现方法
2021/02/26 Python
平面设计岗位职责
2013/12/14 职场文书
卫校中专生的自我评价
2014/01/15 职场文书
《挑山工》的教学反思
2014/02/16 职场文书
土建施工员岗位职责
2014/07/16 职场文书
有关九一八事变的演讲稿
2014/09/14 职场文书
Python打包exe时各种异常处理方案总结
2021/05/18 Python