JS红宝书·读书笔记 -- 下篇
JavaScript 高级程序设计
花了半个多月的时间,终于又把“JS红宝书”又撸了一遍。
第一次读“JS红宝书”还是2015年初学JS的时候,那时候只是把语法部分读了一遍,还有一些浏览器相关知识做了下了解,大概也就读了半本的样子,
就开始了用JS进行开发了,在成长的道路上遇见了JQuery,当时真的是感觉到JQuery太友好了,慢慢放下了原生开发。
现在呢,更多的时候是在用框架进行开发,越来越觉得自己的JS基础很缺乏,然后就开启了“JS红宝书”二刷之路。
下面就把书中自己觉得重要的、没有掌握的知识整理出来。因为我觉得还是会三刷“JS红宝书”,希望把这本700多页的书越读越薄,勉励。
章节
- 事件
- 表单脚本
- HTML5脚本编程
- 错误处理与调试
- JSON
- Ajax 与 Comet
- 高级技巧
- 离线应用与客户端存储
- 新型的API
事件
事件流
事件冒泡
IE的事件流叫做事件冒泡,即事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。
事件捕获
Netscape 团队提出的事件流叫做事件捕获,事件捕获的用意在于在事件到达预定目标之前捕获它。
DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
事件处理程序
DOM0 级事件处理程序
每个元素(包括window和document)都有自己的事件处理程序,这些属性通常全部小写。
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节点中都包含这两个方法,接受三个参数:事件名、事件处理程序和布尔值。最后这个布尔值如果是true,表示在捕获阶段调用事件处理程序;false表示在冒泡阶段调用事件处理程序,默认是false。
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除。如果通过addEventListener()添加的匿名函数将无法移除。
btn.addEventListener('click', function () { //匿名函数
...
})
注:大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段(false),这样可以最大限度地兼容各种浏览器。最好只在需要在事件到达目标之前截获它的时候将事件处理程序添加到捕获阶段。如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。
IE事件处理程序
attachEvent()detachEvent()
这两个方法接受两个参数:事件名(带on)和事件处理函数。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
注:在IE 中使用attachEvent()与使用DOM0 级方法的主要区别在于事件处理程序的作用域。
- DOM0 级作用域是其所属元素
-
attachEvent()方法的作用域是全局(this === window)
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
alert("Clicked");
});
btn.attachEvent("onclick", function(){
alert("Hello world!");
});
注:与DOM方法不同的是,这些事件处理程序不是以添加它们的顺序执行,而是以相反的顺序被触发。单击这个例子中的按钮,首先看到的是"Hello world!",然后才是"Clicked"。
事件对象
在触发DOM上的某个事件时,会产生一个事件对象event。
DOM中的事件对象
event对象成员
| 属性/方法 | 类型 | 读/写 | 说明 |
| : - : | : -- : | : -- : | : -- : | : -- : |
| bubbles | Boolean | 只读 | 表明事件是否冒泡 |
| cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为 |
| currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素 |
| defaultPrevented | Boolean | 只读 | 为true表示已经调用preventDefault() |
| detail | Integer | 只读 | 与事件相关的细节信息 |
| eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1 捕获,2 处于目标,3 冒泡 |
| preventDefault() | Function | 只读 | 取消事件的默认行为。如果cancelable 是true,则可以使用这个方法 |
| stopImmediatePropagation() | Function | 只读 | 取消事件的进一步冒泡或捕获,同时阻止任何事件处理程序被调用 |
| stopPropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 |
| target | Element | 只读 | 事件的目标 |
| trusted | Boolean | 只读 | 为true表示事件是浏览器生成,false是开发人员创建 |
| type | String | 只读 | 被触发的事件类型 |
| view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 |
在事件处理程序内部,对象this 始终等于currentTarget 的值,而target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget 和target 包含相同的值。
document.body.onclick = function(event){
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
};
调用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事件:鼠标在元素内部移动重复触发 -
mouseout事件:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素 -
mouseover事件:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发 -
mouseup事件:释放鼠标按钮时触发
页面上的所有元素都支持鼠标事件。除了mouseenter 和mouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。
只有在同一个元素上相继触发mousedown 和mouseup 事件,才会触发click 事件;如果mousedown 或mouseup 中的一个被取消,就不会触发click 事件。
触摸设备
iOS和Android设备的相关事件:
- 不支持
dbclick事件。双击浏览器窗口会放大画面 - 轻击可单击元素会触发
mousemove事件。。如果此操作会导致内容变化,将不再有其他事件发生;如果屏幕没有因此变化,那么会依次发生mousedown、mouseup和click事件。轻击不可单击的元素不会触发任何事件。可单击的元素是指那些单击可产生默认操作的元素(如链接),或者那些已经被指定了onclick事件处理程序的元素。 -
mousemove事件也会触发mouseover和mouseout事件 - 两个手指放在屏幕上且页面随手指移动而滚动时会触发
mousewheel和scroll事件。
HTML5事件
-
contextmenu事件 -
beforeunload事件 -
DOMContentLoaded事件 -
readystatechange事件-
uninitialized未初始化 loadingloaded-
interactive:可以操作对象,但还没有完全加载 complete
-
-
hashchange事件
设备事件
-
orientationchange事件:横竖屏,有三个值: -90 ,0, 90
触摸与手势事件
- 触摸事件
touchstarttouchmovetouchendtouchcancel
- 手势事件
gesturestartgesturechangegestureend
内存和性能
事件委托
例如在<ul>为添加一个click事件,所有<li>子元素点击事件都会冒泡到<ul>上。
表单脚本
表单基础知识
提交表单
<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"];
//取得表单中包含的字段的数量
var fieldCount = form.elements.length;
文本框脚本
过滤输入
屏蔽特定的字符,需要检测keypress事件对应的字符编码。
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()方法可以检测表单中的某个字段是否有效。是否有效的判断依据是一些<input>的约束条件。
if (document.forms[0].elements[0].checkValidity()){
//字段有效,继续
} else {
//字段无效
}
也可以检测整个表单是否有效
if(document.forms[0].checkValidity()){
//表单有效,继续
} else {
//表单无效
}
禁用验证
<form method="post" action="signup.php" novalidate>
<!--这里插入表单元素-->
</form>
HTML5 脚本编程
跨文档消息传递
跨文档消息传送(cross-document messaging)简称XDM。其核心方法是postMessage()方法。
postMessage()方法接受两个参数:一条消息和一个表示消息接收方来自哪个域的字符串。
// 注意:所有支持XDM的浏览器也支持iframe的`contentWindow`属性
var iframeWindow = document.getElementById('myframe').contentWindow;
iframeWindow.postMessage('A secret', 'https://yeasoenzhang.github.io');
尝试向内嵌框架中发送一条消息,并指定框架中的文档必须来源于https://yeasonzhang.github.io域。
接收到XDM消息时,会触发window对象的message事件,这个事件是以异步形式触发。
传递的onmessage处理程序的事件对象包含三个重要信息:
-
data:作为postMessage()第一个参数传入的字符串数据 -
origin:发送消息的文档所在的域。 -
source:发送消息的文档的window对象的代理。
EventUtil.addHandler(window, "message", function(event){
//确保发送消息的域是已知的域
if (event.origin == "https://yeasonzhang.github.io"){
//处理接收到的数据
processMessage(event.data);
//可选:向来源窗口发送回执
event.source.postMessage("Received!", "http://p2p.wrox.com");
}
});
XDM 还有一些怪异之处。首先,postMessage()的第一个参数最早是作为“永远都是字符串”来实现的。但后来这个参数的定义改了,改成允许传入任何数据结构。可是,并非所有浏览器都实现了这一变化。为保险起见,使用postMessage()时,最好还是只传字符串。如果你想传入结构化的数据,最佳选择是先在要传入的数据上调用JSON.stringify(),通过postMessage()传入得到的字符串,然后再在onmessage 事件处理程序中调用JSON.parse()。
原生拖放
拖放事件
拖动某个元素时,将依次触发的事件:
dragstartdragdragend
当某个元素被拖动到一个有效的放置目标时,会依次触发下列事件:
dragenterdragover-
dragleave(离开)或drag(放进去了)
dataTransfer对象
dataTransfer对象,它是事件对象的一个属性,用于被拖动元素向放置目标传递字符串格式的数据。该对象有两个主要方法:
getData()setData()
//设置和接收文本数据
event.dataTransfer.setData("text", "some text");
var text = event.dataTransfer.getData("text");
//设置和接收URL
event.dataTransfer.setData("URL", "http://www.wrox.com/");
var url = event.dataTransfer.getData("URL");
不过,保存在dataTransfer对象中的数据只能在drap事件处理程序中读取。如果在ondrop 处理程序中没有读到数据,那就是dataTransfer 对象已经被销毁,数据也丢失了。
drapEffect 与 effectAllowed
dateTransfer对象有两个属性:
dropEffecteffectAllowed
dropEffect,属性可以知道被拖动的元素能够执行那种放置行为。
-
none:不能放在这里 -
move:应该把拖放的元素移动到放置目标 -
copy:应该把拖动的元素复制到放置目标 -
link:表示放置目标会打开拖动的元素
要使用dropEffect属性,必须在ondragenter事件处理程序中针对放置目标来设置。
effectAllowed属性表示允许拖动元素的哪种dropEffect。
-
uninitialized:没有给被拖动的元素放置任何放置行为 -
none:被拖动的元素不能有任何行为 -
copy:只允许值为copy的dropEffect -
link:只允许值为link的dropEffect -
move:只允许值为move的dropEffect -
copyLink:允许值为copy和link的dropEffect -
copyMove:允许值为copy和move的dropEffect -
linkMove:允许值为link和move的dropEffect -
all: 允许任意dropEffect
必须在ondragstart 事件处理程序中设置effectAllowed 属性。
可拖动
HTML5为所有元素规定了draggable属性,表示元素是否可以拖动。只有图像和链接的draggable默认是true
<!-- 让这个图像不可以拖动 -->

