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

目录
相关文章
|
前端开发 JavaScript Java
JavaScript闭包深入剖析:性能剖析与优化技巧
JavaScript 闭包是强大而灵活的特性,广泛应用于数据封装、函数柯里化和事件处理等场景。闭包通过保存外部作用域的变量,实现了私有变量和方法的创建,提升了代码的安全性和可维护性。然而,闭包也可能带来性能问题,如内存泄漏和执行效率下降。为优化闭包性能,建议采取以下策略:及时解除对不再使用的闭包变量的引用,减少闭包的创建次数,使用 WeakMap 管理弱引用,以及优化闭包结构以减少作用域链查找的开销。在实际开发中,无论是 Web 前端还是 Node.js 后端,这些优化措施都能显著提升程序的性能和稳定性。
370 70
|
自然语言处理 JavaScript 前端开发
ECMAScript 6 的出现为 JavaScript 带来了许多新的特性和改进
这些只是ES6的一些主要特性,它们极大地增强了JavaScript的功能和表现力,使得JavaScript在大型应用开发、前端框架等领域能够更加高效地编写复杂的应用程序。
486 155
|
自然语言处理 JavaScript 前端开发
当面试官再问我JS闭包时,我能答出来的都在这里了。
闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。
581 16
当面试官再问我JS闭包时,我能答出来的都在这里了。
|
存储 JavaScript 前端开发
|
JavaScript 前端开发 容器
盘点JavaScript中所有声明变量的方式及特性
本文详细介绍了JavaScript中变量定义的多种方式,包括传统的`var`、`let`和`const`,以及通过`this`、`window`、`top`等对象定义变量的方法。每种方式都有其独特的语法和特性,并附有代码示例说明。推荐使用`let`和`const`以避免作用域和提升问题,谨慎使用`window`和`top`定义全局变量,不建议使用隐式全局变量。掌握这些定义方式有助于编写更健壮的JS代码。
433 11
|
缓存 自然语言处理 JavaScript
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
JavaScript 前端开发 安全
JavaScript与TypeScript的对比,分析了两者的特性及在实际项目中的应用选择
本文深入探讨了JavaScript与TypeScript的对比,分析了两者的特性及在实际项目中的应用选择。JavaScript以其灵活性和广泛的生态支持著称,而TypeScript通过引入静态类型系统,提高了代码的可靠性和可维护性,特别适合大型项目。文章还讨论了结合使用两种语言的优势,以及如何根据项目需求和技术背景做出最佳选择。
1923 4
|
JavaScript 前端开发 安全
ECMAScript 6(以下简称 ES6)的出现为 JavaScript 带来了许多新的特性和改进,其中 let 和 const 是两个非常重要的关键字。
ES6 引入了 `let` 和 `const` 关键字,为 JavaScript 的变量管理带来了革新。`let` 提供了块级作用域和暂存死区特性,避免变量污染,增强代码可读性和安全性;`const` 用于声明不可重新赋值的常量,但允许对象和数组的内部修改。两者在循环、函数内部及复杂项目中广泛应用,有助于实现不可变数据结构,提升代码质量。
264 5
|
存储 缓存 自然语言处理
掌握JavaScript闭包,提升代码质量与性能
掌握JavaScript闭包,提升代码质量与性能