JS紅寶書·讀書筆記 -- 下篇

JavaScript 高級程序設(shè)計

個人博客:https://yeaseonzhang.github.io

花了半個多月的時間,終于又把“JS紅寶書”又擼了一遍。

第一次讀“JS紅寶書”還是2015年初學JS的時候,那時候只是把語法部分讀了一遍,還有一些瀏覽器相關(guān)知識做了下了解,大概也就讀了半本的樣子,
就開始了用JS進行開發(fā)了,在成長的道路上遇見了JQuery,當時真的是感覺到JQuery太友好了,慢慢放下了原生開發(fā)。

現(xiàn)在呢,更多的時候是在用框架進行開發(fā),越來越覺得自己的JS基礎(chǔ)很缺乏,然后就開啟了“JS紅寶書”二刷之路。

下面就把書中自己覺得重要的、沒有掌握的知識整理出來。因為我覺得還是會三刷“JS紅寶書”,希望把這本700多頁的書越讀越薄,勉勵。

章節(jié)

  • 事件
  • 表單腳本
  • HTML5腳本編程
  • 錯誤處理與調(diào)試
  • JSON
  • Ajax 與 Comet
  • 高級技巧
  • 離線應(yīng)用與客戶端存儲
  • 新型的API

事件


事件流

事件冒泡

IE的事件流叫做事件冒泡,即事件開始時由最具體的元素接受,然后逐級向上傳播到較為不具體的節(jié)點。

事件捕獲

Netscape 團隊提出的事件流叫做事件捕獲,事件捕獲的用意在于在事件到達預(yù)定目標之前捕獲它。

DOM事件流

“DOM2級事件”規(guī)定的事件流包括三個階段:事件捕獲階段處于目標階段事件冒泡階段

事件處理程序

DOM0 級事件處理程序

每個元素(包括windowdocument)都有自己的事件處理程序,這些屬性通常全部小寫。

var btn = document.getElementById('myBtn');
btn.onclick = function () {
    console.log('clicked');
}

DOM 0級方法指定的事件處理程序被認為是元素的方法,因此,這個時候的事件處理程序是在元素的作用域中運行,也就是說程序中的this可以引用當前元素。

var btn = document.getElementById('myBtn');
btn.onclick = function () {
    console.log(this.id);  // 'myBtn'
}

以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。

DOM2 級事件處理程序

  • addEventListener()
  • removeEventListener()

定義了兩個方法用于處理指定和刪除事件處理程序的操作。所有的DOM節(jié)點中都包含這兩個方法,接受三個參數(shù):事件名事件處理程序布爾值。最后這個布爾值如果是true,表示在捕獲階段調(diào)用事件處理程序;false表示在冒泡階段調(diào)用事件處理程序,默認是false

通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除。如果通過addEventListener()添加的匿名函數(shù)將無法移除。

btn.addEventListener('click', function () {  //匿名函數(shù)
    ...
})

:大多數(shù)情況下,都是將事件處理程序添加到事件流的冒泡階段(false),這樣可以最大限度地兼容各種瀏覽器。最好只在需要在事件到達目標之前截獲它的時候?qū)⑹录幚沓绦蛱砑拥讲东@階段。如果不是特別需要,我們不建議在事件捕獲階段注冊事件處理程序。

IE事件處理程序

  • attachEvent()
  • detachEvent()

這兩個方法接受兩個參數(shù):事件名(帶on)和事件處理函數(shù)。

var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert("Clicked");
});

:在IE 中使用attachEvent()與使用DOM0 級方法的主要區(qū)別在于事件處理程序的作用域。

  • DOM0 級作用域是其所屬元素
  • attachEvent()方法的作用域是全局(this === window
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
    alert("Clicked");
});
btn.attachEvent("onclick", function(){
    alert("Hello world!");
});

:與DOM方法不同的是,這些事件處理程序不是以添加它們的順序執(zhí)行,而是以相反的順序被觸發(fā)。單擊這個例子中的按鈕,首先看到的是"Hello world!",然后才是"Clicked"。

事件對象

在觸發(fā)DOM上的某個事件時,會產(chǎn)生一個事件對象event

DOM中的事件對象

event對象成員

| 屬性/方法 | 類型 | 讀/寫 | 說明 |
| : - : | : -- : | : -- : | : -- : | : -- : |
| bubbles | Boolean | 只讀 | 表明事件是否冒泡 |
| cancelable | Boolean | 只讀 | 表明是否可以取消事件的默認行為 |
| currentTarget | Element | 只讀 | 其事件處理程序當前正在處理事件的那個元素 |
| defaultPrevented | Boolean | 只讀 | 為true表示已經(jīng)調(diào)用preventDefault() |
| detail | Integer | 只讀 | 與事件相關(guān)的細節(jié)信息 |
| eventPhase | Integer | 只讀 | 調(diào)用事件處理程序的階段:1 捕獲,2 處于目標,3 冒泡 |
| preventDefault() | Function | 只讀 | 取消事件的默認行為。如果cancelabletrue,則可以使用這個方法 |
| stopImmediatePropagation() | Function | 只讀 | 取消事件的進一步冒泡或捕獲,同時阻止任何事件處理程序被調(diào)用 |
| stopPropagation() | Function | 只讀 | 取消事件的進一步捕獲或冒泡。如果bubblestrue,則可以使用這個方法 |
| target | Element | 只讀 | 事件的目標 |
| trusted | Boolean | 只讀 | 為true表示事件是瀏覽器生成,false是開發(fā)人員創(chuàng)建 |
| type | String | 只讀 | 被觸發(fā)的事件類型 |
| view | AbstractView | 只讀 | 與事件關(guān)聯(lián)的抽象視圖。等同于發(fā)生事件的window對象 |

在事件處理程序內(nèi)部,對象this 始終等于currentTarget 的值,而target 則只包含事件的實際目標。如果直接將事件處理程序指定給了目標元素,則thiscurrentTargettarget 包含相同的值。

document.body.onclick = function(event){
    alert(event.currentTarget === document.body); //true
    alert(this === document.body); //true
    alert(event.target === document.getElementById("myBtn")); //true
};

調(diào)用event方法

var link = document.getElementById("myLink");
link.onclick = function(event){
    event.preventDefault();
};

跨瀏覽器的事件對象

