众所周知,js是单线程的,从上往下,从左往右依次执行,当咱们有耗时的任务须要处理时,便会阻塞线程形成页面卡顿等问题。web worker的目的,就是为JavaScript创造多线程环境,容许主线程将一些任务分配给子线程。在主线程运行的同时,子线程在后台运行,二者互不干扰。等到子线程完成计算任务,再把结果返回给主线程。所以,每个子线程就好像一个“工人”(worker),默默地完成本身的工做。更多worker的介绍请戳:JavaScript标准参考教程javascript
本文经过web worker 统计博客园总阅读量,来学习一下worker的使用,前段时间想看一下本身的博客有多少的阅读量,发现博客园好像没有提供这个统计功能,恰好以前有了解到worker,js的多线程,恰好适用于去统计总阅读量,又不影响我页面的渲染,主线程渲染页面,子线程负责循环请求博客园随笔列表进行统计,统计好了再将数据发送到主线程。详细思路以下:html
主线程java
一、先追加一个带id=‘statistical’的span标签,并显示“统计中...”web
二、开启worker子线程开始统计,而且开始监听onmessage事件等待子线程返回数据ajax
三、onmessage收到子线程返回的数据,更新id=‘statistical’的span标签的text值数组
子线程服务器
一、循环使用XMLHttpRequest对象请求博客园随笔列表,直到最后一页(直到返回的页面没有文章数据)多线程
二、使用正则处理、匹配数据(每篇文章的阅读量)存入全局变量中,而且判断是否最后一页,以便跳出循环app
三、将收集到的数据进行数据清洗、相加获得总阅读量ide
四、将总阅读量推送给主线程,并结束子线程
在开始写主线程以前,咱们先实现子线程的任务
根据博客园目前的连接规则,访问我的博客主页的地址以下:http://www.cnblogs.com/huanzi-qch/,分页查看随笔列表的地址以下:https://www.cnblogs.com/huanzi-qch/default.html?page=1,并根据响应回来的页面内容格式用正则 /huanzi-qch\s+阅读[(]+[1-9]\d+[)]/g 去匹配,固然也能够用 /阅读[(]+[1-9]\d+[)]/g
2019-08-12补充:最近博客园显示阅读那里发生改动,阅读与(123)中间多了空格,致使咱们以前写的正则匹配不到,如今优化一下咱们的正则
阅读(\s*)[(]+[1-9]\d+[)]
\s 表示匹配任意空白字符
* 表示任意次数
这样同样,无论中间有没有空格咱们都能匹配到
咱们对子线程进行以下封装,name值在主线程new Worker的时候构造:
console.log("我是worker 任务线程 负责统计总阅读量..");
//个人博客园地址名称,要是读取不到this.name的值,默认设置个人博客名称 var myCnblogsName = this.name ? this.name : "huanzi-qch"; //监听主线程发送过来的数据 //this.addEventListener('message', function (e) { // this.postMessage('主线程发送过来的数据: ' + e.data); //}, false); //监听发送报错 //this.addEventListener('messageerror ', function (e) { // this.postMessage('发送数据到主线程报错: ' + e.data); //}, false); //加载其余 JS 脚本。 //this.importScripts(""): //任务线程内部的全局变量数组,用于保存数据 var statisticsArray = []; //发送ajax请求博客园 function getReadData(page){ //是否还要继续 var flag = false; //使用XMLHttpRequest对象请求博客园 var xhr = new XMLHttpRequest(); xhr.open('GET', "https://www.cnblogs.com/"+myCnblogsName+"/default.html?page=" + page, false);//同步 xhr.setRequestHeader("Content-Type", "text/html; charset=utf-8"); //设置响应格式 xhr.onreadystatechange = function() { // readyState == 4说明请求已完成 if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { //使用正则处理HTML字符串,须要设置全局标识 //var myRe = /huanzi-qch(\s*)阅读(\s*)[(]+[1-9]\d+[)]/g; var myRe = /阅读(\s*)[(]+[1-9]\d+[)]/g; var resultArray = xhr.responseText.match(myRe); //合并到全局变量数组中 statisticsArray = statisticsArray.concat(resultArray); //判断这个便可:resultArray.length > 0 若是还有文章集合,则返回true if(resultArray && resultArray.length > 0){ flag = true; } } }; xhr.send(); return flag; } //循环调用getReadData,默认最大页数 100 (100页,每页10条记录,相对于1000篇博客,已经够多了吧?) for(var i = 1;i < 100;i++){ //若是返回false则当即跳出循环 if(!getReadData(i)){ break;} } //处理全局数组 for(var i = 0;i < statisticsArray.length;i++){ if(statisticsArray[i]){ //只保留数字部分 statisticsArray[i] = statisticsArray[i].match(/[1-9]\d+/)[0]; }else{ statisticsArray.splice(i, 1); } } //数组求和,须要返回主线程的最终值 //向产生这个 Worker 线程发送消息。 var count = eval(statisticsArray.join("+")); this.postMessage(count); console.log("统计结束,总阅读量为:"+count); //关闭 Worker 线程 this.close();
刚开始我是想将子线程单独放在一个js文件里,上传到博客园后台管理的文件里,而后引入建立worker对象,不成想博客园门户地址跟保存用户上传文件的地址不一样源,而worker受同源限制,致使没法建立对象
只能将子线程的代码放在同一个页面了,经过<script id="worker" type="app/worker"></script>包起来,经过读取这个script的内容成Blob二进制对象
,而后二进制对象转为URL,再经过这个URL建立worker。
最后代码以下:
// 先追加一个显示标签 $("#profile_block").append("总阅读量:<span id='statistical' style='color: #464646;'>统计中...</span><br/>"); //建立一个Blob,读取同个页面中的script标签 var blob = new Blob([document.querySelector('#worker').textContent]); //这里须要把代码看成二进制对象读取,因此使用Blob接口。而后,这个二进制对象转为URL,再经过这个URL建立worker。 var url = window.URL.createObjectURL(blob); //建立worker对象 var worker = new Worker(url ,{ name : 'huanzi-qch'}); //监放任务线程返回的数据 worker.onmessage = function (event) { //设置总阅读量 $("#statistical").text(event.data); } //error 事件的监听函数。 worker.onerror = function (event) { console.log('error:' + event); } //messageerror 事件的监听函数。发送的数据没法序列化成字符串时,会触发这个事件。 worker.onmessageerror = function (event) { console.log('messageerror:' + event); } //发送数据到任务线程 //worker.postMessage('Hello World');
将全部代码都添加到 博客侧边栏公告 并保存
小扩展:既然添加了总阅读量,不如把积分、排名也放一块儿显示吧!
先前往 博客设置 --> 选项 勾选上“积分与排名”,而后加入如下js代码
//隐藏博客园提供的积分与排名标签,并将内容迁移到指定位置 $("#sidebar_scorerank").hide(); $("#profile_block").append("积分:<span style='color: #464646;'>"+$("#sidebar_scorerank").find(".liScore").text().match(/[1-9]\d+/)[0]+"</span><br/>"); $("#profile_block").append("排名:<span style='color: #464646;'>"+$("#sidebar_scorerank").find(".liRank").text().match(/[1-9]\d+/)[0]+"</span><br/>");
经过这个小例子,咱们之后看本身的博客状况也更加方便了,访问有侧边公告栏的页面都会统计总阅读量(不过这样会无形增长博客园服务器的压力 <手动羞涩脸>),而且也充分的感觉到了worker的威力,以前js受限于单线程模型,没法充分发挥js的潜力,如今有了worker多线程,咱们能够解锁更多姿式了!
更多对worker的介绍请戳:JavaScript标准参考教程。
咱们直接用子线程的代码去统计别人的博客的总阅读量,不须要大幅度改动,直接将myCnblogsName的值改为对应的博客地址名称,咱们进行简单封装成一个function,而后跑去博客主页打开F12在控制台运行代码而后调用function便可,简单方便,即开即用
/** 输入别人的博客园地址名称 */ function statistical(myCnblogsName){ console.log("我是worker 任务线程 正在统计 "+myCnblogsName+" 的博客的总阅读量.."); //任务线程内部的全局变量数组,用于保存数据 var statisticsArray = []; //发送ajax请求博客园 function getReadData(page){ //是否还要继续 var flag = false; //使用XMLHttpRequest对象请求博客园 var xhr = new XMLHttpRequest(); xhr.open('GET', "https://www.cnblogs.com/"+myCnblogsName+"/default.html?page=" + page, false);//同步 xhr.setRequestHeader("Content-Type", "text/html; charset=utf-8"); //设置响应格式 xhr.onreadystatechange = function() { // readyState == 4说明请求已完成 if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { //使用正则处理HTML字符串,须要设置全局标识 //var myRe = /huanzi-qch(\s*)阅读(\s*)[(]+[1-9]\d+[)]/g; var myRe = /阅读(\s*)[(]+[1-9]\d+[)]/g; var resultArray = xhr.responseText.match(myRe); //合并到全局变量数组中 statisticsArray = statisticsArray.concat(resultArray); //判断这个便可:resultArray.length > 0 若是还有文章集合,则返回true if(resultArray && resultArray.length > 0){ flag = true; } } }; xhr.send(); return flag; } //循环调用getReadData,默认最大页数 100 (100页,每页10条记录,相对于1000篇博客,已经够多了吧?) for(var i = 1;i < 100;i++){ //若是返回false则当即跳出循环 if(!getReadData(i)){ break;} } //处理全局数组 for(var i = 0;i < statisticsArray.length;i++){ if(statisticsArray[i]){ //只保留数字部分 statisticsArray[i] = statisticsArray[i].match(/[1-9]\d+/)[0]; }else{ statisticsArray.splice(i, 1); } } //数组求和,须要返回主线程的最终值 var count = eval(statisticsArray.join("+")); console.log("统计结束,总阅读量为:"+count); }
好比查询个人博客总阅读量,在控制台执行上面的方法定义后,再执行,so easy
statistical("huanzi-qch");
咱们去统计一下推荐博客排行榜中的部分大佬看一看他们的总阅读量是多少
看了一下他们的随笔数量,一个是六百多,一个是一百多,咱们定义的循环次数100是够用的,其实改为for(;;)也没有问题,由于咱们已经设置了break的条件
而后去他们的博客主页打开控制台,运行代码,而后调用statistical方法
不愧是大佬啊,总阅读量一个是七百万,一个是三百万