<!-- 让这个元素可以拖动 -->
<div draggable="true">...</div>
其他成员
HTML5规定了dateTransfer对象还应该包含下列方法和属性。
addElement(element)clearData(format)setDragImage(element, x, y)type
错误处理与调试
错误处理
try-catch 语句
try {
// 可能出错的代码
} catch (err) {
// 处理发生的错误
}
finally子句
只要代码中包含finially子句,无论try还是catch语句中的return语句都将被忽略。
错误类型
ErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError
try {
someFunction();
} catch (error) {
if (error instanceof TypeError) {
//...
} else {
//
}
}
抛出错误
与try-catch 语句相配的还有一个throw 操作符,用于随时抛出自定义错误。抛出错误时,必须要给throw 操作符指定一个值,这个值是什么类型,没有要求。
throw 12345;
throw "Hello world!";
throw true;
throw { name: "JavaScript"};
遇到throw操作符时,代码会立即停止执行。只有当try-catch语句捕获到被抛出值,代码才会继续执行
自定义错误类型
可以利用原型链通过继承Error创建自定义错误类型。需要为新创建的错误类型指定name和message属性
function CustomError (message) {
this.name = 'CustomError';
this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError('Error msg');
Error事件
任何没有通过try-catch处理的错误都会触发window对象的error事件。
在任何Web浏览器中,onerror事件处理程序都不会创建event对象,但它可以接受三个参数:错误消息、错误所在的URL和行号。
要指定onerror 事件处理程序,必须使用如下所示的DOM0 级技术,它没有遵循“DOM2 级事件”的标准格式(addEventListener)。
window.onerror = function(message, url, line){
alert(message);
};
只要发生错误,无论是不是浏览器生成的,都会触发error事件,然后让浏览器的默认机制发挥作用,这时候我们需要阻止浏览器的默认行为(return false)。
window.onerror = function (message, url, line) {
console.log(message);
retrun false;
}
常见的错误类型
- 类型转换错误
- 数据类型错误
- 通信错误
在数据检测的时候,基本类型的值应该使用typeof来检测,对象的值应该使用instanceof。
JSON
解析与序列化
JSON对象
JSON对象有两个方法:stringify和parse()。
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对象外,还可以接受两个参数,一个是过滤器(数组或函数),第二个参数是一个选项,表示是都在JSON字符串中保留缩进。
过滤结果
var book = {
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book, ["title", "edition"]);
第二个参数中包含两个字符串"title", "edition",所以只会返回对应的属性
{"title":"Professional JavaScript","edition":3}
过滤器为函数
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()方法的第三个参数用于控制结果中的缩进和空白符。可以是数字,表示缩进的空格数;也可以是字符串,将该字符串作为缩进的表示。
toJSON()方法
解析选项
JSON.parse()方法也可以接受第二参数,该参数是一个函数(被称为还原函数),传入函数的参数均为key, value。
如果还原函数返回undefined,则表示要从结果中删除响应的键。
Ajax与Comet
XMLHttpRequest 对象
XHR的用法
-
open('method', 'url', bool):第三个参数表示是否异步发送 -
send():接受一个参数作为请求主体发送的数据,如果不需要则传入null
XHR对象的属性
-
responseText:作为相应主体被返回的文本 -
responseXML:如果相应的内容类型是"text/xml"或"application/xml",这个属性中将包含这响应数据的XML DOM文档 -
status:响应的HTTP状态 -
statusText:HTTP状态的说明
同步请求
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:表示请求/响应过程的阶段- 0:未初始化,尚未调用
open()方法 - 1:启动,调用了
open()方法,尚未调用send()方法 - 2:发送,调用了
send()方法,尚未接收到响应。 - 3:接收,接收到部分响应数据
- 4:完成,已经接收到全部响应数据
- 0:未初始化,尚未调用
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():在接收到响应之前通过该方法取消异步请求。
建议调用这个方法之后,对XHR对象进行解引用操作。
HTTP 头部信息
默认情况下,在发送XHR请求的同时,还会发送下列头部信息:
-
Accept:浏览器能够处理的内容类型 -
Accept-Charset:浏览器能够显示的字符集 -
Accept-Encoding:浏览器能够处理的压缩编码 -
Accept-Language:浏览器当前设置的语言 -
Connection:浏览器与服务器之间连接的类型 -
Cookie: 当前页面的 Cookie -
Host:发出请求的页面所在的域 -
Referer:发出请求的页面的URI -
User-Agent:浏览器的用户代理
自定义请求头部信息,使用setRequestHeader()方法,该方法接受两个参数:头部字段的名称和头部字段的值。
要成功发送请求头部信息,必须在调用open()方法之后且调用send()方法之前调用serRequestHeader()。
var xhr = createXHR();
xhr.onreadystatechange = function(){
// ...
};
xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
注建议使用自定义的头部字段名称,不要使用浏览器正常发送的字段名称,否则有可能会影响服务器的响应。有的浏览器允许开发人员重写默认的头部信息,但有的浏览器则不允许这样做。
调用XHR对象的getResponseHeader()方法,接受一个参数:头部字段名称。就能取得相应的响应头部信息。
调用getAllResponseHeaders()方法可以取得包含所有头部信息的字符串。
GET请求
使用GET请求经常会发生一个错误,就是查询字符串的格式有问题。查询字符串中每个参数的名称和值都必须使用encodeURIComponent()进行编码,然后才能放到URL的末尾。
function addURLParam(url, name, value) {
url += (url.indexOf("?") == -1 ? "?" : "&");
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
var url = "example.php";
//添加参数
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
//初始化请求
xhr.open("get", url, false);
POST请求
如果我们希望用XHR模仿表单提交,需要将Content-Type头部信息设置为application/x-www-form-urlencoded(表单提交的内容类型)
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为序列化表单以及创建于表单格式相同的数据提供了便利。
var data = new FormData();
data.append('name', 'Yeaseon');
append方法可以将表单的字段和值,传入FormData对象中。也可以预先填入表单中的字段:
var data = new FormData(document.form[0]);
FormData的方便就在于不必手动修改XHR对象的请求头部。
超时设定
XHR对象添加了一个timeout属性,表示请求在等待多少毫秒之后终止。如果规定时间内浏览器没有收到响应,就会触发timeout事件,进而调用ontimeout事件处理程序。
var xhr = createXHR();
xhr.onreadystatechange = function(){
// ...
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; //将超时设置为1 秒钟
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
xhr.send(null);
超时之后请求终止,但是此时的readyState可能已经变为了4,就意味着会调用onreadystatechange事件。
可是,如果在超时终止请求之后再访问status 属性,就会导致错误。为避免浏览器报告错误,可以将检查status 属性的语句封装在一个try-catch语句当中。
overrideMimeType()方法
用于重写XHR响应的MIME类型。因为返回响应的MIME 类型决定了XHR 对象如何处理它,所以提供一种方法能够重写服务器返回的MIME 类型是很有用的。
var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
进度事件
有以下6个进度事件:
-
loadstart:在接收到响应数据的第一个字节触发 -
progress:在接收响应期间持续不断地触发 -
error:在请求发生错误时触发 -
abort:在因为调用abort()方法而终止连接时触发 -
load:在接收到完整的响应数据时触发 -
loadend:在通信完成或者触发error、abort,或load事件后触发
progress事件
onprogress事件处理程序会接收到一个event对象,target属性指向XHR对象,包含着三个额外的属性:
-
lengthComputable:表示进度信息是否可用的布尔值 -
position:表示已经接受的字节数 -
totalSize:表示根据Content-Length响应头部确定的预期字节数。
跨资源共享
IE对CORS的实现
微软在IE8中引入了XDR类型,类似与XHR对象,两者的不同之处:
- cookie不会随请求发送,也不会随响应返回
- 只能设置请求头部信息中的
Content-Type字段 - 不能访问响应头部信息
- 只支持
GET和POST请求
请求返回之后,就会触发load事件,响应数据也会保存在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);
在请求返回之前可以调用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属性,用来表示发送数据的格式。
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的实现
与IE中的XDR对象不同,通过跨域XHR对象可以访问status和statusText属性,并且支持同步请求。同时也有一些限制:
- 不能使用
setRequestHeader()设置自定义头部 - 不能发送和接收
cookie - 调用
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);
其他跨域技术
图像Ping
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
JSONP
JSONP是JSON with padding的简写。JSONP只不过时被包含在函数调用中的JSON:
callback({"name": "Yeaseon"});
JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是请求中指定的。下面是一个经典的JSONP请求
http://freegeoip.net/json/?callback=handleResponse
这里指定的回调函数的名字叫做handleResponse。
JSONP是通过动态<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);
服务器发送事件
SSE支持短轮询、长轮训和HTTP流,而且能在断开连接时自动确定何时重新连接。
SSE API
要预订新的事件流,首先要创建一个新的EventSource对象,并传入一个入口点:
var source = new EventSource('myevents.php');
传入的URL必须与创建对象的页面同源。
EventSource的实例有一个readyState属性:0表示正连接到服务器,1表示打开了连接,2表示关闭了连接。
EventSource实例还有三个事件:
-
open:在建立连接时触发 -
message:在从服务器接收到新事件时触发 -
error:在无法建立连接时触发
服务器发回的数据以字符串形式保存在event.data中。
默认情况下,EventSource对象会保持与服务器的活动连接。如果想强制立即断开连接并且不在重新连接,可以调用close()方法。
Web Sockets
由于 Web Sockets 使用了自定义的协议,所以 URL 模式也略有不同。未加密的连接不再是 http:// ,而是 ws:// ;加密的连接也不是 https:// ,而是 wss:// 。
Web Sockets API
创建一个WebSockets实例对象:
var socket = new WebSocket("ws://www.example.com/server.php");
WebSocket也有一个表示当前状态的readyState属性:
-
WebSocket.OPENING (0):正在建立连接 -
WebSocket.OPEN (1):已经建立连接 -
WebSocket.CLOSING (2):正在关闭连接 -
WebSocket.CLOSE (3):已经关闭连接
发送和接收数据
向服务器发送数据,使用send()方法并传入任意字符串:
var socket = new WebSocket('ws:// www.example.com/server.php');
socket.send('Hello World');
Web Sockets只能发送纯文本数据,对于复杂的数据结构,在发送之前,必须进行序列化。
var message = {
time: new Date(),
text: 'Hello world',
clientId: 'adfalsf39'
};
socket.send(JSON.stringify(message));
当服务器向客户端发来消息时,WebSocket对象就会触发message事件。这个message事件与其他传递消息的协议类似,也是把返回的数据保存在event.data属性中。
socket.onmessage = function (event) {
var data = event.data;
// ....
}
与send()类似,event.data中返回的数据也是字符串,需要手工解析这些数据。
其他事件
WebSocket对象还有其他三个事件,在连接生命周期的不同阶段触发:
-
open:在成功建立连接时触发 -
error:在发生错误时触发,连接不能持续 -
close:在连接关闭时触发
WebSocked对象不支持DOM 2级事件监听:
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.");
};
高级技巧
高级函数
安全的类型检测
用于区分原生和非原生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]";
}
作用域安全的构造函数
防止构造函数内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);
}
}
惰性载入函数
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.');
}
}
})();
函数绑定
bind()函数,语法如下:
function bind (fn, context) {
return function () {
return fn.apply(context, arguments);
}
}
离线应用与客户端存储
离线检测
navigator.onLine属性可以判断设备否能访问网络。
HTML5定义两个事件:online和offline,当网络状态变化时,分别触发这两个事件:
EventUtil.addHandler(window, 'online', function () {
console.log('online');
});
EventUtil.addHandler(window, 'offline', function () {
console.log('offline');
});
数据存储
Web存储机制
Web Storage规范包含两种对象的定义:sessionStorage和globalStorage。这两个对象在支持的浏览器中都是以windows对象属性的形式存在。
Storage类型
Storage类型提供最大的存储空间来存储名值对。
-
clear():删除所有值 -
getItem(name):根据指定的名字name获取对应的值 -
key(index):获得index位置处的值的名字 -
removeItem(name):删除由name指定的名值对 -
setItem(name, value):为指定的name设置一个对应的值
sessionStorage对象
sessionStorage对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。存储在sessionStorage中的数据可以跨越页面刷新而存在。
//使用方法存储数据
sessionStorage.setItem("name", "Nicholas");
//使用属性存储数据
sessionStorage.book = "Professional JavaScript";
//使用方法读取数据
var name = sessionStorage.getItem("name");
//使用属性读取数据
var book = sessionStorage.book;
//使用delete 删除一个值——在WebKit 中无效
delete sessionStorage.name;
//使用方法删除一个值
sessionStorage.removeItem("book");
可以通过结合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循环来迭代sessionStorage中的值:
for (var key in sessionStorage) {
var value = sessionStorage.getItem(key);
console.log(key + ' = ' + value);
}
globalStorage对象
这个对象的目的是跨越会话存储数据,,但有特定的访问限制。要使用globalStorage,首先要指定哪些域可以访问该数据。
// 保存数据
globalStorage['wrox.com'].name = 'Yeaseon';
// 获取数据
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规范中作为持久保存客户端数据的方案,并且取代globalStorage。要访问同一个localStorage对象,页面必须来自同一个域名(子域名无效),必须同源。
//使用方法存储数据
localStorage.setItem("name", "Nicholas");
//使用属性存储数据
localStorage.book = "Professional JavaScript";
//使用方法读取数据
var name = localStorage.getItem("name");
//使用属性读取数据
var book = localStorage.book;
storage事件
对Storage对象进行任何修改,都会在文档上触发storage事件。这个事件的event对象有以下属性。
-
domain:发生变化的存储空间的域名 -
key:设置或删除的键名 -
newValue:如果是设置值,则是新值;如果是删除键,则是null -
oldValue:键被更改之前的值
在这四个属性中,IE8 和Firefox 只实现了domain 属性。在撰写本书的时候,WebKit 尚不支持
storage 事件
IndexedDB
Indexed Database API,简称IndexedDB,是在浏览器中保存结构化数据的一种数据库。IndexedDB设计的操作完全是异步进行。
var indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB;
数据库
IndexedDB就是一个数据库,它最大的特色就是使用对象保存数据,而不是使用表来保存数据。
indexDB.open(),传入一个数据库参数。如果该数据库存在就会发送一个打开它的请求;如果该数据库不存在,就会发送一个创建并打开它的请求。请求会返回一个IDBRequest对象,这个对象上可以添加onerror和onsuccess事件处理程序。
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对象,因此他们可以互换使用。
发生错误了,event.target.errorCode中将会保存一个错误码:
-
IDBDatebaseException.UNKNOWN_ERR(1):意外错误 -
IDBDatebaseException.NON_TRANSIENT_ERR(2):操作不合法 -
IDBDatebaseException.NOT_FOUND_ERR(3):未发现要操作的数据库 -
IDBDatebaseException.CONSTRAINT_ERR(4):违反了数据库约束 -
IDBDatebaseException.DATA_ERR(5):提供给事务的数据不能满足要求 -
IDBDatebaseException.NOT_ALLOWED_ERR(6):操作不合法 -
IDBDatebaseException.TRANSACTION_INACTIVE_ERR(7):试图重用已完成的事务 -
IDBDatebaseException.ABORT_ERR(8):请求中断 -
IDBDatebaseException.READ_ONLY_ERR(9):试图在只读模式下写入或修改数据 -
IDBDatebaseException.TIMEOUT_ERR(10):在有效时间内未完成操作 -
IDBDatebaseException.QUOTA_ERR(11):磁盘空间不足
指定数据库版本号,通过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);
}
对象存储空间
假设要保存的用户记录由用户名、密码等组成,那么保存一条记录的对象应该类似:
var user = {
username: '007',
firstname: 'James',
lastname: 'Bond',
password: 'foo'
}
如果使用username属性作为这个对象存储空间的键,这个username必须全局唯一,而且大部分时候都要通过这个键来访问数据。
var store = db.createObjectStore('users', { keyPath: 'username' });
其中第二个参数中的keyPath属性,就是空间中将要保存的对象的一个属性,而这个属性将作为存储空间的键来使用。
通过add()或put()方法来向存储空间添加数据。着两个方法都接收一个参数,就是要保存的对象。
//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);
}
事务
在数据库对象上调用transaction()可以创建事务,任何时候,只要想读取或修改数据,都要通过事务来组织所有操作。
// 创建事务
var transaction = db.transaction();
可以传入要访问的一或多个对象存储空间。
var transaction = db.transaction('users');
var transaction = db.transaction(['users', 'anotherStore']);
前面这些事务都是以只读方式访问数据。要修改访问方式,必须在创建事务时传入第二个参数,这个参数表示访问模式:
-
IDBTransaction.READ_ONLY(0):只读 -
IDBTransaction.READ_WRITE(1):读写 -
IDBTransaction.VERSION_CHANGE(2):改变
在Chrome中叫做webkitIDBTransaction,可以使用一下代码兼容:
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
这样就能方便的指定transaction()第二个参数:
var transaction = db.transaction('users', IDBTransaction.READ_WRITE);
取得事务的索引后,使用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'
};
也可以针对事务对象本身进行事件处理,存在两个事件onerror,oncomplete:
transaction.onerror = function (event) {
// 整个事务都被取消了
}
transaction.oncomplete = function (event) {
// 整个事务都成功完成了
}
注:在oncomplete事件的事件对象中访问不到get()请求返回的数据,必须在onsuccess事件中处理。
键范围
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
有四种定义键范围的方法:
-
only():取得指定对象的键 -
lowerBound():指定结果集的下界 -
upperBound():指定结果集的上界 -
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 是为了让开发人员知道页面是否对用户可见推出的。
-
document.hidden:表示页面是否隐藏的布尔值。 -
document.visibilityState- 页面在后台标签页中或浏览器最小化
- 页面在前台标签页中
- 实际的页面已经隐藏,但用户可以看到页面的预览
- 页面在屏幕外执行预渲染处理
-
visibilitychange事件:当文档可见性发生改变时,触发该事件。
Geolocation API
Geolocation API 在浏览器中的实现是navigator.geolocation对象。
getCurrentPosition()
调用这个方法就会触发请求用户共享地理定位信息的对话框。这个方法接收三个参数:成功回调,可选的失败回调和可选的选项对象。
成功回调会接收到一个Position对象参数,该对象有两个属性:coords和timestamp。
coords对象中包含于位置相关的信息:
-
latitude:十进制度数表示的纬度 -
longitude:十进制度数表示的经度 -
accuracy:经纬度坐标的精度,以米为单位
navigator.geolocation.getCurrentPosition(function (position) {
drawMapCenteredAt(position.coords.latitude, position.coords.longitude);
});
失败回调在被调用的时候也会接受到一个参数,这个参数是一个对象,包含连个属性:message和code。code保存着一个数值,表示错误的类型:用户拒绝共享(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);
});
第三个参数是一个可选对象,用于设定信息的类型。可以设置的选项有三个:
-
enableHightAccuracy:布尔值,表示必须尽可能使用最准确定的位置信息 -
timeout:以毫秒数表示的等待位置信息的最长时间 -
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 在表单中的文件输入字段的基础上,又添加了一些直接访问文件信息的接口。HTML5在DOM中为文件输入元素添加了一个files集合。每个File对象都有下列只读属性。
-
name:本地文件系统的文件名 -
size:文件的字节大小 -
type:字符串,文件的MIME类型 -
lastModifiedDate:字符串,文件上一次修改的时间
FileReader 类型
FileReader 类型实现的是一种异步文件读取机制。可以把FileReader想象成XMLHttpRequest。
-
readAsText(file, encoding):以纯文本形式读取文件,将读取到的文本保存在result属性中,第二个参数用于指定编码类型(可选) -
readAsDataURL(file):读取文件并将文件以数据URI形式保存在result属性中 -
readAsBinaryString(file):读取文件并将一个字符串保存在result属性中,字符串中的每个字符表示一字节 -
readAsArrayBuffer(file):读取文件并将一个包含文件内容的ArrayBuffer保存在result属性中
由于读取过程是异步的,所以FileReader提供了三个事件:
progresserrorload
progress事件,每50ms就会触发一次,通过事件对象可以获得与XHR的progress事件相同的信息:
lengthComputableloadedtotal
由于种种原因无法读取文件,都会触发error事件,相关信息都会保存到FileReader的error属性中。error.code即错误码:
-
1:为找到文件 -
2:安全性错误 -
3:读取中断 -
4:文件不可读 -
5:编码错误
文件加载成功后会触发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;
};
});
读取拖放的文件
从桌面上把文件拖放到浏览器中会触发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; //转换成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上传文件
创建一个FormDate对象,通过它调用append()方法并传入相应的File对象作为参数,再把FormData对象传递给XHR的send()方法。
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);