web worker介紹以及應用場景


面試遇到一個問題,就是如果有幾十萬條數據,前端該怎麼處理?
我回答用分頁,懶加載去加載數據,但是面試官說後臺不給處理,前端該怎麼處理?
如果都渲染到頁面上來,瀏覽器根本就承擔不了,這個問題真的沒回答上來,
查了一下就查到了web worker,新的知識點,當然要知道what,why,how了。

什麼是web worker ?

web worker 是Web應用程序在獨立於主線程的後臺線程中,運行一個腳本的操作。使用構造函數(例如,Worker())創建一個 worker 對象, 構造函數接受一個 JavaScript文件URL , 這個文件包含了將在 worker 線程中運行的代碼腳本。

主線程和 worker 線程相互之間使用 postMessage() 方法來發送信息, 並且通過 onmessage這個 event handler來接收信息(傳遞的信息包含在 Message 這個事件的data屬性內) 。數據的交互方式爲傳遞副本,而不是直接共享數據。

worker 將運行在與當前 window不同的另一個全局上下文中,這個上下文由一個對象表示,標準情況下爲DedicatedWorkerGlobalScope
在這裏插入圖片描述

爲什麼要用web worker ?

在瀏覽器中javascript的執行是單線程的,一個線程一次只能做一件事情,這樣必然會出現阻塞的情況,非常影響用戶體驗,所以ajax應運而生了。ajax的出現使得頁面在等待服務器響應的這段時間內不再發生阻塞,但是這仍然沒有改變代碼單線程執行的本質,這也意味着我們依舊不能把耗費時間的複雜運算放在頁面上執行。而web worker的出現彌補了這個缺點。
web worker爲 JavaScript 創造多線程環境,允許主線程通過new運算符創建 Worker 線程(子線程),將一些任務分配給Worker線程,這樣一些計算量大的、耗費時間的、複雜的運算都可以放到worker線程上,避免主線程被阻塞。

怎麼使用web worker ?

1.主線程採用new 命令,調用worker構造函數,Worker中接收兩個參數,一個url,就是worker執行計算的腳本的路徑url(爲必須參數),worker線程所要執行的任務,這個腳本必須來自網絡,放到服務器上,如果腳本沒下載成功,就會失敗,第二個參數是配置對象,該對象可選。它的一個作用就是指定 Worker 的名稱,用來區分多個 Worker 線程;

2.javascript主線程中和worker子線程中通過postMessage和onmessage來通信,由主線程中postMessage(params),傳入參數,子線程通過onmessage函數接收從主線程傳遞過來的參數,通過獲取參數中的data來取值。

myworker.postMessage()方法的參數,就是主線程傳給 Worker 的數據。它可以是各種數據類型,包括二進制數據。

//主線程
let  myworker = new Worker('worker.js');
 this.myWorker.postMessage([value1, value2])

子線程接收到主線程傳遞過來的條件參數,將經過處理的數據通過postMessage(Data)傳遞給主線程,

//子線程
onmessage = function(e) {
    console.log(e.data, 'Worker: Message received from main script')
    const result = e.data[0] * e.data[1]
    if (isNaN(result)) {
        postMessage('Please write two numbers')
    } else {
        const workerResult = 'Result: ' + result
        console.log('Worker: Posting message back to main script')
        postMessage(workerResult)
    }
}

接着,主線程通過myworker.onmessage指定監聽函數,這樣主線程就接收到子線程發回來的消息了

//主線程
 this.myWorker.onmessage = function (e) {
      result.textContent = e.data
      console.log('Message received from worker')
 }

主線程接收到經過子線程處理過的數據之後可以關閉子線程:

this.myworker.terminate()

worker線程中有一個監聽函數,監聽message事件,以下寫法二和寫法三中self和this都可以,worker 將運行在與當前 window不同的另一個全局上下文中,這個上下文由一個對象表示,標準情況下爲DedicatedWorkerGlobalScope,self和this都指向這個對象,(標準 workers 由單個腳本使用; 共享workers使用SharedWorkerGlobalScope)
寫法一:

onmessage = function(e) {
 postMessage('You said: ' + e.data);
}

寫法二:

self.addEventListener('message', function(e) {
    
    postMessage('You said: ' + e.data);
})

寫法三:

this.addEventListener('message', function(e) {
   postMessage('You said: ' + e.data);
})

寫法四:

addEventListener('message', function (e) {
  postMessage('You said: ' + e.data);
}, false);

根據主線程發來的數據,Worker 線程可以調用不同的方法,下面是一個例子。

self.addEventListener('message', function (e) {
  var data = e.data;
  switch (data.cmd) {
    case 'start':
      self.postMessage('WORKER STARTED: ' + data.msg);
      break;
    case 'stop':
      self.postMessage('WORKER STOPPED: ' + data.msg);
      self.close(); // Terminates the worker.
      break;
    default:
      self.postMessage('Unknown command: ' + data.msg);
  };
}, false);

上面代碼中,self.close()用於在 Worker 內部關閉自身。

3.worker中加載其他的腳本,有一個專門的方法importScripts()。

importScripts('script1.js');

可同時加載多個腳本

importScripts('script1.js', 'script2.js');

4.監聽錯誤

主線程可以監聽 Worker 是否發生錯誤。如果發生錯誤,Worker 會觸發主線程的error事件。

