學習阮一峯老師的web worker而記錄下來html
JavaScript語言採用的是單線程,全部任務只能在一個線程上完成,一次只能作一件事。前面的任務沒作完,後面的任務只能等着。隨着電腦計算能力的加強,尤爲是多核CPU的出現,單線程帶來很大的不方便,沒法充分發揮計算機的計算能力。Web worker的做用是爲JavaScript創做多線程環境,容許主線程建立worker線程,將一些任務分配給後者運行。在主線程運行的同時,worker線程在後臺運行,二者互不干擾。等到worker線程完成計算任務,再把結果返回給主線程。這樣的好處是,一些計算密集型或高延遲的任務,被worker線程負擔了,主線程(一般負責UI交互)就會很流暢,不會被阻塞或拖慢。web
Worker線程一旦新建成功,就會始終運行,不會被主線程上的活動(好比用戶點擊按鈕,提交表單)打斷。這樣有利於隨時響應主線程的通訊。可是這也形成了worker比較耗費資源,不該該過分使用,並且一旦使用完畢,就應該關閉json
Web worker有如下幾個使用注意點api
分配給worker線程運行的腳本文件,必須與主線程的腳本文件同源。瀏覽器
Worker線程所在的全局對象,與主線程不同,沒法讀取主線程所在網頁的DOM對象,也沒法使用document, window, parent這些對象。可是worker線程可使用navigator, location(只讀)對象緩存
Worker線程和主線程再也不同一個上下文環境,他們不能直接通訊,必須經過消息完成服務器
Worker線程不能執行alert()方法confirm()方法,但可使用XMLHttpRequest對象發出Ajax請求網絡
Worker線程沒法讀取本地文件,即不能打開本機的文件系統(file://),它所加載的腳本必須來自網絡多線程
主線程採用new命令,調用Worker()構造函數,新建一個Worker線程app
Var worker = new Worker(‘worker.js’)
Worker()構造函數的參數是一個腳本文件,該文件就是Worker線程所要執行的任務。因爲Worker不能讀取本地文件,因此這個腳本必須來自網絡。若是下載沒有成功,Worker就會默默地失敗。
而後主線程調用worker.postMassage()方法向Worker發消息
Worker.postMessage(‘hello world’)
Worker.postMessage({method: ‘echo’}, args: [‘Work’])
Worker.postMessage()方法地參數,就是主線程傳給Worker地數據,能夠是各類數據類型包括二進制
接着主線程經過worker.onmessage指定監聽函數,接收子線程發回來地消息
Worker.onmessage = functioin (event) {
Console.log(event.data)
DoSomething()
}
Function doSomething(){
//執行任務
Worker.postMessage(‘work done!’)
}
上面代碼中,事件對象地data熟悉能夠獲取worker發來地數據,worker完成任務之後主線程就能夠把它關掉
Worker.terminate()
Worker線程內部須要有一個監聽函數,監聽message事件
Self.addEventListener(‘message’, function (e) {
Self.postMessage(‘you said:’ + e.data)
}, false)
上面代碼中,self表明子線程自身,即子線程地全局對象,所以等同於下面兩種寫法
//寫法一
This.addEventListerer(‘message’, function (e) {
This.postMessage(‘you said:’ + e.data)
}, false)
AddEventListener(‘message’, function(e) {
PostMessage(‘you said:’ + e.data)
},false)
除了使用addEventListener()指定監聽函數,也可使用self.onmessage指定。監聽函數地參數是一個事件對象。它地data屬性包含主線程發來地數據。Self.postMessage()方法用來向主線程發送消息
根據主線程發來地數據,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 stoped:’ + data.msg)
Self.close()
Break;
Default:
Self.postMessa(‘unknown:’ + data.msg)
}
},false)
Self.close()用於再worker內部關閉自身
Worker內部若是要加載其它腳本,有一個專門的方法importScript()
ImportScript(‘script.js’)
加載多個腳本
ImportScript(‘script1.js’,’script2.js’,’script3.js’)
主線程能夠監聽worker是否發生錯誤。若是發生錯誤worker會觸發主線程的error事件
Worker.onerror(function (event) {
Console.log([
‘error:line’, event.loneno,’ in ’, event.filename, ‘ : ’,event.message
].join(‘’))
})
Worker.addEventListener(‘error’, function (event){
})
Worker內部也能夠監聽error事件
使用完畢,爲了節省系統資源,必須關閉worker
//主線程
Worker.terminate()
//worker線程
Self.close()
前面說過,主線程與worker之間的通訊內容,能夠是文本,也能夠是對象。須要注意的是,這種通訊是拷貝關係,便是傳值而不是傳地址。Worker對通訊內容的修改,不會影響到主線程。事實上,瀏覽器內部的運行機制是先將通訊內容串行化,而後把串行化後的字符串發給worker,後者再將它還原
主線程與worker之間也能夠交換二進制數據,好比file, blob, arrayBuffer等類型。也能夠在線程之間發送。
//主線程
Var uInt8Array = new Uint8Array(new ArrayBuffer(10))
For (var I = 0l I < uInt8Array.length; ++i) {
uInt8Array[i] = I * 2 // [0,2,4,6,7,…….]
}
Worker.postMessage(uInt8Array)
//worker線程
Self.onmessage = function (e) {
Var uInt8Array = e.data
PostMessage(uInt8Array.toString())
PostMessage(uInt8Array.byteLength)
}
可是,拷貝方式發送二進制數據會形成性能問題。好比主線程向worker發送一個500MB文件,默認狀況下瀏覽器會生成一個原文件的拷貝。爲了解決這個問題,JavaScript容許主線程把二進制數據直接轉移給子線程,可是一旦轉移,主線程就沒法再使用這些二進制數據了,這是爲了防止出現多個線程同時修改數據的麻煩。這種轉移數據的方法叫作Transferable Objects。這使得主線程能夠快熟把數據交給worker,對於影像處理,聲音處理,3D運算等就很是方便了,不會產生性能負擔
若是要直接轉移數據的控制權,就要使用下面的寫法
// Transferable Objects 格式
Worker.postMessage(arrayBuffer,[arrayBuffer])
//例子
Var ab = new ArrayBuffer(1)
Worker.posrMessage(ab,[ab])
一般狀況下,worker載入的是一個單獨的JavaScript腳本文件,可是也能夠載入與主線程在同一個網頁的代碼
<!DOCTYPE html>
<body>
<script id=」worker」 type=」app/worker」>
AddEventListener(‘message’, function () {
PostMessage(‘some message’)
}, false)
</script>
</body>
</html>
上面是一段嵌入網頁的腳本,注意必須指定<script標籤的type屬性是一個瀏覽器不認識的值,上例子是app/worker
而後讀取這一段嵌入頁面的腳本用worker來處理
Var blob = new Blob([document.querySeletor(‘#worker’).textContent])
Var url = window.URL.createObjectURL(blob)
Var worker = new Worker(url)
Worker.onmessage = function (e) {
//e.data === ‘some message’
}
上面代碼中,先將嵌入網頁的腳本代碼,專程一個二進制對象,而後爲這個二進制對象生成URL.再讓worker加載這個URL.這樣就作到了,主線程與worker的帶都再同一個網頁上面
有時候瀏覽器須要輪詢服務器狀態,以便第一時間得知狀態改變。這個工資能夠放在worker裏面
Function createWorker(f){
Var blob = new Blob([‘(’ + f.toString() + ‘)()’])
Var url = window.URL.createObjectURL(blob)
Var worker = new Worker(url)
Return worker
}
Var pollingWorker = createWorker (function(e){
Var cache
Function compare (new, old) {…}
SetIntval(function () {
Fetch(‘/my-api-endpoint’).then(function (res){
Var data = res.json()
If (!compare(data,cache)){
Cache = data
Self.postMessage(data)
}
})
},1000)
})
PollingWorker.onmessage = function () {
// render data
}
PollingWorker.postMessage(‘int’)
上面代碼中,worker每秒鐘輪詢一次數據,而後跟緩存作比較。若是不一致說明服務端有了新的變化,所以就要通知主線程
Worker線程內部還能再新城worker線程(目前只有firefox瀏覽器支持)。下面的例子是將一個計算密集的任務分配到10個worker
主線程:
Var worker = new Worker(‘worker.js’)
Worker.onmssage = function(event){
Document.getElementById(‘result’).textContent = event.data
}
Worker線程代碼以下:
// worker.js
//settings
Var num_worker = 10
Var items_per_worker = 1000000
//start the workers
Var result = 0
Var pending_workers = num_workers
For (var I = 0; I < num_workers; i+=1) {
Var worker = new Worker(‘core.js’)
Worker.postMessage(I * items_worker)
Worker.postMessage((i+1) * items_per_worker)
Worker.onmessage = storeResult
}
//handle the results
Function storeResult (event) {
Result += event.data
Pending_worker -=1
If (pending_worker <= 0) {
PostMessage(result) // finished!
}
}
上面代碼中,worker線程內部新建10個worker線程,而且依次向這10個worker發送消息,告知了計算的七點和終點。計算任務腳本的代碼以下
//core.js
Var start
Onmessage = getStart
Function getStart(event){
Start = event.data
Onmessage = getEnd
}
Var end
Function getEnd(event){
End = event.data
Onmessage = null
Work()
}
Function work() {
Var result = 0
For (var I = start; I < end; I += 1) {
//perform some complex calculation here
Result += 1
}
PostMessage(result)
Close()
}
瀏覽器原生提供winker()構造函數,用來供主線程生成worker線程
Var myWorker = new Worker(jsURL,options)
Worker()構造函數,能夠接受兩個參數。第一個參數是腳本的網址(必須遵照同源策略),該參數是必須的,且只能加載JS腳本。第二個參數是配置對象,該對象可選。它的一個做用就是指定worker的名稱,用來區分多個worker線程
// 主線程
Var myWorker = new Worker(‘worker.js’, {name: ‘myWorker’})
//worker線程
Worker()構造函數返回一個worker線程對象,用來供主線程操做worker。Worker線程對象的屬性和方法以下
Worker.onerror:指定error事件的監聽函數
Worker.onmessa:指定messa事件的監聽函數,發慫過來的數據再Event.data屬性中
Worker.onmessageerror:指定messageerror事件的監聽函數。發送的數據沒法序列號稱字符串時,會觸發這個事件
Worker.postMessage():向worker線程發送消息
Worker.termitnate():當即終止worker線程
Web worker有本身的全局對象,不是主線程的window,而是一個專門爲Worker定製的全局對象,所以定義再window上面的對象和方法不是所有均可以
Worker線程有一些本身的全局屬性和方法
Self.nama: worker的名字,該屬性只讀,由構造函數指定
Self.onmessageerror:指定messageerror事件的監聽函數。發送的樹沒法序列號成字符串時,會觸發這個事件
Self.close():關閉worker線程
Self.postMessage():向產生這個worker線程發送消息
Self.importScript():加載JS腳本