1. 简要介绍

  闭包可谓是js中的一大特色了,即使你对闭包没概念,你可能已经在不知不觉中使用到了闭包。闭包是什么,闭包就是一个函数可以访问到另一个函数的变量。这就是闭包,解释起来就这么一句话,不明白?我们来看一个简单的例子:

function getName(){
var name='wenzi';
setTimeout(function(){
console.log(name);
}, 500);
}
getName();

  这就其实已经是闭包了,setTimeout中的function是一个匿名函数,这个匿名函数里的name是geName()作用域中的变量,匿名函数里只有一个输出语句:console.log();

  还有一个很经典的例子也可以帮助我们理解什么是闭包:

function create(){
var i=0;
// 返回一个函数,暂且称之为函数A
return function(){
i++;
console.log(i);
}
}
var c = create(); // c是一个函数
c(); // 函数执行
c(); // 再次执行
c(); // 第三次执行

  在上面的例子中,create()返回的是一个函数,我们暂且称之为函数A吧。在函数A中,有两条语句,一条是变量i自增(i++),一条是输出语句(console.log)。第一次执行执行c()时会产生什么样的结果?嗯,输出自增后的变量i,也就是输出1;那么第二次执行c()呢,对,会输出2;第三次执行c()时会输出3,依次累加。这个create()函数依然满足了我们在刚开始时的定义,函数A使用到了另一个函数create()中的变量i。

  可是为什么会产生这样的输出呢,为什么i就能一直自增呢,create函数已经执行完并返回结果了呀,可是为什么还能接着使用i呢,而且i还能自增。这里就涉及到了三个比较重要的概念,讲解完这三个概念,我们对闭包就可以有一个比较好的理解了。

  2. 三个重要概念

  本节的大部分解释来自于《JavaScript高级程序(第3版)》

  2.1 执行环境与变量对象

  执行环境是JavaScript中一个重要的概念,它决定了变量或函数是否有权访问其他的数据,决定了它们各自的行为。每个执行环境都有一个与之对应的变量对象,执行环境中定义的所有变量和函数都保存在这个对象中。虽然我们的代码无法访问这个对象,但是解析器在处理数据时会在后台使用它。

我们用一个比较简单的比喻来形容这两个概念。执行环境就是一个人,变量对象就是这个人的身份证号,每个人都有其对应的身份证号。他这个人决定了他身上的很多属性和方法(动作),而且这个人的属性和方法都在他的身份证号上,当这个人消亡的时候,身份证号也就随之就注销了。

  全局执行环境是最外层的一个执行环境。在web浏览器中,全局执行环境被认为是window对象,因为所有的全局变量和全局函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或者浏览器——时才会被销毁),被垃圾回收机制回收。

  每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入到一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。

  2.2 作用域链

  作用域链是当代码在一个环境中执行时创建的,作用域链的用途就是要保证执行环境中能有效有序的访问所有变量和函数。作用域链的最前端始终都是当前执行的代码所在环境的变量对象,下一个变量对象是来自其父亲环境,再下一个变量对象是其父亲的父亲环境,直到全局执行环境。

  标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。其实,通俗的理解就是:在本作用域内找不到变量或者函数,则在其父亲的作用域内寻找,再找不到则到父亲的父亲作用域内寻找,直到在全局的作用域内寻找!

  2.3 垃圾回收机制

  在js中有两种垃圾收集的方式:标记清除和引用计数。

  标记清除:垃圾收集器在运行时会给存储在内存中的所有变量都加上标记(具体的标记方式暂时就不清楚了),待变量已不被使用或者引用,去掉该标记或添加另一种标记。最后,垃圾收集器完成内存清除工作,销毁那些已无法访问到的这些变量并回收他们所占用的空间。

  引用计数:一般来说,引用计数的含义是跟踪记录每个值被引用的次数。当声明一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数便是1,如果同一个值又被赋给另一个变量,则该值的引用次数加1,相反,如果包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1。当这个值的引用次数为0时,说明没有办法访问到它了,因而可以将其占用的内存空间回收。

  除了一些极老版本的IE,目前市面上的JS引擎基本采用标记清除来除了垃圾回收。但是需要注意的是IE中的DOM由于机制问题,是采用了引用计数的方式,所以会有循环引用的问题,造成内存泄露。

var ele = document.getElementById(“element”);
var obj = new Object();
ele.obj = obj; // DOM元素ele的obj引用obj变量
obj.ele = ele; // obj变量的ele引用了DOM元素ele

  这样就造成了循环引用的问题,导致垃圾回收机制回收不了ele和obj。不过,可以在不使用ele和obj时,对这两个变量进行 null 赋值,然后垃圾回收机制就会回收它们了。

  3. 理解闭包

  在第2部分讲解了三个重要的概念,这三个概念有助于我们更好的理解闭包。

  我们再次拿出上面的这个例子:

function create(){
var i=0;
// 返回一个函数,暂且称之为函数A
return function(){
i++;
console.log(i);
}
}
var c = create(); // c是一个函数,即函数A
c(); // 函数执行
c(); // 再次执行
c(); // 第三次执行

  从上面的“每个函数都有自己的执行环境”可以知道:create()函数是一个执行环境,函数A也是一个执行环境,且函数A的执行环境在create()的里面。这样就形成了一个作用域链:window->create->A。当执行c()时,函数A就会首先在当前执行环境中寻找变量i,可是没有找到,那么只能顺着作用域链向后找;OK,在create()的执行环境中找到了,那么就可以使用了变量i了。

  可是我们还有一个疑问,按照上面的说法,函数create()执行完毕后,这个函数与里面的变量和方法应该被销毁了呀,可是为什么函数c()多次执行时依然能够输出变量i呢。这就是闭包的独特之处。

  函数create()执行完毕后,虽然它的作用域链会被销毁,即不再存在window->create这个链式关系,但是函数A()[c()]的作用域链还依然引用着create()的变量对象,还存在着window->create->A的链式关系,导致垃圾回收机制不能回收create()的变量对象,create()的变量对象仍然停留在内存中,直到函数A()[c()]被销毁后,create()的变量对象才会被销毁。

  因此,虽然create()已经执行完毕了,但是create()的变量对象并没有被回收,还停留在内存中,依然可以使用。

  从上面的讲解中我们可以看到,闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。当页面中存在过多的闭包,或者闭包的嵌套很多很深时,会导致内存占用过多。因此,在这里建议:慎用闭包。

  4. 闭包与变量

  有很多新手为DOM元素绑定事件时,通常会这么写:

function bindClick(){
var li = document.getElementsByTagName('li'); // 假设一共有5个li标签
for(var i=0; i<li.length; i++){
li[i].onClick = function(){
console.log('click the '+i+' li tag');
}
}
}

  他的本意是想为每个li标签绑定一个单独的事件,点击第几个li标签,就能输出几。可是,最后的结果却是,点击哪个li标签输出的都是5,这是为什么呢?

  其实这位程序员写的bindClick()已经构成了一个闭包,下面的这个函数有他的作用域,而变量i本不属于这个函数的作用域,而是属于bindClick()中的:

// 匿名函数
function(){
console.log('click the '+i+' li tag');
}

  因此,这就构成了一个含有闭包的作用域链:window->bindClick->匿名函数。可是这跟输出的i有关系么?有。作用域链中的每个变量对象保存的是对变量和方法的引用,而不是保存这个变量的某一个值。当执行到匿名函数时,bindClick()其实已经执行完毕了,变量i的值就是5,此时每个匿名函数都引用着同一个变量i。

  不过我们稍微修改一下,以满足我们的预期:

function bindClick(){
var li = document.getElementsByTagName('li');
for(var i=0; i<li.length; i++){
li[i].onClick = (function(j){
console.log('click the '+j+' li tag');
})(i);
}
}

  在这里,我们使用立即执行的匿名函数来保证传入的值就是当前正在操作的变量i,而不是循环完成后的值。

  5. 闭包的应用场景

  (1)在内存中维持一个变量。比如前面讲的小例子,由于闭包,函数create()中的变量i会一直存在于内存中,因此每次执行c(),都会给变量i加1.

  (2)保护函数内的变量安全。还是那个小例子,函数create()中的变量c只有内部的函数才能访问,而无法通过其他途径访问到,因此保护了变量c的安全。

  (3)实现面向对象中的对象。javascript并没有提供类这样的机制,但是我们可以通过闭包来模拟出类的机制,不同的对象实例拥有独立的成员和状态。

  这里我们看一个例子:

function Student(){
var name = 'wenzi'; return {
setName : function(na){
name = na;
}, getName : function(){
return name;
}
}
}
var stu = new Student();
console.log(stu.name); // undefined
console.log(stu.getName()); // wenzi

  这就是一个用闭包实现的简单的类,里面的name属性是私有的,外部无法进行访问,只能通过setName和getName进行访问。

  当然,闭包还存在另外一种形式,刚才我们已经使用到了:

;(function(w){
console.log(w);
})(window)

  以前也写过一次的闭包的文章,两个可以连着一起看:http://www.cnblogs.com/xumengxuan/p/3753713.html

