二十二珊拼、新興的 API

??隨著 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)致用戶無法與頁面交互婶博。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市名党,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌条获,老刑警劉巖帅掘,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件修档,死亡現(xiàn)場離奇詭異,居然都是意外死亡讥邻,警方通過查閱死者的電腦和手機(jī)院峡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門照激,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人励幼,你說我怎么就攤上這事苹粟。” “怎么了俺孙?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵睛榄,是天一觀的道長想帅。 經(jīng)常有香客問我港准,道長,這世上最難降的妖魔是什么轨帜? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任蚌父,我火速辦了婚禮毛萌,結(jié)果婚禮上阁将,老公的妹妹穿的比我還像新娘扁藕。我一直安慰自己,他們只是感情好吹榴,可當(dāng)我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布腊尚。 她就那樣靜靜地躺著,像睡著了一般劝篷。 火紅的嫁衣襯著肌膚如雪娇妓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天只估,我揣著相機(jī)與錄音蛔钙,去河邊找鬼荠医。 笑死彬向,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遍希。 我是一名探鬼主播里烦,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼招驴,長吁一口氣:“原來是場噩夢啊……” “哼别厘!你這毒婦竟也來了拥诡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎仇祭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眯娱,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡徙缴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年嘁信,在試婚紗的時候發(fā)現(xiàn)自己被綠了潘靖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡携御,死狀恐怖啄刹,靈堂內(nèi)的尸體忽然破棺而出凄贩,到底是詐尸還是另有隱情疲扎,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布壹甥,位于F島的核電站句柠,受9級特大地震影響棒假,放射性物質(zhì)發(fā)生泄漏帽哑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一僻族、第九天 我趴在偏房一處隱蔽的房頂上張望述么。 院中可真熱鬧,春花似錦籽前、人聲如沸枝哄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蓖宦,卻和暖如春油猫,著一層夾襖步出監(jiān)牢的瞬間情妖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留料睛,地道東北人秦效。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓涎嚼,卻偏偏與公主長得像法梯,于是被迫代替她去往敵國和親犀概。 傳聞我的和親對象是個殘疾皇子姻灶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)敢会,斷路器鸥昏,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 就要被陰謀置于死地的她 堅毅吏垮,自信,見招拆招 水停留在唇邊 眉頭的鎖唯蝶,悄悄下滑至兩腮 變?yōu)殚_花的鑰匙 奸笑的人遗嗽,真...
    涼爽清風(fēng)閱讀 207評論 0 7
  • 十步之內(nèi)媳谁,必有芳草晴音。這句美麗的正能量雞湯其實在表明芳草相對雜草的稀有珍貴。 看電影的人都不想碰上爛片搁料,交男朋友的女...
    璜楷閱讀 229評論 0 2
  • 今天從技術(shù)角度看郭计,是一個日線大級別周期出現(xiàn)了頂背離的技術(shù)形態(tài)椒振,從技術(shù)形態(tài)形成到目前為止是調(diào)整了4個交易日澎迎,就日線級...
    ViRRo王茜閱讀 190評論 0 0