资讯专栏INFORMATION COLUMN

从闭包引出来的一系列问题

TigerChain / 2983人阅读

摘要:从闭包引出来的一系列问题不起眼的开始很明显,由于异步的作用。追问如果变成该怎样处理首先可以使用闭包来解决这个问题利用立即执行函数,来解决闭包造成的问题。

从闭包引出来的一系列问题 1. 不起眼的开始
for(var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(new Date, i)
    }, 1000)
}
console.log(new Date, i)

很明显,由于异步的作用。到最后输出的结果为6个5

如果用箭头表示前后两次输出有1s的间隔,用,代表前后一起输出,那么输出结果是5->5,5,5,5,5

这个也很容易的就可以进行解释,先执行console.log(),再进行setTimeout()的异步操作。

追问1:如果变成 5 -> 0,1,2,3,4 该怎样处理?

首先可以使用闭包来解决这个问题:

for(var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(new Date, j)
        }, 1000)
    })(i)
}
console.log(new Date, i) // 5

利用立即执行函数,来解决闭包造成的问题。

此外还可以使用setTimeout的第三个参数 文档:

for(var i = 0; i < 5; i++) {
    setTimeout(function(j) {
        console.log(new Date, j)
    }, 1000, i)
}
console.log(new Date, i) // 5

可能会有很多同学采用ES6的方式来避免:

for(let i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(new Date, i)
    }, 1000)
}
console.log(new Date, i)

但是此时并不能正确输出我们想要的结果,因为let声明的 i 产生了块级作用域,导致 for 循环外面的输出不能获取的 i ,所以此时会报错 i is not defined

追问2:如果把输出变为 0->1->2->3->4->5 呢?

其中一种比较容易想到的方法:

for(var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(new Date, j)
        }, 1000*j)
    })(i)
}

setTimeout(function() {
    console.log(new Date, i)
}, 1000*i)

但是js中定时器的触发时机是不确定的,每次循环都会产生一个异步操作之后,会有一个输出,那么完全可以使用Promise来解决这个问题。

const tasks = []
for(var i = 0; i < 5; i++) {
    (function(j){
        tasks.push(new Promise((resolve) => {
            setTimeout(() => {
                console.log(new Date, j)
                resolve()
            }, j*1000)
        }))
    })(i)
}

Promise.all(tasks).then( () => {
    setTimeout( () => {
        console.log(new Date, i)
    }, 1000)
})

将上面代码处理一下:

const tasks = []
const output = function(i) {
    new Promise( (resolve) => {
        setTimeout( () => {
            console.log(new Date, i)
            resolve()
        }, 1000*i)
    })
}

for(var i = 0;i < 5; i++) {
    tasks.push(output(i))
}

// 全部Promise执行完毕,执行最后一个输出i
Promise.all(tasks).then( () => {
    setTimeout( () => {
        console.log(new Date, i)
    }, 1000)
})
追问3:使用 async / await 怎么实现
// 模拟sleep
const sleep = (time) => new Promise((resolve) => {
    setTimeout(resolve, time);
});

(async () => {  // 声明即执行的 async 函数表达式
    for (var i = 0; i < 5; i++) {
        if (i > 0) {
            await sleep(1000);
        }
        console.log(new Date, i);
    }

    await sleep(1000);
    console.log(new Date, i);
})();

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97099.html

相关文章

  • 夯实基础-作用域与闭包

    摘要:作用域分类作用域共有两种主要的工作模型。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。词法作用域词法作用域中,又可分为全局作用域,函数作用域和块级作用域。 一篇巩固基础的文章,也可能是一系列的文章,梳理知识的遗漏点,同时也探究很多理所当然的事情背后的原理。 为什么探究基础?因为你不去面试你就不知道基础有多重要,或者是说当你的工作经历没有亮点的时候,基础就是检验你好坏的一项...

    daydream 评论0 收藏0
  • JS笔记

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。异步编程入门的全称是前端经典面试题从输入到页面加载发生了什么这是一篇开发的科普类文章,涉及到优化等多个方面。 TypeScript 入门教程 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 网络基础知识之 HTTP 协议 详细介绍 HTT...

    rottengeek 评论0 收藏0
  • 【译】闭包并不神秘

    摘要:下面我们就初步尝试一下闭包现在来看一下发生了什么。于是,这种结构就被称作闭包。这就是闭包强大的地方。例如,如果我们可以在我们的计数器里面加一个名字我们可以往闭包里传一个参数可以看出来,在实现过程中不仅能记住局部变量,也记住了传进来的变量。 计数器 首先,从一个计数器开始。 var counter = 0; function increment() { counter = cou...

    sevi_stuo 评论0 收藏0
  • javascript系列--javascript擎执行的过程的理解--语法分析和预编译阶段

    摘要:所以觉得把这个执行的详细过程整理一下,帮助更好的理解。类似的语法报错的如下图所示三预编译阶段代码块通过语法分析阶段之后,语法都正确的下回进入预编译阶段。另开出新文章详细分析,主要介绍执行阶段中的同步任务执行和异步任务执行机制事件循环。 一、概述 js是一种非常灵活的语言,理解js引擎的执行过程对于我们学习js是非常有必要的。看了很多这方便文章,大多数是讲的是事件循环(event loo...

    malakashi 评论0 收藏0
  • JS专题之去抖函数

    摘要:如果本次定时器没有被清除,时间到后就会自然执行事件处理函数。绑定去抖后的事件回调函数绑定回调函数的属性方法,点击页面,重置去抖效果异步请求清空上一次事件触发的定时器重置为从而下一次事件触发就能立即执行。 一、前言 为什么会有去抖和节流这类工具函数? 在用户和前端页面的交互过程中,很多操作的触发频率非常高,比如鼠标移动 mousemove 事件, 滚动条滑动 scroll 事件, 输...

    ivydom 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<