HTML5新特性之Web Workers

我們知道瀏覽器端JavaScript是以單線程的方式執行的,也就是說JavaScript和UI渲染佔用同一個主線程,那就意味着,如果JavaScript進行高負載的數據處理,UI渲染就很有可能被阻斷,從而造成用戶體驗的大打折扣。Web Workers作爲HTML5新特性之一,爲瀏覽器端JavaScript開創了一種新的運行模式,使之能夠在另外的線程中創建新的運行環境,以便使JavaScript能夠在後臺做一些費時的處理。下面我們就來詳細介紹一下Web Workers方面的知識。

Web Workers可以通過加載一個腳本文件,進而創建一個獨立的工作線程,在主線程之外運行。工作線程的創建比較簡單,代碼如下:

var worker = new Worker('js/worker.js');
如代碼所示,我們只需將含有工作線程的代碼文件路徑作爲參數傳入到Worker構造函數中,即可創建一個工作線程,使其能夠在後臺執行。需要注意的是,腳本的加載有同源策略的限制,所以必須指定與主線程同源的文件。

在創建完工作線程後,我們就可以在主線程中與工作線程通信了。主線程中通過postMessage方法向工作線程發送消息,也可以通過捕獲worker實例的message事件來接收來自工作線程的消息。需要注意的是,在postMessage方法中,我們可以相對自由的傳遞各種類型的數據,但是像document等BOM對象是無法被傳遞的。下面就讓我們來看看主線程中是如何和工作線程交互的:

//message sending
worker.postMessage({name: 'Scott'});

//message receiving
worker.onmessage = function(event) {
	var data = event.data;
	//to do
}
同樣的,在工作線程中我們也可以通過postMessage和onmessage方法來發送和接收數據,下面是工作線程文件worker.js中的代碼:

//message sending
self.postMessage({name: 'Worker'});

//message receiving
self.onmessage = function(event) {
	var data = event.data;
	//to do
}

在某些情況下,我們不得不中斷工作線程的運行,主線程中我們可以調用worker實例的terminate方法,在工作線程中我們也可以調用close方法中斷自身,代碼如下:

//interrupt a work thread in main thread
worker.terminate();

//interrupt work thread it self
self.close();
以上介紹了Web Workers的基本用法,接下來我們來以一個實例說明它在實際項目中是如何應用的。

我們經常會加入一些技術羣組裏探討技術,廣交朋友,如果羣組成員太多,查看起來就不太方便,爲此我們打算給這個羣組加一個搜索的功能,根據關鍵字搜索與成員信息匹配的數據,就像下面圖中所示:


在這個案例中,羣組成員信息預先加載到了客戶端,搜索功能也是在客戶端完成的,假如數據非常多,一個簡單的搜索可能就會在用戶體驗上造成不小的折扣,我們打算用Web Workers來實現這個操作。現在來看一下示例項目的基本目錄結構:


我們可以看到,在js目錄中有個worker.js文件,用於放置工作線程的邏輯代碼,此文件在運行時被主線程加載,我們稍後會詳細講解。現在我們先看一下主頁面的基本結構:

<html>
	<head>
		<title>Web Worker</title>
		<link rel="stylesheet" type="text/css" href="css/main.css">
	</head>
	<body>
		<div id="searching">
			<input id="keywords" type="text" placeholder="type in to start searching">
			<button id="search-button" οnclick="search();">search</button>
			<div id="members">
				<!-- <div class="member">
					<img src="img/icon.png">
					<div class="info">
						<p>John Li</p>
						<p>Java programming, Python language</p>
					</div>
					<a class="add">+Add</a>
				</div> -->
				<!-- the search results will be here -->
			</div>
		</div>
		<script type="text/javascript" src="js/jquery.js"></script>
		<script type="text/javascript" src="js/main.js"></script>
	</body>
</html>
看起來很簡單,一個搜索框和搜索按鈕,下面是搜索結果顯示區域。爲了使其運行起來,我們還需要main.js的配合,代碼如下:

//a simple group member data example
var groupMembers = [
	{
		id: 101,
		name: 'John Li',
		skills: 'Java programming, Python language, MySQL'
	},
	{
		id: 102,
		name: 'Lisa Wang',
		skills: 'HTML, JavaScript, CSS, Node.js'
	},
	{
		id: 103,
		name: 'Tom Wang',
		skills: 'JavaScript, CSS, HTML5'
	},
	{
		id: 104,
		name: 'Andy Zhang',
		skills: 'PHP language, JavaScript programming, CSS'
	}
	/*
		a large number of data
	*/
];

//loading the worker.js to instantiate a Worker object
var worker = new Worker('js/worker.js');

//receive message from work thread, and then render the result data
worker.onmessage = function(e) {
	renderGroupMembers(e.data.results);
};

function search() {
	var keywords = $('#keywords').val().trim();
	
	//post a message to work thread
	worker.postMessage({
		groupMembers: groupMembers,
		keywords: keywords
	});
}

function renderGroupMembers(members) {
	var html = '';
	members.forEach(function(member) {
		var resultHtml = '<div class="member">'
			 		   + '	<img class="icon" src="img/icon.png">'
			 		   + '	<div class="info">'
			 		   + '		<p>' + member.name + '</p>'
			 		   + '		<p>' + member.skills + '</p>'
			 		   + '	</div>'
			 		   + '	<a class="add">+Add</a>'
			 		   + '</div>';
		html += resultHtml;
	});

	$('#members').html(html);
}

renderGroupMembers(groupMembers);
在這段代碼中,我們首先會加載worker.js,實例化一個Worker對象,然後捕獲message事件,進而將搜索結果顯示到相應區域。而search函數負責監聽搜索按鈕的點擊事件,然後向工作線程發送帶有羣組數據和關鍵字的消息,將搜索任務交給工作線程。

最後,我們開看看worker.js是如何運行的:

//receive the message from main thread
self.onmessage = function(e) {

	var groupMembers = e.data.groupMembers;
	var keywords = e.data.keywords;

	var results = searchByKeywords(groupMembers, keywords);

	//post the result message to main thread
	self.postMessage({results: results});
};

//it may be quite complicated in real application
function searchByKeywords(groupMembers, keywords) {
	var results = [];

	keywords = keywords.toLowerCase();

	groupMembers.forEach(function(member) {
		var nameMatched = member.name.toLowerCase().indexOf(keywords) > -1;
		var skillsMatched = member.skills.toLowerCase().indexOf(keywords) > -1;
		
		if (nameMatched || skillsMatched) {
			results.push(member);
		}
	});

	return results;
}
從上面的代碼中可以看到,首先我們會捕獲工作線程的message事件,接收羣組數據和關鍵字,然後調用搜索函數,最後將搜索結果發送回主線程,完成這次的搜索任務。

以上就是Web Workers的介紹,在實際開發中,可能面臨很多複雜的操作需要在前端完成,我們都可以考慮用Web Workers實現,將複雜操作交給工作線程,進而減少主線程的阻塞,在用戶體驗上一定會有顯著的提升。

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