闭包算是js里面比较不容易理解的点,尤其是对于没有编程基础的人来说。

其实闭包要注意的就那么几条,如果你都明白了那么征服它并不是什么难事儿。下面就让我们来谈一谈闭包的一些基本原理。

闭包的概念

一个闭包就是一个函数和被创建的函数中的作用域对象的组合。(作用域对象下面会说)

通俗一点的就是 “ 只要一个函数中嵌套了一个或多个函数,那么我们就可以称它们构成了闭包。 ”

类似这样:

 function A() {
var i = 5;
return function() {
console.log('i = '+i);
}
} var a = A();
a(); // i = 5

闭包的原理

  1、外部函数的局部变量若会被闭包函数调用就不会在外部函数执行完毕之后立即被回收。

  我们知道,不管什么语言,操作系统都会存在一个垃圾回收机制,将多余分配的空间回收掉以便减小内存。而一个函数的生命周期的是从调用它开始的,在函数调用完毕的时候函数内部的局部变量等都会被回收机制回收。

  我们拿上述例子来说,当我们的外部函数A调用完毕时,A中的局部变量i按理说就会被操作系统回收而不存在,但是当我们用了闭包结果就不是那样了,i并不会被回收。试想,如果i被回收了那么返回的函数里面岂不是就是打印undefined了?

  i为什么没有被回收?

  在javascript执行一个函数的时候都会创建一个作用域对象,将函数中的局部变量(函数的形参也是局部变量)保存进去,伴随着那些传入函数的变量一起被初始化。

  所以当调用A的时候就创建了一个作用域对象,我们姑且称之为Aa,那么这个Aa应该是这样的: Aa = { i: 5 };  在A函数返回一个函数之后,A执行完毕。Aa对象本应该被回收,但是由于返回的函数使用了Aa的属性i,所以返回的函数保存了一个指向Aa的引用,所以Aa不会被回收。

  所以理解作用域对象,就能理解为什么函数的局部变量在遇到闭包的时候不会在函数调用完毕时立即被回收了。

  再来个例子:

 function A(age) {
var name = 'wind';
var sayHello = function() {
console.log('hello, '+name+', you are '+age+' years old!');
};
return sayHello;
}
var wind = A(20);
wind(); // hello, wind, you are 20 years old!

  你能说出的它的作用域对象Ww是什么吗?

  Ww = { age: 20, name: 'wind' };

  2、每调用一次外部函数就产生一个新的闭包,以前的闭包依旧存在且互不影响。

  3、同一个闭包会保留上一次的状态,当它被再次调用时会在上一次的基础上进行。

  每调用一次外部函数产生的作用域对象都不一样,你可以这样想,上面的例子,你每次传入的参数age不一样,所以就每次生成的对象不一样。

  每调用一次外部函数那么就会生成一个新的作用域对象。

 function A() {
var num = 42;
return function() { console.log(num++); }
}
var a = A();
a(); //
a(); // var b = A(); // 重新调用A(),形成新闭包
b(); // 42

  这个代码让我们发现了两个事情,一、当我们连续调用两次a();,num会在原基础上自加。说明同一个闭包会保留上一次的状态,当它被再次调用时会在上一次的基础上进行。 二、我们的b();的结果为42,说明它是一个新的闭包,并且不受其他闭包的影响。

  我们可以这样想,就好比我们吹肥皂泡一样,我每次吹一下(调用外部函数),就会产生一个新的肥皂泡(闭包),多个肥皂泡可以同时存在且两个肥皂泡之间不会相互影响。

  4、在外部函数中存在的多个函数 “ 同生共死 ”

  以下三个函数被同时声明并且都可以对作用域对象的属性(局部变量)进行访问与操作。

var fun1, fun2, fun3;
function A() {
var num = 42;
fun1 = function() { console.log(num); }
fun2 = function() { num++; }
fun3 = function() { num--; }
} A();
fun1(); //
fun2();
fun2();
fun1(); //
fun3();
fun1(); // var old = fun1; A();
fun1(); //
old(); // 43 上一个闭包的fun1()

  由于函数不能有多个返回值,所以我用了全局变量。我们再次可以看出在我们第二次调用A()时产生了一个新的闭包。

