《JavaScript高級程序設計》筆記:新興的API

requestAnimationFrame()

大多數電腦顯示器的刷新頻率60HZ,大概至關於每秒鐘重繪60次。所以,最平滑動畫的最佳循環間隔是1000ms/60,約等於17ms。javascript

mozRequestAnimationFrame()

mozRequestAnimationFrame()方法接收一個參數,即在重繪屏幕前調用的一個函數。這個函數負責改變下一次重繪時的DOM樣式。爲了建立動畫循環,能夠像之前使用setTimeout()方法同樣,把多個對mozRequestAnimationFrame()的調用連綴起來,以下代碼:php

function updateProgress(){
    var div = document.getElementById("status");
    div.style.width = (parseInt(div.style.width,10) + 5) + "%";
    if(div.style.width != "100%"){
        mozRequestAnimationFrame(updateProgress);
    }
}
mozRequestAnimationFrame(updateProgress);

咱們傳遞的mozRequestAnimationFrame()函數也會接收一個參數,它是一個時間碼(從1970年1月1日起至今的毫秒數),表示下一次重繪的實際發生時間。html

注意:mozRequestAnimationFrame()會根據這個時間碼設定未來的某個時刻進行重繪,而根據這個時間碼,你也能知道那個時刻是什麼時間。而後,在優化動畫效果就有了依據。java

要知道距離上一次重繪已通過去了多長時間,能夠查詢mozAnimationStartTime,其中包含上次重繪的時間碼。用傳入回調函數的時間碼減去這個時間碼,就能計算出在屏幕上重繪下一組變化以前要通過多長時間。使用這個值的典型方式:git

function draw(timestamp){
    //計算兩次重繪的事件間隔
    var diff = timestamp - startTime;
    
    //使用diff肯定下一步的繪製時間
    
    //把startTime重寫爲這一次的繪製時間
    startTime = timestamp;
    
    //重繪UI
    mozRequestAnimationFrame(draw);
}

var startTime = mozAnimationStartTime;
mozRequestAnimationFrame(draw);

webkitRequestAnimationFrame與msRequestAnimationFrame

Chrome和IE10+也都給出了本身的實現,分別是webkitRequestAnimationFrame()msRequestAnimationFrame()。這兩個版本跟mozilla的版本有兩個方面的微小差別。web

  • 首先,不會給回調函數傳遞時間碼,所以你沒法知道下一次重繪將發生在什麼時間;
  • 其次,Chrome又增長了第二個可選的參數,即將要發生變化的DOM元素。知道了重繪將發生在頁面中哪一個特定元素的區域內,就能夠將重繪限定在該區域中。

既然沒有下一次重繪的時間碼,那麼就沒有提供像mozAnimationStartTime的實現,不過,Chrome提供了另外一個方法webkitCancelAnimationFrame(),用於取消以前計劃執行的重繪操做。數組

假如你不須要知道精確的時間差,能夠參考如下模式建立動畫循環:瀏覽器

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

來看個實際的例子,以下代碼:安全

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
    <style>
        *{margin:0;padding:0;}
    #status{width:20px;height:20px;background: red;}
    </style>
</head>
<body>
<div id="status"></div>
<script type="text/javascript">
(function(){
    function draw(timestamp){
                //計算兩次重繪的時間間隔
        var drawStart = timestamp || Date.now(),
            diff = drawStart - startTime;
                
                //使用diff肯定下一步的繪製時間
                console.log(diff)
                
        //把startTime重寫爲這一次的繪製時間
        startTime = drawStart;
                
        var div = document.getElementById("status");
        var computedStyle = document.defaultView.getComputedStyle(div,null);
        div.style.width = (parseInt(computedStyle.width,10) + 5) + "px";
                
        //重繪UI
        if(parseInt(computedStyle.width,10) < 500){
            requestAnimationFrame(draw);
        }
    }
    var requestAnimationFrame = window.requestAnimationFrame ||
                                window.mozRequestAnimationFrame ||
                                window.webkitRequestAnimationFrame ||
                                window.msRequestAnimationFrame,
        startTime = window.mozAnimationStartTime || Date.now();
    requestAnimationFrame(draw);
})();
</script>
</body>
</html>

 requestAnimationFrame簡單兼容方式:服務器

window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();

更全面的兼容方法:

(function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||    // Webkit中此取消方法的名字變了
                                      window[vendors[x] + 'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16.7 - (currTime - lastTime));
            var id = window.setTimeout(function() {
                callback(currTime + timeToCall);
            }, timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
    }
    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
    }
}());

詳細參考地址:《requestAnimationFrame,Web中寫動畫的另外一種選擇》。

 Page visibility API

