国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

JavaScript Web Worker

2018-07-24 11:54 更新

目錄

概述

JavaScript語言采用的是單線程模型,也就是說,所有任務排成一個隊列,一次只能做一件事。隨著電腦計算能力的增強,尤其是多核CPU的出現(xiàn),這一點帶來很大的不便,無法充分發(fā)揮JavaScript的潛力。

Web Worker的目的,就是為JavaScript創(chuàng)造多線程環(huán)境,允許主線程將一些任務分配給子線程。在主線程運行的同時,子線程在后臺運行,兩者互不干擾。等到子線程完成計算任務,再把結果返回給主線程。因此,每一個子線程就好像一個“工人”(worker),默默地完成自己的工作。這樣做的好處是,一些高計算量或高延遲的工作,被worker線程負擔了,所以主進程(通常是UI進程)就會很流暢,不會被阻塞或拖慢。

Worker線程分成好幾種。

  • 普通的Worker:只能與創(chuàng)造它們的主進程通信。
  • Shared Worker:能被所有同源的進程獲?。ū热鐏碜圆煌臑g覽器窗口、iframe窗口和其他Shared worker),它們必須通過一個端口通信。
  • ServiceWorker:實際上是一個在網絡應用與瀏覽器或網絡層之間的代理層。它可以攔截網絡請求,使得離線訪問成為可能。

