衆所周知,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方法
不愧是大佬啊,總閱讀量一個是七百萬,一個是三百萬