若是頁面最小化了或者隱藏在了其餘標籤頁後面,Page visibility API(頁面可見性API)就是爲了讓開發人員知道頁面是否對用戶可見而推出的。

  • document.hidden:表示頁面是否隱藏的布爾值。
  • visibilitychange事件:當文檔從可見變爲不可見或者從不可見變爲可見時,觸發該事件。
document.addEventListener('visibilitychange',function(){
    if(document.hidden){
        console.log('頁面隱藏了')
    }else{
        console.log("頁面顯示了")
    }
},false);

IE10+以及其它高版本瀏覽器支持該API。

Geolocation API

Geolocation API在瀏覽器中實現是navigator.geolocation對象,這個對象包含3個方法,getCurrentPosition()watchPosition()、clearWatch()

getCurrentPosition():接受三個參數,成功回調函數、可選的失敗回調函數和可選的選項對象。

其中成功回調函數會接收一個position對象參數,該對象有兩個屬性:coordstimestamp。coords將包含與位置相關的信息,以下:

  • latitude:以十進制度數表示的緯度。
  • longitude:以十進制度數表示的經度。
  • accuracy:經、緯度座標的精度,以米爲單位。

失敗回調接收一個參數,這個參數是一個對象,包括兩個屬性:message和code。

getCurrentPosition()的第三個參數是一個選項對象,用於設定信息的類型。能夠設置的選項有三個:

  • enableHighAccuracy:布爾值,表示必須儘量使用最準確的位置信息;
  • timeout:是以毫秒數表示的等待位置信息的最長時間;
  • maximumAge:表示上一次取得的座標信息的有效時間,以毫秒數表示,若是時間到從新取得新座標信息。
navigator.geolocation.getCurrentPosition(function(position){
    console.log(position.coords.latitude,position.coords.longitude);
},function(error){
    console.log("Error code:" + error.code);
    console.log("Error message:" + error.message);
},{
    enableHighAccuracy:false,
    timeout:50000,
    maximumAge:25000
})

watchPosition():參數跟getCurrentPosition()同樣。實際上與定時調用getCurrentPosition()的效果相同。在第一次調用watchPostion()方法後,會取得當前位置,執行成功回調或者錯誤回調。而後,watchPosition()就地等待系統發出位置已改變的信號(它不會本身輪詢位置)。調用watchPosition()會返回一個數值標識符,用於跟蹤監控的操做。基於這個返回值能夠取消監控操做,只要將其傳遞給clearWatch()方法便可。

var watchId = navigator.geolocation.watchPosition(function(position){
    console.log(position.coords.latitude,position.coords.longitude);
},function(error){
    console.log("Error code:" + error.code);
    console.log("Error message:" + error.message);
},{
    enableHighAccuracy:false,
    timeout:50000,
    maximumAge:25000
});

navigator.geolocation.clearWatch(watchId);

File API

FIle API在表單中的文件輸入字段的基礎上,又添加了一些直接訪問文件信息的接口。HTML5在DOM中爲文件輸入元素添加了一個files集合。在經過文件輸入字段選擇了一個或者多個文件時,files集合中包含一組File對象,每一個file對象對應了一個文件。每一個File對象都有下列只讀屬性。

  • name:本地文件系統中的文件名。
  • size:文件的字節大小。
  • type:字符串,文件的MIME類型。
  • lastModifiedDate:字符串,文件上一次被修改的時間(只有Chrome實現了這個屬性)。

以下例子:

HTML代碼:

<input type="file" id="files-list" multiple/>

JS代碼:

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++;
    }    
})

FileReader類型

FileReader類型實現的是一種異步文件讀取機制。能夠把FileReader想象成XMLHttpRequest,區別只是它讀取的是文件系統,而不是遠程服務器。爲了讀取文件中的數據,FileReader提供了以下幾個方法。

  • readAsText(file,encoding):以純文本形式讀取文件,將讀取到的文本保存到result屬性中。第二個參數用於指定編碼類型,是可選的。
  • readAsDataURL(file):讀取文件並將文件以數據URI的形式保存在result屬性中。
  • readAsBinaryString(file):讀取文件並將一個字符串保存在result屬性中,字符串中的每一個字符表示一個字節。
  • readAsArrayBuffer(file):讀取文件並將一個包含文件內容的ArrayBuffer保存在result屬性中。

例如,能夠讀取圖像文件並將其保存爲數據URI,以便將其顯示給用戶,或者爲了解析方便,能夠將文件讀取爲文本形式。

因爲讀取是異步的,所以FileReader提供了幾個事件。其中最有用的3個事件是progresserrorload,分別表示是否讀取了新數據、是否發生了錯誤以及是否讀完了整個文件。

