JavaScript语言的特点之一是单线程进程,粗暴的解释来说,JS在同一时间只能做一个动作,采用同步执行的方式进行运行,如果出现阻塞,那么后面的代码将不会执行。 HTML5则提出了web Worker标准,表示JavaScript允许有多个线程,但是子线程完全受主线程的控制,并且子线程不能操作DOM,只有主线程可以操作DOM,所以以主线程为主的单线程执行原理成了JavaScript这门语言的核心。关于
JavaScript
的运行机制可以参考阮一峰的文章JavaScript 运行机制详解:再谈Event Loop (摘引[Web Worker 详细介绍](segmentfault.com/a/119000001…))
一、什么是web Worker
在Javascript单线程执行的基础上,开启一个子线程
,进行程序处理,而不影响主线程的执行,当子线程执行完毕之后再回到主线程上,在这个过程中并不影响主线程的执行过程。
栗子:
while(true){
console.log('Hello World!');
}
如上代码,放在程序中会阻塞,余下代码都不会被执行。利用web Worker,将该代码放置在子线程中进行,不会影响主线程运行。 怎么放过去呢...
二、 创建子线程
在创建线程的时候需要给实例化的Worker传入唯一一个参数,指向一个javascript文件资源的url或者Blob对象(Blob对象就是一个包含有只读原始数据类文件对象),调用这个构造函数之后,一个线程就被创建了,如下:
首先创建work.js作为子线程执行文件,
创建Worker实例对象并传入对应路径js文件或Blob对象,
main.js
三、进程间通信
通过console.log(work)
可以看到,在Worker对象的原型链上带有两个方法,
通过postMessage()
方法,向子线程通信(支持json等格式化数据),
接收某个线程传来的数据可以使用onmessage
来进行接收,
使用onerror
来处理异常,
worker.terminate()
结束进程。 // 需要注意的是,terminate()方法会立即停止子进程。
四、Worker作用域(待补充)
当我们创建一个新的worker时,改代码会运行在一个全新的javascript的环境中(WorkerGlobalScope)运行,是完全和创建worker的脚本隔离,这时我们可以吧创建新worker的脚本叫做主线程,而被创建的新的worker叫做子线程。
WorkerGlobalScope是worker的全局对象,所以它包含所有核心javascript全局对象拥有的属性如JSON等,window的一些属性,也拥有类似于XMLHttpRequest()等。
但是我们所开启的新的worker也就是子线程,并不支持操作页面的DOM。
五、共享线程 SharedWorker
共享线程是为了避免线程的重复创建和销毁过程,降低了系统性能的消耗,共享线程SharedWorker可以同时有多个页面的线程链接。
使用SharedWorker创建共享线程,也需要提供一个javascript脚本文件的URL地址或Blob,该脚本文件中包含了我们在线程中需要执行的代码,如下:
var worker = new SharedWorker("sharedworker.js");
共享线程也使用了message事件监听线程消息,但使用SharedWorker对象的port
属性与线程通信如下。
worker.port.onmessage = function(e){
...
}
同时我们也可以使用SharedWorker对象的port
属性向共享线程发送消息如下(addEventListener
事件并未调研学习)。
worker.port.postMessage("message");
六、web Worker的应用场景
因为自身没有在实际项目中应用,所以简单汇总一下:
web worker的兼容性问题,主流浏览器对web worker的兼容性还不够高(比如Safari ,IE就更不用说了); 在项目中引入web worker并不能带来质的改变(从这一点来说,websocket就不一样,引入websocker后基本可以替换掉项目里所有的轮询,达到性能优化的目的,但正常的项目决不可能设计成让前台来处理复杂的业务逻辑)。 当然,这也不表示web worker毫无用武之地,比如下面几个场景:
-
使用工作线程做后台数值(算法)计算: 我们在主页面中创建一个后台工作线程,并且向这个工作线程分配任务(即传递两个特别大的数字),当工作线程执行完这个任务时,便向主页面程序返回计算结果,而在这个过程中,主页面不需要等待这个耗时的操作,可以继续进行其它的行为或任务。 并不是指数据量非常大,而是要从计算量来看,通常用时不能控制在毫秒级内的运算都可以考虑放在web worker中执行。
-
使用共享线程处理多用户并发连接: 由于线程的构建以及销毁都要消耗很多的系统性能,例如 CPU 的处理器调度,内存的占用回收等,在一般的编程语言中都会有线程池的概念,线程池是一种对多线程并发处理的形式,在处理过程中系统将所有的任务添加到一个任务队列,然后在构建好线程池以后自动启动这些任务。处理完任务后再把线程收回到线程池中,用于下一次任务调用。线程池也是共享线程的一种应用。 在 HTML5 中也引入了共享线程技术,但是由于每个共享线程可以有多个连接,HTML5 对共享线程提供了和普通工作线程稍微有些区别的 API 接口。 创建一个共享线程用于接收从不同连接发送过来的指令,然后实现自己的指令处理逻辑,指令处理完成后将结果返回到各个不同的连接用户。
-
HTML5 线程代理 多线程代理技术 随着多核处理器的流行,现代的计算机一般都拥有多核的 CPU,这也使得任务能够在处理器级别上并发执行。如果我们要在一个具有多核 CPU 的客户端上用单线程去执行程序即处理业务逻辑,往往不能最大化的利用系统资源。因此,在这种情况下我们可以将一个耗时或者复杂的任务拆分成多个子任务,把每一个子任务分担给一个工作线程,这样多个工作现场就共同承担了单个线程的工作负载,同时又能够并发的去执行,最大化的利用了系统资源(CPU、内存、I/O 等)。
-
在web Worker中通过
importScripts(url)
加载另外的脚本文件。 -
可以访问navigator的部分属性。
-
高频的用户交互: 高频的用户交互适用于根据用户的输入习惯、历史记录以及缓存等信息来协助用户完成输入的纠错、校正功能等类似场景,用户频繁输入的响应处理同样可以考虑放在web worker中执行。
-
Service Worker (待补充),个人理解是promise的多线程版的实现方式。
web Worker 弊端
局限性: 1.浏览器兼容问题;(上图,API文档中截图)
2.不能跨域加载JS
3.web Worker内代码不能访问/操作DOM
4.各个浏览器对web Worker的实现不大一致,例如FF里允许worker中创建新的worker,而Chrome中就不行
附:web Worker API
Worker 线程有一些自己的全局属性和方法。
self.name: Worker 的名字。该属性只读,由构造函数指定。 self.onmessage:指定message事件的监听函数。 self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。 self.close():关闭 Worker 线程。 self.postMessage():向产生这个 Worker 线程发送消息。 self.importScripts():加载 JS 脚本。