第二十一章 Ajax 与 Comet
1、XMLHttpRequest对象
1)创建XMLHttpRequest对象
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.");
}
}
2)使用XMLHttpRequest对象:
open() 方法接收 3 个参数——要发送的请求的类型( "get" 、 "post" 等)、请求的 URL 和表示是否异步发送请求的布尔值。调用 open() 方法并不会真正发送请求,而只是启动一个请求以备发送。
send()方法接收一个参数——要作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入 null。调用 send() 之后,请求就会被分派到服务器。
abort() 方法用于取消异步请求。
3)HTTP头部信息
默认情况下,在发送 XHR 请求的同时,还会发送下列头部信息。
Accept :浏览器能够处理的内容类型。
Accept-Charset :浏览器能够显示的字符集。
Accept-Encoding :浏览器能够处理的压缩编码。
Accept-Language :浏览器当前设置的语言。
Connection :浏览器与服务器之间连接的类型。
Cookie :当前页面设置的任何 Cookie。
Host :发出请求的页面所在的域。
Referer :发出请求的页面的 URI。
User-Agent :浏览器的用户代理字符串。
setRequestHeader() 方法用于设置自定义的请求头部信息,接收两个参数——头部字段的名称和头部字段的值。要成功发送请求头部信息,必须在调用 open() 方法之后且调用 send() 方法之前调用 setRequestHeader()。
调用 XHR 对象的 getResponseHeader() 方法并传入头部字段名称,可以取得相应的响应头部信息。而调用 getAllResponseHeaders() 方法则可以取得一个包含所有头部信息的长字符串。
4)GET请求:最常用于向服务器查询某些信息。
5)POST请求:通常用于向服务器发送应该被保存的数据。
2、XMLHttpRequest2级
1)FormData:用于序列化表单以及创建与表单格式相同的数据(用于通过 XHR 传输)
2)超时设定:
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
try {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
} catch (ex){
//假设由 ontimeout 事件处理程序处理
}
}
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; // 将超时设置为 1秒钟(仅适用于 IE8+ )
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
xhr.send(null);
3)overrideMimeType() 方法:用于重写 XHR 响应的 MIME 类型
3、进度事件
1)load事件:响应接收完毕后将触发 load 事件,因此也就没有必要去检查 readyState 属性了。而 onload 事件处理程序会接收到一个 event 对象,其 target 属性就指向 XHR 对象实例,因而可以访问到 XHR 对象的所有方法和属性。
var xhr = createXHR();
xhr.onload = function(){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
2)progress事件: onprogress 事件处理程序会接收到一个 event 对象,其 target 属性是 XHR 对象,但包含着三个额外的属性: lengthComputable 、 position 和 totalSize 。其中, lengthComputable是一个表示进度信息是否可用的布尔值, position 表示已经接收的字节数, totalSize 表示根据Content-Length 响应头部确定的预期字节数。
进度指示器实例:
var xhr = createXHR();
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){
divStatus.innerHTML = "Received " + event.position + " of " +
event.totalSize +" bytes";
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
4、跨源资源共享:ORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
5、其他跨域技术
1)图像Ping:请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或 204 响应。通过图像 Ping,浏览器得不到任何具体的数据,但通过侦听 load 和 error 事件,它能知道响应是什么时候接收到的。图像 Ping 最常用于跟踪用户点击页面或动态广告曝光次数。图像 Ping 有两个主要的缺点,一是只能发送 GET 请求,二是无法访问服务器的响应文本。
2)JSONP:JSONP 是被包含在函数调用中的 JSON。JSONP 由两部分组成——回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的JSON数据。与图像 Ping 相比,它的优点在于能够直接访问响应文本,支持在浏览器与服务器之间双向通信。不过,JSONP 也有两点不足,首先,JSONP 是从其他域中加载代码执行,其次,要确定 JSONP 请求是否失败并不容易。
3)Comet:Ajax 是一种从页面向服务器请求数据的技术,而 Comet 则是一种服务器向页面推送数据的技术。有两种实现 Comet 的方式:长轮询和HTTP流。长轮询是传统轮询(也称为短轮询)的一个翻版,即浏览器定时向服务器发送请求,看有没有更新的数据。HTTP流就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。
4)服务器发送事件(SSE):是围绕只读 Comet 交互推出的 API 或者模式。SSE 支持短轮询、长轮询和 HTTP 流,而且能在断开连接时自动确定何时重新连接。
SSE API:用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。
事件流:服务器事件会通过一个持久的 HTTP 响应发送,这个响应的 MIME 类型为 text/event-stream 。响应的格式是纯文本,最简单的情况是每个数据项都带有前缀 data:。
5)Web Sockets:Web Sockets的目标是在一个单独的持久连接上提供全双工、双向通信。在 JavaScript 中创建了 Web Socket 之后,会有一个 HTTP 请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会使用 HTTP 升级从 HTTP 协议交换为 Web Socket 协议。
6、安全
1)要求以 SSL 连接来访问可以通过 XHR 请求的资源。
2)要求每一次请求都要附带经过相应算法计算得到的验证码。
请注意,下列措施对防范 CSRF 攻击不起作用。
3)要求发送 POST 而不是 GET 请求——很容易改变。
4)检查来源 URL 以确定是否可信——来源记录很容易伪造。
5)基于 cookie 信息进行验证——同样很容易伪造。
第二十二章 高级技巧
1、高级函数
1)安全类型检测
function isFunction(value){
return Object.prototype.toString.call(value) == "[object Function]";
}
2)作用域安全的构造函数:作用域安全的构造函数在进行任何更改前,首先确认 this 对象是正确类型的实例。如果不是,那么会创建新的实例并返回。
3)惰性载入函数:惰性载入表示函数执行的分支仅会发生一次。有两种实现惰性载入的方式,第一种就是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。第二种实现惰性载入的方式是在声明函数时就指定适当的函数。这样,第一次调用函数时就不会损失性能了,而在代码首次加载时会损失一点性能。
4)函数绑定:函数绑定要创建一个函数,可以在特定的 this 环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。
5)函数柯里化:用于创建已经设置好了一个或多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。
2、防篡改对象:一旦把对象定义为防篡改,就无法撤销了。
1)不可扩展对象:在调用了 Object.preventExtensions() 方法后,就不能给对象添加新属性和方法了。在非严格模式下,给对象添加新成员会导致静默失败。
2)密封的对象:密封对象不可扩展,而且已有成员的 [[Configurable]] 特性将被设置为 false ,这就意味着不能删除属性和方法,因为不能使用 Object.defineProperty() 把数据属性修改为访问器属性,或者相反。属性值是可以修改的。使用 Object.isSealed() 方法可以确定对象是否被密封了。因为被密封的对象不可扩展,所以用Object.isExtensible() 检测密封的对象也会返回 false。
3)冻结的对象:冻结的对象既不可扩展,又是密封的,而且对象数据属性的 [[Writable]] 特性会被设置为 false 。如果定义 [[Set]] 函数,访问器属性仍然是可写的。 Object.freeze() 方法可以用来冻结对象。因为冻结对象既是密封的又是不可扩展的,所以用 Object.isExtensible() 和 Object.isSealed() 检测冻结对象将分别返回 false和 true。
3、高级定时器: setTimeout() 和 setInterval(),window.setTimeout(function(){Time();setTimeout(arguments.callee, 1000);}, 1000);
1)Yielding Processes
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
2)函数节流:基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。
function throttle(method, context) {
clearTimeout(method.tId);
method.tId= setTimeout(function(){
method.call(context);
}, 100);
}
4、自定义事件
function EventTarget(){
this.handlers = {};
}
EventTarget.prototype = {
constructor: EventTarget,
addHandler: function(type, handler){
if (typeof this.handlers[type] == "undefined"){
this.handlers[type] = [];
}
this.handlers[type].push(handler);
},
fire: function(event){
if (!event.target){
event.target = this;
}
if (this.handlers[event.type] instanceof Array){
var handlers = this.handlers[event.type];
for (var i=0, len=handlers.length; i < len; i++){
handlers[i](event);
}
}
},
removeHandler: function(type, handler){
if (this.handlers[type] instanceof Array){
var handlers = this.handlers[type];
for (var i=0, len=handlers.length; i < len; i++){
if (handlers[i] === handler){
break;
}
}
handlers.splice(i, 1);
}
}
};
5、拖放
var DragDrop = function(){
var dragging = null;
diffX = 0;
diffY = 0;
function handleEvent(event){
//获取事件和目标
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
//确定事件类型
switch(event.type){
case "mousedown":
if (target.className.indexOf("draggable") > -1){
dragging = target;
diffX = event.clientX - target.offsetLeft;
diffY = event.clientY - target.offsetTop;
}
break;
case "mousemove":
if (dragging !== null){
//指定位置
dragging.style.left = (event.clientX - diffX) + "px";
dragging.style.top = (event.clientY - diffY) + "px";
}
break;
case "mouseup":
dragging = null;
break;
}
};
//公共接口
return {
enable: function(){
EventUtil.addHandler(document, "mousedown", handleEvent);
EventUtil.addHandler(document, "mousemove", handleEvent);
EventUtil.addHandler(document, "mouseup", handleEvent);
},
disable: function(){
EventUtil.removeHandler(document, "mousedown", handleEvent);
EventUtil.removeHandler(document, "mousemove", handleEvent);
EventUtil.removeHandler(document, "mouseup", handleEvent);
}
}
}();
第二十三章 离线应用与客户端存储
1、离线检测: navigator.onLine属性,这个属性值为 true 表示设备能上网,值为 false 表示设备离线。online 和 offline 。当网络从离线变为在线或者从在线变为离线时,分别触发这两个事件,这两个事件在 window 对象上触发。
2、应用缓存
3、数据存储
1)Cookie:最初是在客户端用于存储会话信息的。该标准要求服务器对任意 HTTP 请求发送 Set-Cookie HTTP 头作为响应的一部分,其中包含会话信息。通过为每个请求添加 Cookie HTTP 头将信息发送回服务器,发送回服务器的额外信息可以用于唯一验证客户来自于发送的哪个请求。
2)JavaScript中的cookie:当用来获取属性值时,document.cookie 返回当前页面可用的(根据 cookie 的域、路径、失效时间和安全设置)所有 cookie的字符串,一系列由分号隔开的名值对儿。基本的cookie 操作有三种:读取、写入和删除。
var CookieUtil = {
get: function (name){
var cookieName = encodeURIComponent(name) + "=",
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null;
if (cookieStart > -1){
var cookieEnd = document.cookie.indexOf(";", cookieStart);
if (cookieEnd == -1){
cookieEnd = document.cookie.length;
}
cookieValue=decodeURIComponent(document.cookie. substring(cookieStart+ cookieName.length, cookieEnd));
}
return cookieValue;
},
set: function (name, value, expires, path, domain, secure) {
var cookieText = encodeURIComponent(name) + "=" +
encodeURIComponent(value);
if (expires instanceof Date) {
cookieText += "; expires=" + expires.toGMTString();
}
if (path) {
cookieText += "; path=" + path;
}
if (domain) {
cookieText += "; domain=" + domain;
}
if (secure) {
cookieText += "; secure";
}
document.cookie = cookieText;
},
unset: function (name, path, domain, secure){
this.set(name, "", new Date(0), path, domain, secure);
}
};
3)IE用户数据:用户数据允许每个文档最多128KB 数据,每个域名最多 1MB 数据。要使用持久化用户数据,首先必须如下所示,使用 CSS 在某个元素上指定 userData 行为:
<div style="behavior:url(#default#userData)" id="dataStore"></div>
4)Web存储机制:Web Storage 的目的是克服由 cookie 带来的一些限制,当数据需要被严格控制在客户端上时,无须持续地将数据发回服务器。Web Storage 的两个主要目标是:提供一种在 cookie 之外存储会话数据的途径;提供一种存储大量可以跨会话存在的数据的机制。
5)IndexedDB:IndexedDB 的思想是创建一套 API,方便保存和读取 JavaScript 对象,同时还支持查询及搜索。
IndexedDB大多数操作会以请求方式异步进行,但这些操作会在后期执行,然后如果成功则返回结果,如果失败则返回错误。
数据库:IndexedDB最大的特色是使用对象保存数据,而不是使用表来保存数据。一个 IndexedDB 数据库,就是一组位于相同命名空间下的对象的集合。
对象存储空间:如果数据库的版本与你传入的版本不匹配,那可能就需要创建一个新的对象存储空间。
事务:在数据库对象上调用 transaction() 方法可以创建事务。任何时候,只要想读取或修改数据,都要通过事务来组织所有操作。
使用游标查询:使用事务可以直接通过已知的键检索单个对象。而在需要检索多个对象的情况下,则需要在事务内部创建游标。游标就是一指向结果集的指针。与传统数据库查询不同,游标并不提前收集结果。游标指针会先指向结果中的第一项,在接到查找下一项的指令时,才会指向下一项。
键范围:键范围为使用游标增添了一些灵活性。键范围由 IDBKeyRange 的实例表示。
设定游标方向:
索引:
并发问题:如果浏览器的两个不同的标签页打开了同一个页面,那么一个页面试图更新另一个页面尚未准备就绪的数据库,问题就有可能发生。只有当浏览器中仅有一个标签页使用数据库的情况下,调用 setVersion() 才能完成操作。
限制:首先,IndexedDB 数据库只能由同源(相同协议、域名和端口)页面操作,因此不能跨域共享信息。其次,每个来源的数据库占用的磁盘空间也有限制。
第二十四章 最佳实践
1、可维护性
1)可维护性代码:
可理解性——其他人可以接手代码并理解它的意图和一般途径,而无需原开发人员的完整解释。
q 直观性——代码中的东西一看就能明白,不管其操作过程多么复杂。
q 可适应性——代码以一种数据上的变化不要求完全重写的方法撰写。
q 可扩展性——在代码架构上已考虑到在未来允许对核心功能进行扩展。
2)代码约定
可读性:首先可读性与代码作为文本文件的格式化方式有关,其次是注释。
变量和函数命名:变量名应为名词; 函数名应该以动词开始,返回布尔类型值的函数一般以 is 开头;变量和函数都应使用合乎逻辑的名字,不要担心长度。长度问题可以通过后处理和压缩来缓解。
变量类型透明:第一种方式是初始化。第二种方法是使用匈牙利标记法来指定变量类型, "o" 代表对象, "s" 代表字符串, "i"代表整数, "f" 代表浮点数, "b" 代表布尔型。最后一种指定变量类型的方式是使用类型注释。
3)松散耦合
解耦 HTML/JavaScript:HTML 是数据,JavaScript 是行为。理想情况是,HTML 和 JavaScript 应该完全分离,并通过外部文件和使用 DOM 附加行为来包含 JavaScript。
解耦 CSS/JavaScript:通过只修改某个元素的 CSS 类,就可以让大部分样式信息严格保留在 CSS 中。JavaScript 可以更改样式类,但并不会直接影响到元素的样式。
解耦应用逻辑/事件处理程序:勿将 event 对象传给其他方法;只传来自 event 对象中所需的数据;任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行;任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑。
4)编程实践
尊重对象所有权:不要为实例或原型添加属性;不要为实例或原型添加方法;不要重定义已存在的方法。
避免全局量
避免与null进行比较: 如果值应为一个引用类型,使用 instanceof 操作符检查其构造函数;如果值应为一个基本类型,使用 typeof 检查其类型;如果是希望对象包含某个特定的方法名,则使用 typeof 操作符确保指定名字的方法存在于对象上。
使用常量:重复值;用户界面字符串;URLs;任意可能会更改的值
2、性能
1)注意作用域:避免全局查找;避免 with 语句
2)选择正确方法:避免不必要的属性查找;优化循环——减值迭代、简化终止条件、简化循环体、使用后测试循环;展开循环;避免双重解释;原生方法较快; Switch 语句较快; 位运算符较快
3)最小化语句数
多个变量声明:在 JavaScript 中所有的变量都可以使用单个 var 语句来声明。
插入迭代值:当使用迭代值(也就是在不同的位置进行增加或减少的值)的时候,尽可能合并语句。
使用数组和对象字面量
4)优化DOM交互
最小化现场更新:第一种是将列表从页面上移除,最后进行更新,最后再将列表插回到同样的位置。第二个方法是使用文档片段来构建 DOM 结构,接着将其添加到 List 元素中。
使用innerHTML
使用事件代理
注意 HTMLCollection
第二十五章 新兴的API
1、requestAnimationFrame():
1)mozRequestAnimationFrame() :通过mozRequestAnimationFrame() 告诉浏览器某些 JavaScript 代码将要执行动画,接收一个参数——即在重绘屏幕前调用的一个函数,这个函数用于改变下一次重绘时的 DOM样式;接收一个参数——时间码(从1970年1月1日起至今的毫秒数),表示下一次重绘的实际发生时间。
2)webkitRequestAnimationFrame 与 msRequestAnimationFrame:这两个版本与 Mozilla 的版本有两个方面的微小差异。首先,不会给回调函数传递时间码,因此你无法知道下一次重绘将发生在什么时间。其次,Chrome 又增加了第二个可选的参数,即将要发生变化的 DOM 元素。知道了重绘将发生在页面中哪个特定元素的区域内,就可以将重绘限定在该区域中。
2、Page Visibility API:用于让开发人员知道页面是否对用户可见
document.hidden :表示页面是否隐藏的布尔值。页面隐藏包括页面在后台标签页中或者浏览器最小化。
document.visibilityState :表示下列 4 个可能状态的值。
n 页面在后台标签页中或浏览器最小化。
n 页面在前台标签页中。
n 实际的页面已经隐藏,但用户可以看到页面的预览(就像在 Windows 7 中,用户把鼠标移动到任务栏的图标上,就可以显示浏览器中当前页面的预览)。
n 页面在屏幕外执行预渲染处理。
visibilitychange 事件:当文档从可见变为不可见或从不可见变为可见时,触发该事件。
3、Geolocation API:用于访问到用户的当前位置信息。
4、File API:用于在客户端访问用户计算机中的文件,并更好地对这些文件执行操作。
5、Web Timing API:用于让开发人员通过 JavaScript 就能使用浏览器内部的度量结果,通过直接读取这些信息可以做任何想做的分析。
6、Web Workers:解决JavaScript 进程会导致浏览器冻结用户界面的问题。