【前言】
闭包(closure)是javascript的一大难点,也是它的特色。很多高级应用都要依靠闭包来实现。
理解闭包,首先必须理解变量作用域。
【关于作用域的问题】
[JavaScript]
纯文本查看
复制代码
1 2 3 4 5 | var n = 999;function f1() { console.log(n);}f1() // 999 |
函数内部可以直接读取全局变量,函数 f1 可以读取全局变量 n。
但是,在函数外部无法读取函数内部声明的变量
[JavaScript]
纯文本查看
复制代码
1 2 3 4 5 | function f1() { var n = 99;}f1()console.log(n); |
有时我们却需要在函数外部访问函数内部的变量;正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
[JavaScript]
纯文本查看
复制代码
1 2 3 4 5 6 7 8 9 | function f1() { var n = 999; var f2 = function() { console.log(n); } return f2;}var f = f1();f(); |
上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。
这就是JavaScript语言特有的”链式作用域”结构(chain scope),子级会一层一层地向上寻找所有父级的变量。所以,父级的所有变量,对子级都是可见的,反之则不成立。
既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
闭包就是函数f2,即能够读取其他函数内部变量的函数。由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。
在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
【闭包的作用】
1. 匿名自执行函数
我们知道所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,
比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。
除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,
比如UI的初始化,那么我们可以使用闭包:
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 | var data= { table : [], tree : {} }; (function(dm){ for(var i = 0; i <dm.table.rows; i++){ var row =dm.table.rows; for(var j = 0; j< row.cells; i++){ drawCell(i,j); } } })(data); |
我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在函数执行完后会立刻释放资源,关键是不污染全局对象。
2. 结果缓存
我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,
那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | var CachedSearchBox = (function(){ var cache = {}, count = []; return { attachSearchBox :function(dsid){ if(dsid incache){//如果结果在缓存中 returncache[dsid];//直接返回缓存中的对象 } var fsb = newuikit.webctrl.SearchBox(dsid);//新建 cache[dsid] =fsb;//更新缓存 if(count.length> 100){//保正缓存的大小<=100 deletecache[count.shift()]; } returnfsb; }, clearSearchBox :function(dsid){ if(dsid incache){ cache[dsid].clearSelection(); } } }; })(); CachedSearchBox.attachSearchBox("input"); |
这样我们在第二次调用的时候,就会从缓存中读取到该对象。
3. 封装
[JavaScript]
纯文本查看
复制代码
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | var person = function(){ //变量作用域为函数内部,外部无法访问 var name ="default"; return { getName :function(){ returnname; }, setName :function(newName){ name =newName; } } }(); print(person.name);//直接访问,结果为undefined print(person.getName()); person.setName("abruzzi"); print(person.getName()); |
得到结果如下:
undefined
default
abruzzi
更多技术资讯可关注:gzitcast