闭包算是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. 编写who命令:文件操作,缓冲区与联机帮助

    最近阅读UULP(Understanding Unix/Linux Programming),按照书中介绍对Unix/Linux系统编程进行学习梳理,总结如下. 1. who命令能做什么 who命令用 ...

  2. C#高级特性

    1.接口 接口与抽象基类.抽象类与接口的不同是,抽象类不仅可以定义多态接口还可以定义一些其他的成员以及构造函数.而接口只能包含抽象成员. 抽象父类创建多态接口,只有派生类才可以.而往往很多情况下非派生 ...

  3. Android(java)学习笔记267:Android线程池形态

    1. 线程池简介  多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.     假设一个服务器完成一项任务所需时间为:T1 创建线程时间, ...

  4. emWin使用外部SRAM的方法

    我用的是stm32,加了1MB的外部SRAM,在使用emWin的时候,将一部分内存分配给emWin使用.其实方法很简单,传入SRAM数据总线地址即可,数据位宽我采用16bit,因为使用的SRAM是16 ...

  5. 重构10-Extract Method(提取方法)

    我们要介绍的重构是提取方法.这个重构极其简单但却大有裨益.首先,将逻辑置于命名良好的方法内有助于提高代码的可读性.当方法的名称可以很好地描述这部分代码的功能时,可以有效地减少其他开发者的研究时间.假设 ...

  6. MySQL5.0版本的安装图解教程

    MySQL5.0版本的安装图解教程是给新手学习的,当前mysql5.0.96是最新的稳定版本. mysql 下载地址 http://www.jb51.net/softs/2193.html 下面的是M ...

  7. 剑指Offer29 连续子数组最大和

    /************************************************************************* > File Name: 29_Greate ...

  8. 1166 矩阵取数游戏[区间dp+高精度]

    1166 矩阵取数游戏 2007年NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description [ ...

  9. Mousetrap - Keyboard shortcuts in Javascript

    Mousetrap is a simple library for handling keyboard shortcuts in Javascript. It is around 2kb minifi ...

  10. 【转载】Hadoop可视化分析利器之Hue

    http://qindongliang.iteye.com/blog/2212619 先来看下hue的架构图:  (1)Hue是什么? Hue是一个可快速开发和调试Hadoop生态系统各种应用的一个基 ...