js学习之----深入理解闭包


Posted in Javascript onNovember 21, 2016

闭包算是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(); // 42
a(); // 43

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();  // 42
fun2(); 
fun2(); 
fun1();  // 44
fun3(); 
fun1();  //43

var old = fun1;

A(); 
fun1();  // 42
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 ?

原来当闭包遇到循环变量时都是循环结束之后统一保存变量值,拿我们上面的例子来说,i是循环变量,当循环全部结束的时候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的每一个状态都会被存储,答案就和我们预期的一样了。

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

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

Javascript 相关文章推荐
浅析js预加载/延迟加载
Sep 25 Javascript
JS实现表格数据各种搜索功能的方法
Mar 03 Javascript
jquery实现可点击伸缩与展开的菜单效果代码
Aug 31 Javascript
基于jQuery实现的美观星级评论打分组件代码
Oct 30 Javascript
微信小程序 教程之wxapp 视图容器 view
Oct 19 Javascript
js实现的简练高效拖拽功能示例
Dec 21 Javascript
JS+DIV实现的卷帘效果示例
Mar 22 Javascript
Angular.JS中指令ng-if、ng-show/ng-hide和ng-switch的使用教程
May 07 Javascript
第一个Vue插件从封装到发布
Nov 22 Javascript
基于vue-cli vue-router搭建底部导航栏移动前端项目
Feb 28 Javascript
vue.js轮播图组件使用方法详解
Jul 03 Javascript
vue中v-for循环给标签属性赋值的方法
Oct 18 Javascript
浅谈js原生拖放
Nov 21 #Javascript
weUI应用之JS常用信息提示弹层的封装
Nov 21 #Javascript
js获取浏览器高度 窗口高度 元素尺寸 偏移属性的方法
Nov 21 #Javascript
JS中判断null的方法分析
Nov 21 #Javascript
javascript 利用arguments实现可变长参数
Nov 21 #Javascript
js 点击a标签 获取a的自定义属性方法
Nov 21 #Javascript
浅谈JS读取DOM对象(标签)的自定义属性
Nov 21 #Javascript
You might like
匹配csdn用户数据库与官方用户的重合度并将重叠部分的用户筛选出来
2011/12/25 PHP
PHP中CURL方法curl_setopt()函数的参数分享
2013/01/19 PHP
PHP扩展Memcache分布式部署方案
2015/12/06 PHP
PHP版本的选择5.2.17 5.3.27 5.3.28 5.4 5.5兼容性问题分析
2016/04/04 PHP
php使用变量动态创建类的对象用法示例
2017/02/06 PHP
PHP 并发场景的几种解决方案
2019/06/14 PHP
php设计模式之观察者模式定义与用法经典示例
2019/09/19 PHP
Javascript中的相等与不等运算
2010/04/25 Javascript
XMLHttpRequest处理xml格式的返回数据(示例代码)
2013/11/21 Javascript
JQuery中使用on方法绑定hover事件实例
2014/12/09 Javascript
完美解决jQuery的hover事件在IE中不停闪动的问题
2017/02/10 Javascript
Vue通过URL传参如何控制全局console.log的开关详解
2017/12/07 Javascript
webstrom Debug 调试vue项目的方法步骤
2018/07/17 Javascript
解决v-for中使用v-if或者v-bind:class失效的问题
2018/09/25 Javascript
微信{"errcode":48001,"errmsg":"api unauthorized, hints: [ req_id: 1QoCla0699ns81 ]"}
2018/10/12 Javascript
详解如何实现Element树形控件Tree在懒加载模式下的动态更新
2019/04/25 Javascript
深入了解响应式React Native Echarts组件
2019/05/29 Javascript
基于小程序请求接口wx.request封装的类axios请求
2020/07/02 Javascript
python切片及sys.argv[]用法详解
2018/05/25 Python
python微信公众号开发简单流程实现
2020/03/09 Python
Python word文本自动化操作实现方法解析
2020/11/05 Python
Selenium关闭INFO:CONSOLE提示的解决
2020/12/07 Python
python和opencv构建运动检测器的实现
2021/03/03 Python
HTML5中使用postMessage实现两个网页间传递数据
2016/06/22 HTML / CSS
加拿大最大的相机店:Henry’s
2017/05/17 全球购物
英国奢侈品在线精品店:Hervia
2020/09/03 全球购物
教师自荐书
2013/10/08 职场文书
机电职业生涯规划书范文
2014/03/08 职场文书
社区食品安全实施方案
2014/03/28 职场文书
安全生产责任书范本
2014/04/15 职场文书
企业优秀员工事迹材料
2014/05/28 职场文书
教师自我剖析材料(群众路线)
2014/09/29 职场文书
欢迎词范文
2015/01/27 职场文书
2015年大学团支部工作总结
2015/05/13 职场文书
MySQL事务的ACID特性以及并发问题方案
2022/07/15 MySQL
MySQL数据库查询之多表查询总结
2022/08/05 MySQL