var EventUtil = {
    addHandler: function(element, type, handler){
        //省略的代碼
    },
    getEvent: function(event){
        return event ? event : window.event;
    },
    getTarget: function(event){
        return event.target || event.srcElement;
    },
    preventDefault: function(event){
        if (event.preventDefault){
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    },
    removeHandler: function(element, type, handler){
        //省略的代碼
    },
    stopPropagation: function(event){
        if (event.stopPropagation){
            event.stopPropagation();
        } else {
            event.cancelBubble = true;
        }
    }
};

事件類型

UI 事件

  • load事件
  • unload事件
  • resize事件
  • scroll事件

焦點事件

  • blur事件:失去焦點
  • focus事件:獲得焦點

鼠標與滾動事件

  • click事件
  • dbclick事件
  • mousedown事件:按下鼠標
  • mouseenter事件:光標移入
  • mouseleave事件:光標移出
  • mousemove事件:鼠標在元素內(nèi)部移動重復(fù)觸發(fā)
  • mouseout事件:在鼠標指針位于一個元素上方,然后用戶將其移入另一個元素時觸發(fā)。又移入的另一個元素可能位于前一個元素的外部,也可能是這個元素的子元素
  • mouseover事件:在鼠標指針位于一個元素外部,然后用戶將其首次移入另一個元素邊界之內(nèi)時觸發(fā)
  • mouseup事件:釋放鼠標按鈕時觸發(fā)

頁面上的所有元素都支持鼠標事件。除了mouseentermouseleave,所有鼠標事件都會冒泡,也可以被取消,而取消鼠標事件將會影響瀏覽器的默認行為。

只有在同一個元素上相繼觸發(fā)mousedownmouseup 事件,才會觸發(fā)click 事件;如果mousedownmouseup 中的一個被取消,就不會觸發(fā)click 事件。

觸摸設(shè)備

iOS和Android設(shè)備的相關(guān)事件:

  • 不支持dbclick事件。雙擊瀏覽器窗口會放大畫面
  • 輕擊可單擊元素會觸發(fā)mousemove事件。。如果此操作會導致內(nèi)容變化,將不再有其他事件發(fā)生;如果屏幕沒有因此變化,那么會依次發(fā)生mousedownmouseupclick 事件。輕擊不可單擊的元素不會觸發(fā)任何事件。可單擊的元素是指那些單擊可產(chǎn)生默認操作的元素(如鏈接),或者那些已經(jīng)被指定了onclick 事件處理程序的元素。
  • mousemove事件也會觸發(fā)mouseovermouseout事件
  • 兩個手指放在屏幕上且頁面隨手指移動而滾動時會觸發(fā)mousewheelscroll事件。

HTML5事件

  • contextmenu事件
  • beforeunload事件
  • DOMContentLoaded事件
  • readystatechange事件
    • uninitialized未初始化
    • loading
    • loaded
    • interactive:可以操作對象,但還沒有完全加載
    • complete
  • hashchange事件

設(shè)備事件

  • orientationchange事件:橫豎屏,有三個值: -90 ,0, 90

觸摸與手勢事件

  • 觸摸事件
    • touchstart
    • touchmove
    • touchend
    • touchcancel
  • 手勢事件
    • gesturestart
    • gesturechange
    • gestureend

內(nèi)存和性能

事件委托

例如在<ul>為添加一個click事件,所有<li>子元素點擊事件都會冒泡到<ul>上。

表單腳本


表單基礎(chǔ)知識

提交表單

<input type="submit" value="Submit Form">

重置表單

<input type="reset" value="Reset Form">

表單字段

每個表單都有elements屬性,該屬性是表單中所有表單元素的集合。

var form = document.getElementById("form1");
//取得表單中的第一個字段
var field1 = form.elements[0];
//取得名為"textbox1"的字段
var field2 = form.elements["textbox1"];
//取得表單中包含的字段的數(shù)量
var fieldCount = form.elements.length;

文本框腳本

過濾輸入

屏蔽特定的字符,需要檢測keypress事件對應(yīng)的字符編碼。

EventUtil.addHandler(textbox, 'keypress', function (event) {
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    var charCode = EventUtil.getCharCode(event);

    if (!/\d/.test(String.fromCharCode(charCode))) {
        EventUtil.preventDefault(event);
    }
})

HTML5約束驗證API

輸入模式

HTML5為文本字段新增了pattern屬性。

<input type="text" pattern="\d+" name="count">
檢測有效性

使用checkValidity()方法可以檢測表單中的某個字段是否有效。是否有效的判斷依據(jù)是一些<input>的約束條件。

if (document.forms[0].elements[0].checkValidity()){
    //字段有效,繼續(xù)
} else {
    //字段無效
}

也可以檢測整個表單是否有效

if(document.forms[0].checkValidity()){
    //表單有效,繼續(xù)
} else {
    //表單無效
}
禁用驗證
<form method="post" action="signup.php" novalidate>
    <!--這里插入表單元素-->
</form>

HTML5 腳本編程


跨文檔消息傳遞

跨文檔消息傳送(cross-document messaging)簡稱XDM。其核心方法是postMessage()方法。

postMessage()方法接受兩個參數(shù):一條消息和一個表示消息接收方來自哪個域的字符串。

// 注意:所有支持XDM的瀏覽器也支持iframe的`contentWindow`屬性
var iframeWindow = document.getElementById('myframe').contentWindow;
iframeWindow.postMessage('A secret', 'https://yeasoenzhang.github.io');

嘗試向內(nèi)嵌框架中發(fā)送一條消息,并指定框架中的文檔必須來源于https://yeasonzhang.github.io域。

接收到XDM消息時,會觸發(fā)window對象的message事件,這個事件是以異步形式觸發(fā)。
傳遞的onmessage處理程序的事件對象包含三個重要信息:

  • data:作為postMessage()第一個參數(shù)傳入的字符串數(shù)據(jù)
  • origin:發(fā)送消息的文檔所在的域。
  • source:發(fā)送消息的文檔的window對象的代理。
EventUtil.addHandler(window, "message", function(event){
    //確保發(fā)送消息的域是已知的域
    if (event.origin == "https://yeasonzhang.github.io"){
        //處理接收到的數(shù)據(jù)
        processMessage(event.data);
        //可選:向來源窗口發(fā)送回執(zhí)
        event.source.postMessage("Received!", "http://p2p.wrox.com");
    }
});

XDM 還有一些怪異之處。首先,postMessage()的第一個參數(shù)最早是作為“永遠都是字符串”來實現(xiàn)的。但后來這個參數(shù)的定義改了,改成允許傳入任何數(shù)據(jù)結(jié)構(gòu)。可是,并非所有瀏覽器都實現(xiàn)了這一變化。為保險起見,使用postMessage()時,最好還是只傳字符串。如果你想傳入結(jié)構(gòu)化的數(shù)據(jù),最佳選擇是先在要傳入的數(shù)據(jù)上調(diào)用JSON.stringify(),通過postMessage()傳入得到的字符串,然后再在onmessage 事件處理程序中調(diào)用JSON.parse()

原生拖放

拖放事件

拖動某個元素時,將依次觸發(fā)的事件:

  • dragstart
  • drag
  • dragend

當某個元素被拖動到一個有效的放置目標時,會依次觸發(fā)下列事件:

  • dragenter
  • dragover
  • dragleave(離開)或drag(放進去了)

dataTransfer對象

dataTransfer對象,它是事件對象的一個屬性,用于被拖動元素向放置目標傳遞字符串格式的數(shù)據(jù)。該對象有兩個主要方法:

  • getData()
  • setData()
//設(shè)置和接收文本數(shù)據(jù)
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");

//設(shè)置和接收URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");

不過,保存在dataTransfer對象中的數(shù)據(jù)只能在drap事件處理程序中讀取。如果在ondrop 處理程序中沒有讀到數(shù)據(jù),那就是dataTransfer 對象已經(jīng)被銷毀,數(shù)據(jù)也丟失了。

drapEffect 與 effectAllowed

dateTransfer對象有兩個屬性:

  • dropEffect
  • effectAllowed

dropEffect,屬性可以知道被拖動的元素能夠執(zhí)行那種放置行為。

  • none:不能放在這里
  • move:應(yīng)該把拖放的元素移動到放置目標
  • copy:應(yīng)該把拖動的元素復(fù)制到放置目標
  • link:表示放置目標會打開拖動的元素

要使用dropEffect屬性,必須在ondragenter事件處理程序中針對放置目標來設(shè)置。

effectAllowed屬性表示允許拖動元素的哪種dropEffect

  • uninitialized:沒有給被拖動的元素放置任何放置行為
  • none:被拖動的元素不能有任何行為
  • copy:只允許值為copydropEffect
  • link:只允許值為linkdropEffect
  • move:只允許值為movedropEffect
  • copyLink:允許值為copylinkdropEffect
  • copyMove:允許值為copymovedropEffect
  • linkMove:允許值為linkmovedropEffect
  • all: 允許任意dropEffect

必須在ondragstart 事件處理程序中設(shè)置effectAllowed 屬性。

可拖動

HTML5為所有元素規(guī)定了draggable屬性,表示元素是否可以拖動。只有圖像和鏈接的draggable默認是true

<!-- 讓這個圖像不可以拖動 -->
![](smile.gif)
<!-- 讓這個元素可以拖動 -->
<div draggable="true">...</div>

其他成員

HTML5規(guī)定了dateTransfer對象還應(yīng)該包含下列方法和屬性。

  • addElement(element)
  • clearData(format)
  • setDragImage(element, x, y)
  • type

錯誤處理與調(diào)試


錯誤處理

try-catch 語句

try {
    // 可能出錯的代碼
} catch (err) {
    // 處理發(fā)生的錯誤
}
finally子句

只要代碼中包含finially子句,無論try還是catch語句中的return語句都將被忽略。

錯誤類型
  • Error
  • EvalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError
try {
    someFunction();
} catch (error) {
    if (error instanceof TypeError) {
        //...
    } else {
        //
    }
}

拋出錯誤

try-catch 語句相配的還有一個throw 操作符,用于隨時拋出自定義錯誤。拋出錯誤時,必須要給throw 操作符指定一個值,這個值是什么類型,沒有要求。

throw 12345;
throw "Hello world!";
throw true;
throw { name: "JavaScript"};

遇到throw操作符時,代碼會立即停止執(zhí)行。只有當try-catch語句捕獲到被拋出值,代碼才會繼續(xù)執(zhí)行

自定義錯誤類型

可以利用原型鏈通過繼承Error創(chuàng)建自定義錯誤類型。需要為新創(chuàng)建的錯誤類型指定namemessage屬性

function CustomError (message) {
    this.name = 'CustomError';
    this.message = message;
}

CustomError.prototype = new Error();

throw new CustomError('Error msg');

Error事件

任何沒有通過try-catch處理的錯誤都會觸發(fā)window對象的error事件。

在任何Web瀏覽器中,onerror事件處理程序都不會創(chuàng)建event對象,但它可以接受三個參數(shù):錯誤消息、錯誤所在的URL和行號。

要指定onerror 事件處理程序,必須使用如下所示的DOM0 級技術(shù),它沒有遵循“DOM2 級事件”的標準格式(addEventListener)。

window.onerror = function(message, url, line){
    alert(message);
};

只要發(fā)生錯誤,無論是不是瀏覽器生成的,都會觸發(fā)error事件,然后讓瀏覽器的默認機制發(fā)揮作用,這時候我們需要阻止瀏覽器的默認行為(return false)。

window.onerror = function (message, url, line) {
    console.log(message);
    retrun false;
}

常見的錯誤類型

  • 類型轉(zhuǎn)換錯誤
  • 數(shù)據(jù)類型錯誤
  • 通信錯誤

在數(shù)據(jù)檢測的時候,基本類型的值應(yīng)該使用typeof來檢測,對象的值應(yīng)該使用instanceof

JSON


解析與序列化

JSON對象

JSON對象有兩個方法:stringifyparse()

var book = {
    title: "Professional JavaScript",
    authors: [
        "Nicholas C. Zakas"
    ],
    edition: 3,
    year: 2011
};
var jsonText = JSON.stringify(book);

以上就把Javascript對象序列化為一個JSON字符串(沒有空格和縮進)

{"title":"Professional JavaScript","authors":["Nicholas C. Zakas"],"edition":3,"year":2011}

如果傳給JSON.parse()的字符串不是有效的JSON,會拋出錯誤。

序列化選項

JSON.stringify()除了要序列化的JS對象外,還可以接受兩個參數(shù),一個是過濾器(數(shù)組或函數(shù)),第二個參數(shù)是一個選項,表示是都在JSON字符串中保留縮進。

過濾結(jié)果
var book = {
    "title": "Professional JavaScript",
    "authors": [
        "Nicholas C. Zakas"
    ],
    edition: 3,
    year: 2011
};
var jsonText = JSON.stringify(book, ["title", "edition"]);

第二個參數(shù)中包含兩個字符串"title", "edition",所以只會返回對應(yīng)的屬性

{"title":"Professional JavaScript","edition":3}

過濾器為函數(shù)

var book = {
    "title": "Professional JavaScript",
    "authors": [
        "Nicholas C. Zakas"
    ],
    edition: 3,
    year: 2011
};
var jsonText = JSON.stringify(book, function(key, value){
    switch(key){
        case "authors":
            return value.join(",")
        case "year":
            return 5000;
        case "edition":
            return undefined;
        default:
            return value;
    }
});

:返回undefined刪除該屬性,上例的edition屬性就會被刪除。

字符串縮進

JSON.stringify()方法的第三個參數(shù)用于控制結(jié)果中的縮進和空白符。可以是數(shù)字,表示縮進的空格數(shù);也可以是字符串,將該字符串作為縮進的表示。

toJSON()方法

解析選項

JSON.parse()方法也可以接受第二參數(shù),該參數(shù)是一個函數(shù)(被稱為還原函數(shù)),傳入函數(shù)的參數(shù)均為key, value

如果還原函數(shù)返回undefined,則表示要從結(jié)果中刪除響應(yīng)的鍵。

Ajax與Comet


XMLHttpRequest 對象

XHR的用法

  • open('method', 'url', bool):第三個參數(shù)表示是否異步發(fā)送
  • send():接受一個參數(shù)作為請求主體發(fā)送的數(shù)據(jù),如果不需要則傳入null

XHR對象的屬性

  • responseText:作為相應(yīng)主體被返回的文本
  • responseXML:如果相應(yīng)的內(nèi)容類型是"text/xml""application/xml",這個屬性中將包含這響應(yīng)數(shù)據(jù)的XML DOM文檔
  • status:響應(yīng)的HTTP狀態(tài)
  • statusText:HTTP狀態(tài)的說明

同步請求

xhr.open("get", "example.txt", false);
xhr.send(null);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
    alert(xhr.responseText);
} else {
    alert("Request was unsuccessful: " + xhr.status);
}

  • readyState:表示請求/響應(yīng)過程的階段
    • 0:未初始化,尚未調(diào)用open()方法
    • 1:啟動,調(diào)用了open()方法,尚未調(diào)用send()方法
    • 2:發(fā)送,調(diào)用了send()方法,尚未接收到響應(yīng)。
    • 3:接收,接收到部分響應(yīng)數(shù)據(jù)
    • 4:完成,已經(jīng)接收到全部響應(yīng)數(shù)據(jù)