Web Worker有以下幾個特點:

  • 同域限制。子線程加載的腳本文件,必須與主線程的腳本文件在同一個域。

  • DOM限制。子線程所在的全局對象,與主進程不一樣,它無法讀取網頁的DOM對象,即document、window、parent這些對象,子線程都無法得到。(但是,navigator對象和location對象可以獲得。)

  • 腳本限制。子線程無法讀取網頁的全局變量和函數(shù),也不能執(zhí)行alert和confirm方法,不過可以執(zhí)行setInterval和setTimeout,以及使用XMLHttpRequest對象發(fā)出AJAX請求。

  • 文件限制。子線程無法讀取本地文件,即子線程無法打開本機的文件系統(tǒng)(file://),它所加載的腳本,必須來自網絡。

使用之前,檢查瀏覽器是否支持這個API。

if (window.Worker) {
  // 支持
} else {
  // 不支持
}

新建和啟動子線程

主線程采用new命令,調用Worker構造函數(shù),可以新建一個子線程。

var worker = new Worker('work.js');

Worker構造函數(shù)的參數(shù)是一個腳本文件,這個文件就是子線程所要完成的任務,上面代碼中是work.js。由于子線程不能讀取本地文件系統(tǒng),所以這個腳本文件必須來自網絡端。如果下載沒有成功,比如出現(xiàn)404錯誤,這個子線程就會默默地失敗。

子線程新建之后,并沒有啟動,必需等待主線程調用postMessage方法,即發(fā)出信號之后才會啟動。postMessage方法的參數(shù),就是主線程傳給子線程的信號。它可以是一個字符串,也可以是一個對象。

worker.postMessage("Hello World");
worker.postMessage({method: 'echo', args: ['Work']});

只要符合父線程的同源政策,Worker線程自己也能新建Worker線程。Worker線程可以使用XMLHttpRequest進行網絡I/O,但是XMLHttpRequest對象的responseXMLchannel屬性總是返回null。

子線程的事件監(jiān)聽

在子線程內,必須有一個回調函數(shù),監(jiān)聽message事件。

/* File: work.js */

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

self代表子線程自身,self.addEventListener表示對子線程的message事件指定回調函數(shù)(直接指定onmessage屬性的值也可)?;卣{函數(shù)的參數(shù)是一個事件對象,它的data屬性包含主線程發(fā)來的信號。self.postMessage則表示,子線程向主線程發(fā)送一個信號。

根據(jù)主線程發(fā)來的不同的信號值,子線程可以調用不同的方法。

/* File: work.js */

self.onmessage = function(event) {
  var method = event.data.method;
  var args = event.data.args;

  var reply = doSomething(args);
  self.postMessage({method: method, reply: reply});
};

主線程的事件監(jiān)聽

主線程也必須指定message事件的回調函數(shù),監(jiān)聽子線程發(fā)來的信號。

/* File: main.js */

worker.addEventListener('message', function(e) {
	console.log(e.data);
}, false);

錯誤處理

主線程可以監(jiān)聽子線程是否發(fā)生錯誤。如果發(fā)生錯誤,會觸發(fā)主線程的error事件。

worker.onerror(function(event) {
  console.log(event);
});

// or

worker.addEventListener('error', function(event) {
  console.log(event);
});

關閉子線程

使用完畢之后,為了節(jié)省系統(tǒng)資源,我們必須在主線程調用terminate方法,手動關閉子線程。

worker.terminate();

也可以子線程內部關閉自身。

self.close();

主線程與子線程的數(shù)據(jù)通信

前面說過,主線程與子線程之間的通信內容,可以是文本,也可以是對象。需要注意的是,這種通信是拷貝關系,即是傳值而不是傳址,子線程對通信內容的修改,不會影響到主線程。事實上,瀏覽器內部的運行機制是,先將通信內容串行化,然后把串行化后的字符串發(fā)給子線程,后者再將它還原。

主線程與子線程之間也可以交換二進制數(shù)據(jù),比如File、Blob、ArrayBuffer等對象,也可以在線程之間發(fā)送。

但是,用拷貝方式發(fā)送二進制數(shù)據(jù),會造成性能問題。比如,主線程向子線程發(fā)送一個500MB文件,默認情況下瀏覽器會生成一個原文件的拷貝。為了解決這個問題,JavaScript允許主線程把二進制數(shù)據(jù)直接轉移給子線程,但是一旦轉移,主線程就無法再使用這些二進制數(shù)據(jù)了,這是為了防止出現(xiàn)多個線程同時修改數(shù)據(jù)的麻煩局面。這種轉移數(shù)據(jù)的方法,叫做Transferable Objects

如果要使用該方法,postMessage方法的最后一個參數(shù)必須是一個數(shù)組,用來指定前面發(fā)送的哪些值可以被轉移給子線程。

worker.postMessage(arrayBuffer, [arrayBuffer]);
window.postMessage(arrayBuffer, targetOrigin, [arrayBuffer]);

同頁面的Web Worker

通常情況下,子線程載入的是一個單獨的JavaScript文件,但是也可以載入與主線程在同一個網頁的代碼。假設網頁代碼如下:

<!DOCTYPE html>
    <body>
        <script id="worker" type="app/worker">

            addEventListener('message', function() {
                postMessage('Im reading Tech.pro');
            }, false);
        </script>
    </body>
</html>

我們可以讀取頁面中的script,用worker來處理。

var blob = new Blob([document.querySelector('#worker').textContent]);

這里需要把代碼當作二進制對象讀取,所以使用Blob接口。然后,這個二進制對象轉為URL,再通過這個URL創(chuàng)建worker。

var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);

部署事件監(jiān)聽代碼。

worker.addEventListener('message', function(e) {
   console.log(e.data);
}, false);

最后,啟動worker。

worker.postMessage('');

整個頁面的代碼如下:

<!DOCTYPE html>
<body>
  <script id="worker" type="app/worker">
    addEventListener('message', function() {
      postMessage('Work done!');
    }, false);
   </script>

  <script>
    (function() {
      var blob = new Blob([document.querySelector('#worker').textContent]);
      var url = window.URL.createObjectURL(blob);
      var worker = new Worker(url);

      worker.addEventListener('message', function(e) {
        console.log(e.data);
      }, false);

      worker.postMessage('');
    })();
  </script>
</body>
</html>

可以看到,主線程和子線程的代碼都在同一個網頁上面。

上面所講的Web Worker都是專屬于某個網頁的,當該網頁關閉,worker就自動結束。除此之外,還有一種共享式的Web Worker,允許多個瀏覽器窗口共享同一個worker,只有當所有網口關閉,它才會結束。這種共享式的Worker用SharedWorker對象來建立,因為適用場合不多,這里就省略了。

實例:Worker 進程完成論詢

有時,瀏覽器需要論詢服務器狀態(tài),以便第一時間得知狀態(tài)改變。這個工作可以放在 Worker 進程里面。

var pollingWorker = createWorker(function (e) {
  var cache;

  function compare(new, old) { ... };

  var myRequest = new Request('/my-api-endpoint');

  setInterval(function () {
    fetch('/my-api-endpoint').then(function (res) {
      var data = res.json();

      if(!compare(res.json(), cache)) {
        cache = data;

        self.postMessage(data);
      }
    })
  }, 1000)
});

pollingWorker.onmessage = function () {
  // render data
}

pollingWorker.postMessage('init');

Service Worker

Service worker是一個在瀏覽器后臺運行的腳本,與網頁不相干,專注于那些不需要網頁或用戶互動就能完成的功能。它主要用于操作離線緩存。

Service Worker有以下特點。

  • 屬于JavaScript Worker,不能直接接觸DOM,通過postMessage接口與頁面通信。
  • 不需要任何頁面,就能執(zhí)行。
  • 不用的時候會終止執(zhí)行,需要的時候又重新執(zhí)行,即它是事件驅動的。
  • 有一個精心定義的升級策略。
  • 只在HTTPs協(xié)議下可用,這是因為它能攔截網絡請求,所以必須保證請求是安全的。
  • 可以攔截發(fā)出的網絡請求,從而控制頁面的網路通信。
  • 內部大量使用Promise。

Service worker的常見用途。

  • 通過攔截網絡請求,使得網站運行得更快,或者在離線情況下,依然可以執(zhí)行。
  • 作為其他后臺功能的基礎,比如消息推送和背景同步。

使用Service Worker有以下步驟。

首先,需要向瀏覽器登記Service Worker。

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(function(registration) {
    // 登記成功
    console.log('ServiceWorker登記成功,范圍為', registration.scope);
    }).catch(function(err) {
    // 登記失敗
      console.log('ServiceWorker登記失敗:', err);
    });
}