当闭包遇到循环变量

  当我们说到闭包就不得不说当闭包遇到循环变量这一种情况,看如下代码:

 function buildArr(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
var item = 'item' + i;
result.push( function() {console.log(item + ' ' + arr[i])} );
}
return result;
} var fnlist = buildArr([1,2,3]);
fnlist[0](); // item2 undefined
fnlist[1](); // item2 undefined
fnlist[2](); // item2 undefined

  怎么会这样呢?我们预想的三个输出应该是 item0 1,  item1 2,  item2 3。为什么结果却是返回的result数组里面存储了三个 item2 undefined ?

    我们上文中提到过两点,1、闭包在返回的时候对作用域对象有一个引用。2、在外部函数中存在的多个内部函数 “ 同生共死 ”。

  我们的for循环为外部函数创建了多个“同生共死”的内部函数,它们都共享一个环境,而当result数组返回的时候,所有的内部函数都引用了同一个作用域对象:

 var bArr = {
item: 'item2',
i: 3,
arr: [1,2,3]
}

为什么作用域对象是这样的?拿我们上面的例子来说,当循环全部结束的时候作用域对象中的属性 i 正好是i++之后的3,而arr[3]是没有值的,所以为undefined。

  有朋友会疑惑:为什么item的值是item2,难道不应该是item3吗?

  注意,在最后一次循环的时候也就是 i = 2的时候,item的值为item2,当 i++,i = 3循环条件不满足循环结束,此时的item的值已经被保存下来了,所以此时的arr[i]为arr[3],而item为item2。这样能理解吗?

  如果我们将代码改成这样那就说得通了:

function buildArr(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
result.push( function() {console.log('item' + i + ' ' + arr[i])} );
}
return result;
} var fnlist = buildArr([1,2,3]);
fnlist[1](); // item3 undefined

  那么问题来了,如何改正呢?且看代码:

 function buildArr(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
result.push( (function(n) {
return function() {
var item = 'item' + n;
console.log(item + ' ' + arr[n]);
}
})(i));
}
return result;
} var fnlist = buildArr([1,2,3]);
fnlist[0](); // item0 1
fnlist[1](); // item1 2
fnlist[2](); // item2 3

  我们可以用一个自执行函数将i绑定,这样i的每一个状态都会被存储,答案就和我们预期的一样了。

  所以以后在使用闭包的时候遇到循环变量我们要习惯性的想到用自执行函数来绑定它。

=========================3月14日更新======================================================

  关于上面的问题还有一个更简单的方法:

 function buildArr(arr) {
var result = [];
for (let i = 0; i < arr.length; i++) {
let item = 'item' + i;
result.push( function() {console.log(item + ' ' + arr[i])} );
}
return result;
} var fnlist = buildArr([1,2,3]);
fnlist[0](); // item0 1

  这里使用了let代替var,let的好处是可以“模拟创建”块作用域,点到为止,有兴趣的朋友可以自行深入了解let。

  以上就是我对闭包的理解,如果有什么意见或建议希望我们能在评论区多多交流。感谢,共勉。

