自定义:6901101060740349965
闭包
如果一个函数用到了外部的变量那么这个函数加这个变量就叫做 闭包
一个 Function 将生成一个闭包(通常是返回一个函数引用),这个函数引用从外部作用域(在当前环境下)可以访问闭包内部的作用域。
作用域规则
首先看一下,下面这串代码:
function fn(){
let a= 1
}
console.log(a) // a不存在
问:是不是因为fn没执行导致?
答:即使fn执行了, console.log(a)也访问不到作用域里面的a。
相信不少人都知道是作用域的影响造成的,如果一个变量或者其他表达式不在"当前的作用域中",那么它就是不可用的。
如果多个作用域有同名变量a那么查找a的声明时,就向上取最近的作用域简称「就近原则」
function f1(){
let a = 1
function f2(){
let a=2
console. log( 'f2: '+a)
}
console.log( 'f1: '+a) //
a= 3
f2()
}
// f1:1
// f2:2
查找a的过程与函数执行无关,但a的值与函数执行有关
下面的f2(){}函数就没有声明a的值,所以执行f2()由于变量在函数外被声明,按照就近原则,所以变量a=3(当前作用域不存在的变量和引用,就沿着作用域链继续寻找)
function f1(){
let a = 1
function f2(){
console. log( 'f2: '+a)
}
console.log( 'f1: '+a) //
a= 3
f2()
}
// f1:1
// f2:3
引入一个问题
问:下面代码输出结果为什么是6个6?
其中setTimeout()是设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。这里可以理解为尽快输出i
示例1:
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
// 6 6 6 6 6 6
解:let i = 0会当前块状作用域里新建一个i,这里相当于全局变量,在for里,i的值被由0到6之间修改变化,在循环执行完后 i 的地址指向的值为6。
虽然运行的结果是在一瞬间出现的,但是我们可以认为这个过程中for的循环已经执行完了,而setTimeout()到期后执行并调用console.log(i),此时 i 的值已经最终变化为6,找到 i=6 所以循环6次输出的为6。
————————————————————————————
更新:引用比较权威的说法:
for循环在主线程内,setTimeout是异步方法,在任务队列里面,只有主线程执行完后,任务队列才执行,当for执行结束时候,此时的i值已经是6
问:那么为什么下面这个代码的输出结果是从0到5,符合预期的?
示例2:
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
// 0 1 2 3 4 5
解:这里引用一下方应杭老师的一篇回答 重点如下:
1、for( let i = 0; i< 5; i++) 这句话的圆括号之间,有一个隐藏的作用域
2、for( let i = 0; i< 5; i++) { 循环体 } 在每次执行循环体之前,JS 引擎会把 i 在循环体的上下文中重新声明及初始化一次。
近似近似近似的可以认为
for(let i = 0; i<6; i++){
let i = 隐藏作用域中的i // 看这里看这里看这里
setTimeout(()=>{
console.log(i)
},0)
}
// 0 1 2 3 4 5
let方法在for的圆括号内声明一个变量i后,i的每次变化,都会被重新声明在循环体当中,setTimeout()调用的console.log(i)变量 i 取的当前作用域内的 i ,所以变量 i 是被声明为隐藏域中的 i
参照这个思路,类似的,对示例1 进行优化:
let i = 0
for(i = 0; i<6; i++){
let j=i;
setTimeout(()=>{
console.log(j)
},0)
}
// 0 1 2 3 4 5