zoukankan      html  css  js  c++  java
  • 关于作用域和异步的一道题

    修改下列代码,使得打印结果为0,1,2,3,4

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

    打印结果为5个5.
    我的理解:

    //分解
    var i = 0;
    setTimeout(function() {console.log(i);}, 1000);		//进入任务队列
    i++;
    setTimeout(function() {console.log(i);}, 1000);		//进入任务队列
    i++;
    setTimeout(function() {console.log(i);}, 1000);		//进入任务队列
    i++;
    setTimeout(function() {console.log(i);}, 1000);		//进入任务队列
    i++;
    setTimeout(function() {console.log(i);}, 1000);		//进入任务队列
    

    loupe上查看事件循环过程
    image

    之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是同一个最终值。
    也就是说上面的执行顺序实际相当是这样的:

    //执行栈,执行同步代码
    var i = 0;
    setTimeout();      //停1s后将回调函数送入任务队列
    i++;
    setTimeout();      //停1s后将回调函数送入任务队列
    i++;
    setTimeout();      //停1s后将回调函数送入任务队列
    i++;
    setTimeout();      //停1s后将回调函数送入任务队列
    i++;
    setTimeout();      //停1s后将回调函数送入任务队列
    //任务队列,执行异步代码
    function() {console.log(i)};
    function() {console.log(i)};
    function() {console.log(i)};
    function() {console.log(i)};
    function() {console.log(i)};
    

    JS的解析是由浏览器中的JS解析引擎完成的。JS是单线程运行,也就是说,在同一个时间内只能做一件事,所有的任务都需要排队,前一个任务结束,后一个任务才能开始。但是又存在某些任务比较耗时,如IO读写等,所以需要一种机制可以先执行排在后面的任务,这就是:同步任务(synchronous)和异步任务(asynchronous)。
    ​ JS的执行机制就可以看做是一个主线程加上一个任务队列(task queue)。同步任务就是放在主线程上执行的任务,异步任务是放在任务队列中的任务。所有的同步任务在主线程上执行,形成一个执行栈;异步任务有了运行结果就会在任务队列中放置一个事件;脚本运行时先依次运行执行栈,然后会从任务队列里提取事件,运行任务队列中的任务,这个过程是不断重复的,所以又叫做事件循环(Event loop)。
    image

    方法一:利用let
    在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。

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

    let声明i是一个局部变量,i与异步回调函数绑定了,如下:

    {
    	let i = 0;
    	setTimeout(function() {console.log(i);}, 1000);
    }
    {
    	let i = 1;
    	setTimeout(function() {console.log(i);}, 1000);
    }
    {
    	let i = 2;
    	setTimeout(function() {console.log(i);}, 1000);
    }
    {
    	let i = 3;
    	setTimeout(function() {console.log(i);}, 1000);
    }
    {
    	let i = 4;
    	setTimeout(function() {console.log(i);}, 1000);
    }
    

    方法二:IIFE---用立即调用的函数表达式来构造块作用域

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

    使用 IIFE 可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数。这样位于函数体作用域的变量就像是在块级作用域中一样。

    输出5个5和输出0,1,2,3,4的原因在于回调函数是否绑定了它进入任务队列时的i。
    感谢阅读!

    参考:

    1. 从输入URL到页面展示,你想知道些什么?
    2. JavaScript同步、异步、回调执行顺序分析
    3. JavaScript中的Event Loop(事件循环)机制
    4. 《JavaScript高级程序设计(第4版)》3.3.2 for循环中的let声明
  • 相关阅读:
    [最优化理论与技术]线性规划
    [吴恩达深度学习]神经网络和深度学习
    Linux系统级性能分析工具perf的介绍与使用
    Oracle表变化趋势追踪记录 & 表 历史统计信息查看
    delete noprompt archivelog
    DEPLOYING ORACLE RAC DATABASE 12C RELEASE 2 ON RED HAT ENTERPRISE LINUX 7
    How to Modify SCAN Setting or SCAN Listener Port after Installation (Doc ID 972500.1)
    How to create a RAC Database Service With Physical Standby Role Option? (Doc ID 1129143.1)
    Oracle级联备库0数据丢失--重建控制文件并应用主库online redo logfile的激活方法
    Handling ORL and SRL (Resize) on Primary and Physical Standby in Data Guard Environment (Doc ID 1532566.1)
  • 原文地址:https://www.cnblogs.com/liulangbxc/p/14799672.html
Copyright © 2011-2022 走看看