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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
闭包在编程中有很多应用场景,以下是一些常见的例子: 1. 数据封装和私有变量:闭包可以用来创建私有变量,从而实现数据封装和保护。这在模块化编程和面向对象编程中非常有用。 2. 函数工厂:闭包可以用来创建一系列相关的函数,这些函数共享相同的外部变量。这在创建类似于Python中的装饰器或JavaScript中的高阶函数非常有用。 3. 延迟执行和计器:闭包可以用来实现延迟执行和定器功能。例如,在JavaScript中,setTimeoutsetInterval函数使用闭包来实现延迟执行和定器功能。 4. 记忆化(Memoization):闭包可以用来实现记忆化,即缓存函数的计算结果,以便在后续调用中重用。这可以提高函数的性能,特别是在处理计算密集型任务。 5. 事件处理和回调函数:在JavaScript等事件驱动的编程环境中,闭包常用于实现事件处理和回调函数。闭包可以捕获事件处理函数的上下文,使得事件处理函数可以访问其所需的外部变量。 6. 部分应用(Partial Application)和柯里化(Currying):闭包可以用来实现部分应用和柯里化,这是一种将多参数函数转换为一系列单参数函数的技术。这可以简化函数调用,使得代码更加简洁和可读。 7. 实现迭代器和生成器:在某些编程语言中,例如Python和JavaScript闭包可以用来实现迭代器和生成器,这是一种用于遍历数据结构的高效方法。
07-14

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值