摘要:再看一段代码这样就清晰地展示了闭包的词法作用域能访问的作用域将当做一个值返回执行后,将的引用赋值给执行,输出了变量我们知道通过引用的关系,就是函数本身。
在正式学习闭包之前,请各位同学一定要确保自己对词法作用域已经非常的熟悉了,如果对词法作用域还不够熟悉的话,可以先看:
深入理解闭包之前置知识---作用域与词法作用域
前言现在去面试前端开发的岗位,如果你的面试官也是个前端,并且不是太水的话,你有很大的概率会被问到JavaScript中的闭包。因为这个闭包这个知识点真的很重要,还非常难掌握。
什么是闭包什么是闭包,你可能会搜出很多答案....
《JavaScript高级程序设计》这样描述:
闭包是指有权访问另一个函数作用域中的变量的函数;
《JavaScript权威指南》这样描述:
从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
《你不知道的JavaScript》这样描述:
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
我最认同的是《你不知道的JavaScript》中的描述,虽然前面的两种说法都没有错,但闭包应该是基于词法作用域书写代码时产生的自然结果,是一种现象!你也不用为了利用闭包而特意的创建,因为闭包的在你的代码中随处可见,只是你还不知道当时你写的那一段代码其实就产生了闭包。
讲解闭包上面已经说到,当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
看一段代码
function fn1() { var name = "iceman"; function fn2() { console.log(name); } fn2(); } fn1();
如果是根据《JavaScript高级程序设计》和《JavaScript权威指南》来说,上面的代码已经产生闭包了。fn2访问到了fn1的变量,满足了条件“有权访问另一个函数作用域中的变量的函数”,fn2本身是个函数,所以满足了条件“所有的JavaScript函数都是闭包”。
这的确是闭包,但是这种方式定义的闭包不太好观察。
再看一段代码:
function fn1() { var name = "iceman"; function fn2() { console.log(name); } return fn2; } var fn3 = fn1(); fn3();
这样就清晰地展示了闭包:
fn2的词法作用域能访问fn1的作用域
将fn2当做一个值返回
fn1执行后,将fn2的引用赋值给fn3
执行fn3,输出了变量name
我们知道通过引用的关系,fn3就是fn2函数本身。执行fn3能正常输出name,这不就是fn2能记住并访问它所在的词法作用域,而且fn2函数的运行还是在当前词法作用域之外了。
正常来说,当fn1函数执行完毕之后,其作用域是会被销毁的,然后垃圾回收器会释放那段内存空间。而闭包却很神奇的将fn1的作用域存活了下来,fn2依然持有该作用域的引用,这个引用就是闭包。
总结:某个函数在定义时的词法作用域之外的地方被调用,闭包可以使该函数极限访问定义时的词法作用域。
注意:对函数值的传递可以通过其他的方式,并不一定值有返回该函数这一条路,比如可以用回调函数:
function fn1() { var name = "iceman"; function fn2() { console.log(name); } fn3(fn2); } function fn3(fn) { fn(); } fn1();
本例中,将内部函数fn2传递给fn3,当它在fn3中被运行时,它是可以访问到name变量的。
所以无论通过哪种方式将内部的函数传递到所在的词法作用域以外,它都回持有对原始作用域的引用,无论在何处执行这个函数都会使用闭包。
再次解释闭包以上的例子会让人觉得有点学院派了,但是闭包绝不仅仅是一个无用的概念,你写过的代码当中肯定有闭包的身影,比如类似如下的代码:
function waitSomeTime(msg, time) { setTimeout(function () { console.log(msg) }, time); } waitSomeTime("hello", 1000);
定时器中有一个匿名函数,该匿名函数就有涵盖waitSomeTime函数作用域的闭包,因此当1秒之后,该匿名函数能输出msg。
另一个很经典的例子就是for循环中使用定时器延迟打印的问题:
for (var i = 1; i <= 10; i++) { setTimeout(function () { console.log(i); }, 1000); }
在这段代码中,我们对其的预期是输出1~10,但却输出10次11。这是因为setTimeout中的匿名函数执行的时候,for循环都已经结束了,for循环结束的条件是i大于10,所以当然是输出10次11咯。
究其原因:i是声明在全局作用中的,定时器中的匿名函数也是执行在全局作用域中,那当然是每次都输出11了。
原因知道了,解决起来就简单了,我们可以让i在每次迭代的时候,都产生一个私有的作用域,在这个私有的作用域中保存当前i的值。
for (var i = 1; i <= 10; i++) { (function () { var j = i; setTimeout(function () { console.log(j); }, 1000); })(); }
这样就达到我们的预期了呀,让我们用一种比较优雅的写法改造一些,将每次迭代的i作为实参传递给自执行函数,自执行函数中用变量去接收:
for (var i = 1; i <= 10; i++) { (function (j) { setTimeout(function () { console.log(j); }, 1000); })(i); }闭包的应用
闭包的应用比较典型是定义模块,我们将操作函数暴露给外部,而细节隐藏在模块内部:
function module() { var arr = []; function add(val) { if (typeof val == "number") { arr.push(val); } } function get(index) { if (index < arr.length) { return arr[index] } else { return null; } } return { add: add, get: get } } var mod1 = module(); mod1.add(1); mod1.add(2); mod1.add("xxx"); console.log(mod1.get(2));注
关于闭包还有很多要讲,这里先讲解比较基础的概念,接下来还会有更精彩的内容。
特别注意可以关注我的公众号:icemanFE,接下来持续更新技术文章!
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/95273.html
摘要:一般函数执行完毕,局部活动对象就会被销毁,内存中仅仅保存全局作用域,但是闭包会长期驻扎在内存。我只是想通过这两个例子来说明闭包的用处和好处。闭包会使变量始终保存在内存中,如果使用不当会增大内存消耗。 闭包特性 函数嵌套函数 函数内部可以引用外部的参数和变量 参数和变量不会被垃圾回收机制回收 闭包的作用 具体作用是有权访问函数内部的变量,最常见的就是函数内部创建另一个函数,通过另一个函数...
摘要:和数组遍历方法详解在中常用的种数组遍历方法原始的循环语句数组对象内置方法数组对象内置方法数组对象内置方法数组对象内置方法数组对象内置方法数组对象内置方法数组对象内置方法数组对象内置方法循环语句中新增加了一种循环语句三种数组循环示例如下原始循 ES5和ES6数组遍历方法详解 在ES5中常用的10种数组遍历方法: 1、原始的for循环语句2、Array.prototype.forEach数...
阅读 1885·2021-11-11 16:55
阅读 2082·2021-10-08 10:13
阅读 746·2019-08-30 11:01
阅读 2158·2019-08-29 13:19
阅读 3281·2019-08-28 18:18
阅读 2624·2019-08-26 13:26
阅读 583·2019-08-26 11:40
阅读 1874·2019-08-23 17:17