《JS 函数的执行时机》
回复数(0) 浏览数(24)
{{topic.upvote_count || 0}} 编辑 回复

今天,我们来一起讨论一下关于JS函数的执行时机的问题。

首先,我们来看一下下列两个代码:

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

打印出的结果为:

6 6 6 6 6 6

** 这是为什么呢? **

首先,我们需要了解一下JS的运行机制。JS是单线程环境,也就是说代码的执行是从上到下,依次执行。也就是同一个时间只能做一件事。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。JavaScript将所有任务分成两种,一种是同步任务,另一种是异步任务。

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

在所有同步任务执行完之前,任何的异步任务是不会执行的。 而setTimeout就是一个异步任务,所以会先执行for循环这个同步任务,把setTimeout()放进任务队列中等待主线程的for循环执行完毕,一旦"执行栈"中的所有同步任务执行完毕(循环结束后此时i=6)就会从队列中取出setTimeout()。for循环一次碰到一个 setTimeout(),会将其放到任务队列里面。

** 那么如何打印出我们想要的结果呢? **

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

打印出的结果为:

0 1 2 3 4 5

在这种情况下,js会偷偷的在循环体里,每次声明一个i,但这个i的值又会和上次i++后的值相等,于是,setTimeout函数稍等一会再打印i,就打印出如上的结果了。

那么还有什么方法可以打印出以上的结果呢?

  • 方法一
   for (var i=0; i<6; i++) {
      (function (i) {
        setTimeout(() => console.log(i), 0)
      })(i)
    }
  • 方法二
    function print(i) {
    return new Promise(resolve => {
        setTimeout(() => resolve(i), 0)
        })
    }

    async function main() {
    for(var i = 0; i < 6; i++) {
        var r = await print(i)
        console.log(r)
        }
    }

    main()
  • 方法三
    function print(i) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(i), 0)
         })
    }
    var arr = [];
    for (var i = 0; i < 6; i++) {
        arr.push(print(i))
    }
    for (var i = 0; i < 6; i++) {
        arr[i].then(data => console.log(data))
    }

版权声明

本内容版权归属本人及杭州饥人谷教育科技有限公司(简称:饥人谷)所有。
任何媒体、网站或个人未经授权不得转载、链接和转贴,或以其他方式复制、发布和发表。
对于违反者,将依法追究责任。

{{topic.upvote_count || 0}}

今天,我们来一起讨论一下关于JS函数的执行时机的问题。

首先,我们来看一下下列两个代码:

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

打印出的结果为:

6 6 6 6 6 6

** 这是为什么呢? **

首先,我们需要了解一下JS的运行机制。JS是单线程环境,也就是说代码的执行是从上到下,依次执行。也就是同一个时间只能做一件事。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。JavaScript将所有任务分成两种,一种是同步任务,另一种是异步任务。

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

在所有同步任务执行完之前,任何的异步任务是不会执行的。 而setTimeout就是一个异步任务,所以会先执行for循环这个同步任务,把setTimeout()放进任务队列中等待主线程的for循环执行完毕,一旦"执行栈"中的所有同步任务执行完毕(循环结束后此时i=6)就会从队列中取出setTimeout()。for循环一次碰到一个 setTimeout(),会将其放到任务队列里面。

** 那么如何打印出我们想要的结果呢? **

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

打印出的结果为:

0 1 2 3 4 5

在这种情况下,js会偷偷的在循环体里,每次声明一个i,但这个i的值又会和上次i++后的值相等,于是,setTimeout函数稍等一会再打印i,就打印出如上的结果了。

那么还有什么方法可以打印出以上的结果呢?

  • 方法一
   for (var i=0; i<6; i++) {
      (function (i) {
        setTimeout(() => console.log(i), 0)
      })(i)
    }
  • 方法二
    function print(i) {
    return new Promise(resolve => {
        setTimeout(() => resolve(i), 0)
        })
    }

    async function main() {
    for(var i = 0; i < 6; i++) {
        var r = await print(i)
        console.log(r)
        }
    }

    main()
  • 方法三
    function print(i) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(i), 0)
         })
    }
    var arr = [];
    for (var i = 0; i < 6; i++) {
        arr.push(print(i))
    }
    for (var i = 0; i < 6; i++) {
        arr[i].then(data => console.log(data))
    }

版权声明

本内容版权归属本人及杭州饥人谷教育科技有限公司(简称:饥人谷)所有。
任何媒体、网站或个人未经授权不得转载、链接和转贴,或以其他方式复制、发布和发表。
对于违反者,将依法追究责任。

24
回复 编辑