var xhr = createXHR();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
};
xhr.open("get", "example.txt", true);
xhr.send(null);
  • abort():在接收到響應(yīng)之前通過該方法取消異步請求。
    建議調(diào)用這個方法之后,對XHR對象進行解引用操作。

HTTP 頭部信息

默認情況下,在發(fā)送XHR請求的同時,還會發(fā)送下列頭部信息:

  • Accept:瀏覽器能夠處理的內(nèi)容類型
  • Accept-Charset:瀏覽器能夠顯示的字符集
  • Accept-Encoding:瀏覽器能夠處理的壓縮編碼
  • Accept-Language:瀏覽器當前設(shè)置的語言
  • Connection:瀏覽器與服務(wù)器之間連接的類型
  • Cookie: 當前頁面的 Cookie
  • Host:發(fā)出請求的頁面所在的域
  • Referer:發(fā)出請求的頁面的URI
  • User-Agent:瀏覽器的用戶代理

自定義請求頭部信息,使用setRequestHeader()方法,該方法接受兩個參數(shù):頭部字段的名稱和頭部字段的值。

要成功發(fā)送請求頭部信息,必須在調(diào)用open()方法之后且調(diào)用send()方法之前調(diào)用serRequestHeader()

var xhr = createXHR();
xhr.onreadystatechange = function(){
    // ...
};
xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);

