基于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 相关文章推荐
img的onload的另类用法
Jan 10 Javascript
利用jQuery 实现GridView异步排序、分页的代码
Feb 06 Javascript
Jquery显示和隐藏元素或设为只读(含Ligerui的控件禁用,实例说明介绍)
Jul 09 Javascript
对JavaScript客户端应用编程的一些建议
Jun 24 Javascript
jQuery实现带滑动条的菜单效果代码
Aug 26 Javascript
jquery实现下拉框功能效果【实例代码】
May 06 Javascript
JavaScript必知必会(六) delete in instanceof
Jun 08 Javascript
babel基本使用详解
Feb 17 Javascript
使用Vue-Router 2实现路由功能实例详解
Nov 14 Javascript
JavaScript内存泄漏的处理方式
Nov 20 Javascript
使用vue实现多规格选择实例(SKU)
Aug 23 Javascript
微信小程序点击顶部导航栏切换样式代码实例
Nov 12 Javascript
关于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
相对路径转化成绝对路径
2007/04/10 PHP
phpmyadmin 访问被拒绝的真实原因
2009/06/15 PHP
php下用cookie统计用户访问网页次数的代码
2010/05/09 PHP
MyEclipse常用配置图文教程
2014/09/11 PHP
浅谈PHP中的面向对象OOP中的魔术方法
2017/06/12 PHP
thinkPHP通用控制器实现方法示例
2017/11/23 PHP
PHP的mysqli_rollback()函数讲解
2019/01/23 PHP
JQery jstree 大数据量问题解决方法
2010/03/09 Javascript
JavaScript中常见陷阱小结
2010/04/27 Javascript
js如何判断不同系统的浏览器类型
2013/10/28 Javascript
jQuery如何将选中的对象转化为原始的DOM对象
2014/06/09 Javascript
js监控IE火狐浏览器关闭、刷新、回退、前进事件
2014/07/23 Javascript
js 数组去重的四种实用方法
2014/09/09 Javascript
JavaScript基础语法、dom操作树及document对象
2014/12/02 Javascript
jquery表单验证需要做些什么
2015/11/17 Javascript
Javascript中apply、call、bind的巧妙使用
2016/08/18 Javascript
Vuejs第一篇之入门教程详解(单向绑定、双向绑定、列表渲染、响应函数)
2016/09/09 Javascript
微信小程序 蓝牙的实现实例代码
2017/06/27 Javascript
浅谈express 中间件机制及实现原理
2017/08/31 Javascript
vue构建动态表单的方法示例
2018/09/22 Javascript
Python数据类型详解(一)字符串
2016/05/08 Python
在Python中通过threading模块定义和调用线程的方法
2016/07/12 Python
从头学Python之编写可执行的.py文件
2017/11/28 Python
Python实现PS滤镜的万花筒效果示例
2018/01/23 Python
使用python进行文本预处理和提取特征的实例
2018/06/05 Python
python pygame实现方向键控制小球
2019/05/17 Python
python使用turtle绘制国际象棋棋盘
2019/05/23 Python
Python实现仿射密码的思路详解
2020/04/23 Python
Python pexpect模块及shell脚本except原理解析
2020/08/03 Python
AmazeUI 输入框组的示例代码
2020/08/14 HTML / CSS
维多利亚的秘密官方旗舰店:VICTORIA’S SECRET
2018/04/02 全球购物
普通大学毕业生自荐信
2013/11/04 职场文书
生产部管理制度
2014/01/31 职场文书
2014年内部审计工作总结
2014/12/09 职场文书
财务工作失误检讨书
2015/02/19 职场文书
Golang 实现WebSockets
2022/04/24 Golang