worker.onerror(function (event) {
  console.log([
    'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
  ].join(''));
});

// 或者
worker.addEventListener('error', function (event) {
  // ...
});

Worker 內部也可以監聽error事件。

2.5 關閉 Worker
使用完畢,爲了節省系統資源,必須關閉 Worker。

// 主線程
worker.terminate();
// Worker 線程
self.close();

實例代碼

以下例子在vue項目中使用如下:

webWorker.vue

<template>
    <div  tabindex="0">
      <h1>Web Workers</h1>
      <form>
        <div>
          <label for="number1">number 1: </label>
          <input type="text" id="number1" :value="inputValue1" @change="handleChange1">
        </div>
        <div>
          <label for="number2">number 2: </label>
          <input type="text" id="number2" :value="inputValue2" @change="handleChange2">
        </div>
      </form>
      <p class="result">Result: 0</p>
      </div>
</template>
<script>
export default {
  data () {
    return {
      inputValue1: 0,
      inputValue2: 0
    }
  },
  methods: {
    workerFun (value1, value2, type) {
      const result = document.querySelector('.result')
      if (window.Worker) {
        this.myWorker = new Worker('worker.jsx')
        this.myWorker.postMessage([value1, value2])
        console.log('Message posted to worker', type)
        this.myWorker.onmessage = function (e) {
          result.textContent = e.data
             this.myworker.terminate()
          console.log('Message received from worker')
        }
      } else {
        console.log('Your browser doesn\'t support web workers.')
      }
    },
    handleChange1 (e) {
      this.inputValue1 = e.target.value
      this.workerFun(e.target.value, this.inputValue2, 'first')
    },
    handleChange2 (e) {
      this.inputValue2 = e.target.value
      this.workerFun(this.inputValue1, e.target.value, 'second')
    }
  }
}
</script>

worker.jsx放到根目錄下面,代碼如下:

onmessage = function(e) {
    console.log(e.data, 'Worker: Message received from main script')
    const result = e.data[0] * e.data[1]
    if (isNaN(result)) {
        postMessage('Please write two numbers')
    } else {
        const workerResult = 'Result: ' + result
        console.log('Worker: Posting message back to main script')
        postMessage(workerResult)
    }
}

web worker 注意事項:

(1)同源限制

分配給 Worker 線程運行的腳本文件,必須與主線程的腳本文件同源。

(2)DOM 限制

Worker 線程所在的全局對象,與主線程不一樣,無法讀取主線程所在網頁的 DOM 對象,也無法使用document、window、parent這些對象。但是,Worker 線程可以navigator對象和location對象。

(3)通信聯繫

Worker 線程和主線程不在同一個上下文環境,它們不能直接通信,必須通過消息完成。

(4)腳本限制

Worker 線程不能執行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 對象發出 AJAX 請求。

(5)文件限制

Worker 線程無法讀取本地文件,即不能打開本機的文件系統(file://),它所加載的腳本,必須來自網絡。

web worker的應用場景有哪些?

一般情況下,很少會用到web Worker,返回到瀏覽器到數據在後臺已經經過計算處理,不太可能讓前端處理複雜到業務邏輯
webWorker解決的是js中數據處理導致的UI線程阻塞,所以webWorker的應用場景一般爲需要大量計算的時候,這將避免ui界面渲染被阻塞
大數據的處理:

這裏所說的大數據處理,並不是指數據量非常大,而是要從計算量來看,通常用時不能控制在毫秒級內的運算都可以考慮放在web worker中執行。

高頻的用戶交互:

高頻的用戶交互適用於根據用戶的輸入習慣、歷史記錄以及緩存等信息來協助用戶完成輸入的糾錯、校正功能等類似場景,用戶頻繁輸入的響應處理同樣可以考慮放在web worker中執行。

最後聲明一點,瞭解後臺的同學千萬不要認爲web worker等同於後臺意義的多線程,web worker現在有了多線程的形(有了多線程的用法),但還不具備多線程的神(不具備線程通信、鎖等後臺線程的基本特性),web worker的本質是支持我們把數據刷新與頁面渲染兩個動作拆開執行(不使用web worker的話這兩個動作在主線程中是線性執行的)。

爲了防止主線程造成擁堵,可以開啓worker線程,這樣不影響主線程的數據加載,可以在worker中進行數據請求,也可以創建多個worker線程,不過Web worker也有一些使用限制:無法訪問DOM節點,無法訪問全局變量或是全局函數。所以之前用的是jQuery Ajax發送請求的,這裏就不能用了;只能用原生的ajax請求啦。

self.addEventListener('message', function(e) {
    const result = e.data[0] * e.data[1]
    if (isNaN(result)) {
        postMessage('Please write two numbers')
    } else {
        var xhr = new XMLHttpRequest()
        let ajaxResult
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    ajaxResult = JSON.parse(xhr.responseText)
                    const workerResult = 'Result: ' + result
                    postMessage({
                        workerResult,
                        ajaxResult: ajaxResult.result
                    })
                }
            }
        }

        xhr.open('get', 'https://geo.datav.aliyun.com/areas_v2/bound/100000_full.json', true)
        xhr.send(null)
    }
})

如有錯誤請指出,謝謝。
參考:
http://www.ruanyifeng.com/blog/2018/07/web-worker.html
https://blog.csdn.net/lqlqlq007/article/details/79824122

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章