基于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 相关文章推荐
js innerHTML 的一些问题的解决方法
Jun 22 Javascript
JS传递对象数组为参数给后端,后端获取的实例代码
Jun 28 Javascript
Vue.js父与子组件之间传参示例
Feb 28 Javascript
JavaScript正则表达式简单实用实例
Jun 23 Javascript
信息滚动效果的实例讲解
Sep 18 Javascript
Vue.js devtool插件安装后无法使用的解决办法
Nov 27 Javascript
checkbox在vue中的用法小结
Nov 13 Javascript
angular使用md5,CryptoJS des加密的方法
Jun 03 Javascript
vue中的面包屑导航组件实例代码
Jul 01 Javascript
详解vue 2.6 中 slot 的新用法
Jul 09 Javascript
js实现简单扫雷
Nov 27 Javascript
小程序实现可拖动的悬浮按钮
Sep 07 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下载远程图片并保存到本地方法总结
2016/01/22 PHP
PHP的PDO操作简单示例
2016/03/30 PHP
thinkphp5.1 文件引入路径问题及注意事项
2018/06/13 PHP
jQuery.clean使用方法及思路分析
2013/01/07 Javascript
extjs_02_grid显示本地数据、显示跨域数据
2014/06/23 Javascript
Javascript中的默认参数详解
2014/10/22 Javascript
node.js中的fs.fsyncSync方法使用说明
2014/12/15 Javascript
jQuery实现表格展开与折叠的方法
2015/05/04 Javascript
基于javascript实现样式清新图片轮播特效
2016/03/30 Javascript
浅析在javascript中创建对象的各种模式
2016/05/06 Javascript
逻辑表达式中与或非的用法详解
2016/06/06 Javascript
JavaScript实现相册弹窗功能(zepto.js)
2016/06/21 Javascript
AngularJS获取json数据的方法详解
2017/05/27 Javascript
Angular 中使用 FineReport不显示报表直接打印预览
2019/08/21 Javascript
[00:08]DOTA2勇士令状等级奖励“天外飞星”
2019/05/24 DOTA
Python Tkinter GUI编程入门介绍
2015/03/10 Python
python中base64加密解密方法实例分析
2015/05/16 Python
python实现Flappy Bird源码
2018/12/24 Python
Python获取航线信息并且制作成图的讲解
2019/01/03 Python
Python设计模式之工厂方法模式实例详解
2019/01/18 Python
django 通过url实现简单的权限控制的例子
2019/08/16 Python
Python实现点云投影到平面显示
2020/01/18 Python
python设置环境变量的作用整理
2020/02/17 Python
对python中arange()和linspace()的区别说明
2020/05/03 Python
Django微信小程序后台开发教程的实现
2020/06/03 Python
Python实现一个简单的毕业生信息管理系统的示例代码
2020/06/08 Python
Python创建简单的神经网络实例讲解
2021/01/04 Python
解决import tensorflow导致jupyter内核死亡的问题
2021/02/06 Python
仿CSDN Blog返回页面顶部功能实现原理及代码
2013/06/30 HTML / CSS
MAC彩妆澳洲官网:M·A·C AU
2021/01/17 全球购物
环境科学专业研究生求职信
2013/10/02 职场文书
小学综治宣传月活动总结
2014/07/02 职场文书
六年级情感作文之500字
2019/10/23 职场文书
Python基础详解之描述符
2021/04/28 Python
html实现弹窗的实例
2021/06/09 HTML / CSS
人民币符号
2022/02/17 杂记