JS 函数的执行时机

109 阅读3分钟

核心代码

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

结果:6 6 6 6 6 6


for(let i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

结果:0 1 2 3 4 5

1.结果为什么是6个6

核心:函数的结果与其执行环境与时机、作用域等因素都有直接决定关系

执行环境

核心问题:JS语言的执行环境是单线程的

JS的执行环境是单线程的。代码会从上到下依次执行(同步执行);for循环是同步代码,setTimeout是异步代码。JS在执行代码的过程中,碰到同步代码会依次执行,碰到异步代码就会将其放入任务队列中进行等待,当同步代码执行完毕后再开始执行异步代码(异步执行)。

  • 同步执行:按照代码顺序执行,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的
  • 异步执行:每一个任务有一个或多个回调函数(callback),异步代码任务执行完成后会执行回调函数,后续的任务则是不需要等待之前的任务结束即可执行,程序的执行顺序与任务的排列顺序不一定一致

执行时机

同步:

let  a = 1
function fn(){
    console.log(a)
}
fn()   //1
a=2
console.log(a) // 2

所有的代码按照预定的顺序执行

  1. 声明变量 a,变量的值为1
  2. 声明函数 fn,该函数的作用是在控制台输出 a 的值
  3. 调用函数 fn(),输出a的值为1
  4. a 赋值为 2
  5. 输出a的值为 2

在这段代码中所有的函数按照其编写的顺序执行

异步:

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

setTimeout是异步函数,会根据回调延后执行,整体流程如下:

  1. 声明变量 i
  2. i 赋值为 0
  3. 判断i<6,满足条件
  4. 进入循环,执行setTimeout
  5. setTimeout 根据回调函数延后输出
  6. i自增,重复步骤3、4、5、6,当i=6 时,不满足条件循环结束
  7. setTimeout 开始执行, 输出 i 的值6,并重复6次

即使时间延迟设为0也不改变其作为异步执行代码的本质。JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列负责外一些代码的执行。

作用域

  • 当同步代码执行完毕后,开始执行异步的setTimeout代码,执行setTimeout时需要从当前作用域内寻找一个变量i,此时for循环已执行完毕,当前 i=6,当let在for循环的外部时,其作用域是循环外的,i的值变化的所有执行结果都有效。

  • 所以执行setTimeout时输出为6,任务队列中的剩余5个setTimeout也依次执行,输出为6。

  • 将let声明移动到循环内部后,let只在代码块内才有效,i只在本轮循环中有效,每次循环的i 其实都是一个新的变量,所以setTimeout定时器里面的i,其实是不同的变量,即最后输出0-5。(每次循环的变量i都是重新声明的)

2. 其他方法

核心思想:通过在闭包形成新的作用域,从而在每次迭代中,捕获这次迭代中对应的i

  • 直接使用闭包配合其他变量存储对应的值
let i = 0
for (i = 0; i < 6 ; i++) {
  (function() {
    var j = i // 在闭包作用域中,通过添加自己的变量,每次迭代都捕获i的副本
    setTimeout( function timer() {
      console.log(j);
    }, 1000)    
  })()
}

  • 也可以直接在返回值中进行捕获
let i=0
for (i = 1; i <= 5; i++) {
	setTimeout(function (i) {
		return function timer(){
			console.log(i);
		}
	}(i), 1000)
}

  • 直接执行函数形成的闭包更直观
function timer() {
  let i = 0
  for (i = 0; i < 6; ++i) {
      (function(i){
      setTimeout(function() {
          console.log(i);
      }, 1000)
      })(i)
  }
}
timer()