zoukankan      html  css  js  c++  java
  • 浅析JS异步执行机制

    前言

    JS异步执行机制具有非常重要的地位,尤其体现在回调函数和事件等方面。本文将针对JS异步执行机制进行一个简单的分析。

    从一份代码讲起

    下面是两个经典的JS定时执行函数,这两个函数的区别相信对JS有一定基础的同学是十分清楚的。timeout仅仅只会执行一次,而interval则会执行多次。

        setTimeout(function (args) {
            console.log('timeout')
        }, 1000);
        setInterval(function (args) {
            console.log('interval')
        }, 1000);
    

    那么再看一份代码

        setTimeout(function (args) {
            console.log('timeout');
            setTimeout(arguments.callee, 1000);
        }, 1000);
        setInterval(function (args) {
            console.log('interval')
        }, 1000);
    

    这两份代码是否存在区别呢?在setTimeout中递归调用貌似和setInterval一样,但是实际上由于JS异步执行机制的问题,导致这两个函数存在着一定的差异。

    如何理解JS异步执行机制

    JS是单线程程序,从而避免了并发访问的一系列问题。但也正是由于单线程这样一个机制,导致JS的异步执行并不能按照传统的多线程方式进行异步执行,所有的异步时间要插入到同一个队列中,依次在主线程中执行。
    在浏览器中,一般情况下会存在三个线程,JS执行引擎,HTTP线程,事件触发线程。但是需要注意的是,所有的JS核心逻辑都需要在JS执行引擎线程中执行。
    例如我们可以使用下面这样一段代码发送AJAX请求

        var xmlReq = createXMLHTTP();//创建一个xmlhttprequest对象
        function testAsynRequest() {
            var url = "http://127.0.0.1:5000/";
            xmlReq.open("post", url, true);
            xmlReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            xmlReq.onreadystatechange = function () {
                if (xmlReq.readyState == 4) {
                    if (xmlReq.status == 200) {
                        var jsonData = eval('(' + xmlReq.responseText + ')');
                        alert(jsonData.message);
                    }
                    else if (xmlReq.status == 404) {
                        alert("Requested URL is not found.");
                    } else if (xmlReq.status == 403) {
                        alert("Access denied.");
                    } else {
                        alert("status is " + xmlReq.status);
                    }
                }
            };
            xmlReq.send(null);
        }
        testAsynRequest();//1秒后调用回调函数
        while (true) {
    
        }
    

    服务端代码

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/', methods=['POST', 'GET'])
    def print():
        return 'hello world'
    if __name__ == '__main__':
        app.run()
    

    这段代码是否会输出hello world呢?经过测验,发现并不会输出HelloWorld,浏览器会进入假死状态。造成这种情况的原因正是JS异步回调单线程的运行机制。在发送HTTP请求以后,HTTP请求会启动一个线程进行发送,收到响应以后会,事件触发线程会将响应事件加入到等待队列中,等待JS引擎空闲后执行。
    但是由于while(true)导致JS引擎永远不存在空闲,从而导致响应事件一致无法触发。

    重新思考

    通过一个简单的AJAX DEMO,可以简单了解了JS时间执行的一个流程。那么针对上面的那张图片,和最开始提出的settimeout的问题,JS又是如何调度和处理的呢?
    JS在定时器函数初始化以后就会开始执行定时任务,到达时间之后如果此时JS引擎空闲,则会直接执行定时任务,否则会将定时任务加入到等待队列中。
    对于加入到等待队列中的任务来说,会在JS引擎空闲的时候再不断进行执行。因此如果此时引擎并非空闲,那么setTimeout会等待一段时间后才能执行。
    对于setInterval来说,也是需要加入到等待队列中的,但是setInterval并不会因为加入到等待队列中而停止计时,此时如果到了第二个Interval,而第一个Interval还没有开始执行,那么此时队列中旧有存在两个Interval可能,如果这样累加下去,那么就可能会陷入大量Interval的累加,造成线程严重阻塞的问题,因此JS引擎做了一个轻度的优化,如果队列中有Interval,那么这个Interval不会加入队列。但是如果Interval已经pop出队列开始执行,那么Interval将会加入队列。
    针对上面的分析,我们可以得出一个结论,相比于setTimeout函数递归调用,在JS中由于单线程的异步执行机制,setInterval执行的频率会更高。因为setTimeout在执行完成以后才会开始下一轮定时任务,但是setInterval是持续执行定时任务,尤其是在setTimeout里的任务执行时间较长的时候,setInterval和setTimeout会有比较明显的频率差异。

  • 相关阅读:
    Flask、Vue部署总结
    Recat教程(二)
    C第一个程序
    React教程(一)
    Flask+uwsgi+nginx 部署
    使用Dockerfile部署简单的应用
    Typescript搭建开发环境
    Typescript模块化/命名空间/装饰器
    Typescript的泛型/泛型接口
    修改git密码后,终端fatal: Authentication failed for'http://xxx/xxx.git'
  • 原文地址:https://www.cnblogs.com/zhenlingcn/p/8971081.html
Copyright © 2011-2022 走看看