Bootstrap

JavaScript如何实现多线程?

今天看到一道面试题,问js如何实现多线程?下面来总结一下:

因为 JS是一种单线程语言,即使是一些异步的事件也是在JS的主线程上运行的。像setTimeout、ajax的异步请求,或者是dom元素的一些事件,都是在JS主线程执行的,这些操作并没有在浏览器中开辟新的线程去执行,而是当这些异步操作被操作时或者是被触发时才进入事件队列,然后在JS主线程中开始运行。

首先说一下浏览器的线程,浏览器中主要的线程包括,UI渲染线程,JS主线程,GUI事件触发线程,http请求线程。
在这里插入图片描述
JS作为脚本语言,它的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。(这里这些问题我先不做研究)

但是单线程的语言,有一个很致命的确定。如果说一个脚本语言在执行时,其中某一块的功能在执行时耗费了大量的时间,那么就会造成阻塞。这样的项目,用户体验是非常差的,所以这种现象在项目的开发过程中是不允许存在的。

其实JS为我们提供了一个Worker的类,它的作用就是为了解决这种阻塞的现象。当我们使用这个类的时候,它就会向浏览器申请一个新的线程。这个线程就用来单独执行一个js文件。

var worker = new Worker(js文件路径);

那么这个语句就会申请一个线程用来执行这个js文件。

当然,在主线程中有一些方法来实现对新线程的控制和数据的接收。在这里,我们只说比较常用的几个方法。

 1 //postMessage(msg);
 2 //postMessage方法把在新线程执行的结果发送到浏览器的js引擎线程里
 3 worker.onmessage = function(){
 4     //获取在新线程中执行的js文件发送的数据 用event.data接收数据
 5     console.log( event.data )
 6 };
 7 setTimeout( function(){
 8     worker.terminate();
 9     //terminate方法用于关闭worker线程
10 },2000)
11     
12 setTimeout( function(){
13     worker = new Worker("js/test22.js");
14     //再次开启worker线程
15 },3000)

在新线程中使用postMessage()方法可以向主线程中发送一些数据,主线程中使用worker的onmessage事件来接收这些数据,这样就实现了js的多线程执行和多线程之间数据的传递。



另一篇例子解释

假如我们要执行一些耗时的操作,比如加载一张很大的图片,我们可能需要一个进度条来让用户进行等待,在等待的过程中,整个js线程会被阻塞,后面的代码不能正常运行,这可能大大的降低用户体验,这时候我们就期望拥有一个工作线程来处理这些耗时的操作。在传统的html时代是基本不可能实现的,而现在,我们拥有一种叫做worker的东西。它是js里的一个类,而我们只需要创建它的实例就可以使用它。

var worker = new Worker(js file path);

构造函数的参数填上你的js文件的路径,这个js文件将会在浏览器新开的线程里运行,而与原先的js引擎的线程并不影响。
下面看个例子。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
    </head>
    <body>
        <input type="text" name="ipt" id="ipt" value="" />
        <button id="start">start</button>
        <button id="stop">stop</button>
        <button id="ale">alert</button>
        <script type="text/javascript">
            var ipt = document.getElementById("ipt");
            var stop = document.getElementById("stop");
            var start = document.getElementById("start");
            var ale = document.getElementById("ale");
            var worker = new Worker("js/test22.js");
            worker.onmessage = function(){
                ipt.value = event.data;
            };
            stop.addEventListener("click",function(){
                //用于关闭worker线程
                worker.terminate();
            });
            start.addEventListener("click",function(){
                //开起worker线程
                worker = new Worker("js/test22.js");
            });
            ale.addEventListener("click",function(){
                alert("i'm a dialog");
            });
        </script>
    </body>
</html>
```html
下面是test22.js里的代码,也就是存在于worker线程里的代码
```js
var i = 0;
function mainFunc(){
    i++;
    //把i发送到浏览器的js引擎线程里
    postMessage(i);
}
var id = setInterval(mainFunc,1000);

运行起来我们会发现

在这里插入图片描述
点击确定后,它的数值并非2,而是一个比2更大的数

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

那么既然互不影响,两个线程之间要怎么来联系呢,答案其实已经在代码里了,那就是onPostMessage 和 onmessage这两个函数,其中onPostMessage(data)的参数是你要传递的数据,而onmessage是一个回调函数,只有在接受到数据时,onmessage会被回调,onmessage有一个隐藏的参数,那就是event,我们可以用event.data获取到传递过来的数据来更新主线程。

使用worker线程应注意的是,所有js里集成的对象都在js线程里,而并非worker线程。
例如我们在worker线程里写上:

var a = document.getElementById("a");

结果你会得到一条Error,告诉你找不到document,或者document is undefined。所以我们尽量把需要的东西都写到主线程里,而只把耗时的操作写到worker线程里。

;