Jquery学习笔记---闭包的更多相关文章

  1. jquery学习笔记---闭包,原型链,this关键字

    网上的资料很多,关于闭包,原型链,面向对象之内的.本人也有一点自己的总结. 关于this: this 的值取决于 function 被调用的方式,一共有四种, 如果一个 function 是一个对象的 ...

  2. jQuery 学习笔记

    jQuery 学习笔记   一.jQuery概述    宗旨: Write Less, Do More.    基础知识:        1.符号$代替document.getElementById( ...

  3. jQuery 学习笔记:jQuery 代码结构

    jQuery 学习笔记:jQuery 代码结构 这是我学习 jQuery 过程中整理的笔记,这一部分主要包括 jQuery 的代码最外层的结构,写出来整理自己的学习成果,有错误欢迎指出. jQuery ...

  4. jQuery学习笔记(一)jQuery选择器

    目录 jQuery选择器的优点 基本选择器 层次选择器 过滤选择器 表单选择器 第一次写博客,希望自己能够长期坚持,以写博客的方式作为总结与复习. 最近一段时间开始学习jQuery,通过写一个jQue ...

  5. jQuery学习笔记(一):入门

      jQuery学习笔记(一):入门 一.JQuery是什么 JQuery是什么?始终是萦绕在我心中的一个问题: 借鉴网上同学们的总结,可以从以下几个方面观察. 不使用JQuery时获取DOM文本的操 ...

  6. jQuery学习笔记 - 基础知识扫盲入门篇

    jQuery学习笔记 - 基础知识扫盲入门篇 2013-06-16 18:42 by 全新时代, 11 阅读, 0 评论, 收藏, 编辑 1.为什么要使用jQuery? 提供了强大的功能函数解决浏览器 ...

  7. JQuery学习笔记——层级选择器

    JQuery学习笔记--层级选择器 上一篇学习了基础的五种选择,分别是id选择器,class选择器,element选择器,*选择器 和 并列选择器.根据手册大纲,这篇学习的是层级选择器. 选择器: 1 ...

  8. jQuery学习笔记之Ajax用法详解

    这篇文章主要介绍了jQuery学习笔记之Ajax用法,结合实例形式较为详细的分析总结了jQuery中ajax的相关使用技巧,包括ajax请求.载入.处理.传递等,需要的朋友可以参考下 本文实例讲述了j ...

  9. jQuery学习笔记之插件开发(4)

    jQuery学习笔记之插件开发(4) github源码地址 插件:了让原有功能的增强. 1.插件的种类(3种):局部.全局.选择器插件 1.1封装对象方法的插件 这种类型的插件是把一些常用或者重复使用 ...

随机推荐

  1. Open CV缩放图像

    缩放图像是图像处理中需要经常使用的操作.太小的图像在图像识别中不能很好的处理,需要将其放大,太大的图像不方便储存,需要将其缩小,下面记录OpenCV图片缩放方法. 缩放函数 , , int inter ...

  2. Caffe学习系列(8):solver及其配置

    solver是caffe的核心. net: "examples/mnist/lenet_train_test.prototxt" test_iter: 100 test_inter ...

  3. 网络数据包发送工具PacketSender中文源码

    在网上发现了一个好用的工具PacketSender,数据包发送器.对于写网络程序来说,有很大的便利性.虽然在linux下,netcat工具也很好用,但是这个也不错. 原本是英文的,给翻译了一下.这是基 ...

  4. Objective C 快速入门学习一

    Objective-C程序设计 1. 直接用Xcode作为IDE,舍弃gcc编译方面的学习.2. 入门例子:Eg:打印Hello World 控制台程序 #import<Foundation/F ...

  5. IO复用与select函数

    socket select函数的详细讲解 select函数详细用法解析      http://blog.chinaunix.net/uid-21411227-id-1826874.html linu ...

  6. SQL Server 流程控制

    流程控制语句: BEGIN ... END WAITFOR GOTO WHILE IF ... ELSE BREAK RETURN CONTINURE   1.BEGIN ... END BEGIN ...

  7. linux学习之一些琐碎知识点

    一.python 问:django中project和app之间到底有什么不同? 答:他们的区别就是一个是配置,另一个是代码. 一个project包含很多个django app以及对它们的配置.技术上, ...

  8. C#操作字符串方法总结<转>

    staticvoid Main(string[] args) { string s =""; //(1)字符访问(下标访问s[i]) s ="ABCD"; Co ...

  9. 调试WebService

    1.运行WebService的调用程序 2.浏览器中运行asmx,这一步是为了让w3wp.exe出现在下一步的列表中 3.“工具”或“调试”菜单-->附加到进程 (MS为什么把同一功能放在不同的 ...

  10. Java for LeetCode 216 Combination Sum III

    Find all possible combinations of k numbers that add up to a number n, given that only numbers from ...