前言
眾所周知,js是單線程的,從上往下,從左往右依次執(zhí)行,當(dāng)我們有耗時(shí)的任務(wù)需要處理時(shí),便會(huì)阻塞線程造成頁面卡頓等問題。web
worker的目的,就是為JavaScript創(chuàng)造多線程環(huán)境,允許主線程將一些任務(wù)分配給子線程。在主線程運(yùn)行的同時(shí),子線程在后臺(tái)運(yùn)行,兩者互不干擾。等到子線程完成計(jì)算任務(wù),再把結(jié)果返回給主線程。因此,每一個(gè)子線程就好像一個(gè)“工人”(worker),默默地完成自己的工作。更多worker的介紹請(qǐng)戳:
JavaScript標(biāo)準(zhǔn)參考教程
<https://www.kancloud.cn/kancloud/javascript-standards-reference/46548>
本文通過web worker
統(tǒng)計(jì)博客園總閱讀量,來學(xué)習(xí)一下worker的使用,前段時(shí)間想看一下自己的博客有多少的閱讀量,發(fā)現(xiàn)博客園好像沒有提供這個(gè)統(tǒng)計(jì)功能,剛好之前有了解到worker,js的多線程,剛好適用于去統(tǒng)計(jì)總閱讀量,又不影響我頁面的渲染,主線程渲染頁面,子線程負(fù)責(zé)循環(huán)請(qǐng)求博客園隨筆列表進(jìn)行統(tǒng)計(jì),統(tǒng)計(jì)好了再將數(shù)據(jù)發(fā)送到主線程。詳細(xì)思路如下:
主線程
1、先追加一個(gè)帶id=‘statistical’的span標(biāo)簽,并顯示“統(tǒng)計(jì)中...”
2、開啟worker子線程開始統(tǒng)計(jì),并且開始監(jiān)聽onmessage事件等待子線程返回?cái)?shù)據(jù)
3、onmessage收到子線程返回的數(shù)據(jù),更新id=‘statistical’的span標(biāo)簽的text值
子線程
1、循環(huán)使用XMLHttpRequest對(duì)象請(qǐng)求博客園隨筆列表,直到最后一頁(直到返回的頁面沒有文章數(shù)據(jù))
2、使用正則處理、匹配數(shù)據(jù)(每篇文章的閱讀量)存入全局變量中,并且判斷是否最后一頁,以便跳出循環(huán)
3、將收集到的數(shù)據(jù)進(jìn)行數(shù)據(jù)清洗、相加得到總閱讀量
4、將總閱讀量推送給主線程,并結(jié)束子線程
?
代碼編寫
在開始寫主線程之前,我們先實(shí)現(xiàn)子線程的任務(wù)
?
子線程
根據(jù)博客園目前的鏈接規(guī)則,訪問個(gè)人博客主頁的地址如下:http://www.cnblogs.com/huanzi-qch/,分頁查看隨筆列表的地址如下:https://www.cnblogs.com/huanzi-qch/default.html?page=1,并根據(jù)響應(yīng)回來的頁面內(nèi)容格式用正則
/huanzi-qch\s+閱讀[(]+[1-9]\d+[)]/g去匹配,當(dāng)然也可以用?/閱讀[(]+[1-9]\d+[)]/g
?
我們對(duì)子線程進(jìn)行如下封裝,name值在主線程new Worker的時(shí)候構(gòu)造:
console.log("我是worker 任務(wù)線程 負(fù)責(zé)統(tǒng)計(jì)總閱讀量.."); //我的博客園地址名稱 var myCnblogsName = this
.name;//監(jiān)聽主線程發(fā)送過來的數(shù)據(jù) //this.addEventListener('message', function (e) { //
this.postMessage('主線程發(fā)送過來的數(shù)據(jù): ' + e.data); //}, false); //監(jiān)聽發(fā)送報(bào)錯(cuò) //
this.addEventListener('messageerror ', function (e) { //
this.postMessage('發(fā)送數(shù)據(jù)到主線程報(bào)錯(cuò): ' + e.data); //}, false); //加載其他 JS 腳本。 //
this.importScripts(""): //任務(wù)線程內(nèi)部的全局變量數(shù)組,用于保存數(shù)據(jù) var statisticsArray = []; //
發(fā)送ajax請(qǐng)求博客園 function getReadData(page){ //是否還要繼續(xù) var flag = false; //
使用XMLHttpRequest對(duì)象請(qǐng)求博客園 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"); //設(shè)置響應(yīng)格式
xhr.onreadystatechange =function() { // readyState == 4說明請(qǐng)求已完成 if
(xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { //
使用正則處理HTML字符串,需要設(shè)置全局標(biāo)識(shí) //var myRe = /huanzi-qch\s+閱讀[(]+[1-9]\d+[)]/g; var myRe
= /閱讀[(]+[1-9]\d+[)]/g; var resultArray = xhr.responseText.match(myRe); //
合并到全局變量數(shù)組中 statisticsArray = statisticsArray.concat(resultArray); //
判斷這個(gè)即可:resultArray.length > 0 如果還有文章集合,則返回true if(resultArray &&
resultArray.length > 0){ flag = true; } } }; xhr.send(); return flag; } //
循環(huán)調(diào)用getReadData,默認(rèn)最大頁數(shù) 100 (100頁,每頁10條記錄,相對(duì)于1000篇博客,已經(jīng)夠多了吧?) for(var i = 1;i <
100;i++){ //如果返回false則立即跳出循環(huán) if(!getReadData(i)){ break;} } //處理全局?jǐn)?shù)組 for(var i
= 0;i < statisticsArray.length;i++){ if(statisticsArray[i]){ //只保留數(shù)字部分
statisticsArray[i] = statisticsArray[i].match(/[1-9]\d+/)[0]; }else{
statisticsArray.splice(i,1); } } //數(shù)組求和,需要返回主線程的最終值 //向產(chǎn)生這個(gè) Worker 線程發(fā)送消息。 var
count = eval(statisticsArray.join("+")); this.postMessage(count); console.log(
"統(tǒng)計(jì)結(jié)束,總閱讀量為:"+count); //關(guān)閉 Worker 線程 this.close();
?
? 主線程
? 剛開始我是想將子線程單獨(dú)放在一個(gè)js文件里,上傳到博客園后臺(tái)管理的文件里,然后引入創(chuàng)建worker對(duì)象,不成想博客園門戶地址跟保存用戶上傳文件的地址不同源,而worker受同源限制,導(dǎo)致無法創(chuàng)建對(duì)象
只能將子線程的代碼放在同一個(gè)頁面了,通過<script id="worker" type="app/worker"></script>
包起來,通過讀取這個(gè)script的內(nèi)容成Blob二進(jìn)制對(duì)象,然后二進(jìn)制對(duì)象轉(zhuǎn)為URL,再通過這個(gè)URL創(chuàng)建worker。
最后代碼如下:
// 先追加一個(gè)顯示標(biāo)簽 $("#profile_block").append("總閱讀量:<span id='statistical'
style='color: #464646;'>統(tǒng)計(jì)中...</span><br/>"); //創(chuàng)建一個(gè)Blob,讀取同個(gè)頁面中的script標(biāo)簽 var
blob =new Blob([document.querySelector('#worker').textContent]); //
這里需要把代碼當(dāng)作二進(jìn)制對(duì)象讀取,所以使用Blob接口。然后,這個(gè)二進(jìn)制對(duì)象轉(zhuǎn)為URL,再通過這個(gè)URL創(chuàng)建worker。 var url =
window.URL.createObjectURL(blob);//創(chuàng)建worker對(duì)象 var worker = new Worker(url ,{
name : 'huanzi-qch'}); //監(jiān)聽任務(wù)線程返回的數(shù)據(jù) worker.onmessage = function (event) { //
設(shè)置總閱讀量 $("#statistical").text(event.data); } //error 事件的監(jiān)聽函數(shù)。 worker.onerror =
function (event) { console.log('error:' + event); } //messageerror
事件的監(jiān)聽函數(shù)。發(fā)送的數(shù)據(jù)無法序列化成字符串時(shí),會(huì)觸發(fā)這個(gè)事件。 worker.onmessageerror = function (event) {
console.log('messageerror:' + event); } //發(fā)送數(shù)據(jù)到任務(wù)線程 //worker.postMessage('Hello
World');
?
效果演示
將所有代碼都添加到 博客側(cè)邊欄公告 并保存
?
小擴(kuò)展:既然添加了總閱讀量,不如把積分、排名也放一起顯示吧!
先前往 博客設(shè)置 --> 選項(xiàng) 勾選上“積分與排名”,然后加入以下js代碼
//隱藏博客園提供的積分與排名標(biāo)簽,并將內(nèi)容遷移到指定位置 $("#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/>");
?
? ? ? ??
?
總結(jié)
通過這個(gè)小例子,我們以后看自己的博客情況也更加方便了,訪問有側(cè)邊公告欄的頁面都會(huì)統(tǒng)計(jì)總閱讀量(不過這樣會(huì)無形增加博客園服務(wù)器的壓力
<手動(dòng)羞澀臉>),并且也充分的感受到了worker的威力,之前js受限于單線程模型,無法充分發(fā)揮js的潛力,現(xiàn)在有了worker多線程,我們可以解鎖更多姿勢(shì)了!
更多對(duì)worker的介紹請(qǐng)戳:JavaScript標(biāo)準(zhǔn)參考教程
<https://www.kancloud.cn/kancloud/javascript-standards-reference/46548>。
?
統(tǒng)計(jì)任意博客總閱讀量
我們直接用子線程的代碼去統(tǒng)計(jì)別人的博客的總閱讀量,不需要大幅度改動(dòng),直接將myCnblogsName的值改成對(duì)應(yīng)的博客地址名稱,我們進(jìn)行簡單封裝成一個(gè)function,然后跑去博客主頁打開F12在控制臺(tái)運(yùn)行代碼然后調(diào)用function即可,簡單方便,即開即用
/** 輸入別人的博客園地址名稱 */ function statistical(myCnblogsName){ console.log("我是worker
任務(wù)線程 正在統(tǒng)計(jì) "+myCnblogsName+" 的博客的總閱讀量.."); //任務(wù)線程內(nèi)部的全局變量數(shù)組,用于保存數(shù)據(jù) var
statisticsArray = []; //發(fā)送ajax請(qǐng)求博客園 function getReadData(page){ //是否還要繼續(xù) var
flag =false; //使用XMLHttpRequest對(duì)象請(qǐng)求博客園 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"); //
設(shè)置響應(yīng)格式 xhr.onreadystatechange = function() { // readyState == 4說明請(qǐng)求已完成 if
(xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { //
使用正則處理HTML字符串,需要設(shè)置全局標(biāo)識(shí) //var myRe = /huanzi-qch\s+閱讀[(]+[1-9]\d+[)]/g; var myRe
= /閱讀[(]+[1-9]\d+[)]/g; var resultArray = xhr.responseText.match(myRe); //
合并到全局變量數(shù)組中 statisticsArray = statisticsArray.concat(resultArray); //
判斷這個(gè)即可:resultArray.length > 0 如果還有文章集合,則返回true if(resultArray &&
resultArray.length > 0){ flag = true; } } }; xhr.send(); return flag; } //
循環(huán)調(diào)用getReadData,默認(rèn)最大頁數(shù) 100 (100頁,每頁10條記錄,相對(duì)于1000篇博客,已經(jīng)夠多了吧?) for(var i = 1;i <
100;i++){ //如果返回false則立即跳出循環(huán) if(!getReadData(i)){ break;} } //處理全局?jǐn)?shù)組 for(var i
= 0;i < statisticsArray.length;i++){ if(statisticsArray[i]){ //只保留數(shù)字部分
statisticsArray[i] = statisticsArray[i].match(/[1-9]\d+/)[0]; }else{
statisticsArray.splice(i,1); } } //數(shù)組求和,需要返回主線程的最終值 var count =
eval(statisticsArray.join("+")); console.log("統(tǒng)計(jì)結(jié)束,總閱讀量為:"+count); }
?
我們?nèi)ソy(tǒng)計(jì)一下推薦博客排行榜中的部分大佬看一看他們的總閱讀量是多少
?
看了一下他們的隨筆數(shù)量,一個(gè)是六百多,一個(gè)是一百多,我們定義的循環(huán)次數(shù)100是夠用的,其實(shí)改成for(;;)也沒有問題,因?yàn)槲覀円呀?jīng)設(shè)置了break的條件
? ??
然后去他們的博客主頁打開控制臺(tái),運(yùn)行代碼,然后調(diào)用statistical方法
? ??
?
? 不愧是大佬啊,總閱讀量一個(gè)是七百萬,一個(gè)是三百萬
?
?
熱門工具 換一換