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 相关文章推荐
Prototype的Class.create函数解析
Sep 22 Javascript
Jquery插件easyUi表单验证提交(示例代码)
Dec 30 Javascript
js判断当前浏览器类型,判断IE浏览器方法
Jun 02 Javascript
jQuery实现当前页面标签高亮显示的方法
Mar 10 Javascript
每天一篇javascript学习小结(基础知识)
Nov 10 Javascript
jQuery生成假加载动画效果
Dec 01 Javascript
详谈jQuery中的一些正则匹配表达式
Mar 08 Javascript
js图片放大镜效果实现方法详解
Oct 28 Javascript
Vue数组更新及过滤排序功能
Aug 10 Javascript
JS函数节流和函数防抖问题分析
Dec 18 Javascript
解决layui页面按钮点击无反应,也不报错的问题
Sep 29 Javascript
通过原生vue添加滚动加载更多功能
Nov 21 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
xajax写的留言本
2006/11/25 PHP
PHP 巧用数组降低程序的时间复杂度
2010/01/01 PHP
PHP使用星号隐藏用户名,手机和邮箱的实现方法
2016/09/22 PHP
php实现不通过扩展名准确判断文件类型的方法【finfo_file方法与二进制流】
2017/04/18 PHP
Laravel构建即时应用的一种实现方法详解
2017/08/31 PHP
laravel csrf排除路由,禁止,关闭指定路由的例子
2019/10/21 PHP
在一个form用一个SUBMIT(或button)分别提交到两个处理表单页面的代码
2007/02/15 Javascript
利用JQuery为搜索栏增加tag提示
2009/06/22 Javascript
js获得网页背景色和字体色的方法
2014/03/21 Javascript
仿淘宝TAB切换搜索框搜索切换的相关内容
2014/09/21 Javascript
JS中获取函数调用链所有参数的方法
2015/05/07 Javascript
JS创建对象几种不同方法详解
2016/03/01 Javascript
基于Vue.js实现数字拼图游戏
2016/08/02 Javascript
手机端js和html5刮刮卡效果
2020/09/29 Javascript
jQuery插件HighCharts绘制简单2D折线图效果示例【附demo源码】
2017/03/21 jQuery
BootStrap表单控件之复选框checkbox和单选择按钮radio
2017/05/23 Javascript
vue.js实现数据动态响应 Vue.set的简单应用
2017/06/15 Javascript
jQuery EasyUI Layout实现tabs标签的实例
2017/09/26 jQuery
基于VUE实现的九宫格抽奖功能
2018/09/30 Javascript
JS正则表达式封装与使用操作示例
2019/05/15 Javascript
详解vue-template-admin三级路由无法缓存的解决方案
2020/03/10 Javascript
利用python爬取散文网的文章实例教程
2017/06/18 Python
python中的文件打开与关闭操作命令介绍
2018/04/26 Python
python用户评论标签匹配的解决方法
2018/05/31 Python
在Python中使用gRPC的方法示例
2018/08/08 Python
Python urllib库如何添加headers过程解析
2020/10/05 Python
CSS3移动端vw+rem不依赖JS实现响应式布局的方法
2019/01/23 HTML / CSS
纪伊国屋泰国网上书店:Kinokuniya泰国
2017/12/24 全球购物
Famous Footwear加拿大:美国多品牌运动休闲鞋店
2018/12/05 全球购物
美国电子产品主要品牌的授权在线零售商:DataVision
2019/03/23 全球购物
网络体系结构及协议的定义
2014/03/13 面试题
教育课题研究自我鉴定范文
2013/12/28 职场文书
幸福家庭标语
2014/06/27 职场文书
单位活动策划方案
2014/08/17 职场文书
离婚协议书范文
2015/01/26 职场文书
Mysql中有关Datetime和Timestamp的使用总结
2021/12/06 MySQL