Javascript的setTimeout()使用闭包特性时需要注意的问题-阿里云开发者社区

开发者社区> webmirror> 正文

Javascript的setTimeout()使用闭包特性时需要注意的问题

简介: 这道题考察JavaScript的运行机制。首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。
+关注继续查看
function createFunction1(){
    for(var i=0;i<5;i++){
        function s(){
            console.log(i);
        }
        s();
    }
}
createFunction1();   //0 1 2 3 4;

这是一个正常的函数

function createFunction2(){
    for(var i=0;i<5;i++){
        setTimeout(function timer(){
            console.log(i);
        },i*1000);
    }
}
createFunction2();  //每隔1秒输出‘5’、共输出5次

并不会按照我们预想的每隔1秒分别输出0、1、2、3、4

原因:此函数在for循环的第一层是setTimeout函数,他的执行和createFunction1中的s函数一样,将按分别在1秒后、2秒后、3秒后执行。但这儿需要注意的是,setTimeout的内部函数timer并没有立即执行,for循环中的i将会把值分别赋给setTimeout外部参数中的i,但其内部函数timer()则只会引用包含函数setTimeout()中的变量的最后一个值。因为闭包所保存的是整个变量对象,而不是某个特殊的变量。当然其中的这些处理变化,都是瞬间完成的,与执行时间并无关系,即使把1000改成0效果还是一样的。

重写一下这个函数:

function createFunction3(){
    for (var i=0;i<5;i++){
        (function(i){
            setTimeout(function timer(){
                console.log(i);
            },i*1000);
        })(i);
    }
}
createFunction3();   //每隔1秒分别输出0 1 2 3 4

这个例子,给外部包装了一个立即执行的匿名函数,setTimeout里面的匿名函数不再引用外部函数的参数,而是直接引用外部匿名函数的参数,这时,一切就会按照我们预想的来执行了

另外ES6为我们提供了另一种解决方案:

for (let i=1; i<5; i++) {
    setTimeout( function timer(){
        console.log( i );
    }, 1000 );
}   //每隔1秒分别输出0 1 2 3 4

这两种方法都不是解决异步问题的,而是解决变量作用域的问题的。因为函数 timer() 属于一个新的域,通过 var 定义的变量是无法传入到这个函数执行域中的,于是第一种是通过传入参数,间接的把变量传入到 timer 中;第二种是通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 timer 就能使用 i 这个变量了

setTimeout经常被用于延迟执行某个函数

setTimeout(function(){}, timeout);

有时为了进行异步处理,而使用setTimeout(function…,0);

function f(){
    // get ready
    setTimeout(function(){
        // do something
    }, 0);  
    return …;
}

在setTimeout设定的函数处理器之前,函数f返回;
在使用异步处理时,尤其是使用闭包特性时,要特别小心

for(var i = 0 ; i < 10; i++){
    setTimeout(function(){
        console.log(i);
    }, 0);
}

对于初次使用这种方式的同学来说,很可能会认为程序会打印0…9,可结果确实打印10个10;问题就在于,当循环完成时,function得到执行,而i已经变成10,console.log(i)中使用的是10!

那么可以换一种方式,用函数参数来保存0….9(其实也是利用了闭包):

for(var i = 0 ; i < 10; i++){
    setTimeout((function(i){
        return function(){
            console.log(i);
        }
    })(i), 0);
}

另外再来看一个例子:

function test() {
    for (var i = 0; i < 10; i++) {
        setTimeout(function() {
            console.log(i)
        }, 1000);
    }
}
test();  //9 9 9 9 9 9 9 9 9 9 

把他修改一下:

function test() {
    for (var i = 0; i < 10; i++) {
        setTimeout(console.log(i), 1000);
    }
}
test();  //1 2 3 4 5 6 7 8 9

来看一道考察JavaScript运行机制的题目

setTimeout(function() {
    console.log(1)
}, 0);
new Promise(function executor(resolve) {
    console.log(2);
    for( var i=0 ; i<10000 ; i++ ) {
        i == 9999 && resolve();  
    }
    console.log(3);
}).then(function() {
    console.log(4);
});
console.log(5); //2 3 5 4 1

这道题考察JavaScript的运行机制。首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。因此,应当先输出 5,然后再输出 4 。最后在到下一个 tick,就是 1

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
使用 Java 11 安装 SAP Commerce Cloud 1905 的一些常见问题
使用 Java 11 安装 SAP Commerce Cloud 1905 的一些常见问题
7 0
Android下DrawerLayout的使用
Android下DrawerLayout的使用 DrawerLayout见名知意,就是一个具有抽屉效果的布局,看看这个效果图,是不是感觉很炫酷 这么炫的效果其实不一定非要用类似一些SlidingMenu这样的框架才能实现,原生库就有对这种效果的支持,今天我们就一起来学习一下DrawerLayout的使用。
853 0
使用 Google 抓取方式,测试 React 驱动的网站 SEO
本文讲的是使用 Google 抓取方式,测试 React 驱动的网站 SEO,我最近进行了一项测试,它有关客户端渲染的网站是否能避免被搜索引擎的机器人爬取内容。就如我此文所述,React 并不会破坏搜索引擎的索引。
1412 0
Java中Object的输出问题
今天一个同学读取List中的一个对象,直接用于输出。输出结果是Student@c17164,现在来分析一下哈。 1.Object类   类Object是类层次结构的根类。每个类都使用Object作为超类。
555 0
+关注
webmirror
Good good study, day day up
122
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载