JavaScript在浏览器环境中的异步


参考:

https://blog.csdn.net/qq_26222859/article/details/77622222
https://www.cnblogs.com/aaron—blog/p/10903118.html

单线程与多线程

JavaScript 默认情况下是单线程运行的(除了用H5 Web Workers),但运行js脚本的环境可以是多线程的

众所周知JavaScript是单线程运行的脚本语言,即: 逐行执行语句,上面的语句没有执行完会阻塞下面语句的执行,但在不同的环境下,却可以实现异步的操作(如浏览器环境下,node环境下)。

浏览器的多线程

在JavaScript引擎中负责解析和执行JavaScript代码的线程只有一个。但是除了这个主进程以外,还有其他很多辅助线程。那么诸如onclick回调,setTimeoutAjax这些都是怎么实现的呢?即浏览器搞了几个其他线程去辅助JavaScript线程的运行。

浏览器有很多线程,例如:

  • GUI渲染线程 - 用于更新页面
  • JavaScript引擎线程 - 用于解析JavaScript代码
  • 定时器触发线程 - 浏览器定时计数器并不是 js引擎计数
  • 浏览器事件线程 - 用于解析BOM渲染等工作
  • http线程 - 主要负责数据请求
  • EventLoop轮询处理线程 - 事件被触发时该线程会把事件添加到待处理队列的队尾
  • 等等等

浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程GUI渲染线程浏览器事件触发线程

浏览器的异步逻辑

下面这张图可以说明浏览器的异步逻辑

  • 首先,js引擎解析js代码(称为同步过程,即把整个页面的js代码从头到尾执行下来),当处理到与其他线程相关的代码,就会分发给其他线程(比如onclick, setTimeout,setInterval,ajax请求, 对DOM进行写操作等等…此时的分发过程还是同步过程
  • 其他线程处理完之后(其他线程处理请求的过程就是异步过程)会把需要js引擎执行的任务放入callback queue里(也叫任务队列、消息队列、事件队列),等js线程的同步任务执行完了再从callback queue里按顺序执行其他线程返回来的任务。
  • 其他线程执行完毕后放进callback queue里的事件,却需要等待js引擎执行完毕当前stack里的任务,空闲下来,才会被执行。

总结起来,JS线程就像是领导,而其他线程相当于员工。
领导拿到一个项目,先把整个计划过一遍,把任务下达给其他线程(同步过程),这个出谋划策的过程,JS线程(领导)不会傻等这个员工执行完了再给下一个员工分配任务,而是跳过这个任务,继续分配下一个任务,不然哪个员工没干好岂不是坏了整个团队的进度;
同样地其他线程(员工)拿到任务就开始专注处理自己的事情,接下来领导使唤别人与我无关,毕竟要早点交差么…
员工的事情干完了,发邮件给领导,这个邮件就在领导的 callback queue里躺着,等领导忙完了手上的事情,打开邮箱,嚯,来反馈了,再按照FIFO(first in first out)的顺序一件件处理反馈。

js引擎与GUI引擎是互斥的

虽然不同线程之间是互相独立的,但当GUI引擎需要更新界面的时候,却会受到JS线程的阻塞。

了解过 H5 Web Workers的朋友应该知道,在 H5 Web Workers出现之后,JS也可以“开挂” 实现多线程运行,当然这不在本文的讨论范畴内。这里只提一点, H5 Web Workers的一大用处就是让js引擎中的耗时大的计算不影响界面的响应

例如,如果js引擎需要处理一段耗时很长的代码:递归计算斐波那契数列的第2000项…呵呵, 由于线程阻塞,此时页面是无响应的,比如:

<input>标签里键入无响应,js实现的轮播图不再播放等

所以此时可以把递归计算斐波那契数列的函数交给Workers分线程,当然这不是本篇的主题。据这个例子是想说明:

  • js引擎与GUI引擎是互斥的

也就是说GUI引擎在渲染时会阻塞js引擎计算,反过来也一样。
原因很简单,不同线程的具体语句之间执行顺序是不一定的,如果在GUI渲染的时候,js改变了dom,那页面到底听谁的?这就会造成渲染不同步。

这里插句题外话,无论 H5 Web Workers如何神通广大,都无法改变一个事实: 要实现对DOM的操作,还是只能在js主线程上进行,而不能分到workers分线程上。原因也是:不同线程的具体语句之间执行顺序是不一定的,要避免不同线程对DOM修改造成冲突。

接着上面领导和员工的比方,GUI页面就好比呈现给甲方的最终结果,DOM渲染引擎按照JS引擎(领导)的要求完成了渲染方案,最终要JS引擎拍板,反馈到GUI页面上。

借用一下别人的例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <table border=1>
        <tr><td><button id='do'>Do long calc - bad status!</button></td>
            <td><div id='status'>Not Calculating yet.</div></td>
        </tr>
        <tr><td><button id='do_ok'>Do long calc - good status!</button></td>
            <td><div id='status_ok'>Not Calculating yet.</div></td>
        </tr>
    </table>    
<script>

function long_running(status_div) {

    var result = 0;
    for (var i = 0; i < 1000; i++) {
        for (var j = 0; j < 700; j++) {
            for (var k = 0; k < 300; k++) {
                result = result + i + j + k;
            }
        }
    }
    document.querySelector(status_div).innerHTML = 'calclation done' ;
}

document.querySelector('#do').onclick = function () {
    document.querySelector('#status').innerHTML = 'calculating....';
    long_running('#status');
};

document.querySelector('#do_ok').onclick = function () {
    document.querySelector('#status_ok').innerHTML = 'calculating....';
    window.setTimeout(function (){ long_running('#status_ok') }, 0);
};

</script>
</body>
</html>

我们希望能看到计算的每一个过程,我们在程序开始,计算,结束时,都执行了一个dom操作,插入了代表当前状态的字符串,Not Calculating yet.和calculating…和calclation done.计算中是一个耗时的3重for循环. 在没有使用settimeout的时候,执行结果是由Not Calculating yet 直接跳到了calclation done.这显然不是我们希望的.而造成这样结果的原因正是js的事件循环单线程机制.dom操作是异步的,for循环计算是同步的.异步操作都会被延迟到同步计算之后执行.也就是代码的执行顺序变了.calculating…和calclation done的dom操作都被放到事件队列后面而且紧跟在一起,造成了丢帧.无法实时的反应.这个例子也告诉了我们,在需要实时反馈的操作,如渲染等,和其他相关同步的代码,要么一起同步,要么一起异步才能保证代码的执行顺序.在js中,就只能让同步代码也异步.即给for计算加上settimeout.

setTimeout(0): 手动异步

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    
    <script>
        alert(1); 
        setTimeout("alert(2)", 0); 
        alert(3); 
    </script>
</body>
</html>

上面这个代码的输出顺序是1->3->2,原因就是1和3是JS引擎中的同步过程,优先执行,而2是异步过程,要等JS自己的同步过程执行完了,再被执行。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值