目录
JavaScript 中的作用域是什么意思?
JavaScript 的作用域通俗来讲,就是指变量能够被访问到的范围,在 JavaScript 中作用域也分为好几种,ES5 之前只有全局作用域和函数作用域两种。ES6 出现之后,又新增了块级作用域。
全局作用域
全局变量也是拥有全局的作用域,无论你在何处都可以使用它,在浏览器控制台输入 window.vName 的时候,就可以访问到 window 上所有全局变量。
函数作用域
function getName () {
var name = 'inner';
console.log(name); //inner
}
getName();
console.log(name);
上面代码中,name 这个变量是在 getName 函数中进行定义的,所以 name 是一个局部的变量,它的作用域就是在 getName 这个函数里边,也称作函数作用域。
除了这个函数内部,其他地方都是不能访问到它的。同时,当这个函数被执行完之后,这个局部变量也相应会被销毁。所以你会看到在 getName 函数外面的 name 是访问不到的。从这点看,可以更好地理解闭包的含义:
闭包其实就是一个可以访问其他函数内部变量的函数。即一个定义在函数内部的函数,或者直接说闭包是个内嵌函数也可以。
块级作用域
其实就是在 JS 编码过程中 if 语句及 for 语句后面 {...} 这里面所包括的,就是块级作用域
ES6 中新增了块级作用域,最直接的表现就是新增的 let 关键词,使用 let 关键词定义的变量只能在块级作用域中被访问,有“暂时性死区”的特点,也就是说这个变量在定义之前是不能被使用的。
闭包会在哪些场景中使用?
红宝书闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数。
MDN:一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
闭包其实就是一个可以访问其他函数内部变量的函数。即一个定义在函数内部的函数,或者直接说闭包是个内嵌函数也可以。
闭包产生的原因
作用域链
其实很简单,当访问一个变量时,代码解释器会首先在当前的作用域查找,如果没找到,就去父级作用域去查找,直到找到该变量或者不存在父级作用域中,这样的链路就是作用域链。
在创建compare()函数的时候,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]],如下图所示
当调用函数的时候,会为函数创建一个执行环境,然后复制函数的[[Scope]]属性中,此后还有活动对象
function compare(value1,value2){
if (value1 >value2){
return -1
}else if(value1 < value2){
return 1
}else{
return 0
}
}
var res = compare(5,10)
console.log(compare.prototype)
通过定时器循环输出自增的数字通过 JS 的代码如何实现?
console.log("循环输出问题")
for(var i = 1; i <= 5; i ++){
setTimeout(function() {
console.log(i)
}, 0)
}
这是为什么呢
-
setTimeout 为宏任务,由于 JS 中单线程 eventLoop 机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后 setTimeout 中的回调才依次执行。
-
因为 setTimeout 函数也是一种闭包,往上找它的父级作用域链就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 已经就是 6 了,因此最后输出的连续就都是 6。
利用 IIFE
可以利用 IIFE(立即执行函数),当每次 for 循环时,把此时的变量 i 传递到定时器中,然后执行,改造之后的代码如下。
for(var i = 1;i <= 5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
}, 0)
})(i)
}
使用 ES6 中的 let
ES6 中新增的 let 定义变量的方式,使得 ES6 之后 JS 发生革命性的变化,让 JS 有了块级作用域,代码的作用域以块级为单位进行执行。通过改造后的代码,可以实现上面想要的结果。
for(let i = 1; i <= 5; i++){
setTimeout(function() {
console.log(i);
},0)
}
定时器传入第三个参数
setTimeout 作为经常使用的定时器,它是存在第三个参数的,日常工作中我们经常使用的一般是前两个,一个是回调函数,另外一个是时间,而第三个参数用得比较少。那么结合第三个参数,调整完之后的代码如下
for(var i=1;i<=5;i++){
setTimeout(function(j) {
console.log(j)
}, 0, i)
}