点击上方“IT平头哥联盟”,选择“置顶或者星标”

与你一起成长~

作者:chunpengliu

浏览器端JavaScript是以单线程的方式执行的,也就是说JavaScript和UI渲染占用同一个主线程,那就意味着,如果JavaScript进行高负载的数据处理,UI渲染就很有可能被阻断,浏览器就会出现卡顿,降低了用户体验。

为此,JavaScript提供了异步操作,比如定时器(setTimeout、setInterval)事件、Ajax请求、I/O回调等。我们可以把高负载的任务使用异步处理,它们将会被放入浏览器的事件任务队列(event loop)中去,等到JavaScript运行时执行线程空闲时候,事件队列才会按照先进先出的原则被一一执行。

通过类似定时器,回调函数等异步编程方式在平常的工作中已经足够,但是如果做复杂运算,这种方式的不足就逐渐体现出来,比如settimeout拿到的值并不正确,或者页面有复杂运算的时候很容易触发假死状态,异步代码会影响主线程的代码执行,异步终究还是单线程,不能从根本上解决问题。

多线程(Web Worker)就应运而生,它是HTML5标准的一部分,这一规范定义了一套 API,允许一段JavaScript程序运行在主线程之外的另外一个线程中。将一些任务分配给后者运行。在主线程运行的同时,Worker(子)线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

什么是web worker

worker是window对象的一个方法,就是用它来创建多线程。可以通过以下方式来检测你的浏览器是否支持worker

if (window.Worker) {…… your code ……}

一个worker是使用一个构造函数(Worker())创建的一个对象,这个构造函数需要传入一个的JavaScript文件,这个文件包含将在工作线程中运行的代码。类似于这样:

let myWorker = new Worker('worker.js');

主线程和子线程的数据不是共享的,worker通过postMessage() 方法和onmessage事件进行数据通信。主线程和子线程是双向的,都可以发送和监听事件。向一个worker发送消息需要这样做(main.js):

myWorker.postMessage('hello, world'); // 发送
worker.onmessage = function (event) { // 接收console.log('Received message ' + event.data);doSomething();
}

postMessage所传的数据都是拷贝传递(ArrayBuffer类型除外),所以子线程也是类似传递(worker.js)

addEventListener('message', function (e) {postMessage('You said: ' + e.data);
}, false);

当子线程运行结束后,使用完毕,为了节省系统资源,可以手动关闭子线程。如果worker没有监听消息,那么当所有任务执行完毕(包括计数器)后,它就会自动关闭。

// 在主线程中关闭
worker.terminate();
// 在子线程里线程
close();

Worker也提供了错误处理机制,当出错时会触发error事件。

// 监听 error 事件
worker.addEventListener('error', function (e) {console.log('ERROR', e);
});

web worker本身很简单,但是它的限制特别多。

使用的问题

1、同源限制

