基于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 相关文章推荐
Prototype Hash对象 学习
Jul 19 Javascript
利用onresize使得div可以随着屏幕大小而自适应的代码
Jan 15 Javascript
基于jquery的一个简单的脚本验证插件
Apr 05 Javascript
为radio类型的INPUT添加客户端脚本(附加实现JS来禁用onClick事件思路代码)
Nov 11 Javascript
window.parent与window.openner区别介绍
Apr 12 Javascript
jQuery 处理页面的事件详解
Jan 20 Javascript
jQuery实现的左右移动焦点图效果
Jan 14 Javascript
JavaScript sort数组排序方法和自我实现排序方法小结
Jun 06 Javascript
JS控制div跳转到指定的位置的几种解决方案总结
Nov 05 Javascript
深入学习jQuery中的data()
Dec 22 Javascript
JS学习笔记之闭包小案例分析
May 29 Javascript
vue 源码解析之虚拟Dom-render
Aug 26 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
php快递单号查询接口使用示例
2014/05/05 PHP
php继承中方法重载(覆盖)的应用场合
2015/02/09 PHP
php中return的用法实例分析
2015/02/28 PHP
jQuery中append()方法用法实例
2014/12/25 Javascript
NodeJs中的VM模块详解
2015/05/06 NodeJs
非常实用的js验证框架实现源码 附原理方法
2016/06/08 Javascript
带有定位当前位置的百度地图前端web api实例代码
2016/06/21 Javascript
js实现文字截断功能
2016/09/14 Javascript
Vue2.0利用vue-resource上传文件到七牛的实例代码
2017/07/28 Javascript
AngularJS实现的select二级联动下拉菜单功能示例
2017/10/25 Javascript
浅析vue-router原理
2018/10/19 Javascript
javascript数组常见操作方法实例总结【连接、添加、删除、去重、排序等】
2019/06/13 Javascript
angularjs自定义过滤器demo示例
2019/08/24 Javascript
微信提示 在浏览器打开 效果实现过程解析
2019/09/10 Javascript
Vue Render函数原理及代码实例解析
2020/07/30 Javascript
原生js实现自定义消息提示框
2020/11/19 Javascript
[01:55]TI9显影之尘系列 - Evil Geniuses
2019/08/22 DOTA
Python中的index()方法使用教程
2015/05/18 Python
TF-IDF算法解析与Python实现方法详解
2017/11/16 Python
对Python实现简单的API接口实例讲解
2018/12/10 Python
Python中如何导入类示例详解
2019/04/17 Python
使用Python正则表达式操作文本数据的方法
2019/05/14 Python
python中的反斜杠问题深入讲解
2019/08/12 Python
HTML5页面音视频在微信和app下自动播放的实现方法
2016/10/20 HTML / CSS
HTML5输入框下拉菜单功能的示例代码
2020/09/08 HTML / CSS
您的健身减肥和健康饮食专家:vitafy
2017/06/06 全球购物
校园达人秀策划书
2014/01/12 职场文书
高中军训感言500字
2014/02/24 职场文书
大学军训感言800字
2014/02/27 职场文书
空乘英文求职信
2014/04/13 职场文书
《春笋》教学反思
2014/04/15 职场文书
汽车转让协议书
2015/01/29 职场文书
龙潭大峡谷导游词
2015/02/10 职场文书
逃课检讨书范文
2015/05/06 职场文书
企业培训简报范文
2015/07/20 职场文书
关于HTML编码导致的乱码问题
2021/09/04 HTML / CSS