worker技术,是让js在浏览器有多线程能力的一项技术。一个最直观的例子是看whatwg的例子
https://html.spec.whatwg.org/multipage/workers.html#a-background-number-crunching-worker
这个例子是开了一个线程在找素数,如果找到了,就通知前台,如果是直接写,UI线程的阻塞非常明显。
在worker里面的上下文与外面的不一样
interface WorkerGlobalScope : EventTarget { readonly attribute WorkerGlobalScope self; readonly attribute WorkerLocation location; readonly attribute WorkerNavigator navigator; void importScripts(USVString... urls); attribute OnErrorEventHandler onerror; attribute EventHandler onlanguagechange; attribute EventHandler onoffline; attribute EventHandler ononline; attribute EventHandler onrejectionhandled; attribute EventHandler onunhandledrejection; };
可以看到,全局有三个对象 self
, location
, navigator
,一个函数 importScripts
,还有几个事件句柄。其中 self
对象指向全局对象本身,类似于我们熟悉的 window
。可以看到,worker与一般的浏览器环境最大的不同是 没有DOM
, document.getElementById
什么的就不要用了。然后与外面进行通讯是靠事件句柄。所以worker很适合 计算量大
、 异步
的事情。
这个例子 告诉我们worker中可以用XMLHttpRequest对象。结合前面#18 的工程化方案,我项目里面已经是用webpack基于commonjs打包的,而worker构造函数new Worker(params)里面的参数是js脚本的路径,所以我们需要worker-loader去帮我们屏蔽路径的问题。
接上,worker-loader要载入的chunk都打包在一起,这会涉及到一个代码分割的问题,worker里面的代码与外面的上下文不一样,不能与外面共用代码。所以要尽量做到代码不重复载入,我在实践过程中的思路是这样的,用Promise,根据手机支持的情况,载入不同的代码分支,然后resolve不同的接口对象
let worker = null; function getAPIObject(){ return new Promise((resolve,reject)=>{ if(window.Worker){ require.ensure([],require=>{ let Worker = require('worker!worker-chunk'); worker = worker || new Worker(); //让worker只初始化一次 resolve(worker); }) }else{ require.ensure([],require=>{ let api = require('api-chunk'); resolve(api); }) } }) }
然后等首屏载入完之后,执行代码,让后台生成一个专门进行接口请求的的线程,处理报文封装的问题,主要是里面涉及到一个加密计算的的模块
然后整个请求的模块就从左边的模型变成右边的模型
+----------------------+ +----------------------+ | | | | | UI thread && encrypt | | UI thread | | | | | +--------^-------------+ +---------^------------+ | | | |Promise | +---------v------------+ |ajax | worker(encrypt) | | +---------^------------+ | | +--------v-------------+ |ajax | | +---------v------------+ | service | | | | | | service | +----------------------+ +----------------------+
因为历史代码的问题,我的接口请求模块是用jQuery写的,当时主要是为了方便管理ajax池。但前面也说了,worker中是没有DOM的,像这样的代码是绝对不行的。所以我参考
http://youmightnotneedjquery.com/ 中的ajax重写了一个很简单的接口请求代码。
因为项目的脚手架是参考 https://github.com/davezuko/react-redux-starter-kit 的。里面有一段代码是这样的
development : (config) => ({ compiler_public_path : `http://${config.server_host}:${config.server_port}/` }),
可见在调试的时候,脚本载入的路径是一段完整的路径,比如你的debug机器是 192.168.1.2,端口8080,那么webpack给你生成的js引用代码就是
<script src="http://192.168.1.2:8080/chunk.hash.js"></script>
而不是
<script src="chunk.hash.js"></script>
然后我用手机真机调试的时候,一般会用代理(fiddler/charles)进行请求转发,顺便可以调用到桩数据,不用直接请求接口。代理的端口是 8888
,所以在浏览器看来,我打开了一个 http://192.168.1.2:8888/
的页面,却载入了一个 http://192.168.1.2:8080/
的脚本。一般的操作不会有问题,但是涉及到worker,会报 DOM Exception 18
错误。好吧,是跨域的问题,所以就修改成在手机上调试时 public_path
为空。