分配给Worker 线程运行的脚本文件(worker.js),必须与主线程的脚本文件(main.js)同源。这里的同源限制包括协议、域名和端口,不支持本地地址(file://)。这会带来一个问题,我们经常使用CDN来存储js文件,主线程的worker.js的域名指的是html文件所在的域,通过new Worker(url)加载的url属于CDN的域,会带来跨域的问题,实际开发中我们不会吧所有的代码都放在一个文件中让子线程加载,肯定会选择模块化开发。通过工具或库把代码合并到一个文件中,然后把子线程的代码生成一个文件url。

解决方法: (1)将动态生成的脚本转换成Blob对象。 (2)然后给这个Blob对象创建一个URL。 (3)最后将这个创建好的URL作为地址传给Worker的构造函数。

let script = 'console.log("hello world!");'
let workerBlob = new Blob([script], { type: "text/javascript" });
let url = URL.createObjectURL(workerBlob);
let worker = new Worker(url);

2、访问限制

Worker子线程所在的全局对象,与主线程不在同一个上下文环境,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象,global对象的指向有变更,window需要改写成self,不能执行alert()方法和confirm()等方法,只能读取部分navigator对象内的数据。另外chrome的console.log()倒是可以使用,也支持debugger断点,增加调试的便利性。

3、使用异步

Worker子线程中可以使用XMLHttpRequest 对象发出 AJAX 请求,可以使用setTimeout() setInterval()方法,也可使用websocket进行持续链接。也可以通过importScripts(url)加载另外的脚本文件,但是仍然不能跨域。

应用场景:

1、使用专用线程进行数学运算

Web Worke设计的初衷就是用来做计算耗时任务,大数据的处理,而这种计算放在worker中并不会中断前台用户的操作,避免代码卡顿带来不必要的用户体验。例如处理ajax返回的大批量数据,读取用户上传文件,计算MD5,canvas的位图的过滤,分析视频和声频文件等。worker中除了缺失了DOM和BOM操作能力以外,还是拥有非常强大的js逻辑运算处理的能力的,相当于nodejs一个级别的的运行环境。

2、高频的用户交互

高频的用户交互适用于根据用户的输入习惯、历史记录以及缓存等信息来协助用户完成输入的纠错、校正功能等类似场景,用户频繁输入的响应处理同样可以考虑放在web worker中执行。例如,我们可以 做一个像Word一样的应用:当用户打字时,后台立即在词典中进行查找,帮助用户自动纠错等等。

3、数据的预取

对于一些有大量数据的前后台交互产品,可以新开一个线程专门用来进行数据的预取和缓冲数据,worker可以用在本地web数据库的行写入和更改,长时间持续的运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断,也有利于随时响应主线程的通信。也可以配合XMLHttpRequest和websocket进行不断开的通信,实现守卫进程。

兼容性:

总体来说,兼容性还是不错的, 移动端可以放心使用,桌面端要求不高的话,也可以使用。

superWorker

为了更方便快捷的使用web worker,我们封装了一个工具,可以通过模块化的方式编写运行在web worker中的脚本,避免同源策略,减少服务端发送一个额外的url请求,无需了解web worker,就像使用setTimeout一样,快速使用superWorker,提升你的编码效率和运行效率,它有以下优点: 1、原生JS实现,无任何依赖库。 2、简单快速,摈弃繁琐的创建文件、绑定事件,实现无侵入、无感知运行新线程的代码。 3、返回Promise类型的数据,支持链式调用,清晰明了。 4、支持多种方式新建worker,包括匿名函数、函数列表、文本文件、html片段、url、类,方便快捷。 5、gzipped压缩后仅仅 1.2kb。

使用教程:

import superWorker from 'superWorker'
let worker = superWorker(function (a, b) {// 子线程中要运行的代码return a + b;
});
worker.start(1, 2).then((r)=>console.log(r)); // 3

用法: superWorker(code,[type])

参数: code:运行的代码, type(非必须):代码类型,目前支持0、1、2、3、4。

实现原理:

先进行源代码转文件:

let workerBlob = new Blob(code, { type: "text/javascript" });
let url = URL.createObjectURL(workerBlob);

对类型拆分,code参数支持传入匿名函数、函数列表、文本文件、url、HTML内嵌标签、类等功能,首先对传入的代码进行分类匹配,字符串化,然后进行拼接运行

code = `(${Function.prototype.toString.call(code)})(${exportsObjName})`;

对于传入的方法,分别在主线程中的exports对象进行标记,和worker子线程中的exportsObjName对象中进行赋值。对于ES6 模块化的代码,进行过滤转译。

// 处理 \nexport default function xxx(){}  => exports.default = true; exportsObjName.default = function xx(){}
code = code.replace(/^(\s*)export\s+default\s+/m, (s, before) => {exports.default = true;return `${before}${exportsObjName}.default=`;
});

形成主线程exports和子线程exportsObjName中的方法进行一一对应。

worker主线程与主线程进行通讯则是仍然需要通过postMessage方法和onmessage回调事件来进行,这个我们统一进行了双向绑定,分别对主线程和子线程执行setup。

function setup(ctx, pmMethods, callbacks) {ctx.addEventListener('message', ({ data }) => {// ……})
}

在主线程中对worker封装了一些快捷的方法,比如关闭线程:

worker.terminate = () => {URL.revokeObjectURL(url);term.call(this);
};

并把子线程拥有的方法、属性,暴露出来,方便主线程通过传递参数调用。

worker.expose = methodName => {worker[i] = function () {return worker['call'](methodName, [].slice.call(arguments));};
};

大致如下图:

欢迎小伙伴们使用以及批评指正。有问题多多反馈,多多交流。

小结

对于web worker这项新技术,无论在PC还是在移动web,都很实用,腾讯新闻前端组进行了广泛的尝试,Web Worker 的实现为前端程序带来了后台计算的能力,实现了主 UI 线程与复杂计运算线程的分离,从而极大减轻了因计算量大而造成 UI 阻塞而出现的界面渲染卡、掉帧的情况,并且更大程度地利用了终端硬件的性能。superWorker能解决掉事件绑定,同源策略等繁琐的问题,它目前最大的问题在于不兼容IE9,在兼容性要求不是那么严格的地方,尽可能的使用吧!

- end -

用心分享 一起成长 做有温度的攻城狮

每天记得对自己说:你是最棒的!

好文阅读

事件循环机制的那些事

如何撸一份高薪架构级的工程师简历

深入理解JS 执行上下文与执行栈~

各种资源免费共享:简历模板、面试题等

浅谈easy-mock 最好的备胎没有之一

HTTP跳槽涨薪篇,通俗易懂~

该如何以正确的姿势插入SVG Sprites?

优秀的前端如何编写高质量的函数 -- 函数底层篇

页面可视化配置搭建工具技术要点

都看到这里了,给个“好看”再走呗~

涨姿势 , JavaScript 玩转多线程编程~相关推荐

  1. 5天玩转C#并行和多线程编程 —— 第五天 多线程编程大总结

    5天玩转C#并行和多线程编程 -- 第五天 多线程编程大总结 5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 -- 第一天 认识Parallel 5天玩转C#并行和多线程编程 ...

  2. Javascript 多线程编程​的前世今生

    作者:jolamjiang,腾讯 WXG 前端开发工程师 一篇关于 Web Worker.SharedArrayBuffer.Atomics 的文章. 为什么要多线程编程 大家看到文章的标题<J ...

  3. Web Worker javascript多线程编程(一)

    什么是Web Worker? web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验. 一般来说Javascript ...

  4. JavaScript多线程编程介绍

    综述:两种JavaScript多线程编程的方法,在以后的具有大量的数据计算的业务场景下可能会使用到 1.背景介绍 JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成, ...

  5. python多进程编程_【玩树莓】编程篇(八)Python多线程、多进程编程

    1.多任务编程 除了计算性能和图形显示以外,树莓派区别于Arduino的一大特点就是运行多任务操作系统.通过多任务系统用户可以同时执行多个互相独立的程序(任务),来完成不同的操作. 利用Python的 ...

  6. 完毕port(CompletionPort)具体解释 - 手把手教你玩转网络编程系列之三

       手把手叫你玩转网络编程系列之三    完毕port(Completion Port)具体解释                                                    ...

  7. 对比Java和.NET多线程编程

    这篇文章以对比的方式总结Java和.NET多线程编程. 基本概念 多线程:很多开发语言都提供多线程编程支持,比如Java,C#. 并发(concurrent):即使对于单核CPU,我们也会采用多线程等 ...

  8. 《趣学JavaScript——教孩子学编程》——第1章 认识JavaScript1.1 认识JavaScript

    本节书摘来自异步社区<趣学JavaScript--教孩子学编程>一书中的第1章,第1.1节,作者: [美]Nick Morgan(摩根)译者: 李强,更多章节内容可以访问云栖社区" ...

  9. wpf绑定 dictionary 给定关键字不再字典中_为什么要在 JavaScript 中学习函数式编程?...

    请忘掉你认为你知道的有关 JavaScript 的任何东西,以初学者心态来接触这份资料. 为帮助你这样做,我们打算从头开始复习 JavaScript 的基础知识, 就好像你以前从来没有看到过 Java ...

  10. 《趣学JavaScript——教孩子学编程》——2.4 Boolean

    本节书摘来自异步社区<趣学JavaScript--教孩子学编程>一书中的第2章,第2.4节, 作者: [美]Nick Morgan(摩根)译者: 李强,更多章节内容可以访问云栖社区&quo ...

最新文章

  1. BZOJ 3105:[cqoi2013]新Nim游戏
  2. PMcaff每日推荐5本--产品经理必读的互联网专业书籍
  3. python爬虫-初步使用Scrapy分布式爬虫(爬取mcbbs整合包保存名称及主要mod),大爱MC
  4. 【JavaWeb】前端框架之Bootstrap
  5. 阿里开发者招聘节 | 面试题01:如何实现一个高效的单向链表逆序输出?
  6. 大学生创新项目认真投一篇中文核心的坎坷故事
  7. 【华为云技术分享】小白学YOLO:YOLOv3网络结构细致解析
  8. mysql hang and srv_error_monitor_thread using 100% cpu(已解决)
  9. 绘图库:Matplotlib
  10. java 避免重定向_java – 避免循环重定向使用HttpClient 4.1.1
  11. LintCode 52: Next Permutation
  12. 刘毅5000词汇_不熟词汇整理_lesson_15 and part_2
  13. CRM项目半途而废 “烂摊子”该如何收拾?
  14. Django安全认证机制CSRF
  15. uniapp调试ESC指令热敏打印机 打印图片 以及 打印途中报10007特性不支持解决方案
  16. 挪威飞鸽_我成为挪威高级分析初级顾问的美好旅程
  17. 阿松嘚嘚嘚-数据库篇3-查询优化不简单(上)
  18. win10没有android驱动安装,win10系统电脑没有手机驱动的解决方法介绍
  19. 破产清算!王思聪也没想到会有这一天。。。
  20. imp文件,以及IDEA编译中的警告

热门文章

  1. MySQL压缩包下载及解压安装
  2. VS Code 下载、下载慢、更新(适用于压缩包下载)
  3. typora中的图片加载不出来
  4. SAP MM 供应工厂1050和凭证类型ZIC没有定义供货类型
  5. The APR based Apache Tomcat Native library which allows optimal performance in production environme
  6. 好看的皮囊 · 也是大自然的杰作 · 全球高质量 · 美图 · 集中营 · 美女 · 2017-08-20期...
  7. 好看的皮囊 · 也是大自然的杰作 · 全球高质量 · 美图 · 集中营 · 美女 · 2017-08-23期...
  8. PDF文件编辑指南4:PDF文档加密和移除PDF文档密码方法
  9. 【Excle数据透视表】如何移动数据透视表的位置
  10. 【摘抄】领导力21法则-约翰·C·马克斯韦尔