上面代碼向瀏覽器登記sw.js腳本,實質就是瀏覽器加載sw.js。這段代碼可以多次調用,瀏覽器會自行判斷sw.js是否登記過,如果已經登記過,就不再重復執(zhí)行了。注意,Service worker腳本必須與頁面在同一個域,且必須在HTTPs協(xié)議下正常運行。

sw.js位于域名的根目錄下,這表明這個Service worker的范圍(scope)是整個域,即會接收整個域下面的fetch事件。如果腳本的路徑是/example/sw.js,那么Service worker只對/example/開頭的URL有效(比如/example/page1/、/example/page2/)。如果腳本不在根目錄下,但是希望對整個域都有效,可以指定scope屬性。

navigator.serviceWorker.register('/path/to/serviceworker.js', {
  scope: '/'
});

一旦登記完成,這段腳本就會用戶的瀏覽器之中長期存在,不會隨著用戶離開你的網站而消失。

.register方法返回一個Promise對象。

登記成功后,瀏覽器執(zhí)行下面步驟。

  1. 下載資源(Download)
  2. 安裝(Install)
  3. 激活(Activate)

安裝和激活,主要通過事件來判斷。

self.addEventListener('install', function(event) {
  event.waitUntil(
    fetchStuffAndInitDatabases()
  );
});

self.addEventListener('activate', function(event) {
  // You're good to go!
});

Service worker一旦激活,就開始控制頁面。網頁加載的時候,可以選擇一個Service worker作為自己的控制器。不過,頁面第一次加載的時候,它不受Service worker控制,因為這時還沒有一個Service worker在運行。只有重新加載頁面后,Service worker才會生效,控制加載它的頁面。

你可以查看navigator.serviceWorker.controller,了解當前哪個ServiceWorker掌握控制權。如果后臺沒有任何Service worker,navigator.serviceWorker.controller返回null。

Service worker激活以后,就能監(jiān)聽fetch事件。

self.addEventListener('fetch', function(event) {
  console.log(event.request);
});

fetch事件會在兩種情況下觸發(fā)。

  • 用戶訪問Service worker范圍內的網頁。
  • 這些網頁發(fā)出的任何網絡請求(頁面本身、CSS、JS、圖像、XHR等等),即使這些請求是發(fā)向另一個域。但是,iframe<object>標簽發(fā)出的請求不會被攔截。

fetch事件的event對象的request屬性,返回一個對象,包含了所攔截的網絡請求的所有信息,比如URL、請求方法和HTTP頭信息。

Service worker的強大之處,在于它會攔截請求,并會返回一個全新的回應。

self.addEventListener('fetch', function(event) {
  event.respondWith(new Response("Hello world!"));
});

respondWith方法的參數(shù)是一個Response對象實例,或者一個Promise對象(resolved以后返回一個Response實例)。上面代碼手動創(chuàng)造一個Response實例。

下面是完整的代碼。

先看網頁代碼index.html。

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      white-space: pre-line;
      font-family: monospace;
      font-size: 14px;
    }
  </style>
</head>
<body><script>
    function log() {
      document.body.appendChild(document.createTextNode(Array.prototype.join.call(arguments, ", ") + '\n'));
      console.log.apply(console, arguments);
    }
    window.onerror = function(err) {
      log("Error", err);
    };
    navigator.serviceWorker.register('sw.js', {
      scope: './'
    }).then(function(sw) {
      log("Registered!", sw);
      log("You should get a different response when you refresh");
    }).catch(function(err) {
      log("Error", err);
    });
  </script></body>