建議使用自定義的頭部字段名稱,不要使用瀏覽器正常發(fā)送的字段名稱,否則有可能會影響服務(wù)器的響應(yīng)。有的瀏覽器允許開發(fā)人員重寫默認的頭部信息,但有的瀏覽器則不允許這樣做。

調(diào)用XHR對象的getResponseHeader()方法,接受一個參數(shù):頭部字段名稱。就能取得相應(yīng)的響應(yīng)頭部信息。
調(diào)用getAllResponseHeaders()方法可以取得包含所有頭部信息的字符串。

GET請求

使用GET請求經(jīng)常會發(fā)生一個錯誤,就是查詢字符串的格式有問題。查詢字符串中每個參數(shù)的名稱和值都必須使用encodeURIComponent()進行編碼,然后才能放到URL的末尾。

function addURLParam(url, name, value) {
    url += (url.indexOf("?") == -1 ? "?" : "&");
    url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
    return url;
}

var url = "example.php";
//添加參數(shù)
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
//初始化請求
xhr.open("get", url, false);

POST請求

如果我們希望用XHR模仿表單提交,需要將Content-Type頭部信息設(shè)置為application/x-www-form-urlencoded(表單提交的內(nèi)容類型)

function submitData(){
    var xhr = createXHR();
    xhr.onreadystatechange = function(){
        if (xhr.readyState == 4){
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
                alert(xhr.responseText);
            } else {
                alert("Request was unsuccessful: " + xhr.status);
            }
        }
    };
    xhr.open("post", "postexample.php", true);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    var form = document.getElementById("user-info");
    xhr.send(serialize(form));
}

XMLHttpRequest 2 級

FormData

FormData為序列化表單以及創(chuàng)建于表單格式相同的數(shù)據(jù)提供了便利。

var data = new FormData();
data.append('name', 'Yeaseon');

append方法可以將表單的字段和值,傳入FormData對象中。也可以預(yù)先填入表單中的字段:

var data = new FormData(document.form[0]);

FormData的方便就在于不必手動修改XHR對象的請求頭部。

超時設(shè)定

XHR對象添加了一個timeout屬性,表示請求在等待多少毫秒之后終止。如果規(guī)定時間內(nèi)瀏覽器沒有收到響應(yīng),就會觸發(fā)timeout事件,進而調(diào)用ontimeout事件處理程序。

var xhr = createXHR();
xhr.onreadystatechange = function(){
    // ...
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; //將超時設(shè)置為1 秒鐘
xhr.ontimeout = function(){
    alert("Request did not return in a second.");
};
xhr.send(null);

超時之后請求終止,但是此時的readyState可能已經(jīng)變?yōu)榱?code>4,就意味著會調(diào)用onreadystatechange事件。

可是,如果在超時終止請求之后再訪問status 屬性,就會導致錯誤。為避免瀏覽器報告錯誤,可以將檢查status 屬性的語句封裝在一個try-catch語句當中。

overrideMimeType()方法

用于重寫XHR響應(yīng)的MIME類型。因為返回響應(yīng)的MIME 類型決定了XHR 對象如何處理它,所以提供一種方法能夠重寫服務(wù)器返回的MIME 類型是很有用的。

var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);

進度事件

有以下6個進度事件:

  • loadstart:在接收到響應(yīng)數(shù)據(jù)的第一個字節(jié)觸發(fā)
  • progress:在接收響應(yīng)期間持續(xù)不斷地觸發(fā)
  • error:在請求發(fā)生錯誤時觸發(fā)
  • abort:在因為調(diào)用abort()方法而終止連接時觸發(fā)
  • load:在接收到完整的響應(yīng)數(shù)據(jù)時觸發(fā)
  • loadend:在通信完成或者觸發(fā)errorabort,或load事件后觸發(fā)