每過50ms左右,就會觸發一次progress事件,經過事件對象能夠得到與XHR的progress事件相同的信息(屬性):lengthComputableloadedtotal。另外,儘管可能沒有包含所有數據,但每次progress事件中均可以經過FileReader的result屬性讀取到文件內容。

因爲種種緣由沒法讀取到文件時,就會觸發error事件。觸發error事件時,相關的信息將保存到FileReader的error屬性中。這個屬性中將保存一個對象,該對象只有一個屬性code,即錯誤碼。這個錯誤碼以下:

  • 1:未找到文件。
  • 2:安全性錯誤。
  • 3:讀取中斷。
  • 4:文件不可讀。
  • 5:編碼錯誤。

文件成功加載後會觸發load事件;若是發生error事件,就不會觸發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){
            output.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;
    };
});

讀取部份內容

有時候咱們想讀取文件的一部分而不是所有內容。爲此,File對象還支持一個slice()方法,這個方法在Firefox的實現爲mozSlice(),在Chrome中的實現爲webkitSlice()。slice()方法接收兩個參數:起始字節及要讀取的字節數。這個方法返回一個Blob實例,Blob是File類型的父類型。下面一個通用的方法實現兼容的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()方法,以便進一步切割數據。經過FileReader也能夠從Blob中讀取數據。下面這個例子只讀取文件的32B內容。

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().");
    }
});

對象URL

對象URL也被成爲blob URL,指的是引用保存在File或Blob中數據的URL。使用對象URL的好處是不用把文件內容讀取到JS中而直接使用文件內容。爲此,只要在須要文件內容的地方提供對象URL便可。要建立對象URL,可使用window.URL.createObjectURL()方法,並傳入File或者Blob對象。這個方法在Chrome中的實現爲window.webkitURL.createObjectURL(),所以下面兼容寫法:

function createObjectURL(blob){
    if(window.URL){
        return window.URL.createObjectURL(blob);
    }else if(window.webkitURL){
        return window.webkitURL.createObjectURL(blob);
    }else{
        return null;
    }
}

這個函數返回值是一個字符串,指向一塊內存的地址。由於這個字符串是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,
        url = createObjectURL(files[0]);
    
    if(url){
        if(/image/.test(files[0].type)){
            output.innerHTML = "<img src=\"" + url +"\"/>";
        }else{
            output.innerHTML = "Not an image."
        }
    }else{
        alert("Your browser doesn't support URLs.");
    }
});

直接把對象URL放到<img>標籤中,就省去了把數據先讀到JS中的麻煩。另外一方面,<img>標籤則會找到響應的內存地址,直接讀取數據並將圖像顯示在頁面中。

若是再也不須要相應的數據,最好釋放它佔用的內存。但只要有代碼在引用對象URL,內存就不會釋放。要手工釋放內存,能夠把對象URL傳給window.URL.revokeObjectURL()(在Chrome中是window.webkitURL.revokeObjectURL()),兼容寫法以下:

function revokeObjectURL(blob){
    if(window.URL){
        return window.URL.revokeObjectURL(blob);
    }else if(window.webkitURL){
        return window.webkitURL.revokeObjectURL(blob);
    }else{
        return null;
    }
}

支持對象URL的瀏覽器爲IE10+、Firefox和Chrome。

讀取拖放的文件

從桌面上把文件拖放到瀏覽器中也會觸發drop事件。並且能夠在event.dataTransfer.files中讀取到被放置的文件,固然此時它是一個File對象,與經過文件輸入字段取得的File對象同樣。

下面例子會將放置到頁面中自定義的放置目標中的文件信息顯示出來:

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 + "byte)<br/>";
            i ++;
        }
        output.innerHTML = info;
    }
}

EventUtil.addHandler(droptarget,"dragenter",handleEvent);
EventUtil.addHandler(droptarget,"dragover",handleEvent);
EventUtil.addHandler(droptarget,"drop",handleEvent);

使用XHR上傳文件

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","FileAPIUpload.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);

Web計時

Web計時機制的核心是window.performance對象。window.performance對象有兩個屬性performance.navigationperformance.timing

Web Workers

隨着Web應用複雜性的與日俱進,愈來愈複雜的計算在所不免。長時間運行的Javascript進程會致使瀏覽器凍結用戶界面,讓人感受屏幕「凍結」了。Web Workers規範經過讓JS在後臺運行解決了這個問題。瀏覽器實現Web Workers規範的方式有不少種,可使用線程、後臺進程或者運行在其餘處理器核心上的進程,等等。

目前支持Web Workers的瀏覽器IE10+以及其它高版本瀏覽器。

使用Worker

