封闭了内心却包容了天下,闭包你并不孤独
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了封闭了内心却包容了天下,闭包你并不孤独相关的知识,希望对你有一定的参考价值。
起点本文之所以会写这种老生常谈的文章,是为了接下来的设计模式做铺垫。既然已经提笔了,就打算不改了,继续写下去,相信也一定有很多人对闭包这样的概念有些模糊,那就瞧一瞧、看一看
毕竟闭包和高阶函数这两种概念,在开发中是非常有分量的。好处多多,妙处多多,那么我们就不再兜圈子了,直接开始今天的主题,闭包&高阶函数
闭包
闭包是前端er离不开的一个话题,而且也是一个难懂又必须明白的概念。说起闭包,它与变量的作用域和变量的生命周期密切相关。
这两个知识点我们也无法绕开,那么就一起了解下吧
变量作用域
首先变量作用域分为两类:全局作用域和局部作用域,这个没话说大家都懂。我们常说的变量作用域其实也主要是在函数中声明的作用域
在函数中声明变量时没有var关键字,就代表是全局变量
在函数中声明变量带有var关键字的即是局部变量,局部变量只能在函数内才能访问到
function fn()
var a = 110; // a为局部变量
console.log(a); // 110
fn();
console.log(a); // a is not defined 外部访问不到内部的变量
上面代码展示了在函数中声明的局部变量a在函数外部确实无法拿到。小样儿的还挺嚣张,对于迎难而上的coder来说,还不信拿不下a了
客官,莫急,且听风吟。大家是否还记得在js中,函数可是“一等公民”啊,大大滴厉害
函数可以创造函数作用域,在函数作用域中如果要查找一个变量的时候,如果在该函数内没有声明这个变量,就会向该函数的外层继续查找,一直查到全局变量为止
所以变量的查找是由内而外的,这也形成了所谓的作用域链
var a = 7;
function outer()
var b = 9;
function inner()
var c = 8;
alert(b);
alert(a);
inner();
alert(c); // c is not defined
outer(); // 调用函数
利用作用域链,我们试着去拿到a,改造一下fn函数
function fn()
var a = 110; // a为局部变量
return function()
console.log(a);
console.log(a); // 110
var fn2 = fn();
fn2(); // 110
如此这般,这般如此,轻而易举,小case的事,就可以从外面访问到局部变量a了
那么到此为止,我们已经发现了闭包的其中一个意义:闭包就是能够读取其他函数内部变量的函数,嗯,没毛病,继续往下看
变量生命周期
在解决了上面如何拿到小样儿a的问题,我们不妨再把变量生命周期这个概念先简单地过一遍。
对于全局变量来说,它的生命周期自然是永久的(forever),除非我们不高兴,主动干掉它,报销它。
而对于在函数中通过var声明的局部变量来说,就没那么幸运了,当函数执行完毕,局部变量们就失去了价值,就被垃圾回收机制给当成垃圾处理掉了
比如像下面这样的代码就很可怜
function fn()
var a = 123; // fn执行完毕后,变量a就将被销毁了
console.log(a);
fn();
虽然以上垃圾回收的过程我们无法亲眼看见,但是听者伤心闻者流泪啊。可不可以不要如此残忍,我愿倾其所有,换你三生三世。
悲伤的到来,我们无法拒绝,那就让我们想办法去改变这一切。现在再让我们来看下这段代码:
function add()
var a = 1;
return function()
a++;
console.log(a);
var fn = add();
fn(); // 2
fn(); // 3
fn(); // 4
这段代码最神奇的地方就是,当add函数执行完后,局部变量a并没有被销毁,而是依然存在,这其中到底发生了什么?让我们慢慢分析一下:
当fn = add()时,fn返回了一个函数的引用,这个函数里有局部变量a
既然这个局部变量还能被外部访问fn(),就没有必要把它给销毁了,于是就保留了下来
闭包是个好东西,可以完成很多工作,其中就包括一道网上常考的经典题目
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
var aLi = document.getElementsByTagName(‘li‘);
for (var i = 0; i < aLi.length; i++)
aLi[i].onclick = function()
console.log(i); // ?
;
</script>
见过这道题的观众请举手,确实这道题的目的就是为了考对闭包的理解。上面的答案无论怎么点结果都是4。
这是因为li节点的onclick事件属于异步的,在click被触发的时候,for循环以迅雷不及掩耳盗铃的速度就执行完毕了,此时变量i的值已经是4了
因此在li的click事件函数顺着作用域链从内向外开始找i的时候,发现i的值已经全是4了
解决方法就需要通过闭包,把每次循环的i值都存下来。然后当click事件继续顺着作用域链查找的时候,会先找到被存下来的i,这样每一个li点击都可以找到对应的i值了
<script>
var aLi = document.getElementsByTagName(‘li‘);
for (var i = 0; i < aLi.length; i++)
(function(n) // n为对应的索引值
aLi[i].onclick = function()
console.log(n); // 0, 1, 2, 3
;
)(i); // 这里i每循环一次都存一下,然后把0,1,2,3传给上面的形参n
</script>
其他作用
闭包应用非常广泛,我们这里就说一下大家熟知的,比如可以封装私有变量,可以把一些不需要暴露在全局的变量封装成私有变量,这样可以防止造成变量的全局污染
var sum = (function()
var cache = ; // 将cache放入函数内部,避免被其他地方修改
return function()
var args = Array.prototype.join.call(arguments, ‘,‘);
if (args in cache)
return cache[args];
var a = 0;
for (var i = 0; i < arguments.length; i++)
a += arguments[i];
return cache[args] = a;
)();
除此之外相信很多人都见过一些库如jQuery,underscore他们的最外层都是类似如下样子的代码
(function(win, undefined)
var a = 1;
var obj = ;
obj.fn = function() ;
// 最后把想要暴露出去的内容可以挂载到window上
win.obj = obj;
)(window);
是的,没错,利用闭包也可以做到模块化。另外还可以将变量的使用延长,再来看一个例子
var monitor = (function()
var imgs = [];
return function(src)
var img = new Image();
imgs.push(img);
img.src = src;
)();
monitor(‘http://dd.com/srp.gif‘);
上面的代码是用于打点进行统计数据的情形,在之前的一些浏览器中,会出现打点丢失的情况,因为img是函数内的局部变量,当函数执行完后img就被销毁了,而此时可能http请求还没有发出。
所以遇到这种情况的时候,把img变量用闭包封装起来,就可以解决了
内存管理
很多人都听过一个版本,就是闭包会造成内存泄漏,所以要尽量减少闭包的使用
Just now就来为闭包来正名,不是你想象那样的:
局部变量本来应该随着函数的执行完毕被销毁,但如果局部变量被封装在闭包形成的环境中,那这个局部变量就一直能存在。从我们上面实践得出的结果来看,这话说的没毛病
But之所以使用闭包是因为我们想要把一些变量存起来方便以后使用,这和放到全局下,对内存的影响是一致的,并不算是内存泄漏。如果在将来想回收这些变量,直接把变量设为null即可了
还有就是在使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,此时就有可能造成内存泄漏。但这本身并非闭包的问题,也并非js的问题
要怪就怪老版本的IE同志吧,它内部实现的垃圾回收机制采用的是引用计数策略。在老同志IE中,如果两个对象之间形成了循环引用,那么这两个对象都不能被回收,但循环引用造成的内存泄漏其本质也不是闭包的错
同样要解决循环引用代理的内存泄漏问题,只需把循环引用中的变量设为null就好
上面就是我们替闭包的正名,闭包也不容易,被人用还不讨好。它明白,不是它的锅,它是不需要背的!
以上是关于封闭了内心却包容了天下,闭包你并不孤独的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript:闭包是不是可以通过值而不是像 PHP 中的引用来访问封闭范围内的变量?