来自 软件资讯 2019-11-08 03:31 的文章
当前位置: 威尼斯国际官方网站 > 软件资讯 > 正文

奥门威尼斯网址javascript的单线程事件循环及多线

小结:

JavaScript单线程原因


JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这和JavaScript的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。例如:假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

(3)一旦“执行栈”中的所有同步任务执行完毕,系统就会读取“任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态。进入执行栈,开始执行。

js单线程事件循环


针对事件循环机制,有如下总结:

一、javascript是单线程的,这个线程中拥有唯一的一个事件循环。

二、JavaScript 代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(taskqueue)来搞定另外一些代码的执行。

奥门威尼斯网址 1

enter.png


三、一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。

四、任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务

(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务(见上图);异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。


五、任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

六、macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

七、micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

八、setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。

九、来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。

十、事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。

之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。


十一、其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。

  一般不同的异步任务的回调函数都会放入不同的任务队列中,等到调用栈中所有的task执行完毕,接着去执行任务队列中的task(回调函数)

js多线程的实现

我们先看一个例子:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <input type="text" name="haorooms" id="haorooms" value="" />
        <button id="start">start</button>
        <button id="stop">stop</button>
        <button id="ale">alert</button>
        <script type="text/javascript">
            var haorooms= document.getElementById("haorooms"),stop = document.getElementById("stop"),start = document.getElementById("start"),ale = document.getElementById("ale"), worker = new Worker("js/haorooms_test.js");
            worker.onmessage = function(){
                haorooms.value = event.data;
            };
            stop.addEventListener("click",function(){
                //用于关闭worker线程
                worker.terminate();
            });
            start.addEventListener("click",function(){
                //开起worker线程
                worker = new Worker("js/haorooms_test.js");
            });
            ale.addEventListener("click",function(){
                alert("i'm a alert worker test");
            });
        </script>
    </body>
</html>

下面是text.js里的代码,也就是存在于worker线程里的代码。

var i = 0;
function mainFunc(){
    i++;
    //把i发送到浏览器的js引擎线程里
    postMessage(i);
}
var id = setInterval(mainFunc,1000);

虽然alert的弹出框会阻塞js引擎线程,但是并不影响worker线程的运行,所以,在我们点击确定后,只是在js引擎线程上更新了新的内容,而数值是一直在跑动的,这就说明worker线程和原本的js线程互不影响。

这就是简单的js多线程的实现。

异步:如果在调用函数A的时候,调用者无法立即得到预期的结果,而是需要在将来通过一定的手段(耗时,延时,事件触发)得到,那么这个函数就是异步的。

前言


其实我前面文章对于改变js的执行顺序及多线程都有相关介绍!例如,我们可以用[setTimeout(fn,0)]改变代码执行循序,文章最后也提及了Event Loop(事件循环)。同时,js可以模拟实现多线程。这篇文章,我详细介绍一下js单线程事件循环,及多线程的实现。

(4)主线程不断重复上面的第三步

例子


setTimeout(function() {
    console.log('timeout1');
})

new Promise(function(resolve) {
    console.log('promise1');
    for(var i = 0; i < 1000; i++) {
        i == 99 && resolve();
    }
    console.log('promise2');
}).then(function() {
    console.log('then1');
})

console.log('global1');

上面代码的执行顺序是什么?

首先,代码执行遇到setTimeout,setTimeout为一个宏任务源,那么他的作用就是将任务分发到它对应的队列中。
其次,script执行时遇到Promise实例。Promise构造函数中的第一个参数,是在new的时候执行,因此不会进入任何其他的队列,而是直接在当前任务直接执行了,而后续的.then则会被分发到micro-task的Promise队列中去。 因此输出:promise1、promise2。
接着、script任务继续往下执行,最后只有一句输出了globa1,然后,全局任务就执行完毕了。
第四步:第一个宏任务script执行完毕之后,就开始执行所有的可执行的微任务。这个时候,微任务中,只有Promise队列中的一个任务then1,因此直接执行就行了,执行结果输出then1。
第五步:当所有的micro-tast执行完毕之后,表示第一轮的循环就结束了。这个时候就得开始第二轮的循环。第二轮循环仍然从宏任务macro-task开始。这个时候,我们发现宏任务中,只有在setTimeout队列中还要一个timeout1的任务等待执行。因此就直接执行即可。

在上图中,调用栈中遇到DOM请求、ajax请求以及setTimeout等WebAPIs的时候就会交给浏览器内核的其他模块进行处理,webkit内核在Javascript执行引擎之外,有一个重要的模块是webcore模块。对于图中WebAPIs提到的三种API,webcore分别提供了DOM Binding、network、timer模块来处理底层实现。等到这些模块处理完这些操作的时候将回调函数放入任务队列中,之后等栈中的task执行完之后再去执行任务队列中的回调函数。

什么是js多线程


为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

  JS引擎在解析JS代码的时候,会创建一个主线程(main thread)和一个调用堆栈(call-stack),在对一个调用堆栈中的task处理的时候,其他的都要等着。

事件循环的顺序是从script开始第一次循环,随后全局上下文进入函数调用栈,碰到macro-task就将其交给处理它的模块处理完之后将回调函数放进macro-task的队列之中,碰到micro-task也是将其回调函数放进micro-task的队列之中。直到函数调用栈清空只剩全局执行上下文,然后开始执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次执行macro-task中的一个任务队列,执行完之后再执行所有的micro-task,就这样一直循环。

虽然JS是单线程的但是浏览器的内核是多线程的,浏览器为一些耗时任务开辟了另外的线程,不同的异步操作会由不同的浏览器内核模块调度执行,例如onlcik,setTimeout,ajax处理的方式都不同,分别是由浏览器内核中的DOM Bingding、network、timer模块执行,当执行的任务得到运行结果时,会将对应的回调函数放到任务队列中。所以说,JS是一直都是单线程的,实现异步操作的其实是浏览器。

所以,为了避免复杂性,从一诞生,Javascript就是单线程,这已经成为了这门语言的核心特征,将来也不会改变。

 

 

1.全局上下文入栈,开始执行其中的代码。

  1.所有的代码都要通过函数调用栈中调用执行

 

作为浏览器脚本语言,Javascript的主要用途是与用户互动,以及操作DOM,这决定了它只能是单线程,否则会带来很复杂的同步问题。比如:假定Javascript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了找个节点,这时浏览器应该以哪一个为准?

小结:

8.这时候所有的micao-task执行完毕,第一轮循环结束。第二轮循环从setTimeout的任务队列开始,setTimeout的回调函数入栈执行完毕之后出栈,此时输出4。

 

二、同步和异步

JS的运行机制如下:

 

任务队列

3.执行到Promise实例,将Promise入栈,第一个参数是在当前任务直接执行输出1。

为了利用多核CPU的计算能力,HTML5提出Web Woker标准,允许Javascript脚本创建多个线程,但是子线程完全受主线程控制,且不能操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

总结:

2.micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver

  2.当遇到前文中提到的APIs的时候,会交给浏览器内核的其他模块进行处理

4.执行循环体,遇到resolve函数,入栈执行后出栈,改变promise状态为Fulfilled,随后输出2

本文由威尼斯国际官方网站发布于软件资讯,转载请注明出处:奥门威尼斯网址javascript的单线程事件循环及多线

关键词: