作用域
当访问一个变量时,代码解释器会首先在当前的作用域查找,如果没找到,就去父级作用域去查找,直到找到该变量或者不存在父级作用域中,这样的链路就是作用域链。
作用域链: 即当前函数一般都会存在上层函数的作用域的引用,那么他们就形成了一条作用域链。
闭包
闭包其实就是一个可以访问其他函数内部变量的函数。即一个定义在函数内部的函数,或者直接说闭包是个内嵌函数也可以
闭包产生的原因
闭包产生的本质就是:当前环境中存在指向父级作用域的引用
function fun1() {
var a = 2
function fun2() {
console.log(a); //2
}
return fun2;
}
var result = fun1();
result();
闭包的表现形式
-
返回一个函数
-
在定时器、事件监听、Ajax 请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包
// 定时器 setTimeout(function handler(){ console.log('1'); },1000); // 事件监听 $('#app').click(function(){ console.log('Event Listener'); });
-
作为函数参数传递的形式
var a = 1; function foo(){ var a = 2; function baz(){ console.log(a); } bar(baz); } function bar(fn){ // 这就是闭包 fn(); } foo(); // 输出2,而不是1
-
IIFE(立即执行函数),创建了闭包,保存了全局作用域(window)和当前函数的作用域,因此可以输出全局的变量
var a = 2; (function IIFE(){ console.log(a); // 输出2 })();
常见问题
// 以下代码执行会输出什么
for(var i = 1; i <= 5; i ++){
setTimeout(function() {
console.log(i)
}, 0)
}
结果输出的是 5 个 6,那么一般面试官都会先问为什么都是 6?我想让你实现输出 1、2、3、4、5 的话怎么办呢?
- setTimeout 为宏任务,由于 JS 中单线程 eventLoop 机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后 setTimeout 中的回调才依次执行。
- 因为 setTimeout 函数也是一种闭包,往上找它的父级作用域链就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 已经就是 6 了,因此最后输出的连续就都是 6。
方式一【利用 IIFE(立即执行函数)】
for(var i = 1; i <= 5; i ++){
(function(j) {
setTimeout(function() {
console.log(j)
}, 0)
})(i)
}
方式二【使用ES6中的let】
for(let i = 1; i <= 5; i ++){
setTimeout(function() {
console.log(i)
}, 0)
}
方式三【定时器传入第三个参数】
for(var i=1;i<=5;i++){
setTimeout(function(j) {
console.log(j)
}, 0, i)
}