zoukankan      html  css  js  c++  java
  • 一个定时器相关的题目引发的思考

      定时器是我们经常使用的一个异步函数,它的用处十分广泛,比如图片轮播、各种小的动画、延时操作等等;

      定时器函数只有两个setTimeout、setInterval,这两个工作原理相同,唯一的区别是:setTimeout只执行一次,setInterval循环执行;

      通过以下实例看看对定时器原理掌握程度:

      定时器3个实例

        首先声明这三个实例输出皆不同,先思考输出结果,以及为何不同

        实例一:

    console.log('test1')
    for(var i=0;i<10;i++){
        setTimeout(()=>{console.log(i)},1000);
    }
    console.log('test2')

        实例二(使用了ES6 let关键字):

    console.log('test1')
    for(let i=0;i<10;i++){
        setTimeout(()=>{console.log(i)},1000);
    }
    console.log('test2')

        实例三(使用了ES6 let关键字):

    console.log('test1')
    for(let i=0;i<10;i++){
        setTimeout(()=>{console.log(i)},1000*i);
    }
    console.log('test2')

        结果如下:

          实例一:'test1' --> 'test2' -->  同时输出十个10

          实例二:'test1' --> 'test2' -->  同时输出0-9数字

          实例三:'test1' --> 'test2' --> 每哥1s输出一个数字,数字从0-9

        至于原因等会再讲,首先要先明白定时器的工作原理,而定时器的原理正是javascript事件循环模型的体现;其次要掌握闭包;

    js运行机制:Event Loop

      必须明确一点:javascript是单线程,同一时间只能做一件事;

      单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

      所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。不妨叫它主线程。但是实际上还存在其他的线程。例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程(例如在Node.js中)等等。这些线程可能存在于JS引擎之内,也可能存在于JS引擎之外,在此我们不做区分。不妨叫它们工作线程

      如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。

      JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

      具体来说,异步执行的运行机制如下(以ajax为例):

                 

    解答上述实例

      第一个实例:

        1.主线程自上向下同步执行,首先输出'test1';

        2.遇到for循环后,发现里面是异步函数定时器,就把定时器放到消息队列中,执行了10次,因此消息队列中有10个定时器消息;注意:此时全局变量i=10;

        3.将定时器放到消息队列中后,主线程继续执行同步任务;输出'test2';

           此时主线程中的同步任务执行完了,主线程上是空的;开始执行消息队列中的消息;

        4.由于定时器延时1s,因此1s后消息队列中的消息进入主线程(注意:此时符合条件的有10个定时器消息);

                   在主线程中执行定时器回调函数时,该回调函数是个闭包,i是全局变量,此时的i=10,因此执行结果是10个10;

      第二个实例:

         此实例跟第一个实例唯一的区别是 let i 此时i是局部变量;以上四个步骤都是一样的,区别在主线程执行定时器回调函数;此处代码经过babel编译后:

    var _loop = function (arg) {
        setTimeout(function () {
            console.log(arg);
        }, 1000);
    };
    
    for (var _i = 0; _i < 10; _i++) {
        _loop(_i);
    }

          新定义了一个函数_loop,这个是定时器回调函数的父作用域;

        当主线程处理定时器回调函数时,回调函数通过 作用域链 从_loop中获取arg的值;

      延伸实例

        上述方法使用了ES6中的let关键字,如何使用ES5闭包解决这个问题?

        其实,想法就是创建个局部作用域,让定时器的回调函数从局部作用域中读取数值;

    for(var i=0;i<10;i++){
        (function(arg){
            setTimeout(function(){
                cosnole.log(arg)
            },1000)
        })(i)
    }
    // 在for语句中创建立即执行函数,形成一个局部作用域;
    // 定时器的回调函数从局部作用域中读取数值

      还有别的方法吗?

      可以从定时器的回调函数入手,将这个参数构造成闭包;

    function backFun(arg){
        return function(){
            console.log(arg)
        }
    }
    for(var i=0;i<10;i++){
        setTimeout(backFun(i),1000)
    }
    // 同样是利用闭包创建局部作用域
    // 定时器的回调函数 读取的仍是局部作用域中的数值

    总结一下,这个题目考察了哪些知识点:

      1.闭包

      2.作用域链

      3.定时器运行的原理(异步执行原理)

      4.js运行机制

    参考:(感谢以下文档)

    [1] javascript: 彻底理解同步、异步、事件循环(Event Loop)
    [2] javascript 运行机制详解:在谈Event Loop 

    [3] 如何使用定时器传递参数

    [4] 什么是Event Loop

  • 相关阅读:
    2251: [2010Beijing Wc]外星联络
    1500 后缀排序
    1492: [NOI2007]货币兑换Cash【CDQ分治】
    P3380 【模板】二逼平衡树(树套树)
    python opencv
    pycharm调试
    pycharm中选择python interpreter
    创建使用pycharm virtualenv
    reload函数
    python3编写发送四种http请求的脚本
  • 原文地址:https://www.cnblogs.com/RocketV2/p/7240971.html
Copyright © 2011-2022 走看看