??隨著 HTML5 的出現(xiàn)食呻,面向未來 Web 應(yīng)用的 JavaScript API 也得到了極大的發(fā)展。這些 API 沒有包含在 HTML5 規(guī)范中澎现,而是各自有各自的規(guī)范仅胞。
??但是,他們都屬于“HTML5 相關(guān)的 API”昔头。本節(jié)介紹的所有 API 都在持續(xù)制定中饼问,還沒有完全固定下來。
??無論如何揭斧,瀏覽器已經(jīng)著手實現(xiàn)這些 API,而 Web 應(yīng)用開發(fā)人員也都開始使用它們了峻堰。
??其中很多 API 都帶有特定于瀏覽器的前綴讹开,比如微軟是 ms,而Chrome 和 Safari 是 webkit捐名。通過添加這些前綴旦万,不同的瀏覽器可以測試還在開發(fā)中的新 API,不過請記住镶蹋,去掉前綴之后的部分在所有瀏覽器中都是一致的成艘。
1赏半、requestAnimationFrame()
??很長時間以來,計時器和循環(huán)間隔一直都是 JavaScript 動畫的最核心技術(shù)淆两。雖然 CSS 變換及動畫為 Web 開發(fā)人員提供了實現(xiàn)動畫的簡單手段断箫,但 JavaScript 動畫開發(fā)領(lǐng)域的狀況這些年來并沒有大的變化。
??Firefox 4 最早為 JavaScript 動畫添加了一個新 API秋冰,即 mozRequestAnimationFrame()仲义。這個方法會告訴瀏覽器:有一個動畫開始了。進(jìn)而瀏覽器就可以確定重繪的最佳方式剑勾。
1.1埃撵、早期動畫循環(huán)
??在 JavaScript 中創(chuàng)建動畫的典型方式,就是使用 setInterval() 方法來控制所有動畫虽另。以下是一個使用 setInterval() 的基本動畫循環(huán):
(function(){
function updateAnimations(){
doAnimation1();
doAnimation2();
// 其他動畫
}
setInterval(updateAnimations, 100);
})();
??為了創(chuàng)建一個小型動畫庫暂刘,updateAnimations() 方法就得不斷循環(huán)地運(yùn)行每個動畫,并相應(yīng)地改變不同元素的狀態(tài)(例如捂刺,同時顯示一個新聞跑馬燈和一個進(jìn)度條)谣拣。
??如果沒有動畫需要更新,這個方法可以退出叠萍,什么也不用做芝发,甚至可以把動畫循環(huán)停下來,等待下一次需要更新的動畫苛谷。
??編寫這種動畫循環(huán)的關(guān)鍵是要知道延遲時間多長合適辅鲸。一方面,循環(huán)間隔必須足夠短腹殿,這樣才能讓不同的動畫效果顯得更平滑流暢独悴;另一方面,循環(huán)間隔還要足夠長锣尉,這樣才能確保瀏覽器有能力渲染產(chǎn)
生的變化刻炒。
??大多數(shù)電腦顯示器的刷新頻率是 60Hz,大概相當(dāng)于每秒鐘重繪 60 次自沧。大多數(shù)瀏覽器都會對重繪操作加以限制坟奥,不超過顯示器的重繪頻率,因為即使超過那個頻率用戶體驗也不會有提升拇厢。
??因此爱谁,最平滑動畫的最佳循環(huán)間隔是 1000ms/60,約等于 17ms孝偎。以這個循環(huán)間隔重繪的動畫是最平滑的访敌,因為這個速度最接近瀏覽器的最高限速。為了適應(yīng) 17ms 的循環(huán)間隔衣盾,多重動畫可能需要加以節(jié)制寺旺,以便不會完成得太快爷抓。
??雖然與使用多組 setTimeout() 的循環(huán)方式相比,使用 setInterval() 的動畫循環(huán)效率更高阻塑,但后者也不是沒有問題蓝撇。無論是 setInterval() 還是 setTimeout() 都不十分精確。
??為它們傳入的第二個參數(shù)叮姑,實際上只是指定了把動畫代碼添加到瀏覽器 UI 線程隊列中以等待執(zhí)行的時間唉地。如果隊列前面已經(jīng)加入了其他任務(wù),那動畫代碼就要等前面的任務(wù)完成后再執(zhí)行传透。
??簡言之耘沼,以毫秒表示的延遲時間并不代表到時候一定會執(zhí)行動畫代碼,而僅代表到時候會把代碼添加到任務(wù)隊列中朱盐。如果 UI 線程繁忙群嗤,比如忙于處理用戶操作,那么即使把代碼加入隊列也不會立即執(zhí)行兵琳。
1.2狂秘、循環(huán)間隔的問題
??知道什么時候繪制下一幀是保證動畫平滑的關(guān)鍵。然而躯肌,直至最近者春,開發(fā)人員都沒有辦法確保瀏覽器按時繪制下一幀。
??隨著<canvas>元素越來越流行清女,新的基于瀏覽器的游戲也開始嶄露頭腳钱烟,面對不十分精確的 setInterval() 和 setTimeout(),開發(fā)人員一籌莫展嫡丙。
??瀏覽器使用的計時器的精度進(jìn)一步惡化了問題拴袭。具體地說,瀏覽器使用的計時器并非精確到毫秒級別曙博。以下是幾個瀏覽器的計時器精度拥刻。
- IE8 及更早版本的計時器精度為 15.625ms。
- IE9 及更晚版本的計時器精度為 4ms父泳。
- Firefox 和 Safari 的計時器精度大約為 10ms般哼。
- Chrome 的計時器精度為 4ms。
??IE9 之前版本的計時器精度為 15.625ms惠窄,因此介于 0 和 15 之間的任何值只能是 0 和 15逝她。
??IE9 把計時器精度提高到了 4ms,但這個精度對于動畫來說仍然不夠明確睬捶。Chrome 的計時器精度為 4ms,而 Firefox 和 Safari 的精度是 10ms近刘。
??更為復(fù)雜的是擒贸,瀏覽器都開始限制后臺標(biāo)簽頁或不活動標(biāo)簽頁的計時器臀晃。因此,即使你優(yōu)化了循環(huán)間隔介劫,結(jié)果仍然只能接近你想要的效果徽惋。
1.2、mozRequestAnimationFrame()
??Mozilla 的 Robert O’Callahan 認(rèn)識到了這個問題座韵,提出了一個非常獨特的方案险绘。他指出,CSS 變換和動畫的優(yōu)勢在于瀏覽器知道動畫什么時候開始誉碴,因此會計算出正確的循環(huán)間隔宦棺,在恰當(dāng)?shù)臅r候刷新 UI。
??而對于 JavaScript 動畫黔帕,瀏覽器無從知曉什么時候開始代咸。因此他的方案就是創(chuàng)造一個新方法 mozRequestAnimationFrame(),通過它告訴瀏覽器某些 JavaScript 代碼將要執(zhí)行動畫成黄。這樣瀏覽器可以在運(yùn)行某些代碼后進(jìn)行適當(dāng)?shù)膬?yōu)化呐芥。
??mozRequestAnimationFrame() 方法接收一個參數(shù),即在重繪屏幕前調(diào)用的一個函數(shù)奋岁。這個函數(shù)負(fù)責(zé)改變下一次重繪時的 DOM 樣式思瘟。為了創(chuàng)建動畫循環(huán),可以像以前使用 setTimeout() 一樣闻伶,把多個對 mozRequestAnimationFrame() 的調(diào)用連綴起來滨攻。比如:
function updateProgress(){
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.left != "100%"){
mozRequestAnimationFrame(updateProgress);
}
}
mozRequestAnimationFrame(updateProgress);
??因為 mozRequestAnimationFrame() 只運(yùn)行一次傳入的函數(shù),因此在需要再次修改 UI 從而生成動畫時虾攻,需要再次手工調(diào)用它铡买。同樣,也需要同時考慮什么時候停止動畫霎箍。這樣就能得到非常平滑流暢的動畫奇钞。
??目前來看,mozRequestAnimationFrame() 解決了瀏覽器不知道 JavaScript 動畫什么時候開始漂坏、不知道最佳循環(huán)間隔時間的問題景埃,但不知道代碼到底什么時候執(zhí)行的問題呢?同樣的方案也可以解決這個問題顶别。
??我們傳遞的 mozRequestAnimationFrame() 函數(shù)也會接收一個參數(shù)谷徙,它是一個時間碼(從 1970年 1 月 1 日起至今的毫秒數(shù)),表示下一次重繪的實際發(fā)生時間驯绎。注意完慧,這一點很重要:mozRequestAnimationFrame() 會根據(jù)這個時間碼設(shè)定將來的某個時刻進(jìn)行重繪,而根據(jù)這個時間碼剩失,你也能知道那個時刻是什么時間屈尼。然后册着,再優(yōu)化動畫效果就有了依據(jù)。
??要知道距離上一次重繪已經(jīng)過去了多長時間脾歧,可以查詢mozAnimationStartTime甲捏,其中包含上一次重繪的時間碼。用傳入回調(diào)函數(shù)的時間碼減去這個時間碼鞭执,就能計算出在屏幕上重繪下一組變化之前要經(jīng)過多長時間司顿。使用這個值的典型方式如下:
function draw(timestamp){
// 計算兩次重繪的時間間隔
var diff = timestamp - startTime;
// 使用 diff 確定下一步的繪制時間
// 把 startTime 重寫為這一次的繪制時間
startTime = timestamp;
// 重繪 UI
mozRequestAnimationFrame(draw);
}
var startTime = mozAnimationStartTime;
mozRequestAnimationFrame(draw);
??這里的關(guān)鍵是第一次讀取 mozAnimationStartTime 的值,必須在傳遞給 mozRequestAnimationFrame() 的回調(diào)函數(shù)外面進(jìn)行兄纺。如果是在回調(diào)函數(shù)內(nèi)部讀取 mozAnimationStartTime大溜,得到的值與傳入的時間碼是相等的。
1.4囤热、webkitRequestAnimationFrame 與 msRequestAnimationFrame
??基于 mozRequestAnimationFrame()猎提,Chrome 和 IE10+也都給出了自己的實現(xiàn),分別叫 webkitRequestAnimationFrame() 和 msRequestAnimationFrame()旁蔼。這兩個版本與 Mozilla 的版本有兩個方面的微小差異锨苏。
??首先,不會給回調(diào)函數(shù)傳遞時間碼棺聊,因此你無法知道下一次重繪將發(fā)生在什么時間伞租。
??其次,Chrome 又增加了第二個可選的參數(shù)限佩,即將要發(fā)生變化的 DOM 元素葵诈。知道了重繪將發(fā)生在頁面中哪個特定元素的區(qū)域內(nèi),就可以將重繪限定在該區(qū)域中祟同。
??既然沒有下一次重繪的時間碼作喘,那 Chrome 和 IE 沒有提供 mozAnimationStartTime 的實現(xiàn)也就很容易理解了——沒有那個時間碼,實現(xiàn)這個屬性也沒有什么用晕城。
??不過泞坦,Chrome 倒是又提供了另一個方法 webkitCancelAnimationFrame(),用于取消之前計劃執(zhí)行的重繪操作砖顷。
??假如你不需要知道精確的時間差贰锁,那么可以在 Firefox 4+、IE10+和 Chrome 中可以參考以下模式創(chuàng)建動畫循環(huán)滤蝠。
(function(){
function draw(timestamp){
// 計算兩次重繪的時間間隔
var drawStart = (timestamp || Date.now()),
diff = drawStart - startTime;
// 使用 diff 確定下一步的繪制時間
// 把 startTime 重寫為這一次的繪制時間
startTime = drawStart;
// 重繪 UI
requestAnimationFrame(draw);
}
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame,
startTime = window.mozAnimationStartTime || Date.now();
requestAnimationFrame(draw);
})();
??以上模式利用已有的功能創(chuàng)建了一個動畫循環(huán)豌熄,大致計算出了兩次重繪的時間間隔。在 Firefox 中物咳,計算時間間隔使用的是既有的時間碼锣险,而在 Chrome 和 IE 中,則使用不十分精確的 Date 對象。這個模
式可以大致體現(xiàn)出兩次重繪的時間間隔囱持,但不會告訴你在 Chrome 和 IE 中的時間間隔到底是多少夯接。不過,大致知道時間間隔總比一點兒概念也沒有好些纷妆。
??因為首先檢測的是標(biāo)準(zhǔn)函數(shù)名,其次才是特定于瀏覽器的版本晴弃,所以這個動畫循環(huán)在將來也能夠使用掩幢。
??目前,W3C 已經(jīng)著手起草 requestAnimationFrame() API上鞠,而且作為 Web Performance Group 的一部分际邻,Mozilla 和 Google 正共同參與該標(biāo)準(zhǔn)草案的制定工作。
2芍阎、Page Visibility API
??不知道用戶是不是正在與頁面交互世曾,這是困擾廣大 Web 開發(fā)人員的一個主要問題。如果頁面最小化了或者隱藏在了其他標(biāo)簽頁后面谴咸,那么有些功能是可以停下來的轮听,比如輪詢服務(wù)器或者某些動畫效果。
??而 Page Visibility API(頁面可見性 API)就是為了讓開發(fā)人員知道頁面是否對用戶可見而推出的岭佳。
??這個 API 本身非常簡單血巍,由以下三部分組成。
- document.hidden:表示頁面是否隱藏的布爾值珊随。頁面隱藏包括頁面在后臺標(biāo)簽頁中或者瀏覽器最小化述寡。
- document.visibilityState:表示下列 4 個可能狀態(tài)的值。
- 頁面在后臺標(biāo)簽頁中或瀏覽器最小化叶洞。
- 頁面在前臺標(biāo)簽頁中鲫凶。
- 實際的頁面已經(jīng)隱藏,但用戶可以看到頁面的預(yù)覽(就像在 Windows 7 中衩辟,用戶把鼠標(biāo)移動到任務(wù)欄的圖標(biāo)上螟炫,就可以顯示瀏覽器中當(dāng)前頁面的預(yù)覽)。
- 頁面在屏幕外執(zhí)行預(yù)渲染處理惭婿。
- visibilitychange 事件:當(dāng)文檔從可見變?yōu)椴豢梢娀驈牟豢梢娮優(yōu)榭梢姇r不恭,觸發(fā)該事件。
??在編寫本書時财饥,只有 IE10 和 Chrome 支持 Page Visibility API换吧。IE 的版本是在每個屬性或事件前面加上 ms 前綴,而 Chrome 則是加上 webkit 前綴钥星。因此 document.hidden 在 IE 的實現(xiàn)中就是 document.msHidden沾瓦,而在 Chrome 的實現(xiàn)中則是 document.webkitHidden。檢查瀏覽器是否支持這個 API 的最佳方式如下:
function isHiddenSupported(){
return typeof (document.hidden || document.msHidden || document.webkitHidden) != "undefined";
}
??類似地,使用同樣的模式可以檢測頁面是否隱藏:
if (document.hidden || document.msHidden || document.webKitHidden) {
// 頁面隱藏了
} else {
// 頁面未隱藏
}
??注意贯莺,以上代碼在不支持該 API 的瀏覽器中會提示頁面未隱藏风喇。這是 Page Visibility API 有意設(shè)計的結(jié)果,目的是為了向后兼容缕探。
??為了在頁面從可見變?yōu)椴豢梢娀驈牟豢梢娮優(yōu)榭梢姇r收到通知魂莫,可以偵聽 visibilitychange 事件。在 IE 中爹耗,這個事件叫 msvisibilitychange耙考,而在 Chrome 中這個事件叫 webkitvisibilitychange。為了在兩個瀏覽器中都能偵聽到該事件潭兽,可以像下面的例子一樣倦始,為每個事件都指定相同的事件處理程序:
function handleVisibilityChange(){
var output = document.getElementById("output"),
msg;
if (document.hidden || document.msHidden || document.webkitHidden){
msg = "Page is now hidden. " + (new Date()) + "<br>";
} else {
msg = "Page is now visible. " + (new Date()) + "<br>";
}
output.innerHTML += msg;
}
// 要為兩個事件都指定事件處理程序
EventUtil.addHandler(document, "msvisibilitychange", handleVisibilityChange);
EventUtil.addHandler(document, "webkitvisibilitychange", handleVisibilityChange);
??以上代碼同時適用于 IE 和 Chrome。而且山卦,API 的這一部分已經(jīng)相對穩(wěn)定鞋邑,因此在實際的 Web 開發(fā)中也可以使用以上代碼。
??關(guān)于這一 API 的實現(xiàn)账蓉,差異最大的是 document.visibilityState 屬性枚碗。IE10 PR 2 的 document.msVisibilityState 是一個表示如下 4 種狀態(tài)的數(shù)字值。
????(1) document.MS_PAGE_HIDDEN (0)
????(2) document.MS_PAGE_VISIBLE (1)
????(3) document.MS_PAGE_PREVIEW (2)
????(4) document.MS_PAGE_PRERENDER (3)
??在 Chrome 中剔猿,document.webkitVisibilityState 可能是下列 3 個字符串值:
????(1) "hidden"
????(2) "visible"
????(3) "prerender"
??Chrome 并沒有給每個狀態(tài)定義對應(yīng)的常量视译,但最終的實現(xiàn)很可能會使用常量。由于存在以上差異归敬,所以建議大家先不要完全依賴帶前綴的 document.visibilityState酷含,最好只使用 document.hidden 屬性。
3汪茧、Geolocation API
??地理定位(geolocation)是最令人興奮椅亚,而且得到了廣泛支持的一個新 API。通過這套 API舱污,JavaScript 代碼能夠訪問到用戶的當(dāng)前位置信息呀舔。
??當(dāng)然,訪問之前必須得到用戶的明確許可扩灯,即同意在頁面中共享其位置信息媚赖。如果頁面嘗試訪問地理定位信息,瀏覽器就會顯示一個對話框珠插,請求用戶許可共享其位置信息惧磺。
??Geolocation API 在瀏覽器中的實現(xiàn)是 navigator.geolocation 對象,這個對象包含 3 個方法捻撑。
??第一個方法是 getCurrentPosition()磨隘,調(diào)用這個方法就會觸發(fā)請求用戶共享地理定位信息的對話框缤底。
??這個方法接收 3 個參數(shù):成功回調(diào)函數(shù)、可選的失敗回調(diào)函數(shù)和可選的選項對象番捂。
??其中个唧,成功回調(diào)函數(shù)會接收到一個Position 對象參數(shù),該對象有兩個屬性:coords 和timestamp设预。而 coords 對象中將包含下列與位置相關(guān)的信息徙歼。
- latitude:以十進(jìn)制度數(shù)表示的緯度。
- longitude:以十進(jìn)制度數(shù)表示的經(jīng)度絮缅。
- accuracy:經(jīng)鲁沥、緯度坐標(biāo)的精度,以米為單位耕魄。
??有些瀏覽器還可能會在 coords 對象中提供如下屬性。
- altitude:以米為單位的海拔高度彭谁,如果沒有相關(guān)數(shù)據(jù)則值為 null吸奴。
- altitudeAccuracy:海拔高度的精度,以米為單位缠局,數(shù)值越大越不精確则奥。
- heading:指南針的方向,0°表示正北狭园,值為 NaN 表示沒有檢測到數(shù)據(jù)读处。
- speed:速度,即每秒移動多少米唱矛,如果沒有相關(guān)數(shù)據(jù)則值為 null罚舱。
??在實際開發(fā)中,latitude 和 longitude 是大多數(shù) Web 應(yīng)用最常用到的屬性绎谦。例如管闷,以下代碼將在地圖上繪制用戶的位置:
navigator.geolocation.getCurrentPosition(function(position){
drawMapCenteredAt(position.coords.latitude, positions.coords.longitude);
});
??以上介紹的是成功回調(diào)函數(shù)。
??getCurrentPosition() 的第二個參數(shù)窃肠,即失敗回調(diào)函數(shù)包个,在被調(diào)用的時候也會接收到一個參數(shù)。這個參數(shù)是一個對象冤留,包含兩個屬性:message 和 code碧囊。
??其中,message 屬性中保存著給人看的文本消息纤怒,解釋為什么會出錯糯而,而 code 屬性中保存著一個數(shù)值,表示錯誤的類型:
- 用戶拒絕共享(1)
- 位置無效(2)
- 超時(3)
??實際開發(fā)中肪跋,大多數(shù) Web 應(yīng)用只會將錯誤消息保存到日志文件中歧蒋,而不一定會因此修改用戶界面土砂。例如:
navigator.geolocation.getCurrentPosition(function(position){
drawMapCenteredAt(position.coords.latitude, positions.coords.longitude);
}, function(error){
console.log("Error code: " + error.code);
console.log("Error message: " + error.message);
});
??getCurrentPosition() 的第三個參數(shù)是一個選項對象,用于設(shè)定信息的類型谜洽÷苡常可以設(shè)置的選項有三個:
- enableHighAccuracy 是一個布爾值,表示必須盡可能使用最準(zhǔn)確的位置信息阐虚;
- timeout 是以毫秒數(shù)表示的等待位置信息的最長時間序臂;
- maximumAge 表示上一次取得的坐標(biāo)信息的有效時間,以毫秒表示实束,如果時間到則重新取得新坐標(biāo)信息奥秆。
??例如:
navigator.geolocation.getCurrentPosition(function(position){
drawMapCenteredAt(position.coords.latitude, positions.coords.longitude);
}, function(error){
console.log("Error code: " + error.code);
console.log("Error message: " + error.message);
}, {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 25000
});
??這三個選項都是可選的,可以單獨設(shè)置咸灿,也可以與其他選項一起設(shè)置构订。除非確實需要非常精確的信息,否則建議保持 enableHighAccuracy 的 false 值(默認(rèn)值)避矢。將這個選項設(shè)置為 true 需要更長的時候悼瘾,而且在移動設(shè)備上還會導(dǎo)致消耗更多電量。
??類似地审胸,如果不需要頻繁更新用戶的位置信息亥宿,那么可以將 maximumAge 設(shè)置為 Infinity,從而始終都使用上一次的坐標(biāo)信息砂沛。
??如果你希望跟蹤用戶的位置烫扼,那么可以使用另一個方法 watchPosition()。這個方法接收的參數(shù)與 getCurrentPosition() 方法完全相同碍庵。
??實際上映企, watchPosition() 與定時調(diào)用 getCurrentPosition() 的效果相同。在第一次調(diào)用 watchPosition() 方法后怎抛,會取得當(dāng)前位置卑吭,執(zhí)行成功回調(diào)或者錯誤回調(diào)。然后马绝,watchPosition() 就地等待系統(tǒng)發(fā)出位置已改變的信號(它不會自己輪詢位置)豆赏。
??調(diào)用 watchPosition() 會返回一個數(shù)值標(biāo)識符,用于跟蹤監(jiān)控的操作富稻≈腊睿基于這個返回值可以取消監(jiān)控操作,只要將其傳遞給 clearWatch() 方法即可(與使用 setTimeout() 和 clearTimeout() 類似)椭赋。例如:
var watchId = navigator.geolocation.watchPosition(function(position){
drawMapCenteredAt(position.coords.latitude, positions.coords.longitude);
}, function(error){
console.log("Error code: " + error.code);
console.log("Error message: " + error.message);
});
clearWatch(watchId);
??以上例子調(diào)用了 watchPosition() 方法抚岗,將返回的標(biāo)識符保存在了 watchId 中。然后哪怔,又將 watchId 傳給了 clearWatch()宣蔚,取消了監(jiān)控操作向抢。
??支持地理定位的瀏覽器有 IE9+、Firefox 3.5+胚委、Opera 10.6+挟鸠、Safari 5+、Chrome亩冬、iOS 版 Safari艘希、Android 版 WebKit。要了解使用地理定位的更多精彩范例硅急,請訪問 http://html5demos.com/geo覆享。
4、File API
??不能直接訪問用戶計算機(jī)中的文件营袜,一直都是 Web 應(yīng)用開發(fā)中的一大障礙撒顿。2000 年以前,處理文件的唯一方式就是在表單中加入<input type="file">字段荚板,僅此而已核蘸。
??File API(文件 API)的宗旨是為 Web 開發(fā)人員提供一種安全的方式,以便在客戶端訪問用戶計算機(jī)中的文件啸驯,并更好地對這些文件執(zhí)行操作舰蟆。
??支持 File API 的瀏覽器有 IE10+漩蟆、Firefox 4+、Safari 5.0.5+苍日、Opera 11.1+和 Chrome宅楞。
??File API 在表單中的文件輸入字段的基礎(chǔ)上针姿,又添加了一些直接訪問文件信息的接口。HTML5 在 DOM 中為文件輸入元素添加了一個 files 集合厌衙。在通過文件輸入字段選擇了一或多個文件時距淫,files 集合中將包含一組 File 對象,每個 File 對象對應(yīng)著一個文件婶希。每個 File 對象都有下列只讀屬性榕暇。
- name:本地文件系統(tǒng)中的文件名。
- size:文件的字節(jié)大小喻杈。
- type:字符串彤枢,文件的 MIME 類型。
- lastModifiedDate:字符串筒饰,文件上一次被修改的時間(只有 Chrome 實現(xiàn)了這個屬性)缴啡。
??舉個例子,通過偵聽 change 事件并讀取 files 集合就可以知道選擇的每個文件的信息:
var filesList = document.getElementById("files-list");
EventUtil.addHandler(filesList, "change", function(event){
var files = EventUtil.getTarget(event).files,
i = 0,
len = files.length;
while (i < len){
console.log(files[i].name + " (" + files[i].type + ", " + files[i].size +
" bytes) ");
i++;
}
});
??這個例子把每個文件的信息輸出到了控制臺中瓷们。僅僅這一項功能业栅,對 Web 應(yīng)用開發(fā)來說就已經(jīng)是非常大的進(jìn)步了秒咐。
??不過,F(xiàn)ile API 的功能還不止于此碘裕,通過它提供的 FileReader 類型甚至還可以讀取文件中的數(shù)據(jù)携取。
4.1、FileReader 類型
??FileReader 類型實現(xiàn)的是一種異步文件讀取機(jī)制娘汞〈醪瑁可以把FileReader 想象成 XMLHttpRequest,區(qū)別只是它讀取的是文件系統(tǒng)你弦,而不是遠(yuǎn)程服務(wù)器惊豺。
??為了讀取文件中的數(shù)據(jù),F(xiàn)ileReader 提供了如下幾個方法禽作。
- readAsText(file, encoding):以純文本形式讀取文件尸昧,將讀取到的文本保存在 result 屬性中。第二個參數(shù)用于指定編碼類型旷偿,是可選的烹俗。
- readAsDataURL(file):讀取文件并將文件以數(shù)據(jù) URI 的形式保存在 result 屬性中。
- readAsBinaryString(file):讀取文件并將一個字符串保存在 result 屬性中萍程,字符串中的每個字符表示一字節(jié)幢妄。
- readAsArrayBuffer(file):讀取文件并將一個包含文件內(nèi)容的 ArrayBuffer 保存在 result 屬性中。
??這些讀取文件的方法為靈活地處理文件數(shù)據(jù)提供了極大便利茫负。例如蕉鸳,可以讀取圖像文件并將其保存為數(shù)據(jù) URI,以便將其顯示給用戶忍法,或者為了解析方便潮尝,可以將文件讀取為文本形式。
??由于讀取過程是異步的饿序,因此 FileReader 也提供了幾個事件勉失。其中最有用的三個事件是 progress、error 和 load原探,分別表示是否又讀取了新數(shù)據(jù)乱凿、是否發(fā)生了錯誤以及是否已經(jīng)讀完了整個文件。
??每過 50ms 左右踢匣,就會觸發(fā)一次 progress 事件告匠,通過事件對象可以獲得與 XHR 的 progress 事件相同的信息(屬性):lengthComputable、loaded 和 total离唬。
??另外后专,盡管可能沒有包含全部數(shù)據(jù),但每次 progress 事件中都可以通過 FileReader 的 result 屬性讀取到文件內(nèi)容输莺。
??由于種種原因無法讀取文件戚哎,就會觸發(fā) error 事件裸诽。觸發(fā) error 事件時,相關(guān)的信息將保存到 FileReader 的 error 屬性中型凳。這個屬性中將保存一個對象丈冬,該對象只有一個屬性 code,即錯誤碼甘畅。
??這個錯誤碼
- 1 表示未找到文件埂蕊,
- 2 表示安全性錯誤,
- 3 表示讀取中斷疏唾,
- 4 表示文件不可讀蓄氧,
- 5 表示編碼錯誤。
??文件成功加載后會觸發(fā) load 事件槐脏;如果發(fā)生了 error 事件喉童,就不會發(fā)生 load 事件。以下是一個使用上述三個事件的例子顿天。
var filesList = document.getElementById("files-list");
EventUtil.addHandler(filesList, "change", function(event){
var info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = EventUtil.getTarget(event).files,
type = "default",
reader = new FileReader();
if (/image/.test(files[0].type)){
reader.readAsDataURL(files[0]);
type = "image";
} else {
reader.readAsText(files[0]);
type = "text";
}
reader.onerror = function(){
output.innerHTML = "Could not read file, error code is " + reader.error.code;
};
reader.onprogress = function(event){
if (event.lengthComputable){
progress.innerHTML = event.loaded + "/" + event.total;
}
};
reader.onload = function(){
var html = "";
switch(type){
case "image":
html = "<img src=\"" + reader.result + "\">";
break;
case "text":
html = reader.result;
break;
}
output.innerHTML = html;
};
});
??這個例子讀取了表單字段中選擇的文件堂氯,并將其內(nèi)容顯示在了頁面中。如果文件有 MIMI 類型牌废,表示文件是圖像咽白,因此在 load 事件中就把它保存為數(shù)據(jù) URI,并在頁面中將這幅圖像顯示出來鸟缕。
??如果文件不是圖像局扶,則以字符串形式讀取文件內(nèi)容,然后如實在頁面中顯示讀取到的內(nèi)容叁扫。這里使用了 progress 事件來跟蹤讀取了多少字節(jié)的數(shù)據(jù),而 error 事件則用于監(jiān)控發(fā)生的錯誤畜埋。
??如果想中斷讀取過程莫绣,可以調(diào)用 abort() 方法,這樣就會觸發(fā) abort 事件悠鞍。在觸發(fā) load对室、error 或 abort 事件后,會觸發(fā)另一個事件 loadend咖祭。loadend 事件發(fā)生就意味著已經(jīng)讀取完整個文件掩宜,或者讀取時發(fā)生了錯誤,或者讀取過程被中斷么翰。
??實現(xiàn) File API 的所有瀏覽器都支持 readAsText() 和 readAsDataURL() 方法牺汤。但 IE10 PR 2 并未實現(xiàn) readAsBinaryString() 和 readAsArrayBuffer() 方法。
4.2浩嫌、讀取部分內(nèi)容
??有時候檐迟,我們只想讀取文件的一部分而不是全部內(nèi)容补胚。為此,F(xiàn)ile 對象還支持一個 slice() 方法追迟,這個方法在 Firefox 中的實現(xiàn)叫 mozSlice()溶其,在 Chrome 中的實現(xiàn)叫 webkitSlice(),Safari 的 5.1 及
之前版本不支持這個方法敦间。
??slice() 方法接收兩個參數(shù):起始字節(jié)及要讀取的字節(jié)數(shù)瓶逃。這個方法返回一個 Blob 的實例,Blob 是 File 類型的父類型廓块。下面是一個通用的函數(shù)厢绝,可以在不同實現(xiàn)中使用 slice() 方法:
function blobSlice(blob, startByte, length){
if (blob.slice){
return blob.slice(startByte, length);
} else if (blob.webkitSlice){
return blob.webkitSlice(startByte, length);
} else if (blob.mozSlice){
return blob.mozSlice(startByte, length);
} else {
return null;
}
}
??Blob 類型有一個 size 屬性和一個 type 屬性,而且它也支持 slice() 方法剿骨,以便進(jìn)一步切割數(shù)據(jù)代芜。
??通過 FileReader 也可以從 Blob 中讀取數(shù)據(jù)。下面這個例子只讀取文件的 32B 內(nèi)容浓利。
var filesList = document.getElementById("files-list");
EventUtil.addHandler(filesList, "change", function(event){
var info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = EventUtil.getTarget(event).files,
reader = new FileReader(),
blob = blobSlice(files[0], 0, 32);
if (blob){
reader.readAsText(blob);
reader.onerror = function(){
output.innerHTML = "Could not read file, error code is " + reader.error.code;
};
reader.onload = function(){
output.innerHTML = reader.result;
};
} else {
alert("Your browser doesn' t support slice().");
}
});
??只讀取文件的一部分可以節(jié)省時間挤庇,非常適合只關(guān)注數(shù)據(jù)中某個特定部分(如文件頭部)的情況。
4.3贷掖、對象 URL
??對象 URL 也被稱為 blob URL嫡秕,指的是引用保存在 File 或 Blob 中數(shù)據(jù)的 URL。
??使用對象 URL 的好處是可以不必把文件內(nèi)容讀取到 JavaScript 中而直接使用文件內(nèi)容苹威。為此昆咽,只要在需要文件內(nèi)容的地方提供對象 URL 即可。
??要創(chuàng)建對象 URL牙甫,可以使用 window.URL.createObjectURL() 方法掷酗,并傳入 File 或 Blob 對象。
??這個方法在 Chrome 中的實現(xiàn)叫 window.webkitURL.createObjectURL()窟哺,因
此可以通過如下函數(shù)來消除命名的差異:
function createObjectURL(blob){
if (window.URL){
return window.URL.createObjectURL(blob);
} else if (window.webkitURL){
return window.webkitURL.createObjectURL(blob);
} else {
return null;
}
}
??這個函數(shù)的返回值是一個字符串泻轰,指向一塊內(nèi)存的地址。因為這個字符串是 URL且轨,所以在 DOM 中也能使用浮声。例如,以下代碼可以在頁面中顯示一個圖像文件:
var filesList = document.getElementById("files-list");
EventUtil.addHandler(filesList, "change", function(event){
var info = "",
output = document.getElementById("output"),
progress = document.getElementById("progress"),
files = EventUtil.getTarget(event).files,
reader = new FileReader(),
url = createObjectURL(files[0]);
if (url){
if (/image/.test(files[0].type)){
output.innerHTML = "<img src=\"" + url + "\">";
} else {
output.innerHTML = "Not an image.";
}
} else {
output.innerHTML = "Your browser doesn't support object URLs.";
}
});
??直接把對象 URL 放在<img>標(biāo)簽中旋奢,就省去了把數(shù)據(jù)先讀到 JavaScript 中的麻煩泳挥。另一方面,<img>標(biāo)簽則會找到相應(yīng)的內(nèi)存地址至朗,直接讀取數(shù)據(jù)并將圖像顯示在頁面中屉符。
??如果不再需要相應(yīng)的數(shù)據(jù),最好釋放它占用的內(nèi)容。但只要有代碼在引用對象 URL筑煮,內(nèi)存就不會釋放辛蚊。
??要手工釋放內(nèi)存,可以把對象 URL 傳給 window.URL.revokeOjbectURL()(在 Chrome 中是 window.webkitURL.revokeObjectURL())真仲。要兼容這兩種方法的實現(xiàn)袋马,可以使用以下函數(shù):
function revokeObjectURL(url){
if (window.URL){
window.URL.revokeObjectURL(url);
} else if (window.webkitURL){
window.webkitURL.revokeObjectURL(url);
}
}
??頁面卸載時會自動釋放對象 URL 占用的內(nèi)存。不過秸应,為了確保盡可能少地占用內(nèi)存虑凛,最好在不需要某個對象 URL 時,就馬上手工釋放其占用的內(nèi)存软啼。
??支持對象 URL 的瀏覽器有 IE10+桑谍、Firefox 4 和 Chrome。
4.4祸挪、讀取拖放文件
??圍繞讀取文件信息锣披,結(jié)合使用 HTML5 拖放 API 和文件 API,能夠創(chuàng)造出令人矚目的用戶界面:在頁面上創(chuàng)建了自定義的放置目標(biāo)之后贿条,你可以從桌面上把文件拖放到該目標(biāo)雹仿。與拖放一張圖片或者一個鏈接類似,從桌面上把文件拖放到瀏覽器中也會觸發(fā) drop 事件整以。而且可以在 event.dataTransfer. files 中讀取到被放置的文件胧辽,當(dāng)然此時它是一個 File 對象,與通過文件輸入字段取得的 File 對象一樣公黑。
??下面這個例子會將放置到頁面中自定義的放置目標(biāo)中的文件信息顯示出來:
var droptarget = document.getElementById( "droptarget");
function handleEvent(event){
var info = "",
output = document.getElementById("output"),
files, i, len;
EventUtil.preventDefault(event);
if (event.type == "drop"){
files = event.dataTransfer.files;
i = 0;
len = files.length;
while (i < len){
info += files[i].name + " (" + files[i].type + ", " + files[i].size +
" bytes)<br>";
i++;
}
output.innerHTML = info;
}
}
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);
??與之前展示的拖放示例一樣邑商,這里也必須取消 dragenter、dragover 和 drop 的默認(rèn)行為凡蚜。
??在 drop 事件中人断,可以通過 event.dataTransfer.files 讀取文件信息。還有一種利用這個功能的流行做法朝蜘,即結(jié)合 XMLHttpRequest 和拖放文件來實現(xiàn)上傳含鳞。
4.5、使用 XHR 上傳文件
??通過 File API 能夠訪問到文件內(nèi)容芹务,利用這一點就可以通過 XHR 直接把文件上傳到服務(wù)器。當(dāng)然啦鸭廷,把文件內(nèi)容放到 send() 方法中枣抱,再通過 POST 請求,的確很容易就能實現(xiàn)上傳辆床。但這樣做傳遞的是文件內(nèi)容佳晶,因而服務(wù)器端必須收集提交的內(nèi)容,然后再把它們保存到另一個文件中讼载。其實轿秧,更好的做法是以表單提交的方式來上傳文件中跌。
??這樣使用 FormData 類型就很容易做到了。首先菇篡,要創(chuàng)建一個 FormData 對象漩符,通過它調(diào)用 append() 方法并傳入相應(yīng)的 File 對象作為參數(shù)。然后驱还,再把 FormData 對象傳遞給 XHR 的 send() 方法嗜暴,結(jié)果與通過表單上傳一模一樣。
var droptarget = document.getElementById("droptarget");
function handleEvent(event){
var info = "",
output = document.getElementById("output"),
data, xhr,
files, i, len;
EventUtil.preventDefault(event);
if (event.type == "drop"){
data = new FormData();
files = event.dataTransfer.files;
i = 0;
len = files.length;
while (i < len){
data.append("file" + i, files[i]);
i++;
}
xhr = new XMLHttpRequest();
xhr.open("post", "FileAPIExample06Upload.php", true);
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
alert(xhr.responseText);
}
};
xhr.send(data);
}
}
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);
??這個例子創(chuàng)建一個 FormData 對象议蟆,與每個文件對應(yīng)的鍵分別是 file0闷沥、file1、file2 這樣的格式咐容。注意舆逃,不用額外寫任何代碼,這些文件就可以作為表單的值提交戳粒。而且路狮,也不必使用 FileReader,只要傳入 File 對象即可享郊。
??使用 FormData 上傳文件览祖,在服務(wù)器端就好像是接收到了常規(guī)的表單數(shù)據(jù)一樣,一切按部就班地處理即可炊琉。
??換句話說展蒂,如果服務(wù)器端使用的是 PHP,那么$_FILES 數(shù)組中就會保存著上傳的文件苔咪。支持以這種方式上傳文件的瀏覽器有 Firefox 4+锰悼、Safari 5+和 Chrome。
5团赏、Web 計時
??頁面性能一直都是 Web 開發(fā)人員最關(guān)注的領(lǐng)域箕般。但直到最近,度量頁面性能指標(biāo)的唯一方式舔清,就是提高代碼復(fù)雜程度和巧妙地使用 JavaScript 的 Date 對象丝里。
??Web Timing API 改變了這個局面,讓開發(fā)人員通過 JavaScript 就能使用瀏覽器內(nèi)部的度量結(jié)果体谒,通過直接讀取這些信息可以做任何想做的分析杯聚。
??與本章介紹過的其他 API 不同,Web Timing API 實際上已經(jīng)成為了 W3C 的建議標(biāo)準(zhǔn)抒痒,只不過目前支持它的瀏覽器還不夠多幌绍。
??Web 計時機(jī)制的核心是 window.performance 對象。對頁面的所有度量信息,包括那些規(guī)范中已經(jīng)定義的和將來才能確定的傀广,都包含在這個對象里面颁独。
??Web Timing 規(guī)范一開始就為 performance 對象定義了兩個屬性。
??其中伪冰,performance.navigation 屬性也是一個對象誓酒,包含著與頁面導(dǎo)航有關(guān)的多個屬性,如下所示糜值。
- redirectCount:頁面加載前的重定向次數(shù)丰捷。
- type:數(shù)值常量,表示剛剛發(fā)生的導(dǎo)航類型寂汇。
- performance.navigation.TYPE_NAVIGATE (0):頁面第一次加載病往。
- performance.navigation.TYPE_RELOAD (1):頁面重載過。
- performance.navigation.TYPE_BACK_FORWARD (2):頁面是通過“后退”或“前進(jìn)”按鈕打開的骄瓣。
??另外停巷,performance.timing 屬性也是一個對象,但這個對象的屬性都是時間戳(從軟件紀(jì)元開始經(jīng)過的毫秒數(shù))榕栏,不同的事件會產(chǎn)生不同的時間值畔勤。這些屬性如下所示。
- navigationStart:開始導(dǎo)航到當(dāng)前頁面的時間扒磁。
- unloadEventStart:前一個頁面的 unload 事件開始的時間庆揪。但只有在前一個頁面與當(dāng)前頁面來自同一個域時這個屬性才會有值;否則妨托,值為 0缸榛。
- unloadEventEnd:前一個頁面的 unload 事件結(jié)束的時間。但只有在前一個頁面與當(dāng)前頁面來自同一個域時這個屬性才會有值兰伤;否則内颗,值為 0。
- redirectStart:到當(dāng)前頁面的重定向開始的時間敦腔。但只有在重定向的頁面來自同一個域時這個屬性才會有值均澳;否則,值為 0符衔。
- redirectEnd:到當(dāng)前頁面的重定向結(jié)束的時間找前。但只有在重定向的頁面來自同一個域時這個屬性才會有值;否則判族,值為 0躺盛。
- fetchStart:開始通過 HTTP GET 取得頁面的時間。
- domainLookupStart:開始查詢當(dāng)前頁面 DNS 的時間五嫂。
- domainLookupEnd:查詢當(dāng)前頁面 DNS 結(jié)束的時間。
- connectStart:瀏覽器嘗試連接服務(wù)器的時間。
- connectEnd:瀏覽器成功連接到服務(wù)器的時間沃缘。
- secureConnectionStart:瀏覽器嘗試以 SSL 方式連接服務(wù)器的時間躯枢。不使用 SSL 方式連接時,這個屬性的值為 0槐臀。
- requestStart:瀏覽器開始請求頁面的時間锄蹂。
- responseStart:瀏覽器接收到頁面第一字節(jié)的時間。
- responseEnd:瀏覽器接收到頁面所有內(nèi)容的時間水慨。
- domLoading:document.readyState 變?yōu)?loading"的時間得糜。
- domInteractive:document.readyState 變?yōu)?interactive"的時間。
- domContentLoadedEventStart:發(fā)生 DOMContentLoaded 事件的時間晰洒。
- domContentLoadedEventEnd:DOMContentLoaded 事件已經(jīng)發(fā)生且執(zhí)行完所有事件處理程序的時間朝抖。
- domComplete:document.readyState 變?yōu)?complete"的時間。
- loadEventStart:發(fā)生 load 事件的時間谍珊。
- loadEventEnd:load 事件已經(jīng)發(fā)生且執(zhí)行完所有事件處理程序的時間治宣。
??通過這些時間值,就可以全面了解頁面在被加載到瀏覽器的過程中都經(jīng)歷了哪些階段砌滞,而哪些階段可能是影響性能的瓶頸侮邀。給大家推薦一個使用 Web Timing API 的絕好示例,地址是 http://webtimingdemo.appspot.com/贝润。
??支持 Web Timing API 的瀏覽器有 IE10+和 Chrome绊茧。
6、Web Workers
??隨著 Web 應(yīng)用復(fù)雜性的與日俱增打掘,越來越復(fù)雜的計算在所難免华畏。長時間運(yùn)行的 JavaScript 進(jìn)程會導(dǎo)致瀏覽器凍結(jié)用戶界面,讓人感覺屏幕“凍結(jié)”了胧卤。
??Web Workers 規(guī)范通過讓 JavaScript 在后臺運(yùn)行解決了這個問題唯绍。
??瀏覽器實現(xiàn) Web Workers 規(guī)范的方式有很多種,可以使用線程枝誊、后臺進(jìn)程或者運(yùn)行在其他處理器核心上的進(jìn)程况芒,等等。具體的實現(xiàn)細(xì)節(jié)其實沒有那么重要叶撒,重要的是開發(fā)人員現(xiàn)在可以放心地運(yùn)行 JavaScript绝骚,而不必?fù)?dān)心會影響用戶體驗了。
??目前支持 Web Workers 的瀏覽器有 IE10+祠够、Firefox 3.5+压汪、Safari 4+、Opera 10.6+古瓤、Chrome 和 iOS 版的 Safari止剖。
6.1腺阳、使用 Worker
??實例化 Worker 對象并傳入要執(zhí)行的 JavaScript 文件名就可以創(chuàng)建一個新的 Web Worker。例如:
var worker = new Worker("stufftodo.js");
??這行代碼會導(dǎo)致瀏覽器下載 stufftodo.js穿香,但只有 Worker 接收到消息才會實際執(zhí)行文件中的代碼亭引。要給 Worker 傳遞消息,可以使用 postMessage() 方法(與 XDM 中的 postMessage()方法類似):
worker.postMessage(“start! ");
??消息內(nèi)容可以是任何能夠被序列化的值皮获,不過與 XDM 不同的是焙蚓,在所有支持的瀏覽器中,postMessage() 都能接收對象參數(shù)(Safari 4 是支持 Web Workers 的瀏覽器中最后一個只支持字符串參數(shù)的)洒宝。因此购公,可以隨便傳遞任何形式的對象數(shù)據(jù),如下面的例子所示:
worker.postMessage({
type: "command",
message: "start! "
});
??一般來說雁歌,可以序列化為 JSON 結(jié)構(gòu)的任何值都可以作為參數(shù)傳遞給 postMessage()宏浩。換句話說,這就意味著傳入的值是被復(fù)制到 Worker 中将宪,而非直接傳過去的(與 XDM 類似)绘闷。
??Worker 是通過 message 和 error 事件與頁面通信的。這里的 message 事件與 XDM 中的 message 事件行為相同较坛,來自 Worker 的數(shù)據(jù)保存在 event.data 中印蔗。Worker 返回的數(shù)據(jù)也可以是任何能夠被序列化的值:
worker.onmessage = function(event){
var data = event.data;
// 對數(shù)據(jù)進(jìn)行處理
}
??Worker 不能完成給定的任務(wù)時會觸發(fā) error 事件。具體來說丑勤,Worker 內(nèi)部的 JavaScript 在執(zhí)行過程中只要遇到錯誤华嘹,就會觸發(fā) error 事件。
??發(fā)生 error 事件時法竞,事件對象中包含三個屬性:filename耙厚、lineno 和 message,分別表示發(fā)生錯誤的文件名岔霸、代碼行號和完整的錯誤消息薛躬。
worker.onerror = function(event){
console.log("ERROR: " + event.filename + " (" + event.lineno + "): " + event.message);
};
??建議大家在使用 Web Workers 時,始終都要使用 onerror 事件處理程序呆细,即使這個函數(shù)(像上面例子所示的)除了把錯誤記錄到日志中什么也不做都可以型宝。否則,Worker 就會在發(fā)生錯誤時絮爷,悄無聲息
地失敗了趴酣。
??任何時候,只要調(diào)用 terminate() 方法就可以停止 Worker 的工作坑夯。而且岖寞,Worker 中的代碼會立即停止執(zhí)行,后續(xù)的所有過程都不會再發(fā)生(包括 error 和 message 事件也不會再觸發(fā))柜蜈。
worker.terminate(); // 立即停止 Worker 的工作
6.2仗谆、Worker 全局作用域
??關(guān)于 Web Worker指巡,最重要的是要知道它所執(zhí)行的 JavaScript 代碼完全在另一個作用域中,與當(dāng)前網(wǎng)頁中的代碼不共享作用域隶垮。
??在 Web Worker 中厌处,同樣有一個全局對象和其他對象以及方法。但是岁疼,Web Worker 中的代碼不能訪問 DOM,也無法通過任何方式影響頁面的外觀缆娃。
??Web Worker 中的全局對象是 worker 對象本身捷绒。也就是說,在這個特殊的全局作用域中贯要,this 和 self 引用的都是 worker 對象暖侨。為便于處理數(shù)據(jù),Web Worker 本身也是一個最小化的運(yùn)行環(huán)境崇渗。
- 最小化的 navigator 對象字逗,包括 onLine、appName宅广、appVersion葫掉、userAgent 和 platform 屬性;
- 只讀的 location 對象跟狱;
- setTimeout()俭厚、setInterval()、clearTimeout() 和 clearInterval() 方法驶臊;
- XMLHttpRequest 構(gòu)造函數(shù)挪挤。
??顯然,Web Worker 的運(yùn)行環(huán)境與頁面環(huán)境相比关翎,功能是相當(dāng)有限的扛门。
??當(dāng)頁面在 worker 對象上調(diào)用 postMessage() 時,數(shù)據(jù)會以異步方式被傳遞給 worker纵寝,進(jìn)而觸發(fā) worker 中的 message 事件论寨。為了處理來自頁面的數(shù)據(jù),同樣也需要創(chuàng)建一個 onmessage 事件處理程序店雅。
// Web Worker 內(nèi)部的代碼
self.onmessage = function(event){
var data = event.data;
// 處理數(shù)據(jù)
};
??大家看清楚政基,這里的 self 引用的是 Worker 全局作用域中的 worker 對象(與頁面中的 Worker 對象不同一個對象)。Worker 完成工作后闹啦,通過調(diào)用 postMessage() 可以把數(shù)據(jù)再發(fā)回頁面沮明。例如,下面的例子假設(shè)需要 Worker 對傳入的數(shù)組進(jìn)行排序窍奋,而 Worker 在排序之后又將數(shù)組發(fā)回了頁面:
// Web Worker 內(nèi)部的代碼
self.onmessage = function(event){
var data = event.data;
// 別忘了荐健,默認(rèn)的 sort() 方法只比較字符串
data.sort(function(a, b){
return a – b;
});
self.postMessage(data);
};
??傳遞消息就是頁面與 Worker 相互之間通信的方式酱畅。在 Worker 中調(diào)用 postMessage() 會以異步方式觸發(fā)頁面中 Worker 實例的 message 事件。如果頁面想要使用這個 Worker江场,可以這樣:
//在頁面中
var data = [23,4,7,9,2,14,6,651,87,41,7798,24],
worker = new Worker("WebWorkerExample01.js");
worker.onmessage = function(event){
var data = event.data;
// 對排序后的數(shù)組進(jìn)行操作
};
// 將數(shù)組發(fā)送給 worker 排序
worker.postMessage(data);
??排序的確是比較消耗時間的操作纺酸,因此轉(zhuǎn)交給 Worker 做就不會阻塞用戶界面了。另外址否,把彩色圖像轉(zhuǎn)換成灰階圖像以及加密解密之類的操作也是相當(dāng)費時的餐蔬。
??在 Worker 內(nèi)部,調(diào)用 close() 方法也可以停止工作佑附。就像在頁面中調(diào)用 terminate() 方法一樣樊诺,Worker 停止工作后就不會再有事件發(fā)生了。
// Web Worker 內(nèi)部的代碼
self.close();
6.3音同、包含其他腳本
??既然無法在 Worker 中動態(tài)創(chuàng)建新的<script>元素词爬,那是不是就不能向 Worker 中添加其他腳本了呢?
??不是权均,Worker 的全局作用域提供這個功能顿膨,即我們可以調(diào)用 importScripts() 方法。這個方法接收一個或多個指向 JavaScript 文件的 URL叽赊。每個加載過程都是異步進(jìn)行的恋沃,因此所有腳本加載并執(zhí)行之后,importScripts() 才會執(zhí)行必指。例如:
// Web Worker 內(nèi)部的代碼
importScripts("file1.js", "file2.js");
??即使 file2.js 先于 file1.js 下載完芽唇,執(zhí)行的時候仍然會按照先后順序執(zhí)行。而且取劫,這些腳本是在 Worker 的全局作用域中執(zhí)行匆笤,如果腳本中包含與頁面有關(guān)的 JavaScript 代碼,那么腳本可能無法正確運(yùn)行谱邪。請記住炮捧,Worker 中的腳本一般都具有特殊的用途,不會像頁面中的腳本那么功能寬泛惦银。
6.4咆课、Web Workers 的未來
??Web Workers 規(guī)范還在繼續(xù)制定和改進(jìn)之中。本節(jié)所討論的 Worker 目前被稱為“專用 Worker”(dedicated worker)扯俱,因為它們是專門為某個特定的頁面服務(wù)的书蚪,不能在頁面間共享。
??該規(guī)范的另外一個概念是“共享 Worker”(shared worker)迅栅,這種 Worker 可以在瀏覽器的多個標(biāo)簽中打開的同一個頁面間共享。雖然 Safari 5读存、Chrome 和 Opera 10.6 都實現(xiàn)了共享 Worker呕屎,但由于該規(guī)范尚未完稿敬察,因此很可能還會有變動秀睛。
??另外莲祸,關(guān)于在 Worker 內(nèi)部能訪問什么不能訪問什么,到如今仍然爭論不休锐帜。有人認(rèn)為 Worker 應(yīng)該像頁面一樣能夠訪問任意數(shù)據(jù)藤抡,不光是 XHR,還有 localStorage、sessionStorage药蜻、Indexed DB替饿、Web Sockets、Server-Send Events 等踱卵。好像支持這個觀點的人更多一些据过,因此未來的 Worker 全局作用域很可能會有更大的空間。
小結(jié)
??與 HTML5 同時興起的是另外一批 JavaScript API西饵。從技術(shù)規(guī)范角度講鳞芙,這批 API 不屬于 HTML5,但從整體上可以稱它們?yōu)?HTML5 JavaScript API驯嘱。這些 API 的標(biāo)準(zhǔn)有不少雖然還在制定當(dāng)中鞠评,但已經(jīng)得到了瀏覽器的廣泛支持壕鹉,因此本章重點討論了它們煌贴。
- requestAnimationFrame():是一個著眼于優(yōu)化 JavaScript 動畫的 API牛郑,能夠在動畫運(yùn)行期間發(fā)出信號敬鬓。通過這種機(jī)制,瀏覽器就能夠自動優(yōu)化屏幕重繪操作础芍。
- Page Visibility API:讓開發(fā)人員知道用戶什么時候正在看著頁面,而什么時候頁面是隱藏的数尿。
- Geolocation API:在得到許可的情況下右蹦,可以確定用戶所在的位置。在移動 Web 應(yīng)用中晨汹,這個 API 非常重要而且常用贷盲。
- File API:可以讀取文件內(nèi)容巩剖,用于顯示、處理和上傳氧骤。與 HTML5 的拖放功能結(jié)合吃引,很容易就能創(chuàng)造出拖放上傳功能镊尺。
- Web Timing:給出了頁面加載和渲染過程的很多信息,對性能優(yōu)化非常有價值语稠。
- Web Workers:可以運(yùn)行異步 JavaScript 代碼,避免阻塞用戶界面输涕。在執(zhí)行復(fù)雜計算和數(shù)據(jù)處理的時候莱坎,這個 API 非常有用寸士;要不然,這些任務(wù)輕則會占用很長時間弱卡,重則會導(dǎo)致用戶無法與頁面交互婶博。