关于Web Worker
HTML5几个优势特性里,就包括了Web Worker,这货可以了解为多线程,正常形况下,浏览器执行某段程序的时候会阻塞直到运行结束后在恢复到正常状态,而HTML5的Web Worker就是为了解决这个问题。
所以我理解它能解决两个问题:
- 解决程序阻塞问题
- 提升效率
例子
通常测试效率最常用的无非就是fibonacci了,我们也来个fibonacci性能测试
var start = (new Date()).getTime();
var fibonacci = function (n) {
return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
};
fibonacci(38);
console.log((new Date()).getTime() - start);
每台电脑性能不一样,浏览器不一样可能会有变化,38执行需要接近5s,两次就差不多10s。
fibonacci(38);
fibonacci(38);
// 两次执行需要 9694ms
我们引入Web Worker,提升效率,缩短运算时差
var start = (new Date()).getTime();
// 实例化一个worker实例,参数必须是一篇JavaScript脚本
var worker = new Worker('worker.js');
// 监听worker通信
worker.addEventListener('message', function(event) {
console.log('Worker result: ' + ((new Date()).getTime() - start));
}, false);
// 向worker post数据
worker.postMessage(38);
var fibonacci = function (n) {
return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
};
// 主页面仅剩一个,另外一个已经转移到worker里执行了
setTimeout(function () {
fibonacci(38);
console.log((new Date()).getTime() - start);
}, 100);
worker.js
var fibonacci = function (n) {
return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
};
self.addEventListener('message', function (event) {
self.postMessage(fibonacci(event.data));
}, false);
执行结果:
5394
script.js:9 Worker result: 5399
封装
但如果要连续执行好几个,可不能这样:
worker.postMessage(38);
worker.postMessage(38);
worker.postMessage(38);
因为只是new了一个Worker,所以它会顺序执行
script.js:26 5369
script.js:9 Worker result: 5374
script.js:9 Worker result: 9960
script.js:9 Worker result: 14557
我们可以同时new多个
var start = (new Date()).getTime();
var fibonacci = function (n) {
// 实例化一个worker实例,参数必须是一篇JavaScript脚本
var worker = new Worker('worker.js');
// 监听worker通信
worker.addEventListener('message', function(event) {
console.log('Worker result: ' + ((new Date()).getTime() - start));
}, false);
// 向worker post数据
worker.postMessage(n);
};
fibonacci(38);
fibonacci(38);
fibonacci(38);
执行3次结果:
Worker result: 7062
Worker result: 7068
Worker result: 7128
执行4次的结果:
script.js:11 Worker result: 9323
script.js:11 Worker result: 9335
script.js:11 Worker result: 9340
script.js:11 Worker result: 9350
可见实例越多,单个执行效率就越高,因为new一个Worker也是需要耗费时间的,但即使这样也比在浏览器里阻塞顺序执行效率更高。
跨域与脚本引入
Worker在实例化的时候必须要传入一个脚本URL,而且必须是在本域下,否则会报跨域错误:
Uncaught SecurityError: Failed to construct 'Worker': Script at 'http://localhost/worker.js' cannot be accessed from origin 'http://localhost:63342'.
但可以在Worker里通过importScripts方法引入任何域下的脚本,就如同HTML里的script标签一样
script.js
console.log('Hello world! from http://localhost/script.js');
Worker里引入它
self.importScripts('http://localhost/script.js');
new Worker
与importScripts
是异步还是同步呢?做个测试
Python Code
class Worker(tornado.web.RequestHandler):
def get(self):
time.sleep(5)
self.write("""
console.log('From worker');
""")
sleep 5秒,然后再返回,控制台
script.js:1 start....
script.js:3 end!
worker:2 From worker
没有阻塞,再看看importScripts:
worker.js
self.importScripts('/script1');
self.importScripts('/script2');
Python Code
class Script1(tornado.web.RequestHandler):
def get(self):
time.sleep(5)
self.write("""
console.log('From script1');
""")
class Script2(tornado.web.RequestHandler):
def get(self):
self.write("""
console.log('From script2');
""")
Script1延迟5秒返回,然后console.log还是顺序打印,说明了它是顺序加载
start....
end!
From script1
From script2
调试与浏览器支持
再牛逼的功能,如果不方便调试也是渣,还好的是Chrome里非常容易就能调试,主流浏览器都支持,被人一直唾弃的IE10也都支持了
弱点
Worker还有有局限性,它不能操作DOM,目前大多JavaScript应用都在处理DOM,需要耗费非常大性能的运算通常都放Server端了,否则浏览器跑一个累死它的脚本会让它时不时弹出恶心的无响应。
它的API非常少,几乎都能数的出来,说几个非常实用
- XMLHttpRequest,有了它,才能发出Ajax请求
- setTimeout/setInterval,js最神奇的地方
- importScripts,在worker里载入外部js脚本
- addEventListener/postMessage,有了它们才能与主页互相通信
有些人曾说worker里没有document API,那我从主页获取DOM对象通过postMessage过去。首先,是徒劳的,console会打出
Uncaught DataCloneError: Failed to execute 'postMessage' on 'Worker': An object could not be cloned.
即使,你很牛逼,终于把这个Object传过去了,但那已经是它的一个副本,而不是同一个内存地址了
核心JavaScript代码
var obj = {};
// 实例化一个worker实例,参数必须是一篇JavaScript脚本
var worker = new Worker('worker.js');
// 向worker post数据
worker.postMessage(obj);
worker.addEventListener('message', function (event){
console.log(event.data === obj); // Output: false
});
worker.js
self.addEventListener('message', function (event) {
self.postMessage(event.data);
}, false);
其实,仔细想想,不能共享DOM也是正常的,否则我这边正在操作DOM,Worker那边也在操作DOM,甚至把DOM删除了,这不是冲突了吗?