js----深入理解闭包的更多相关文章

  1. js深入理解"闭包"

    一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量 ...

  2. js中的闭包之我理解

    闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...

  3. 理解闭包的微观世界和JS垃圾回收机制

    function a() { ; function b() { alert(++i); } return b; } var c = a(); c(); 一.闭包的微观世界 如果要更加深入的了解闭包以及 ...

  4. js 理解闭包

    学习Javascript闭包(Closure) 引用: 阮一峰 http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures. ...

  5. js中的闭包理解一

    闭包是一个比较抽象的概念,尤其是对js新手来说.书上的解释实在是比较晦涩,对我来说也是一样. 但是他也是js能力提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候.你的答案的深度,对术语的 ...

  6. 【学习笔记】深入理解js原型和闭包系列学习笔记——精华

    深入理解js原型和闭包笔记: 1.“一切皆是对象”,对象是属性的集合. 丨 函数也是对象,但是使用typeof时为什么函数返回function而 丨  不是object呢,js为何要对函数做这样的区分 ...

  7. 【学习笔记】深入理解js原型和闭包(18)——补充:上下文环境和作用域的关系

    本系列用了大量的篇幅讲解了上下文环境和作用域,有些人反映这两个是一回儿事.本文就用一个小例子来说明一下,作用域和上下文环境绝对不是一回事儿. 再说明之前,咱们先用简单的语言来概括一下这两个的区别. 0 ...

  8. 【学习笔记】深入理解js原型和闭包(17)——补this

    本文对<深入理解js原型和闭包(10)——this>一篇进行补充,原文链接:https://www.cnblogs.com/lauzhishuai/p/10078307.html 原文中, ...

  9. 【学习笔记】深入理解js原型和闭包(16)——完结

    之前一共用15篇文章,把javascript的原型和闭包讲解了一下. 首先,javascript本来就“不容易学”.不是说它有多难,而是学习它的人,往往都是在学会了其他语言之后,又学javascrip ...

  10. 【学习笔记】深入理解js原型和闭包(15)——闭包

    前面提到的上下文环境和作用域的知识,除了了解这些知识之外,还是理解闭包的基础. 至于“闭包”这个词的概念的文字描述,确实不好解释,我看过很多遍,但是现在还是记不住. 但是你只需要知道应用的两种情况即可 ...

随机推荐

  1. oc语言复制视频文件

    void copyFile() { NSString *home=NSHomeDirectory(); NSString *path=[NSString stringWithFormat:@" ...

  2. MySQL(23):事务的隔离级别出现问题之 脏读

    1. 脏读 所谓的脏读就是指一个事务读取了另一个事务未提取的数据. 试想一下:a账户要给b账户100元购买商品,如果a账户开启一个事务,执行下面的update语句做了如下转账的工作: update a ...

  3. Android开发——实现固定在ScrollView顶部的View,类似于新浪微博的评论列表的顶部

    现在很多App都实现了这个功能,例如新浪微博评论页面的评论.转发.赞的数字可以固定在屏幕上方.我个人很喜欢这种设计,所以利用一点空余时间简单实现了一个类似的功能. 先来看一下上面这张图的效果 这个是新 ...

  4. crawler4j:轻量级多线程网络爬虫实例

    crawler4j是Java实现的开源网络爬虫.提供了简单易用的接口,可以在几分钟内创建一个多线程网络爬虫. 下面实例结合jsoup(中文版API),javacvs 爬取自如租房网(http://sh ...

  5. ubuntu系统下安装gstreamer的ffmpeg支持

    当您在安装gstreamer到您的ubuntu系统中时,为了更好地进行流媒体开发,需要安装ffmpeg支持,但一般情况下,直接使用 sudo apt-get install gstreamer0.10 ...

  6. 剑指Offer27 数组中超过一半的数

    /************************************************************************* > File Name: 27_MoreTh ...

  7. hdu-5714 拍照(二分)

    题目链接: 拍照 Time Limit: 6000/3000 MS (Java/Others)     Memory Limit: 65536/65536 K (Java/Others) Proble ...

  8. 有一种风格,叫做 Low Poly 3D

    原作:Simon阿文    杂交编辑者:RhinoC       个人更推崇使用第二款神器 ImageTriangulator :http://www.conceptfarm.ca/2013/port ...

  9. Smart法则

    设立目标不是一个很简单的事情.人们经常会犯一些基本的错误,即过于理想化.不符合客观情况.不容易执行落实,因此计划容易变成一种“美好的愿望”而已.在20世纪70年代,一位美国人为设立目标做出了一个简单而 ...

  10. ViewPager 可左右滑动和缩放的图片浏览

    最近因为要做一个项目,需要使用到图片的浏览.我就自己在网上找了些资料,然后加以修改整理后出来一个demo,希望可以帮助到需要的人.同时这也是我第一个技术博客. 在做之前首先需要了解一下什么是ViewP ...