實例化Worker對象並傳入要執行的JS文件名就能夠建立一個新的Worker,以下:

var worker = new Worker("js/index.js");

這行代碼會致使瀏覽器下載index.js,但只有接收到消息纔會實際執行文件中的代碼。要給Worker傳遞消息,可使用postMessage()方法(與XDM中的postMessage()方法相似):

worker.postMessage("start!");

消息內容能夠是任何能被序列化的值,不過與XDM不一樣的是,在全部支持的瀏覽器中,postMessage()都能接收對象參數。所以,能夠隨便傳遞任何形式的對象數據,以下例子:

worker.postMessage({
    type:"Command",
    message:"start!"
});

Worker是經過messageerror事件與頁面通訊的。來自Worker的數據保存在event.data中。Worker返回的數據也能夠是任何能被序列化的值:

worker.onmessage = function(event){
    var data = event.data;
    
    //對數據執行處理
}

Worker不能完成給定的任務時會觸發error事件。具體來講,Worker內部的JS在執行過程當中只要遇到錯誤,就會觸發error事件。發生error事件時,事件對象包含三個屬性:filenamelinenomessage,分別表示發生錯誤的文件名、代碼行號和完整的錯誤信息。

worker.onerror = function(event){
    console.log("ERROR:" + event.filename + "(" + event.lineno + "):" + event.message);
}

只要調用terminate()方法就能夠中止Worker的工做。

worker.terminate(); //當即中止Worker的工做

Worker全局做用域

Web Worker中的全局對象是worker對象自己。也就是說,在這個特殊的全局做用域中this和self引用的都是worker對象。爲便於處理數據,Web Worker自己也是一個最小化的運行環境。

  • 最小化的navigator對象,包括onLine、appName、appVersion、userAgent和platform屬性。
  • 只讀的location對象。
  • setTimeout()、setInterval()、clearTimeout()和clearInterVal()方法。
  • XMLHttpRequest構造函數。

顯然,Web Worker的運行環境與頁面環境相比,功能是至關有限的。

當頁面在worker對象上調用postMessage()時,數據會以異步方式被傳遞給worker,進而觸發worker中的message事件。爲了處理來自頁面的數據,一樣也須要建立一個onmessage事件處理程序。

//Web Worker內部代碼
self.onmessage = function(event){
    var data = event.data;
    
    //處理數據
}

你們看清楚,這裏的self引用的是Worker全局做用域中的worker對象(與頁面中的Worker對象不一樣一個對象)。Worker完成工做後,經過調用postMessage()能夠把數據再發回頁面。例以下面的例子假如須要Worker對傳入的數組進行排序,而Worker在排序以後又將數組發回了頁面:

//Web Worker內部代碼
self.onmessage = function(event){
    var data = event.data;
    
    //別忘了,默認的sort方法只比較字符串
    data.sort(function(a,b){
        return a - b;
    })
    self.postMessage(data);
}

 傳遞消息就是頁面與Worker相互之間通訊的方式。在Worker中調用postMessage()會以異步的方式觸發頁面中Worker實例的message事件。若是頁面想要使用這個Worker,能夠這樣:

//在頁面中
var data = [23,4,7,59,11,24,222,10,3],
    worker = new Worker("index.js");

worker.onmessage = function(event){
    var data = event.data;
    
    //對排序後的數組進行操做
    console.log(data);  //[3, 4, 7, 10, 11, 23, 24, 59, 222]
}

//將數組發送給worker排序
worker.postMessage(data);

在上面創建的index.js中,也就是在Worker做用域下代碼以下:

self.onmessage = function(event){
    var data = event.data;
    data.sort(function(a,b){
        return a - b;
    })
    self.postMessage(data);
}

排序的確是比較消耗時間的操做,所以轉交給Worker作就不會阻塞用戶界面了。另外把彩色圖像轉換成灰階圖像以及加密解密之類的操做也是至關費時的。

在Worker內部,調用close()方法也能夠中止工做,Worker中止工做後就不會再有事件發生了。

//web worker內部的代碼
    self.close();

包含其它腳本

雖然沒法在Worker中動態建立<script>元素,Worker的全局做用域提供了一個方法是importScripts(),這個方法接收一個或者多個指向JS文件的URL。每一個加載過程都是異步的,所以全部腳本加載並執行以後,importScripts()纔會執行,以下代碼:

//web worker內部的代碼
importScripts("file1.js","file2.js");

即便file2.js優先於file1.js下載完,執行的時候仍然會按照前後順序執行。

Web Worker詳細可參考:《Web Worker 使用教程 - 阮一峯的網絡日誌_阮一峯的我的網站

相關文章
相關標籤/搜索