基于javascript 闭包基础分享


Posted in Javascript onJuly 10, 2013

如果对作用域,函数为独立的对象这样的基本概念理解较好的话,理解闭包的概念并在实际的编程实践中应用则颇有水到渠成之感。

在DOM的事件处理方面,大多数程序员甚至自己已经在使用闭包了而不自知,在这种情况下,对于浏览器中内嵌的JavaScript引擎的bug可能造成内存泄漏这一问题姑且不论,就是程序员自己调试也常常会一头雾水。
用简单的语句来描述JavaScript中的闭包的概念:由于JavaScript中,函数是对象,对象是属性的集合,而属性的值又可以是对象,则在函数内定义函数成为理所当然,如果在函数func内部声明函数inner,然后在函数外部调用inner,这个过程即产生了一个闭包。

闭包的特性:
我们先来看一个例子,如果不了解JavaScript的特性,很难找到原因:

var outter = [];
        function clouseTest() {
            var array = ["one", "two", "three", "four"];
            for (var i = 0; i < array.length; i++) {
                var x = {};
                x.no = i;
                x.text = array[i];
                x.invoke = function () {
                    print(i);
                }
                outter.push(x);
            }
        }
        //调用这个函数
        clouseTest();
        print(outter[0].invoke());
        print(outter[1].invoke());
        print(outter[2].invoke());
        print(outter[3].invoke());

运行的结果如何呢?很多初学者可能会得出这样的答案:
0
1
2
3
然而,运行这个程序,得到的结果为:
4
4
4
4
其实,在每次迭代的时候,这样的语句x.invoke = function(){print(i);}并没有被执行,只是构建了一个函数体为”print(i);”的函数对象,如此而已。而当i=4时,迭代停止,外部函数返回,当再去调用outter[0].invoke()时,i的值依旧为4,因此outter数组中的每一个元素的invoke都返回i的值:4。如何解决这一问题呢?我们可以声明一个匿名函数,并立即执行它:
var outter = [];
        function clouseTest2() {
            var array = ["one", "two", "three", "four"];
            for (var i = 0; i < array.length; i++) {
                var x = {};
                x.no = i;
                x.text = array[i];
                x.invoke = function (no) {
                    return function () {
                        print(no);
                    }
                }(i);
                outter.push(x);
            }
        }
        clouseTest2();
    </script>

这个例子中,我们为x.invoke赋值的时候,先运行一个可以返回一个函数的函数,然后立即执行之,这样,x.invoke的每一次迭代器时相当与执行这样的语句:
//x == 0
x.invoke = function(){print(0);}
//x == 1
x.invoke = function(){print(1);}
//x == 2
x.invoke = function(){print(2);}
//x == 3
x.invoke = function(){print(3);}

这样就可以得到正确结果了。闭包允许你引用存在于外部函数中的变量。然而,它并不是使用该变量创建时的值,相反,它使用外部函数中该变量最后的值。
闭包的用途:
现在,闭包的概念已经清晰了,我们来看看闭包的用途。事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。

缓存:
再来看一个例子,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。
闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
var CachedSearchBox = (function () {
            var cache = {},
               count = [];
            return {
                attachSearchBox: function (dsid) {
                    if (dsid in cache) {//如果结果在缓存中
                        return cache[dsid];//直接返回缓存中的对象
                    }
                    var fsb = document.getElementById(dsid);//新建
                    cache[dsid] = fsb;//更新缓存
                    if (count.length > 100) {//保正缓存的大小<=100
                        delete cache[count.shift()];
                    }
                    return fsb;
                },
                clearSearchBox: function (dsid) {
                    if (dsid in cache) {
                        cache[dsid].clearSelection();
                    }
                }
            };
        })();
        var obj1 = CachedSearchBox.attachSearchBox("input1");
        //alert(obj1);
        var obj2 = CachedSearchBox.attachSearchBox("input1");

实现封装:
var person = function(){
    //变量作用域为函数内部,外部无法访问
    var name = "default";       return {
       getName : function(){
           return name;
       },
       setName : function(newName){
           name = newName;
       }
    }
}();
print(person.name);//直接访问,结果为undefined
print(person.getName());
person.setName("jack");
print(person.getName());

得到结果如下:
undefined
default
jack

闭包的另一个重要用途是实现面向对象中的对象,传统的对象语言都提供类的模板机制,这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,我们可以模拟出这样的机制。还是以上边的例子来讲:

function Person(){
    var name = "default";       return {
       getName : function(){
           return name;
       },
       setName : function(newName){
           name = newName;
       }
    }
};
var john = Person();
print(john.getName());
john.setName("john");
print(john.getName());
var jack = Person();
print(jack.getName());
jack.setName("jack");
print(jack.getName());

运行结果如下:
default
john
default
jack

javascript闭包应该注意的问题:
1.内存泄漏:
在不同的JavaScript解释器实现中,由于解释器本身的缺陷,使用闭包可能造成内存泄漏,内存泄漏是比较严重的问题,会严重影响浏览器的响应速度,降低用户体验,甚至会造成浏览器无响应等现象。JavaScript的解释器都具备垃圾回收机制,一般采用的是引用计数的形式,如果一个对象的引用计数为零,则垃圾回收机制会将其回收,这个过程是自动的。但是,有了闭包的概念之后,这个过程就变得复杂起来了,在闭包中,因为局部的变量可能在将来的某些时刻需要被使用,因此垃圾回收机制不会处理这些被外部引用到的局部变量,而如果出现循环引用,即对象A引用B,B引用C,而C又引用到A,这样的情况使得垃圾回收机制得出其引用计数不为零的结论,从而造成内存泄漏。

2.上下文的引用:

$(function(){
    var con = $("div#panel");
    this.id = "content";
    con.click(function(){
       alert(this.id);//panel
    });
});

此处的alert(this.id)到底引用着什么值呢?很多开发者可能会根据闭包的概念,做出错误的判断:
content
理由是,this.id显示的被赋值为content,而在click回调中,形成的闭包会引用到this.id,因此返回值为content。然而事实上,这个alert会弹出”panel”,究其原因,就是此处的this,虽然闭包可以引用局部变量,但是涉及到this的时候,情况就有些微妙了,因为调用对象的存在,使得当闭包被调用时(当这个panel的click事件发生时),此处的this引用的是con这个jQuery对象。而匿名函数中的this.id = “content”是对匿名函数本身做的操作。两个this引用的并非同一个对象。
如果想要在事件处理函数中访问这个值,我们必须做一些改变:
$(function(){
    var con = $("div#panel");
    this.id = "content";
    var self = this;
    con.click(function(){
       alert(self.id);//content
    });
});

这样,我们在事件处理函数中保存的是外部的一个局部变量self的引用,而并非this。这种技巧在实际应用中多有应用,我们在后边的章节里进行详细讨论。关于闭包的更多内容,我们将在第九章详细讨论,包括讨论其他命令式语言中的“闭包”,闭包在实际项目中的应用等等。
附:由于本身水平有限,文中难免有纰漏错误等,或者语言本身有不妥当之处,欢迎及时指正,提出建议。本文只为抛砖引玉,谢谢大家!
Javascript 相关文章推荐
[IE&amp;FireFox兼容]JS对select操作
Jan 07 Javascript
jquery 得到当前页面高度和宽度的两个函数
Feb 21 Javascript
js将字符串转成正则表达式的实现方法
Nov 13 Javascript
js实现全屏漂浮广告移入光标停止移动
Dec 02 Javascript
一个获取第n个元素节点的js函数
Sep 02 Javascript
node.js中的fs.stat方法使用说明
Dec 16 Javascript
使用控制台破解百小度一个月只准改一次名字
Aug 13 Javascript
利用Node.js对文件进行重命名
Mar 12 Javascript
详解用node.js实现简单的反向代理
Jun 26 Javascript
layui问题之模拟select点击事件的实例讲解
Aug 15 Javascript
简单了解node npm cnpm的具体使用方法
Feb 27 Javascript
如何理解Vue前后端数据交互与显示
May 10 Vue.js
关于include标签导致js路径找不到的问题分析及解决
Jul 09 #Javascript
等待指定时间后自动跳转或关闭当前页面的js代码
Jul 09 #Javascript
浅析js封装和作用域
Jul 09 #Javascript
js正则表达式的使用详解
Jul 09 #Javascript
解析Jquery的LigerUI如何实现文件上传
Jul 09 #Javascript
Js(JavaScript)中,弹出是或否的选择框示例(confirm用法的实例分析)
Jul 09 #Javascript
探讨在JQuery和Js中,如何让ajax执行完后再继续往下执行
Jul 09 #Javascript
You might like
点评山进PR-D3L三波段收音机
2021/03/02 无线电
ASP和PHP都是可以删除自身的
2007/04/09 PHP
PHP 作用域解析运算符(::)
2010/07/27 PHP
PHP session会话的安全性分析
2011/09/08 PHP
一组PHP可逆加密解密算法实例代码
2014/01/21 PHP
php删除txt文件指定行及按行读取txt文档数据的方法
2017/01/30 PHP
jquery scrollTop方法根据滚动像素显示隐藏顶部导航条
2013/05/27 Javascript
jquery eval解析JSON中的注意点介绍
2013/08/23 Javascript
判断某个字符在一个字符串中是否存在的js代码
2014/02/28 Javascript
jQuery中大家不太了解的几个方法
2015/03/04 Javascript
javascript字符串函数汇总
2015/12/06 Javascript
正则表达式(语法篇推荐)
2016/06/24 Javascript
JS中事件冒泡和事件捕获介绍
2016/12/13 Javascript
Angular实现预加载延迟模块的示例
2017/10/12 Javascript
javascript中UMD规范的代码推演
2018/08/29 Javascript
分享5个好用的javascript文件上传插件
2018/09/16 Javascript
json前后端数据交互相关代码
2018/09/19 Javascript
JavaScript实现简单音乐播放器
2020/04/17 Javascript
利用百度echarts实现图表功能简单入门示例【附源码下载】
2019/06/10 Javascript
详解vue+axios给开发环境和生产环境配置不同的接口地址
2019/08/16 Javascript
vue + node如何通过一个Txt文件批量生成MP3并压缩成Zip
2020/06/02 Javascript
Python面向对象之继承和组合用法实例分析
2018/08/27 Python
Python 创建新文件时避免覆盖已有的同名文件的解决方法
2018/11/16 Python
查看Python依赖包及其版本号信息的方法
2019/08/13 Python
Python学习笔记之For循环用法详解
2019/08/14 Python
Python多线程threading创建及使用方法解析
2020/06/17 Python
Django:使用filter的pk进行多值查询操作
2020/07/15 Python
next在python中返回迭代器的实例方法
2020/12/15 Python
html5中的input新属性range使用记录
2014/09/05 HTML / CSS
使用HTML5和CSS3制作一个模态框的示例
2018/03/07 HTML / CSS
印度尼西亚最完整和最大的在线药房网站:Farmaku.com
2019/11/23 全球购物
是什么让J2EE适合用来开发多层的分布式的应用
2015/01/16 面试题
年终总结会议主持词
2014/03/17 职场文书
党员承诺书内容
2014/03/26 职场文书
销售团队激励口号
2014/06/06 职场文书
毕业论文致谢格式模板
2015/05/14 职场文书