progress事件
onprogress事件處理程序會接收到一個event對象,target屬性指向XHR對象,包含著三個額外的屬性:

  • lengthComputable:表示進度信息是否可用的布爾值
  • position:表示已經(jīng)接受的字節(jié)數(shù)
  • totalSize:表示根據(jù)Content-Length響應(yīng)頭部確定的預(yù)期字節(jié)數(shù)。

跨資源共享

IE對CORS的實現(xiàn)

微軟在IE8中引入了XDR類型,類似與XHR對象,兩者的不同之處:

  • cookie不會隨請求發(fā)送,也不會隨響應(yīng)返回
  • 只能設(shè)置請求頭部信息中的Content-Type字段
  • 不能訪問響應(yīng)頭部信息
  • 只支持GETPOST請求

請求返回之后,就會觸發(fā)load事件,響應(yīng)數(shù)據(jù)也會保存在responseText屬性中:

var xdr = new XDomainRequest();
xdr.onload = function () {
    console.log(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.open('get', 'http://..../xxx/');
xdr.send(null);

在請求返回之前可以調(diào)用abort()方法終止請求。

xdr.abort();

XDR對象也支持timeout屬性以及ontimeout事件處理程序

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
    alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

為了支持POST請求,XDR對象提供了contentType屬性,用來表示發(fā)送數(shù)據(jù)的格式。

var xdr = new XDomainRequest();
xdr.onload = function () {
    //
}
xdr.onerror = function () {
    //
}
xdr.open('post', 'http://www.somewhere-else.com/page/');
xdr.contentType = 'application/x-www-form-urlencoded';
xdr.send('name1=value1&name2=value2');

其他瀏覽器對CORS的實現(xiàn)

與IE中的XDR對象不同,通過跨域XHR對象可以訪問statusstatusText屬性,并且支持同步請求。同時也有一些限制:

  • 不能使用setRequestHeader()設(shè)置自定義頭部
  • 不能發(fā)送和接收cookie
  • 調(diào)用getAllResponseHeaders()方法總會返回空字符串
var xhr = createXHR();
xhr.onreadystatechange = function(){
    if (xhr.readyState == 4){
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    }
};
xhr.open("get", "http://www.somewhere-else.com/page/", true);
xhr.send(null);

其他跨域技術(shù)

圖像Ping

var img = new Image();
img.onload = img.onerror = function(){
    alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";

JSONP

JSONPJSON with padding的簡寫。JSONP只不過時被包含在函數(shù)調(diào)用中的JSON

callback({"name": "Yeaseon"});

JSONP由兩部分組成:回調(diào)函數(shù)和數(shù)據(jù)。回調(diào)函數(shù)是當響應(yīng)到來時應(yīng)該在頁面中調(diào)用的函數(shù)。回調(diào)函數(shù)的名字一般是請求中指定的。下面是一個經(jīng)典的JSONP請求

http://freegeoip.net/json/?callback=handleResponse

這里指定的回調(diào)函數(shù)的名字叫做handleResponse

JSONP是通過動態(tài)<script>元素來使用的,使用時可以為src屬性指定一個跨域URL

function handleResponse(response){
    alert("You’re at IP address " + response.ip + ", which is in " +
        response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

服務(wù)器發(fā)送事件

SSE支持短輪詢、長輪訓和HTTP流,而且能在斷開連接時自動確定何時重新連接。

SSE API

要預(yù)訂新的事件流,首先要創(chuàng)建一個新的EventSource對象,并傳入一個入口點:

var source = new EventSource('myevents.php');

傳入的URL必須與創(chuàng)建對象的頁面同源。
EventSource的實例有一個readyState屬性:0表示正連接到服務(wù)器,1表示打開了連接,2表示關(guān)閉了連接。
EventSource實例還有三個事件:

  • open:在建立連接時觸發(fā)
  • message:在從服務(wù)器接收到新事件時觸發(fā)
  • error:在無法建立連接時觸發(fā)

服務(wù)器發(fā)回的數(shù)據(jù)以字符串形式保存在event.data中。
默認情況下,EventSource對象會保持與服務(wù)器的活動連接。如果想強制立即斷開連接并且不在重新連接,可以調(diào)用close()方法。

Web Sockets

由于 Web Sockets 使用了自定義的協(xié)議,所以 URL 模式也略有不同。未加密的連接不再是 http:// ,而是 ws:// ;加密的連接也不是 https:// ,而是 wss:// 。

Web Sockets API

創(chuàng)建一個WebSockets實例對象:

var socket = new WebSocket("ws://www.example.com/server.php");

WebSocket也有一個表示當前狀態(tài)的readyState屬性:

  • WebSocket.OPENING (0) :正在建立連接
  • WebSocket.OPEN (1):已經(jīng)建立連接
  • WebSocket.CLOSING (2):正在關(guān)閉連接
  • WebSocket.CLOSE (3):已經(jīng)關(guān)閉連接
發(fā)送和接收數(shù)據(jù)

向服務(wù)器發(fā)送數(shù)據(jù),使用send()方法并傳入任意字符串:

var socket = new WebSocket('ws:// www.example.com/server.php');
socket.send('Hello World');

Web Sockets只能發(fā)送純文本數(shù)據(jù),對于復(fù)雜的數(shù)據(jù)結(jié)構(gòu),在發(fā)送之前,必須進行序列化。

var message = {
    time: new Date(),
    text: 'Hello world',
    clientId: 'adfalsf39'
};

socket.send(JSON.stringify(message));

當服務(wù)器向客戶端發(fā)來消息時,WebSocket對象就會觸發(fā)message事件。這個message事件與其他傳遞消息的協(xié)議類似,也是把返回的數(shù)據(jù)保存在event.data屬性中。

socket.onmessage = function (event) {
 var data = event.data;
 // ....
}

send()類似,event.data中返回的數(shù)據(jù)也是字符串,需要手工解析這些數(shù)據(jù)。

其他事件

WebSocket對象還有其他三個事件,在連接生命周期的不同階段觸發(fā):

  • open:在成功建立連接時觸發(fā)
  • error:在發(fā)生錯誤時觸發(fā),連接不能持續(xù)
  • close:在連接關(guān)閉時觸發(fā)

WebSocked對象不支持DOM 2級事件監(jiān)聽:

var socket = new WebSocket("ws://www.example.com/server.php");
socket.onopen = function(){
    alert("Connection established.");
};
socket.onerror = function(){
    alert("Connection error.");
};
socket.onclose = function(){
    alert("Connection closed.");
};

高級技巧


高級函數(shù)

安全的類型檢測

用于區(qū)分原生和非原生JavaScript對象,通過Object.prototype.toString()

function isArray(value){
    return Object.prototype.toString.call(value) == "[object Array]";
}

function isFunction(value){
    return Object.prototype.toString.call(value) == "[object Function]";
}

function isRegExp(value){
    return Object.prototype.toString.call(value) == "[object RegExp]";
}

作用域安全的構(gòu)造函數(shù)

防止構(gòu)造函數(shù)內(nèi)this指針的指向被改變(指向window

function Person (name, age, job) {
    if (this instanceof Person) {
        this.name = name;
        this.age = age;
        this.job = job;
    } else {
        return new Person(name, age, job);
    }
}

惰性載入函數(shù)

function createXHR(){
    if (typeof XMLHttpRequest != "undefined"){
        return new XMLHttpRequest();
    } else if (typeof ActiveXObject != "undefined"){
        if (typeof arguments.callee.activeXString != "string"){
            var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
                i,len;
            for (i=0,len=versions.length; i < len; i++){
                try {
                    new ActiveXObject(versions[i]);
                    arguments.callee.activeXString = versions[i];
                    break;
                } catch (ex){
                    //跳過
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
    } else {
        throw new Error("No XHR object available.");
    }
}

第一種改法:

function createXHR () {
    if (typeof XMLHttpRequest != 'undefined') {
        createXHR = function () {
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObjext != 'undefined') {
        createXHR = function () {
            if (typeof arguments.callee.activeXString != 'string') {
                var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
                    i,len;
                for (i = 0; len = versions.length; i < len; i++) {
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        break;
                    } catch (e) {
                        // skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        createXHR = function () {
            throw new Error('No XHR object available.');
        }
    }
    return createXHR();
}

第二種改法:

var createXHR = (function () {
    if (typeof XMLHttpRequest != 'undefined') {
        return function () {
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObjext != 'undefined') {
        return function () {
            if (typeof arguments.callee.activeXString != 'string') {
                var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"],
                    i,len;
                for (i = 0; len = versions.length; i < len; i++) {
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        break;
                    } catch (e) {
                        // skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        return function () {
            throw new Error('No XHR object available.');
        }
    }
})();

函數(shù)綁定

bind()函數(shù),語法如下:

function bind (fn, context) {
    return function () {
        return fn.apply(context, arguments);
    }
}

離線應(yīng)用與客戶端存儲


離線檢測

navigator.onLine屬性可以判斷設(shè)備否能訪問網(wǎng)絡(luò)。

HTML5定義兩個事件:onlineoffline,當網(wǎng)絡(luò)狀態(tài)變化時,分別觸發(fā)這兩個事件:

EventUtil.addHandler(window, 'online', function () {
    console.log('online');
});
EventUtil.addHandler(window, 'offline', function () {
    console.log('offline');
});

數(shù)據(jù)存儲

Web存儲機制

Web Storage規(guī)范包含兩種對象的定義:sessionStorageglobalStorage。這兩個對象在支持的瀏覽器中都是以windows對象屬性的形式存在。

Storage類型

Storage類型提供最大的存儲空間來存儲名值對。

  • clear():刪除所有值
  • getItem(name):根據(jù)指定的名字name獲取對應(yīng)的值
  • key(index):獲得index位置處的值的名字
  • removeItem(name):刪除由name指定的名值對
  • setItem(name, value):為指定的name設(shè)置一個對應(yīng)的值
sessionStorage對象

sessionStorage對象存儲特定于某個會話的數(shù)據(jù),也就是該數(shù)據(jù)只保持到瀏覽器關(guān)閉。存儲在sessionStorage中的數(shù)據(jù)可以跨越頁面刷新而存在。

//使用方法存儲數(shù)據(jù)
sessionStorage.setItem("name", "Nicholas");
//使用屬性存儲數(shù)據(jù)
sessionStorage.book = "Professional JavaScript";

//使用方法讀取數(shù)據(jù)
var name = sessionStorage.getItem("name");
//使用屬性讀取數(shù)據(jù)
var book = sessionStorage.book;

//使用delete 刪除一個值——在WebKit 中無效
delete sessionStorage.name;
//使用方法刪除一個值
sessionStorage.removeItem("book");

可以通過結(jié)合length屬性和key()方法來迭代sessionStorage中的值:

for (var i = 0, len = sessionStorage.length; i < len; i++) {
    var key = sessionStorage.key(i);
    var value = sessionStorage.getItem(key);
    console.log(key + ' = ' + value);
}

還可以使用for-in循環(huán)來迭代sessionStorage中的值:

for (var key in sessionStorage) {
    var value = sessionStorage.getItem(key);
    console.log(key + ' = ' + value);
}
globalStorage對象

這個對象的目的是跨越會話存儲數(shù)據(jù),,但有特定的訪問限制。要使用globalStorage,首先要指定哪些域可以訪問該數(shù)據(jù)。

// 保存數(shù)據(jù)
globalStorage['wrox.com'].name = 'Yeaseon';

// 獲取數(shù)據(jù)
var name = globalStorage['wrox.com'].name;

上例,訪問的是針對域名wrox.com的存儲空間。globalStorage對象不是Storage的實例,
而具體的globalStorage['wrox.com']才是。這個存儲空間對于wrox.com及其所有子域都是可以訪問的。

globalStorage["www.wrox.com"].name = "Yeaseon";
globalStorage["www.wrox.com"].book = "Professional JavaScript";
globalStorage["www.wrox.com"].removeItem("name");
var book = globalStorage["www.wrox.com"].getItem("book");
localStorage對象

localStorage對象是HTML5規(guī)范中作為持久保存客戶端數(shù)據(jù)的方案,并且取代globalStorage。要訪問同一個localStorage對象,頁面必須來自同一個域名(子域名無效),必須同源。

//使用方法存儲數(shù)據(jù)
localStorage.setItem("name", "Nicholas");
//使用屬性存儲數(shù)據(jù)
localStorage.book = "Professional JavaScript";

//使用方法讀取數(shù)據(jù)
var name = localStorage.getItem("name");
//使用屬性讀取數(shù)據(jù)
var book = localStorage.book;
storage事件

Storage對象進行任何修改,都會在文檔上觸發(fā)storage事件。這個事件的event對象有以下屬性。

  • domain:發(fā)生變化的存儲空間的域名
  • key:設(shè)置或刪除的鍵名
  • newValue:如果是設(shè)置值,則是新值;如果是刪除鍵,則是null
  • oldValue:鍵被更改之前的值

在這四個屬性中,IE8 和Firefox 只實現(xiàn)了domain 屬性。在撰寫本書的時候,WebKit 尚不支持
storage 事件

IndexedDB

Indexed Database API,簡稱IndexedDB,是在瀏覽器中保存結(jié)構(gòu)化數(shù)據(jù)的一種數(shù)據(jù)庫。IndexedDB設(shè)計的操作完全是異步進行。

var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB;
數(shù)據(jù)庫

IndexedDB就是一個數(shù)據(jù)庫,它最大的特色就是使用對象保存數(shù)據(jù),而不是使用表來保存數(shù)據(jù)。

indexDB.open(),傳入一個數(shù)據(jù)庫參數(shù)。如果該數(shù)據(jù)庫存在就會發(fā)送一個打開它的請求;如果該數(shù)據(jù)庫不存在,就會發(fā)送一個創(chuàng)建并打開它的請求。請求會返回一個IDBRequest對象,這個對象上可以添加onerroronsuccess事件處理程序。

var request, database;

request = indexedDB.open('admin');
request.onerror = function (event) {
    console.log(event.target.errorCode);
};
request.onsuccess = function (event) {
    database = event.target.result;
};

event.target都指向request對象,因此他們可以互換使用。
發(fā)生錯誤了,event.target.errorCode中將會保存一個錯誤碼:

  • IDBDatebaseException.UNKNOWN_ERR(1):意外錯誤
  • IDBDatebaseException.NON_TRANSIENT_ERR(2):操作不合法
  • IDBDatebaseException.NOT_FOUND_ERR(3):未發(fā)現(xiàn)要操作的數(shù)據(jù)庫
  • IDBDatebaseException.CONSTRAINT_ERR(4):違反了數(shù)據(jù)庫約束
  • IDBDatebaseException.DATA_ERR(5):提供給事務(wù)的數(shù)據(jù)不能滿足要求
  • IDBDatebaseException.NOT_ALLOWED_ERR(6):操作不合法
  • IDBDatebaseException.TRANSACTION_INACTIVE_ERR(7):試圖重用已完成的事務(wù)
  • IDBDatebaseException.ABORT_ERR(8):請求中斷
  • IDBDatebaseException.READ_ONLY_ERR(9):試圖在只讀模式下寫入或修改數(shù)據(jù)
  • IDBDatebaseException.TIMEOUT_ERR(10):在有效時間內(nèi)未完成操作
  • IDBDatebaseException.QUOTA_ERR(11):磁盤空間不足

指定數(shù)據(jù)庫版本號,通過setVersion()方法:

if (database.version != '1.0') {
    request = database.setVersion('1.0');
    request.onerror = function (event) {
        console.log(event.target.errorCode);
    };
    request.onsuccess = function (event) {
        console.log(''Database name: ' + database.name + ', Version: ' + database.version);
    }
} else {
    console.log(''Database name: ' + database.name + ', Version: ' + database.version);
}
對象存儲空間

假設(shè)要保存的用戶記錄由用戶名、密碼等組成,那么保存一條記錄的對象應(yīng)該類似:

var user = {
    username: '007',
    firstname: 'James',
    lastname: 'Bond',
    password: 'foo'
}

如果使用username屬性作為這個對象存儲空間的鍵,這個username必須全局唯一,而且大部分時候都要通過這個鍵來訪問數(shù)據(jù)。

var store = db.createObjectStore('users', { keyPath: 'username' });

其中第二個參數(shù)中的keyPath屬性,就是空間中將要保存的對象的一個屬性,而這個屬性將作為存儲空間的鍵來使用。

通過add()put()方法來向存儲空間添加數(shù)據(jù)。著兩個方法都接收一個參數(shù),就是要保存的對象。

//users 中保存著一批用戶對象
var i=0,
    request,
    requests = [],
    len = users.length;
while(i < len){
    request = store.add(users[i++]);
    request.onerror = function(){
        //處理錯誤
    };
    request.onsuccess = function(){
        //處理成功
    };
    requests.push(request);
}
事務(wù)

在數(shù)據(jù)庫對象上調(diào)用transaction()可以創(chuàng)建事務(wù),任何時候,只要想讀取或修改數(shù)據(jù),都要通過事務(wù)來組織所有操作。

// 創(chuàng)建事務(wù)
var transaction = db.transaction();

可以傳入要訪問的一或多個對象存儲空間。

var transaction = db.transaction('users');

var transaction = db.transaction(['users', 'anotherStore']);

前面這些事務(wù)都是以只讀方式訪問數(shù)據(jù)。要修改訪問方式,必須在創(chuàng)建事務(wù)時傳入第二個參數(shù),這個參數(shù)表示訪問模式:

  • IDBTransaction.READ_ONLY(0):只讀
  • IDBTransaction.READ_WRITE(1):讀寫
  • IDBTransaction.VERSION_CHANGE(2):改變

在Chrome中叫做webkitIDBTransaction,可以使用一下代碼兼容:

var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;

這樣就能方便的指定transaction()第二個參數(shù):

var transaction = db.transaction('users', IDBTransaction.READ_WRITE);

取得事務(wù)的索引后,使用objectStore()方法并傳入存儲空間的名稱,就可以訪問指定的存儲空間。然后通過如下方法操作對象:

  • add()
  • put()
  • get()
  • delete()
  • clear()
var request = db.transaction('users').objectStore('users').get('007');
request.onerror = function (event) {
    console.log('Did not get the object!');
};
request.onsuccess = function (event) {
    var result = event.target.result;
    console.log(result.firstName);  // 'James'
};

也可以針對事務(wù)對象本身進行事件處理,存在兩個事件onerroroncomplete

transaction.onerror = function (event) {
    // 整個事務(wù)都被取消了
}
transaction.oncomplete = function (event) {
    // 整個事務(wù)都成功完成了
}

:在oncomplete事件的事件對象中訪問不到get()請求返回的數(shù)據(jù),必須在onsuccess事件中處理。

鍵范圍
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;

有四種定義鍵范圍的方法:

  • only():取得指定對象的鍵
  • lowerBound():指定結(jié)果集的下界
  • upperBound():指定結(jié)果集的上界
  • bound():同時指定上、下界
var onlyRange = IDBKeyRange.only("007");

//從鍵為"007"的對象開始,然后可以移動到最后
var lowerRange = IDBKeyRange.lowerBound("007");
//從鍵為"007"的對象的下一個對象開始,然后可以移動到最后
var lowerRange = IDBKeyRange.lowerBound("007", true);

//從頭開始,到鍵為"ace"的對象為止
var upperRange = IDBKeyRange.upperBound("ace");
//從頭開始,到鍵為"ace"的對象的上一個對象為止
var upperRange = IDBKeyRange.upperBound("ace", true);

//從鍵為"007"的對象開始,到鍵為"ace"的對象為止
var boundRange = IDBKeyRange.bound("007", "ace");
//從鍵為"007"的對象的下一個對象開始,到鍵為"ace"的對象為止
var boundRange = IDBKeyRange.bound("007", "ace", true);
//從鍵為"007"的對象的下一個對象開始,到鍵為"ace"的對象的上一個對象為止
var boundRange = IDBKeyRange.bound("007", "ace", true, true);
//從鍵為"007"的對象開始,到鍵為"ace"的對象的上一個對象為止
var boundRange = IDBKeyRange.bound("007", "ace", false, true);

新型的API


Page Visibility API

Page Visibility API 是為了讓開發(fā)人員知道頁面是否對用戶可見推出的。

  • document.hidden:表示頁面是否隱藏的布爾值。
  • document.visibilityState
    • 頁面在后臺標簽頁中或瀏覽器最小化
    • 頁面在前臺標簽頁中
    • 實際的頁面已經(jīng)隱藏,但用戶可以看到頁面的預(yù)覽
    • 頁面在屏幕外執(zhí)行預(yù)渲染處理
  • visibilitychange事件:當文檔可見性發(fā)生改變時,觸發(fā)該事件。

Geolocation API

Geolocation API 在瀏覽器中的實現(xiàn)是navigator.geolocation對象。

  • getCurrentPosition()

調(diào)用這個方法就會觸發(fā)請求用戶共享地理定位信息的對話框。這個方法接收三個參數(shù):成功回調(diào),可選的失敗回調(diào)和可選的選項對象。

成功回調(diào)會接收到一個Position對象參數(shù),該對象有兩個屬性:coordstimestamp

coords對象中包含于位置相關(guān)的信息:

  • latitude:十進制度數(shù)表示的緯度
  • longitude:十進制度數(shù)表示的經(jīng)度
  • accuracy:經(jīng)緯度坐標的精度,以米為單位
navigator.geolocation.getCurrentPosition(function (position) {
    drawMapCenteredAt(position.coords.latitude, position.coords.longitude);
});

失敗回調(diào)在被調(diào)用的時候也會接受到一個參數(shù),這個參數(shù)是一個對象,包含連個屬性:messagecodecode保存著一個數(shù)值,表示錯誤的類型:用戶拒絕共享(1)、位置無效(2)或者超時(3)。

navigator.geolocation.getCurrentPosition(function (position) {
    drawMapCenteredAt(position.coords.latitude, position.coords.longitude);
}, function (error) {
    console.log('Error code:' + error.code);
    console.log('Error message:' + error.message);
});

第三個參數(shù)是一個可選對象,用于設(shè)定信息的類型。可以設(shè)置的選項有三個:

  • enableHightAccuracy:布爾值,表示必須盡可能使用最準確定的位置信息
  • timeout:以毫秒數(shù)表示的等待位置信息的最長時間
  • maximumAge:表示上一次取得的坐標信息的有效時間,以毫秒表示,如果時間到則重新取得新坐標信息
navigator.geolocation.getCurrentPosition(function (position) {
    drawMapCenteredAt(position.coords.latitude, position.coords.longitude);
}, function (error) {
    console.log('Error code:' + error.code);
    console.log('Error message:' + error.message);
}, {
    enableHighAccuracy: true,
    timeout: 5000,
    maximumAge: 25000
});

File API

File API 在表單中的文件輸入字段的基礎(chǔ)上,又添加了一些直接訪問文件信息的接口。HTML5在DOM中為文件輸入元素添加了一個files集合。每個File對象都有下列只讀屬性。

  • name:本地文件系統(tǒng)的文件名
  • size:文件的字節(jié)大小
  • type:字符串,文件的MIME類型
  • lastModifiedDate:字符串,文件上一次修改的時間

FileReader 類型

FileReader 類型實現(xiàn)的是一種異步文件讀取機制。可以把FileReader想象成XMLHttpRequest

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

由于讀取過程是異步的,所以FileReader提供了三個事件:

  • progress
  • error
  • load

progress事件,每50ms就會觸發(fā)一次,通過事件對象可以獲得與XHRprogress事件相同的信息:

  • lengthComputable
  • loaded
  • total

由于種種原因無法讀取文件,都會觸發(fā)error事件,相關(guān)信息都會保存到FileReadererror屬性中。error.code即錯誤碼:

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

文件加載成功后會觸發(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.readAsDateURL(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 () {
        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;
    };
});

讀取拖放的文件

從桌面上把文件拖放到瀏覽器中會觸發(fā)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;  //轉(zhuǎn)換成File對象
        i = 0;
        len = files.length;

        while (i < len) {
            info += files[i].name + ' (' + files[i].type + ', ' + files[i].size + ' bytes)<br>';
            i++;
        }
        output.innerHTML = info;
    }
}
// 阻止默認事件,只有 drop 事件會被處理
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);

使用XHR上傳文件

創(chuàng)建一個FormDate對象,通過它調(diào)用append()方法并傳入相應(yīng)的File對象作為參數(shù),再把FormData對象傳遞給XHRsend()方法。

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', 'FileAPIExapleUpload.php', true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                console.log(xhr.responseText);
            }
        };
        xhr.send(data);
    }
}
EventUtil.addHandler(droptarget, "dragenter", handleEvent);
EventUtil.addHandler(droptarget, "dragover", handleEvent);
EventUtil.addHandler(droptarget, "drop", handleEvent);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

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

  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實現(xiàn)同樣的效果;這時候需要使用工廠模式。簡單...
    舟漁行舟閱讀 7,798評論 2 17
  • 單例模式 適用場景:可能會在場景中使用到對象,但只有一個實例,加載時并不主動創(chuàng)建,需要時才創(chuàng)建 最常見的單例模式,...
    Obeing閱讀 2,087評論 1 10
  • 1.JQuery 基礎(chǔ) 改變web開發(fā)人員創(chuàng)造搞交互性界面的方式。設(shè)計者無需花費時間糾纏JS復(fù)雜的高級特性。 1....
    LaBaby_閱讀 1,192評論 0 1
  • 1.JQuery 基礎(chǔ) 改變web開發(fā)人員創(chuàng)造搞交互性界面的方式。設(shè)計者無需花費時間糾纏JS復(fù)雜的高級特性。 1....
    LaBaby_閱讀 1,362評論 0 2
  • 我喜歡上你時的內(nèi)心活動-(電影《喜歡你》主題曲) - 陳綺貞 作詞:韓寒 作曲:陳光榮 編曲:陳光榮 監(jiān)制:陳光榮...
    小太陽xl閱讀 778評論 0 0