</html>

然后是Service worker腳本sw.js。

// The SW will be shutdown when not in use to save memory,
// be aware that any global state is likely to disappear
console.log("SW startup");

self.addEventListener('install', function(event) {
  console.log("SW installed");
});

self.addEventListener('activate', function(event) {
  console.log("SW activated");
});

self.addEventListener('fetch', function(event) {
  console.log("Caught a fetch!");
  event.respondWith(new Response("Hello world!"));
});

每一次瀏覽器向服務器要求一個文件的時候,就會觸發(fā)fetch事件。Service worker可以在發(fā)出這個請求之前,前攔截它。

self.addEventListener('fetch', function (event) {
  var request = event.request;
  ...
});

實際應用中,我們使用fetch方法去抓取資源,該方法返回一個Promise對象。

self.addEventListener('fetch', function(event) {
  if (/\.jpg$/.test(event.request.url)) {
    event.respondWith(
      fetch('//www.google.co.uk/logos/example.gif', {
        mode: 'no-cors'
      })
    );
  }
});

上面代碼中,如果網頁請求JPG文件,就會被Service worker攔截,轉而返回一個Google的Logo圖像。fetch方法默認會加上CORS信息頭,,上面設置了取消這個頭。

下面的代碼是一個將所有JPG、PNG圖片請求,改成WebP格式返回的例子。

"use strict";

// Listen to fetch events
self.addEventListener('fetch', function(event) {

  // Check if the image is a jpeg
  if (/\.jpg$|.png$/.test(event.request.url)) {
    // Inspect the accept header for WebP support
    var supportsWebp = false;
    if (event.request.headers.has('accept')){
      supportsWebp = event.request.headers.get('accept').includes('webp');
    }

    // If we support WebP
    if (supportsWebp) {
      // Clone the request
      var req = event.request.clone();
      // Build the return URL
      var returnUrl = req.url.substr(0, req.url.lastIndexOf(".")) + ".webp";
      event.respondWith(fetch(returnUrl, {
        mode: 'no-cors'
      }));
    }
  }
});

如果請求失敗,可以通過Promise的catch方法處理。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    fetch(event.request).catch(function() {
      return new Response("Request failed!");
    })
  );
});

登記成功后,可以在Chrome瀏覽器訪問chrome://inspect/#service-workers,查看整個瀏覽器目前正在運行的Service worker。訪問chrome://serviceworker-internals,可以查看瀏覽器目前安裝的所有Service worker。

一個已經登記過的Service worker腳本,如果發(fā)生改動,瀏覽器就會重新安裝,這被稱為“升級”。

Service worker有一個Cache API,用來緩存外部資源。

self.addEventListener('install', function(event) {
  // pre cache a load of stuff:
  event.waitUntil(
    caches.open('myapp-static-v1').then(function(cache) {
      return cache.addAll([
        '/',
        '/styles/all.css',
        '/styles/imgs/bg.png',
        '/scripts/all.js'
      ]);
    })
  )
});

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});

上面代碼中,caches.open方法用來建立緩存,然后使用addAll方法添加資源。caches.match方法則用來建立緩存以后,匹配當前請求是否在緩存之中,如果命中就取出緩存,否則就正常發(fā)出這個請求。一旦一個資源進入緩存,它原來指定是否過期的HTTP信息頭,就會被忽略。緩存之中的資源,只在你移除它們的時候,才會被移除。

單個資源可以使用cache.put(request, response)方法添加。

下面是一個在安裝階段緩存資源的例子。

var staticCacheName = 'static';
var version = 'v1::';

self.addEventListener('install', function (event) {
  event.waitUntil(updateStaticCache());
});

function updateStaticCache() {
  return caches.open(version + staticCacheName)
    .then(function (cache) {
      return cache.addAll([
        '/path/to/javascript.js',
        '/path/to/stylesheet.css',
        '/path/to/someimage.png',
        '/path/to/someotherimage.png',
        '/',
        '/offline'
      ]);
    });
};

上面代碼將JavaScript腳本、CSS樣式表、圖像文件、網站首頁、離線頁面,存入瀏覽器緩存。這些資源都要等全部進入緩存之后,才會安裝。

安裝以后,就需要激活。

self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys()
      .then(function (keys) {
        return Promise.all(keys
          .filter(function (key) {
            return key.indexOf(version) !== 0;
          })
          .map(function (key) {
            return caches.delete(key);
          })
        );
      })
  );
});

參考鏈接

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號