js作用域、作用域链、闭包
作用域可以分为函数作用域和全局作用域:
全局作用域:代码在程序任何地方都能访问,window对象的内置属性都属于全局作用域
函数作用域:在固定的代码片段才能被访问
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。一般情况下,变量会到创建这个变量的函数的作用域中取值,如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
function fn() { console.log(x); } function show(f) { var x = 20; f(); } show(fn);
上面的代码中,x现在fn作用域中查找,没有找到值,所以向上级作用域(全局作用域)中查找,找到x=10,所以这里会输出10。
闭包是一种特殊的对象。它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。因此,闭包就是指函数作用域中的内部变量被另一个函数访问。我们通过一个例子来详细了解:
var fn = null; function foo() { var a = 2; function innnerFoo() { console.log(a); } fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn } function bar() { fn(); // 此处保留innerFoo的引用 } foo(); bar(); // 2
在上面的例子中,foo()执行完毕之后,按照常理,其执行环境生命周期会结束,JavaScript拥有自动的垃圾回收机制,它所占内存被垃圾收集器释放。但是通过fn = innerFoo,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,导致了foo的变量对象,也被保留了下来。于是,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象,所以此刻仍然能够访问到变量a的值。这样,我们就可以称foo形成了一个闭包。
虽然例子中的闭包被保存在了全局变量中,但是闭包的作用域链并不会发生任何改变。在闭包中,能访问到的变量,仍然是作用域链上能够查询到的变量。我们将上面的例子稍作调整,如果我们在函数bar中声明一个变量c,并在fn中试图访问该变量,运行结果会抛出错误,因为变量c没有在innnerFoo的作用域链上。
var fn = null; function foo() { var a = 2; function innnerFoo() { console.log(c); // 在这里,试图访问函数bar中的c变量,会抛出错误 console.log(a); } fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn } function bar() { const c = 100; fn(); // 此处保留innerFoo的引用 } foo(); bar(); // 2
闭包有如下使用场景:
循环中使用异步事件,在事件执行机制中我们说过,异步事件会进入Event Table并注册函数,等主线程执行完后才会调用此事件队列,所以需要通过闭包保存循环中的某些变量以供异步事件使用。
setTimeout,原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。
这里我们通过一个循环闭包的经典题目来讲解以上两种场景:
// 利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5 for (var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i); }, i * 1000); }
我们知道在函数中闭包判定的准则,即执行时是否在内部定义的函数中访问了上层作用域的变量,因此我们只需要2个操作就可以完成题目需求,一是使用自执行函数提供闭包条件,二是传入i值并保存在闭包中。
for (var i = 1; i <= 5; i++) { (function (i) { setTimeout(function timer() { console.log(i); }, i * 1000); })(i) }
前面我们说到原生的setTimeout传递的第一个函数不能带参数的问题,所以这里我们也可以在setTimeout的第一个参数处利用闭包。
for (var i = 1; i <= 5; i++) { setTimeout((function (i) { return function () { console.log(i); } })(i), i * 1000); }
此外,闭包还可运用于封装私有变量、模块化与柯里化。
本站部分文章、数据、素材收集于网络,所有版权均归源网站或原作者所有!
如果侵犯了您的权益,请来信告知我们下线删除,邮箱:357234902@qq.com
上一篇:JavaScript事件执行机制