Web Worker

Web Worker

學習阮一峯老師的web worker而記錄下來html

一律述

JavaScript語言採用的是單線程,全部任務只能在一個線程上完成,一次只能作一件事。前面的任務沒作完,後面的任務只能等着。隨着電腦計算能力的加強,尤爲是多核CPU的出現,單線程帶來很大的不方便,沒法充分發揮計算機的計算能力。Web worker的做用是爲JavaScript創做多線程環境,容許主線程建立worker線程,將一些任務分配給後者運行。在主線程運行的同時,worker線程在後臺運行,二者互不干擾。等到worker線程完成計算任務,再把結果返回給主線程。這樣的好處是,一些計算密集型或高延遲的任務,被worker線程負擔了,主線程(一般負責UI交互)就會很流暢,不會被阻塞或拖慢。web

Worker線程一旦新建成功,就會始終運行,不會被主線程上的活動(好比用戶點擊按鈕,提交表單)打斷。這樣有利於隨時響應主線程的通訊。可是這也形成了worker比較耗費資源,不該該過分使用,並且一旦使用完畢,就應該關閉json

Web worker有如下幾個使用注意點api

  1. 同源限制

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

  1. DOM限制

Worker線程所在的全局對象,與主線程不同,沒法讀取主線程所在網頁的DOM對象,也沒法使用document, window, parent這些對象。可是worker線程可使用navigator, location(只讀)對象緩存

  1. 通訊聯繫

Worker線程和主線程再也不同一個上下文環境,他們不能直接通訊,必須經過消息完成服務器

  1. 腳本限制

Worker線程不能執行alert()方法confirm()方法,但可使用XMLHttpRequest對象發出Ajax請求網絡

  1. 文件限制

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

二.基本用法

2.1主線程

主線程採用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()

2.2Worker線程

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內部關閉自身

2.3worker加載腳本

Worker內部若是要加載其它腳本,有一個專門的方法importScript()

ImportScript(‘script.js’)

加載多個腳本

ImportScript(‘script1.js’,’script2.js’,’script3.js’)

2.4錯誤處理

主線程能夠監聽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事件

2.5關閉worker

使用完畢,爲了節省系統資源,必須關閉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])

四同頁面的web worker

一般狀況下,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線程完成輪詢

有時候瀏覽器須要輪詢服務器狀態,以便第一時間得知狀態改變。這個工資能夠放在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

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()

}

七 API

7.1主線程

瀏覽器原生提供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線程

7.2worker線程

Web worker有本身的全局對象,不是主線程的window,而是一個專門爲Worker定製的全局對象,所以定義再window上面的對象和方法不是所有均可以

Worker線程有一些本身的全局屬性和方法

Self.nama: worker的名字,該屬性只讀,由構造函數指定

Self.onmessageerror:指定messageerror事件的監聽函數。發送的樹沒法序列號成字符串時,會觸發這個事件

Self.close():關閉worker線程

Self.postMessage():向產生這個worker線程發送消息

Self.importScript():加載JS腳本

相關文章
相關標籤/搜索