with語句額做用是將代碼的做用域設置到一個特定的對象中。語法:with(expression) statement;
定義with語句的目的主要是爲了簡化屢次編寫同一個對象的工做,
with(location){
var qs=search.substring(1);
var hostName=hostname;
var url=href;
}
使用with語句關聯了location對象
嚴格模式下不容許使用with語句,不然將視爲語法錯誤。
還有大量使用with語句會致使性能降低,同時也會增長調試困難,所以開發大型應用程序時,不建議使用with語句。
ECMAscript變量可能包含兩種不一樣數據類型的值:基本類型值和引用類型值。基本類型指的是簡單的數據段,而引用類型值指那些可能由多個值構成的對象
基本類型:underfined,Null,Boolean,Number,String
咱們不能給基本類型添加屬性,即便不會報錯;
因此,引用值類型和值類型的內存地址也是跟C#中的類型保存地址差很少,棧堆和託管堆
值類型傳值,引用值類型傳地址
DOM2級事件定義了兩個方法,用於處理制定和刪除事件處理程序的操做:addEventListener()和removeEventListener()。
使用DOM2級方法添加事件處理程序的主要好處是能夠添加多個事件處理程序。
經過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除;移除時傳入的參數與添加處理程序時使用的參數相同。這有意味着經過addEventListener()添加的匿名函數將沒法移除;
在觸發DOM上的某個事件時會產生一個事件對象event,這個對象中包含着全部與事件有關的信息。包括致使事件的元素,事件的類型以及其餘與特定事件相關的信息。
在須要經過一個函數處理多個事件時,能夠使用type屬性。例如:
var btn=document.getElementById("myBtn");
var handler=function(event){
switch(event.type){
case "click":
alert("clicked");
break;
case "mouseover":
event.target.style.backgroundColor="red";
break;
case "mouseout":
event.target.style.backgroundColor="";
break;
}
};
btn.onclick=handler;
btn.onmouseover=handler;
btn.onmouseout=handler;
在DOM出現以前,開發人員常用Image對象在客戶端預先加載圖像。能夠像使用<img>元素同樣使用Image對象,只不過沒法將其添加到DOM樹中
hashchange事件,以便在URL的參數列表(及URL中「#」號後面的全部字符串)發生變化時通知開發人員。之因此新增這個事件,是由於在Ajax應用中,開發人員常常要利用URL參數列表來保存狀態或導航信息。
在使用事件時,須要考慮以下一些內存與性能方面的問題:
有必要限制一個頁面中事件處理程序的數量,數量太多會致使佔用大量內存,並且也會讓用戶感受頁面反應不夠靈敏。
創建在事件冒泡機制之上的事件委託技術,能夠有效地減小事件處理程序的數量。
建議在瀏覽器卸載頁面以前移除頁面中的全部處理事件處理程序。
EventUtil對象,能夠跨瀏覽器處理事件。而且調用方法;
對於提交表單的事件,最好使用submit事件去添加事件處理程序。事件觸發後,代碼取得了提交按鈕並將其按鈕的disabled的屬性設置爲true。注意,不能經過onclick事件處理程序來實現這個功能,緣由是不一樣瀏覽器之間存在「時差」:有的瀏覽器會在觸發表單的submit事件之間就觸發click事件,而又的瀏覽器則相反。對於先觸發click事件的瀏覽器,意味着會在提交發生以前禁用按鈕,結果永遠都不會提交表單。所以,最好是經過submi事件來禁用提交按鈕。不過,這種方式不適合表單中不包含提交按鈕的狀況;如前所述,只有在包含提交按鈕的狀況下,纔有可能觸發表單submit;
文本框:
利用<input>元素的type特性設置爲"text"。而經過設置size特性,來限制輸入的字數
富文本編輯:(在iframe裏面插入一個html頁面,將這個頁面設置爲可編輯,而後就變成了富文本編輯)
又稱爲WYSIWYG(What You See Is What You Get,所見即所得)。
經過設置designMode屬性,這個空白的HTML頁面能夠被編輯,而編輯對象則是該頁面<body>元素的HTML代碼。
經過設置使用名爲contenteditable的特殊屬性,這個也是能夠編輯富文本內容的方式。
canvas:
var drawing=document.getElementById('mycanvas');
var context=drawing.getContext('2d');
2D上下文的兩種基本繪圖操做是填充和描邊。
操做的結果取決於兩個屬性:fillStyle和strokeStyle
繪製矩形 矩形是惟一一種能夠直接在2D上下文中繪製的形狀,與矩形有關的方法包括fillRect(),strokeRect()和clearRect()。這三個方法都能接收4個參數:矩形的x座標,矩形的y座標,矩形寬度,矩形高度。這些參數單位都是像素;
toDataURL()是Canvas對象的方法,不是上下文對象的方法。
漸變:
在使用漸變的時候,爲了讓漸變覆蓋整個矩形,而不是僅應用到矩形的一部分,矩形和漸變對象的座標必須匹配才行,
var gradient=context.createLinearGradient(30,30,80,80);
gradient.addColorStop(0,"White");
gradient.addColorStop(1,"black");
context.fillStyle=gradient;
context.fillRect(30,30,50,50);
有時候能夠考慮使用函數來確保座標合適。
function createRectLinearGradient(context,x,y,width,height){
return context.createLinearGradient(x,y,x+width,y+height);
}
整合成下面的代碼:
var gradient = createRectLinearGradient(context, 30, 30, 50, 50);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
//
繪製漸變矩形
context.fi llStyle = gradient;
context.fillRect(30, 30, 50, 50);
建立徑向漸變(或放射漸變):
var gradient=context.createRadialGradient(55,55,10,55,55,30);
gradient.addColorStop(0,"White");
gradient.addColorStop(1,"black");
context.fillStyle=gradient;
context.fillRect(30,30,50,50);
通常來講,讓起點圓和終點圓保持爲同心圓的狀況比較多,這時候只要考慮給兩個圓設置不一樣的半徑就行了。
使用圖像數據;
2D上下文的一個明顯的長處就是,能夠經過getImageData()取得原始圖像數據。這個方法接收4個參數:要取得其數據的畫面區域的x和y座標以及該區域的像素寬度和高度。能夠使用一下代碼:
var imageData=context.getImageData(10,5,50,50);
這裏返回的對象是ImageData的實例。每一個ImageData對象都有三個屬性:width,height和data。
HTML5的<canvas>元素提供了一組JavaScriptAPI,讓咱們能夠動態地建立圖形和圖像。圖形是在一個特定的上下文中建立的,而上下文對象目前有兩種。
第一種是2D上下文,能夠執行原始的繪圖操做,好比:
1.設置填充,描邊顏色和模式
2.繪製矩形
3.繪製路徑
4.繪製文本
5.建立漸變和模式
第二種是3D上下文,即WebGL上下文。WebGL是從OpenGLES 2.0移植到瀏覽器的,而OpenGLES2.0是遊戲開發人員在建立計算機圖形圖像時常用的一種語言。WebGL支持比2D上下文更豐富和更強大的圖形圖像處理能力,好比:
1.用GLSL(OpenGL Shading Language,OpenGL着色語言)編寫的頂點和片斷着色器
2.組成骨類型化數組,即可以將數組中的數據限定爲某種特定的數值類型
3.建立和操做文理
HTML5腳本編程
跨文檔消息傳送(cross-document messaging),有時候簡稱爲XDM,指的是在來自不一樣域的頁面間傳遞消息.XDM把這種機制規範化,讓咱們能既穩妥又簡單地實現跨文檔通訊。
XDM核心方法是postMessage()
Audio類型
<audio>元素還有一個原生的JavaScript構造函數Audio,能夠再任什麼時候候播放音頻。從同爲DOM元素的角度看,Audio與Image很類似,但Audio不用像Image那樣必須插入到文檔中。只要建立一個新勢力,並傳入音頻源文件便可。
var audio = new Audio("sound.mp3");
EventUtil.addHandler(audio, "canplaythrough", function(event){
audio.play();
});
建立新的Audio實例便可開始下載指定的文件。下載完成後,調用play()就能夠播放音頻。
討論了以下幾個API:
1.跨文檔消息傳遞API可以讓咱們在不下降同源策略安全性的前提下,在來自不一樣域的文檔間傳遞消息。
2.原生拖放功能讓咱們能夠方便地制定某個元素可拖動,並在操做系統要放置時作出響應。還能夠建立自定義的可拖動元素及放置目標。
3.新的媒體元素<audio>和<video>擁有本身的音頻和視頻交互的API。並不是全部瀏覽器支持全部的媒體格式,所以應該使用canPlayType()檢查瀏覽器是否支持特定的格式。
4.歷史狀態管理讓咱們沒必要卸載當前頁面便可修改瀏覽器的歷史狀態棧。有了這種機制,用戶就能夠經過「後退」和「前進「按鈕在頁面狀態間切換,而這些狀態徹底由JavaScript進行控制。
錯誤處理與調試
try-catch語句
ECMA-262第3版引入了try-catch語句,做爲JavaScript中處理異常的一種標準方式。
try{
//可能會致使錯誤的代碼
}
catch(error){
//在錯誤發生時怎麼處理
}
1.finally子句:
雖然在try-catch語句中式可選的,但finally子句一經使用,其代碼不管如何都會執行。換句話說,try語句中的代碼所有正常執行,finally子句會執行;若是由於出錯而執行了catch語句塊,finally子句照樣還會執行。只要代碼中包含finally子句,則不管try或catch語句塊中包含什麼代碼--甚至return語句,都不會阻止finally子句的執行。
(只要代碼中包含finally子句,那麼不管try仍是catch語句塊中的return語句都將被忽略。所以,在使用finally子句以前,必定要很是清楚你想讓代碼怎麼樣)
2.錯誤類型
Error
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
拋出錯誤
與try-catch語句相配的還有一個throw操做符,用於隨時拋出自定義錯誤。拋出錯誤時,必需要給throw操做符制定一個值,這個值是什麼類型,沒有要求,下列代碼都是有效的。
throw 1234;
throw "hello";
throw true;
throw {name:"JavaScript"};
function process(values){
if (!(values instanceof Array)){
throw new Error("process(): Argument must be an array.");
}
values.sort();
for (var i=0, len=values.length; i < len; i++){
if (values[i] > 100){
return values[i];
}
}
return -1;
}
常見的錯誤類型:
通常來講,須要關注三種錯誤:
1.類型轉換錯誤:
類型轉換錯誤發生在使用某個操做符,或者使用其餘可能會自動轉換值得數據類型的語言結構時。在使用相等(==)和不相等(!=)操做符,或者在if,for及while等流控制語句中使用非布爾值時,最常發生類型轉換錯誤。
像if之類的語句在肯定下一步操做以前,會自動把任何值轉換成布爾值。
出錯:
function concat(str1, str2, str3){
var result = str1 + str2;
if (str3){ //
絕對不要這樣
!!!
result += str3;
}
return result;
}
正確:
function concat(str1, str2, str3){
var result = str1 + str2;
if (typeof str3 == "string"){ //
恰當的比較
result += str3;
}
return result;
}
數據類型錯誤
JavaScript是鬆散類型的,也就是說,在使用變量和函數參數以前,不會對它們進行比較以確保它們的數據類型正確,爲了保證不會發生數據類型錯誤,只能依靠開發人員編寫適當的數據類型檢測代碼。
function getQueryString(url){
if (typeof url == "string"){ //
經過檢查類型確保安全
var pos = url.indexOf("?");
if (pos > -1){
return url.substring(pos +1);
}
}
return "";
}
使用indexOf獲取到?的下標位置;
而後substring對url?下標位置後的全部字符串取出來;
//
安全,非數組值將被忽略
function reverseSort(values){
if (values instanceof Array){ //
問題解決了
values.sort();
values.reverse();
}
}
體來講,基本類型的值應該使用typeof來檢測,而對象的值則應該使用instanceof來檢測。根據使用函數的方式,有時候並不須要逐個檢測全部參數的數據類型。可是,面向公衆的API則必須無條件地執行類型檢查。
大
JS中的變量是鬆散類型(即弱類型)的,能夠用來保存任何類型的數據。php
typeof 能夠用來檢測給定變量的數據類型,可能的返回值:1. 'undefined' --- 這個值未定義;html
2. 'boolean' --- 這個值是布爾值;程序員
3. 'string' --- 這個值是字符串;web
4. 'number' --- 這個值是數值;正則表達式
5. 'object' --- 這個值是對象或null;
6. 'function' --- 這個值是函數。
instanceof 用於判斷一個變量是否某個對象的實例,如
var a=new Array();
alert(a instanceof Array);
通訊錯誤
隨着Ajax編程的星期,Web應用程序在其生命週期內動態加載信息或功能,已經成爲一件司空見慣的事。不過,JavaScript與服務器之間的任何一次通訊,都有可能會產生錯誤。
下面是集中避免瀏覽器響應JavaScript錯誤的方法
1.在可能發生錯誤的地方使用try-catch語句,這樣你還有機會以適當的方式對錯誤給出響應,而沒必要沿用瀏覽器處理錯誤的機制。
2.使用window.onerror事件處理程序,這種方式能夠接受try-catch不能處理的全部錯誤(僅限IE,Firefox,Chrome).
另外,對任何Web應用程序都應該分析肯呢個的錯誤來源,並制定處理錯誤的方法。
3.首先,必需要明確什麼是指明錯誤,什麼是非致命錯誤。
4.其次,再分析代碼,以判斷最肯呢個發生的錯誤。JavaScript中發生錯誤的主要緣由以下。
類型轉換,未充分檢測數據類型,發送給服務器或從服務器接收到的數據又錯誤
JSON
JSON是一個輕量級的數據格式,能夠簡化表示複雜數據結構的工做量。JSON使用JavaScript語法的子集表示對象,數組,字符串,數值,布爾值和null。即便XML也能表示贊成複雜的數據結果,但JSON沒有那麼繁瑣,並且在JavaScript中使用更便利。
ECMAScript 5定義了一個原生的JSON對象,能夠用來將對象序列化爲JSON字符串或者將JSON數據解析爲JavaScript對象。JSON.stringify()和JSON.parse()方法分別用來實現上述兩項功能。這兩個方法都有一些選項,經過它們能夠改變過濾方式,或者改變序列化的過程。
JSON的語法能夠表示如下三種類型的值;
簡單值:
使用與JavaScript相同的語法,能夠再JSON中表示字符串,數值,布爾值和null。但JSON不支持JavaScript中的特殊值underfined;
JavaScript字符串與JSON字符串的最大區別在於,JSON字符必須使用雙引號(單引號會致使語法錯誤)。
布爾值和null也是有效的JSON形式。可是,在實際應用中,JSON更多地用來表示更復雜的數據結構,而簡單值只是整個數據結構中的一部分。
對象:
對象做爲一種複雜數據類型,表示的是一組無序的鍵值對兒。而每一個鍵值對兒中的值能夠是簡單值,也能夠是複雜數據類型。
{
"name": "Nicholas",
"age": 29,
"school": {
"name": "Merrimack College",
"location": "North Andover, MA"
}
}
與JavaScript不一樣,JSON中對象的屬性名任什麼時候候都必須加雙引號。手工編寫JSON時,忘了給對象屬性名加雙引號或者把雙引號寫成單引號都是常見的錯誤。
數組:
數組也是一種複雜數據類型的值,表示一組有序的值得列表,能夠經過數值索引來訪問其中的值。數組的值也能夠是任意類型-簡單值,對象或數組。
JSON不支持變量,函數或對象實例,它就是一種表示結構化數據的格式,雖然與JavaScript中表示數據的某些語法相同,但它並不侷限於JavaScript的範疇。
JSON中的第二種複雜數據類型是數組。JSON數組採用的就是JavaScript中的數組字面量形式。
下面是JavaScript中的數組字面量:
var values = [25, "hi", true];
在
JSON
中,能夠採用一樣的語法表示同一個數組:
[25, "hi", true]
[
{
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
},
{
"title": "Professional JavaScript",
"authors": [
"Nicholas C. Zakas"
],
edition: 2,
year: 2009
},
{
"title": "Professional Ajax",
"authors": [
"Nicholas C. Zakas",
"Jeremy McPeak",
"Joe Fawcett"
],
edition: 2,
year: 2008
}
]
固然,這裏是假設把解析
JSON
數據結構後獲得的對象保存到了變量
books
中。再看看下面在
DOM
結構中查找數據的代碼:
doc.getElementsByTagName("book")[2].getAttribute("title")
JSON對象
早起的JSON解析器基本上就是使用JavaScript的eval()函數。因爲JSON是JavaScript語法的子集,所以eval()函數能夠解析,解釋並返回JavaScript對象和數組。
對於不能原生支持JSON解析的瀏覽器,使用這個shim是最佳選擇。
JSON對象有兩個方法:stringify()和parse()。在最簡單的狀況下,這兩個方法分辨用於把JavaScript對象序列化爲JSON字符串和把JSON字符串解析爲原生JavaScript值。例如
var book = {
title: "Professional JavaScript",
authors: [
"Nicholas C. Zakas"
],
edition: 3,
year: 2011
};
var jsonText = JSON.stringify(book);
{"title":"Professional JavaScript","authors":["Nicholas C. Zakas"],"edition":3,
"year":2011}
Ajax:
XHR的用法:
在使用XHR對象時,要調用的第一個方法是open(),它接受3個參數:要發送的請求的類型,請求的URL和表示是否異步發送請求的布爾值
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.");
}
}
var xhr=createXHR();
xhr.open("get","example.php",false);
xhr.send(null);
使用readyState在異步模式的狀況下
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.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
setRequestHeader()方法能夠設置自定義的請求頭部信息。這個方法接受兩個參數:頭部字段的名稱和頭部字段的值
下面這個函數能夠輔助向現有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);
在這裏使用addURLParam()函數能夠確保查詢字符串的格式良好,並可靠地用於XHR對象;
FormData:
爲序列化表單以及建立與表單格式相同的數據(用於經過XHR傳輸)提供便利;
var data=new FormData();
data.append("name","Nicholas");
var data=new FormData(document.forms[0]);
使用FormData的方便之處體如今沒必要明確地在XHR對象上設置請求頭部。XHR對象可以識別傳入的數據類型是FormData的實例,並配置適當的頭部信息;
超時設定:
IE8------timeout屬性,表示請求在等待響應多少毫秒以後就終止。在給timeout設置一個數值後,若是在規定的事件內瀏覽器尚未接受到響應,那麼久會觸發timeout事件,進而會調用ontimeout事件處理程序。(後被收入XMLRequestHttp2級規範)
xhr.timeout = 1000; //
將超時設置爲
1
秒鐘(僅適用於
IE8+
)
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
重寫XHR響應的MIME類型:
overrideMimeType()方法
可以重寫服務器返回的MIME類型是頗有用的。
調用overrideMimeType()必須在send()方法以前,才能保證重寫響應的MIME類型。
Progress Events 進度事件:
總的有6個進度事件:
loadstart:在接收到響應數據的第一個字節時觸發。
progress:在接收響應期間持續不斷地觸發
error:在請求發生錯誤時觸發
abort:在由於調用abort()方法而終止鏈接時觸發。
load:在接收到完整的響應數據時觸發
loadend:在通訊完成或者觸發error,abort或load事件後觸發
其中兩個事件有一些細節須要注意
1,load事件 在接收到完整的響應數據時觸發(就是爲了替代readystatuschange事件。
只要瀏覽器接收到服務器的響應,無論其狀態如何,都會觸發load事件。而這意味着你必需要檢查status屬性,才能肯定數據是否真的已經可用了。
xhr.onload = function(){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
2,progress事件 在接收響應期間持續不斷地觸發
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){
divStatus.innerHTML = "Received " + event.position + " of " +
event.totalSize +" bytes";
}
};
每次觸發progress事件,都會以新的狀態信息更新HTML元素的內容。若是響應頭部中包含content-Length字段,那麼也能夠利用此信息來計算從響應中已經接收到的數據的百分比。
跨源資源共享(CORS):
經過XHR實現Ajax通訊的一個主要限制,來源於跨域安全策略。默認狀況下,XHR對象只能訪問與包含它的頁面屬於同一域中的資源。這種安全策略 能夠預防某些惡意行爲。可是,實現合理的跨域請求對開發某些瀏覽器應用程序也是相當重要的。
CORS是W3C的一個工做草案,定義了在必須訪問跨域資源時,瀏覽器與服務器應該如何溝通。
CROS背後的基本思想,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應式應該層共,仍是應該失敗。
其餘瀏覽器對CORS的實現:
因爲不管同源請求仍是跨源請求都使用相同的接口,所以對於本地資源,最好使用相對URL,在訪問遠程資源時再使用絕對URL。這樣就能消除歧義,避免出現限制訪問頭部或本地cookie信息等問題;
Preflighted Reqeusts:
CORS經過一種叫作Prelighted Requests的透明服務器驗證機制支持開發人員使用自定義的頭部,GET或POST以外的方法,已經不一樣類型的主體內容。在使用下列高級選項來發送請求時,就會向服務器發送一個Preflight請求。
這種請求使用OPTIONS方法,發送下列頭部:
Origin:與簡單的請求相同。
Access-Control-Request-Method:請求自身使用的方法。
Access-Control-Request-Headers:(可選)自定義的頭部信息,多個頭部以逗號分隔。
其餘跨域技術:
1)圖像Ping :
一個網頁能夠從任何網頁中加載圖像,不用擔憂跨域不跨域。這也是在線廣告跟蹤瀏覽量的主要方式。動態地建立圖像,使用它們的onload和onerror事件處理程序來肯定是否接收到了響應。
動態建立圖像常常用於圖像Ping。圖像Ping是與服務器進行簡單,單向的跨域通訊的一種方式。請求的數據是經過查詢字符串形式發送的,而響應能夠使任意內容,但一般是像素圖或204響應。
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
圖像Ping最經常使用於跟蹤用戶點擊頁面或動態廣告曝光次數。圖像Ping有兩個主要的缺點,一是隻能發送GET請求,二是沒法訪問服務器的響應文本。所以,圖像Ping只能用於瀏覽器與服務器間單向通訊。
2)JSONP
JSONP是JSON with padding(填充式JSON或參數式JSON)的簡稱,是應用JSON的一種新方法,在後來的Web服務中很是流行。JSONP看起來與JSON差很少,只不過是被包含在函數調用中的JSON,就像這樣:callback({"name":"Nicholas"})
JSONP由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數。回調函數的名字通常是在請求中指定的。而數據就是傳入回調函數中的JSON數據。
http://freegeoip.net/json/?callback=handleResponse
JSONP是經過動態<script>元素來使用的,使用時能夠爲src屬性指定一個跨域URL。這裏的<script>元素與<img>元素相似,都有能力不受限制地從其餘域加載資源。
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);
優勢在於可以直接訪問響應文本,支持在瀏覽器與服務器之間雙向通訊。不過JSONP也有亮點不足。
首先JSONP是從其餘域中夾雜代碼執行。若是其餘域不安全,極可能會在響應中夾帶一些惡意代碼,而此時除了徹底放棄JSONP調用以外,沒有辦法追究。所以在使用㐊你本身運維的Web服務時,必定得保證它安全可靠
其次,要肯定JSONP請求是否失敗並不容易。雖然HTML5給<script>元素新增了一個onerror事件處理程序,但目前尚未獲得任何瀏覽器支持。爲此,開發人員不得不使用計時器檢測指定時間內是否接收到了響應。
3)Comet
Comet是Alex Russell發明的一個詞兒,指的是一種更高級的Ajax技術(常常也有人稱爲「服務器推送」)。Ajax是一種從頁面向服務器請求數據的技術,而Comet則是一種服務器向頁面推送數據的技術。Comet可以讓信息近乎實時地推送到頁面上,很是適合處理體育比賽的分數和股票的報價。
有兩種實現Comet的方式:長輪詢和流,長輪詢是傳統輪詢(也稱爲段輪詢)的一個翻版,即瀏覽器定時向服務器發送請求,看有沒有更新的數據。
長輪詢把短輪詢顛倒了一下。頁面 發起一個到服務器的請求,而後服務器一直保持鏈接打開,直到有數據可發送。發送完數據以後,瀏覽器關閉鏈接,隨即又發送一個到服務器的新請求。這一過程在頁面打開期間一直持續不斷。
不管是短輪詢仍是長輪詢,瀏覽器都要再接收數據以前,先發起對服務器的鏈接。二者最大的區別在於服務器如何發送數據。短輪詢是服務器當即發送響應,不管數據是否有效,而長輪詢是等待發送響應。輪詢的優點是全部瀏覽器都支持,由於使用XHR對象和setTimeout()就能實現。而你要作的就是決定何時發送請求。
第二種流行的Comet實現HTTP流。流不一樣於上述兩種輪詢,由於它在頁面的整個生命週期內只使用一個HTTP鏈接。具體來講,就是瀏覽器向服務器發送一個請求,而服務器保持鏈接打開,而後週期性地向瀏覽器發送數據。好比下面這段PHP腳本就是採用流實現的瀏覽器中常見的形式。
$i = 0;
while(true){
//輸出一些數據,而後當即刷新輸出緩存
echo "Number is $i";
flush();
//等幾秒鐘
sleep(10);
$i++;
}
全部服務器端語言都支持打印到輸出緩存而後刷新(將輸出緩存中的內容一次性所有發送到客戶端)。而這正是實現HTTP流的關鍵所在。
使用XHR對象實現HTTP流典型代碼以下所示;
function createStreamingClient(url, progress, finished){
var xhr = new XMLHttpRequest(),
received = 0;
xhr.open("get", url, true);
xhr.onreadystatechange = function(){
var result;
if (xhr.readyState == 3){
//只取得最新數據並調整計數器
result = xhr.responseText.substring(received);
received += result.length;
//調用progress 回調函數
progress(result);
} else if (xhr.readyState == 4){
finished(xhr.responseText);
}
};
xhr.send(null);
return xhr;
}
var client = createStreamingClient("streaming.php", function(data){
alert("Received: " + data);
}, function(data){
alert("Done!");
});
這個
createStreamingClient()
函數接收三個參數:要鏈接的
URL
、在接收到數據時調用的函
數以及關閉鏈接時調用的函數
管理Comet的鏈接時很容易出錯的,須要時間不短改進才能達到完美。
瀏覽器社區爲Comet建立了兩個新的接口:
服務器發送事件(SSE)是圍繞只讀Comet交互推出的API或者模式。
SSE API用於建立到服務器的單向鏈接,服務器經過這個鏈接能夠發送任意數量的數據。服務器響應的MIME類型必須是text/event-stream,並且是瀏覽器中的JavaScript API能解析格式輸出。SSE支持短輪詢,長輪詢,HTTP流,並且能再斷開鏈接時自動肯定什麼時候從新鏈接。
1.SSE API SSE的JavaScript API很類似。要預約新的事件流,首先要建立一個新的EventSource對象,並傳進一個入口點:
var source=new EventSource("myevents.php");
注意傳入的URL必須與建立對象的頁面同源(相同的URL模式,域及端口)。EventSource的實例有一個readyState屬性,值爲0表示正鏈接到服務器,值爲1表示打開了鏈接,值爲2表示關閉了鏈接。另外還有如下三個時間。open:在創建鏈接時觸發。message:在從服務器接收到新時間時觸發。error:在沒法創建鏈接時觸發。
默認狀況下,
EventSource
對象會保持與服務器的活動鏈接。若是鏈接斷開,還會從新鏈接。這
就意味着
SSE
適合長輪詢和
HTTP
流。若是想強制當即斷開鏈接而且再也不從新鏈接,能夠調用
close()
方法。
2.事件流
所謂的服務器事件會經過一個持久的HTTP響應發送,這個響應的MIME類型爲text/event-stream。響應的格式是純文本,最簡單的狀況是每一個數據項都帶有前綴data:
經過
id:
前綴能夠給特定的事件指定一個關聯的
ID
,這個
ID
行位於
data:
行前面或後面皆可:
data: foo
id: 1
設置了
ID
後,
EventSource
對象會跟蹤上一次觸發的事件。若是鏈接斷開,會向服務器發送一個
包含名爲
Last-Event-ID
的特殊
HTTP
頭部的請求,以便服務器知道下一次該觸發哪一個事件。在屢次
鏈接的事件流中,這種機制能夠確保瀏覽器以正確的順序收到鏈接的數據段。
Web Sockets 的目標是在一個單獨的持久鏈接上提供全雙工,雙向通訊。在JavaScript中建立了Web Socket以後,會有一個HTTP請求發送到瀏覽器以發起鏈接。在取得服務器響應後,創建的鏈接會使用HTTP升級從HTTP協議轉換爲Web Socket協議。也就是說,使用標準的HTTP服務器沒法實現Web Sockets,只有支持這種協議的專門服務器才能正常工做。
因爲Web sockets使用了自定義的協議,因此URL模式也略有不一樣。未加密的鏈接再也不是http://,而是ws://;加密的鏈接也不是https://,而是wss://。在使用Web SocketURL時,必須帶着這個模式,由於未來還有可能支持其餘模式;
使用自定義協議而非HTTP協議的好處是,可以在客戶端和服務器之間放很是少許的數據,而沒必要擔憂HTTP那樣字節級的開銷。因爲傳遞的數據包很小,所以Web Sockets很是適合移動應用。
使用自定義協議的缺點在於,制定協議的事件比制定JAvaScript API的事件還要長。
1.web Sockets API
要建立WeSocket,先實例一個websocket對象並傳入要鏈接的URL:
var socket=new Websocket("ws://www.example.con/server.php");
注意,必須給Websocket構造函數傳入絕對URL。同源策略對Web Socket不適用 ,所以能夠經過它打開到任何站點的鏈接。至因而否會與某個域中的頁面通訊,則徹底取決於服務器(經過握手信息就能夠知道請求來自何方。)
發送和接收數據
send()方法;向服務器發送數據(發送給服務器的數據要記得序列化)
socket.onmessage = function(event){
var data = event.data;
//
處理數據
};
event.data中返回的數據也是字符串。若是要獲得其餘格式的數據,必須手工解析這些數據才行
websocket對象還有其餘三個事件,在鏈接生命週期的不一樣階段觸發
open:在成功創建鏈接時觸發
error:在發生錯誤時觸發,鏈接不能持續
close:在鏈接關閉時觸發
websocket對象不支持DOM2級事件偵聽器,所以必須使用DOM0級語法分別定義每一個事件處理程序。
SSE和Web Sockets
面對某個具體的用力,在考慮是使用SSE仍是使用Web Sockets時,能夠考慮以下幾個因素。首先,你是否有自由度創建和維護Web Sockets服務器?由於Web Socket協議不一樣於HTTP,因此現有服務器不能用於Web Socket通訊。SSE卻是經過常規HTTP通訊,所以現有服務器就能夠知足需求。
第二個要考慮的問題是到底需不須要雙向通訊。若是用例只需讀取服務器數據(如比賽成績),那麼SSE比較容易實現。若是用例必須雙向通訊(如聊天室),那麼WebSockets顯然更好。別忘了,在不能選擇WebSockets的狀況下,組合XHR和SSE也是能顯示雙向通訊的。
高級技巧
高級函數 函數是JavaScript中最有趣的部分之一。
安全的類型檢測:
instanceof操做符存在多個全局做用域(像一個頁面包含多個frame)的狀況下,也是問題多多。一個經典的例子 就是像下面這樣將對象表示爲數組
var isArray=value instanceof Array;
以上代碼要返回true,value必須是一個數組,並且還必須與Array構造函數在同個全局做用域中。別忘了,(Array是window的屬性)。若是value是在另個frame中定義的數組,那麼以上代碼就會返回false。
在任何值上調用Object原生的toString()方法,都會返回一個[object NativeConstructorName]格式的字符串。每一個類在內部都有一個[[Class]]屬性,這個屬性就指定了上述字符串中的構造函數名。
alert(Object.prototype.toString.call(value)); //"[object Array]"
因爲原生數組的構造函數名與全局做用域無關,所以使用toString()就能保證返回一致的值。所以能夠用這個來測試某個值是否是原生函數或正則表達式:
function isArray(value){
return Object.prototype.toString.call(value) == "[object Array]";
}
這一技巧也普遍應用於檢測原生JSON對象。object的toString()方法不能檢測非原生構造函數的構造函數名。所以,開發人員定義的任何構造函數都將返回[object object]。有些JavaScript庫會包含於下面相似的代碼。
var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) ==
"[object JSON]";
做用域安全的構造函數:
使用new來調用該構造函數的狀況。
問題出在當沒有使用new操做符來調用該構造函數的狀況上。因爲該this對象是在運行時綁定的,因此直接調用Person(),this會映射到全局對象window上,致使錯誤對象屬性的意外增長。例如:
var person = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //"Nicholas"
alert(window.age); //29
alert(window.job); //"Software Engineer"
爲了防止出現這種狀況,能夠使用一下解決方法,也就是在函數內部做一個判斷:
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);
}
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //""
alert(person1.name); //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name); //"Shelby"
最後的結果是,調用Person構造函數時不管是否使用new操做符,都會返回一個Person的新實例,這就避免了在全局對象上的意外設置屬性。
惰性載入函數:
減小if語句的使用,也就是說即便只有一個if語句的代碼,也確定要比沒有if語句的慢,因此若是if語句沒必要每次執行,那麼代碼能夠運行地更快一些。解決方案就是稱之爲惰性載入的技巧。
惰性載入表示函數執行的分支僅會發生一次。有兩種實現惰性載入的方式,第一種就是再函數被調用時再處理函數。在第一次調用的過程當中,該函數會被覆蓋爲另一個按合適方式執行的函數,這樣就職何對原函數的調用都不用再通過執行的分支了。
載入函數的優勢是指在執行分支代碼時犧牲一點兒性能。至於哪一種方式更海華絲,就要看你的具體需求而定了。不過這兩種方式都能避免執行沒必要要的代碼。
函數綁定:
函數綁定要建立一個函數,
能夠在特定的this環境中以指定參數調用另外一個函數。
該技巧經常和回調函數與事件處理程序一塊兒使用,以便在將函數做爲變量傳遞的同時保留代碼執行環境。看看例子:
var handle={
message:"Event handled",
handleClick:function(event){
alert(this.message);
}
};
var btn=document.getElementById("my-btn");
EventUtil.addHandler(btn,"click",handler.handleClick);
//EventUtil.addHandler(btn,"click",function(event){
handler.handleClick(event);
})
一個簡單bind()函數接收一個函數和一個環境,並返回一個在給定環境中調用給定函數的函數,而且將全部參數原封不動傳遞過去。語法以下:
function bind(fn,context){
return function(){
return fn.apply(context,arguments);
};
}
//EventUtil.addHandler(btn,"click",bind(handler.handleClick,handler)); ?
ECMAScript 5爲全部函數定義了一個原生的bind()方法,進一步簡單了操做。換句話說,你不用再本身定義bind()函數了,而是能夠直接在函數上調用這個方法。例如:
//EventUtil.addHandler(btn,"click"
,handler.handleClick.bind(handler));
原生的bind()方法與前面介紹的自定義bind()方法相似,都是要傳入做爲this值的對象。支持原生bind()方法的瀏覽器有IE9+,Frefox 4+,Chrome.
只要是將某個函數指針以值得形式進行傳遞,同時該函數必須在特定環境中執行,被綁定函數的效用就突顯出來了。它們主要用於事件處理程序以及setTimeout()和s etInterval()。然而,被綁定函數與普通函數相比有更多的開銷,它們須要更多內存,同時也由於多重函數調用稍微慢點,因此最好指在必要時使用。
函數柯里化:
它用於建立已經設置好了一個或多個參數的函數。函數柯里化的基本方法和函數綁定是同樣的:使用一個閉包返回一個函數。二者的區別在於,
當函數被調用時,返回的函數還須要設置一些傳入的參數。
例子:
function add(num1,num2){
return num1+num2;
}
function curriedAdd(num2){
return add(5,num2);
}
alert(add(2,3)); //5
alert(curriedAdd(3)); //8
從技術上來講curriedAdd()並不是柯里化的函數,但它很好地展現了其概念。
柯里化函數一般由如下步驟動態建立:調用另外一個函數併爲它傳入要柯里化的函數和必要參數。下面是建立柯里化函數的通用方式。
function curry(fn){
var args=Array.prototype.slice.call(arguments,1);
return function(){
var innerArgs=Array.prototype.slice.call(arguments);
var finalArgs=args.concat(innerArgs);
return fn.apply(null,finalArgs);
}
}
curry()函數的主要工做就是將被返回函數的參數進行排序。curry()的第一個參數是要進行柯里化的函數,其餘參數是要傳入的值。爲了獲取第一個參數以後的全部參數,在arguments對象上調用了slice()方法,並傳入參數1表示被返回的數組包含從第二個參數開始的全部參數。而後args數組包含了來自外部函數的參數。在內部函數中,建立了innerArgs數組用來存放全部傳入的參數(又一次用到了slice())。有了存放來自外部函數和內部函數的參數數組後,就能夠使用concat()方法將它們組合爲finalArgs,而後使用apply()將結果傳遞給該函數。注意這個函數並無考慮到執行環境,全部調用apply()時第一個參數是null。curry()函數能夠按一下方式應用。
function add(num1,num2){
return num1+num2;
}
var curriedAdd=curry(add,5);
alert(curriedAdd(3)); //8
JavaScript中的柯里化函數和綁定函數提供了強大的動態函數建立功能。使用bind()仍是curry()要根據是否須要object對象響應來決定。它們都能用於建立複雜的算法和功能,固然二者都不該濫用,由於每一個函數都會帶來額外的開銷。
防篡改對象
JavaScript共享的本質一直是開發人員心頭的痛。由於任何對象均可以被在同一環境中運行的代碼修改。開發人員極可能會意外地修改別人的代碼,甚至更糟糕地,用不兼容的功能重寫原生對象。
ECMAScript5 也增長了幾個方法,經過它們能夠指定對象的行爲。不過請注意:一旦把把對象定義爲防篡改,就沒法撤銷了。
不可擴展對象:(不能添加屬性和方法)
默認狀況下,全部對象都是能夠擴展的。也就是說,任什麼時候候均可以向對象中添加屬性和方法。例如,能夠像下面這樣先定義一個對象,後來再給它添加一個屬性。
var person={name:"Nicolas"};
person.age=29;
如今,使用Object.preventExtensions()方法能夠改變這個行爲,讓你不能再給對象添加屬性和方法。
例如:
var person={name:"Nicholas"};
alert(object.isExtensible(person)); //true
Objext.preventExtensions(person);
alert(object.isExtensible(person)); //false
在調用了object.preventExtensions()方法後,就不能給person對象添加新屬性和方法了。在非嚴格模式下,給對象添加新成員會致使靜默失敗,所以person.age將是undefined。而在嚴格模式下,致使給不可擴展的對象添加新成員會致使拋出錯誤。
雖然不能給對象添加新成員,但已有的成員則絲絕不受影響。你仍然還能夠修改和刪除已有的成員。
密封的對象:(不能添加,也不能刪除)
ECMAScript 5 爲對象定義的第二個保護級別是密封對象(sealed object)。密封對象不可擴展,並且已有成員的[[Configurable]]特性將被設置爲false。這就意味着不能刪除屬性和方法,由於不能使用Object.defineProperty()把數據屬性修改成訪問器屬性,或者相反。屬性值是能夠修改的。
要密封對象,能夠使用Object.seal()方法。
var person={name:"Nicholas"};
Object.seal(person);
person.age=29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"Nicholas"
這個代碼裏面添加以及刪除操做都被忽略掉了。這是在非嚴格模式下的行爲。在嚴格模式下,嘗試添加或刪除對象成員都會致使拋出錯誤。
使用Object.isSealed()方法能夠肯定對象是否被密封了。由於被密封的對象不可擴展,因此用Object.isExtendible()檢測密封的對象也會返回false.
var person = { name: "Nicholas" };
alert(Object.isExtensible(person)); //true
alert(Object.isSealed(person)); //false
Object.seal(person);
alert(Object.isExtensible(person)); //false
alert(Object.isSealed(person)); //true
凍結的對象:(增刪改,所有禁止掉)
最嚴格的防篡改級別是凍結對象(frozen object)。凍結的對象既不可擴展,優點密封的,並且對象數據屬性的[[Writable]]特性會被設置爲false。若是定義[[Set]]函數,訪問器舒心仍然是可寫的。
ECMAScirpt5定義的object.freeze()方法能夠用來凍結對象。
var person = { name: "Nicholas" };
Object.freeze(person);
person.age = 29;
alert(person.age); //undefined
delete person.name;
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
與密封和不容許擴展同樣,對象凍結的對象執行非法操做在非嚴格模式下會被忽略,而在嚴格模式下會拋出錯誤。
固然,也有一個Object.isFrozen()方法用於檢測凍結對象。由於凍結對象既是密封的又是不可擴展的,因此用Object.isExtensible()和Object.isSealed()檢測凍結對象將分別返回false和true。
對
JavaScript
庫的做者而言,凍結對象是頗有用的。由於
JavaScript
庫最怕有人意外(或有意)地修
改了庫中的核心對象。凍結(或密封)主要的庫對象可以防止這些問題的發生。
高級定時器
setTimeout()和setInterval()建立的定時器能夠用於實現有趣且有用的功能。雖然人們對JavaScript的定時器存在廣泛的誤解,認爲它們是線程,其實JavaScript是運行於單線程的環境中的,而定時器僅僅只是計劃代碼在將來的某個時間執行。執行時機是不能保證的,由於在頁面的生命週期中,不一樣時間可能有其餘代碼在控制JavaScript進程。在頁面下載完後的代碼運行,事件處理程序,Ajax回調函數都必須使用一樣的的線程來執行。實際上上,瀏覽器負責進行排序,指派某段代碼在某個時間點運行的優先級。
定時器對隊列的工做方式是,當特定事件過去後將代碼插入。注意,給隊列添加代碼並不意味着對它馬上執行,而只能表示它會盡快執行。設定一個150ms後執行的定時器不表明到了150ms代碼就馬上執行,它表示代碼會在150ms後被加入到隊列中,若是在這個時間點上,隊列中沒有其餘東西,那麼這段代碼就會被執行,表面上看上去好像代碼就字精確制定的時間點上執行了。其餘狀況下,代碼可能明顯地等待更長時間才執行。
關於定時器要記住的最重要的事情是,指定的時間間隔表示什麼時候將定時器的代碼添加到隊列,而不是什麼時候實際執行代碼。若是前面例子中的onclick事件處理程序執行了300ms,那麼定時器的代碼至少要在定時器設置以後的300ms後纔會被執行。隊列中全部的代碼都要等到JavaScript進程空閒以後才能執行,而無論它們是如何添加到隊列中的。
在Firefox中定時器的實現還能讓你肯定定時器過了多久才執行,這需傳遞一個實際執行的時間與指定的間隔的差值。
以下:
//僅僅在Firefox中
setTimeout(function(diff){
if(diff>0){
//晚調用
}else if(diff<0){
//早調用
}else{
//調用及時
}
},250);
執行完一套代碼後,JavaScript進程返回一段很短的時間,這樣頁面上的其餘處理就能夠進行了。因爲JavaScript進程會阻塞其餘頁面處理,因此必須有這些小間隔來防止用戶界面被鎖定(代碼長時間運行中還有可能出現)。這樣設置一個定時器,能夠確保在定時器代碼執行前至少有一個進程間隔。
重複定時器
使用setInterval()建立的定時器確保了定時器代碼規則地插入隊列中。這個方式的問題在於,定時器代碼可能在代碼再次被添加到隊列以前尚未完成執行,結果致使定時器代碼連續運行好幾回,而之間沒有任何停頓。幸虧,JavaScript引擎夠聰明,能避免這個問題。當使用setInterval()時,僅當沒有該定時器的任何掐代碼實例時,纔將定時器代碼添加到隊列。這確保了定時器代碼加入到隊列中的最小時間間隔爲指定間隔。
這種重複定時器的規則有兩個問題:1)某些間隔會被跳過;2)多個定時器的代碼之間的間隔可能會比預期的小。
爲了不setInterval()的重複定時器的這2個缺點,你能夠用以下模式使用鏈式setTimeout()調用。
setTimeout(function(){
//處理中
setTimeout(arguments.callee,interval);
},interval);
這個模式鏈式調用了setTimeout(),每次函數執行的時候都會建立一個新的定時器。第二個setTimeout()調用使用了arguments.callee來獲取對當前執行的函數的引用,併爲其設置另一個定時器。這樣做的好處是,在前一個定時器代碼執行完以前,不會向隊列插入新的定時器代碼,確保不會有任何缺失的間隔。並且,它能夠保證在下一次定時器代碼執行以前,至少要等待指定的間隔,避免了連續的運行。
這個模式主要用於重複定時器,以下例所示:
setTimeout(function(){
var div = document.getElementById("myDiv");
left = parseInt(div.style.left) + 5;
div.style.left = left + "px";
if (left < 200){
setTimeout(arguments.callee, 50);
}
}, 50);
每一個瀏覽器窗口、標籤頁、或者 frame 都有其各自的代碼執行隊列。這意味着,進行跨 frame 或者跨窗口的定時調用,當代碼同時執行的時候可能會致使競爭條件。不管什麼時候須要使用這種通訊類型,最好是在接收 frame 或者窗口中建立一個定時器來執行代碼。
Yielding Process
運行在瀏覽器中的JavaScript都被分配了一個肯定數量的資源。不一樣於桌面應用每每可以隨意控制他們要的內存大小和處理器事件,JavaScript被嚴格限制了,以防止惡意的Web程序員把用戶的計算機搞掛了。其中一個限制是長時間運行腳本的制約,若是代碼運行是超過特定的時間或者特定語句數量就不讓它繼續執行。若是代碼達到了這個限制,會彈出一個瀏覽器錯誤的對話框,告訴用戶某個腳本會用過長的事件執行,詢問是容許其繼續執行仍是中止它。全部JavaScript開發人員的目標就是,確保用戶永遠不會在瀏覽器中看到這個使人費解的對話框。定時器是繞開此限制的方法之一。
腳本長時間運行的問題一般是因爲兩個緣由之一形成的:過長的,過深嵌套的函數調用或者時進行大量處理的循環。
數組分塊的技術,小塊小塊地處理數組,一般每次一小塊。基本的思路是爲要處理的項目建立一個隊列,而後使用定時器取出下一個要處理的項目進行處理,接着再設置另外一個定時器。基本模式以下:
setTimeout(function(){
//
取出下一個條目並處理
var item = array.shift();
process(item);
//
若還有條目,再設置另外一個定時器
if(array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
在數組分塊模式中,array變量本質上就是一個「待辦事宜」列表,它包含了要處理的項目。使用shift()方法能夠獲取隊列中下一個要處理的項目,而後將其傳遞給某個函數。若是在隊列中還有其餘項目,則設置另外一個定時器,並經過arguments.callee調用同一個匿名函數。
要實現數組分塊很是簡單,能夠使用一下函數。
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
函數節流
瀏覽器中某而寫計算和處理要比其餘的昂貴不少。例如,DOM操做比非DOM交互須要更多的內存和CPU時間。連續嘗試進行過多的DOM相關操做可能會致使瀏覽器掛起,有時候甚至會崩潰。尤爲在IE中使用onresize事件處理程序的時候容易發生,當調整瀏覽器大小的時候,該事件會連續觸發。在inresize事件處理程序內部若是嘗試進行DOM操做,其高頻率的更改可能會讓瀏覽器崩潰、爲了繞開這個問題,你能夠使用定時器對該函數進行節流。
函數節流背後的基本思想是指,某些代碼不能夠在沒有間斷的狀況連續重複執行。第一次調用函數,建立一個定時器,在制定的時間間隔以後運行代碼。當第二次調用該函數時,它會清楚前一次的定時器並設置另外一個。若是前一個定時器已經執行過了,這個操做就沒有任何意義。然而,若是前一個定時器還沒有執行,其實就是將其替換爲一個新的定時器。目的是隻有在執行函數的請求中止一段時間以後才執行。
自定義事件:
事件是一種叫觀察者的設計模式,這是一種建立鬆散耦合代碼的技術。對象能夠發佈事件,用來表示在該度一項生命週期中某個有趣的時刻到了。而後其餘對象能夠觀察該對象,等待這些有趣的時刻到來並經過運行代碼來響應。
觀察者模式由兩類對象組成:
主體和觀察者。主體負責發佈事件,同時觀察者經過訂閱事件來觀察該主體。該模式的一個關鍵概念是主體並不知道觀察者的任何事情,也就是說它能夠獨自存在並正常運做即便觀察者不存在。從另外一方面來講,觀察者知道主體並能註冊事件的回調函數(事件處理程序)。涉及DOM上時,DOM元素即是主體,你的事件處理代碼即是觀察者。
事件是與DOM交互的最多見的方式,但它們也能夠用於非DOM代碼中---經過實現自定義事件。自定義事件背後的概念是建立一個管理事件的對象,讓其餘對象監聽那些事件。實現此功能的基本模式能夠以下定義:
拖放:
點擊某個對象,並按住鼠標按鈕不放,將鼠標移動到另外一個區域,而後釋放鼠標按鈕將對象「放」在這裏。
這個技術源自一種叫作「鼠標拖尾」的經典網頁技巧。鼠標拖尾是一個或者多個圖片在頁面上跟着鼠標指針移動。單元素鼠標拖尾的基本代碼須要爲文檔設置一個onmousemove事件處理程序,它老是將指定元素移動到鼠標指針位置
避免一個以上全局變量;
能夠建立一個包含二者的對象:
var MyApplication={
name:"Nicholas",
sayName:function(){
alert(this.name);
}
}
單一的全局量的延伸即是命名空間的概念,由
YUI
(
Yahoo! User Interface
)庫普及。命名空間包括
建立一個用於放置功能的對象。在
YUI
的
2.x
版本中,有若干用於追加功能的命名空間。好比:
q
YAHOO.util.Dom
—— 處理
DOM
的方法;
q
YAHOO.util.Event
—— 與事件交互的方法;
q
YAHOO.lang
—— 用於底層語言特性的方法。
對於
YUI
,單一的全局對象
YAHOO
做爲一個容器,其中定義了其餘對象。用這種方式將功能組合
在一塊兒的對象,叫作命名空間。整個
YUI
庫即是構建在這個概念上的,讓它可以在同一個頁面上與其餘
的
JavaScript
庫共存。
//
建立全局對象
var Wrox = {};
//
爲
Professional JavaScript
建立命名空間
Wrox.ProJS = {};
//
將書中用到的對象附加上去
Wrox.ProJS.EventUtil = { ... };
Wrox.ProJS.CookieUtil = { ... };
避免與null進行比較:
JavaScript不作任何自動的類型檢查,因此它就成了開發人員的責任
使用常量:
儘管JavaScript沒有常量的正是概念,但它仍是頗有用的。這種將數據從應用邏輯分離出來的思想,能夠在不冒引入錯誤的風險的同時,就改變數據。
function validate(value){
if (!value){
alert("Invalid value!");
location.href = "/errors/invalid.php";
}
}
險在這個函數中有兩段數據:要顯示給用戶的信息以及URL。顯示在用戶界面上的字符串應該以容許進行語言國際化的方式抽取出來。URL也應被抽取出來,由於它們有隨着應用成長而改變的傾向。基本上,有着可能因爲這樣那樣緣由會變化的這些數據,那麼都會須要找到函數並在其中修改代碼。而每次修改應用邏輯的代碼,均可能會引入錯誤。能夠經過將數據抽取出來變成單獨定義的常量的方式,將應用邏輯與數據修改隔離來。
栗子;
var Constants = {
INVALID_VALUE_MSG: "Invalid value!",
INVALID_VALUE_URL: "/errors/invalid.php"
};
function validate(value){
if (!value){
alert(Constants.INVALID_VALUE_MSG);
location.href = Constants.INVALID_VALUE_URL;
}
}
在這段重寫的代碼中,消息和URL都被定義於Constans對象中,而後函數引用這些值。這些設置容許數據在無須接觸使用它的函數的狀況下進行變量。
關鍵在於將數據和使用它的邏輯進行分離。要注意的值得類型以下所示。
重複值--任何在多處用到的值都應抽取爲一個常量。這就限制了當一個值變了而另外一個沒變的時候會形成的錯誤。這也包含了CSS類名。
用戶界面字符串--任何用於顯示給用戶的字符串,都應被抽取出來以方便國際化。
URLs--在Web應用中,資源位置很容易變動,因此推薦用一個公共地方存放全部的URL。
任意可能會更改的值--每當你在用到字面量值得時候,你都要問一下本身在這個值在將來是否是會變化。若是答案是「是」,那麼這個值就應該被提取出來做爲一個常量。
對於企業級的JavaScript開發而言,使用常量是很是重要的技巧,由於它能讓代碼更容易維護,而且在數據更改的同時保護代碼。
性能
注意做用域:
隨着做用域鏈中的做用域數量的增長,訪問當前做用域之外的變量的事件也在增長。訪問全局變量老是要比訪問局部變量慢,由於須要遍歷做用域鏈。只要能減小花費在做用域鏈上的時間,就能增長腳本的總體性能。
1.避免全局查找
可能優化腳本性能最重要的就是注意全局查找。使用全局變量和函數確定要比局部的開銷更大,由於要涉及做用域鏈上的查找。
function updateUI(){
var imgs = document.getElementsByTagName("img");
for (var i=0, len=imgs.length; i < len; i++){
imgs[i].title = document.title + " image " + i;
}
var msg = document.getElementById("msg");
msg.innerHTML = "Update complete.";
}
該函數可能看上去徹底正常,可是它包含了三個對於全局document對象的引用。若是在頁面上有多個圖片,那麼for循環中的document引用就會被執行屢次甚至上百次,每次都會要進行做用域查找。經過建立一個紙箱document對象的局部變量,就能夠經過限制一次全局查找來改進這個函數的性能:
function updateUI(){
var doc = document;
var imgs = doc.getElementsByTagName("img");
for (var i=0, len=imgs.length; i < len; i++){
imgs[i].title = doc.title + " image " + i;
}
var msg = doc.getElementById("msg");
msg.innerHTML = "Update complete.";
}
2.避免with語句( with 語句能夠方便地用來引用某個特定對象中已有的屬性,可是不能用來給對象添加屬性。)
在性能很是重要的地方必須避免使用with語句。和函數相似,with語句會建立本身的做用域,所以會增長其中執行的代碼的做用域鏈的長度。用於額外的做用域鏈查找,在with語句中執行的代碼確定會比外面執行的代碼要慢。
必須使用with語句的狀況不多,由於它主要用於消除額外的字符。在大多數狀況下,能夠用局部變量完成相同的事情而不引入新的做用域。下面是一個例子:
function updateBody(){
with(document.body){
alert(tagName);
innerHTML="Hello world!";
}
}
這段代碼中的with語句讓document.body變得更容易使用。其實能夠使用局部變量達到相同的效果,以下所示:
function updateBody(){
var body=document.body;
alert(body.tagName);
body.innerHTML="Hello world!";
}
雖然
代碼稍微長了點,可是閱讀起來比with語句版本更好,它確保讓你知道tagName和innerHTML是屬於哪一個對象的。同時,這段代碼經過將document.body存儲在局部變量中省去了額外的全局查找。
選擇正確方法
和其餘語言同樣,性能問題的一部分是和用於解決問題的算法或者方法有關的。老練的開發人員根據經驗能夠得知哪一種方法可能得到更好的性能。恩多應用字其餘編程語言中的技術和方法也能夠在JavaScript中使用。
優化循環:
減值迭代--大多數循環使用一個從0開始,增長到某個特定值的迭代器。在不少狀況下,從最大值開始,在循環中不斷減值的迭代器更加高效。
簡化終止條件--因爲每次循環過程都會計算終止條件,因此必須保證它儘量快。也就是說避免屬性查找或其餘O(n)的操做。
簡化循環體--循環體是執行最多的,因此要確保其被最大限度地優化。確保沒有某些能夠被很容易移除循環的密集計算。
使用後測試循環--最經常使用for循環和while循環都是前測試循環。而如do-while這種後測試循環,能夠避免最終終止條件的計算,所以運行更快。
展開循環:
當循環次數是肯定的,消除循環並使用屢次函數調用每每更快。例如:若是數組的長度是同樣的,對每一個元素都調用pocess()可能更優,如如下代碼所示:
//消除循環
process(values[0]);
process(values[1]);
process(values[2]);
這個例子假設values數組裏面只有3個元素,直接對每一個元素調用process()。這樣展開循環能夠消除創建循環和處理終止條件的額外開銷,使代碼運行得更快;
若是循環中的迭代次數不能事先肯定,那麼能夠考慮使用一種叫作Duff裝置的技術。這個技術是以其建立者Tom Duff命名的,他最先在C語言中使用這項技術。正是Jeff Greenberg用JavaScript實現了Duff裝置。Duff裝置的基本概念是經過計算迭代的次數是否爲8的倍數將一個循環展開爲一系列語句。
請看如下代碼:
//credit: Jeff Greenberg for JS implementation of Duff’s Device
//
假設
values.length > 0
var iterations = Math.ceil(values.length / 8);
var startAt = values.length % 8;
var i = 0;
do {
switch(startAt){
case 0: process(values[i++]);
case 7: process(values[i++]);
case 6: process(values[i++]);
case 5: process(values[i++]);
case 4: process(values[i++]);
case 3: process(values[i++]);
case 2: process(values[i++]);
case 1: process(values[i++]);
}
startAt = 0;
} while (--iterations > 0);
Duff裝置的實現是經過將values數組中元素個數除以8來計算出循環須要進行多少次迭代的。而後使用取整的上限函數確保結果是整數。若是徹底根據除8來進行迭代,可能會有一些不能被處理到的元素,這個數量保存在startAt變量中。首次執行該循環時,會檢查StartAt變量看有須要多少額外調用。例如,若是數組中有10個值,startAt則等於2,那麼最開始的時候process()則只會被調用2次。在接下來的循環中,startAt被重置爲0,這樣以後的每次循環都會調用8次process()。展開循環能夠提高大數據集的處理速度。
由Andrew B.King 所著的Speed Up your Site提出了一個更快的Duff裝置技術,將do-while循環分紅2個單獨的循環。如下是例子:
//credit: Speed Up Your Site (New Riders, 2003)
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;
if (leftover > 0){
do {
process(values[i++]);
} while (--leftover > 0);
}
do {
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
} while (--iterations > 0);
在這個實現中,剩餘的計算部分不會再實際循環中處理,而是在一個初始化循環中進行除以8的操做。當處理掉了額外的元素,繼續執行每次調用8次process()的主循環。這個方法幾乎比原始的Duff裝置實現快上40%。
針對大數據集使用展開循環能夠節省不少時間,但對於小數據集,額外的開銷則可能得不償失。它是要花更多的代碼來完成一樣的任務,若是處理的不是大數據集,通常來講並不值得。
避免雙重解釋:
當JavaScript代碼想解析JavaScript的時候就會存在雙重解釋懲罰。當使用eval()函數或者是Function構造函數以及使用setTimeout()傳一個字符串參數時都會發生這種狀況。下面有一些例子:
//
某些代碼求值——避免
!!
eval("alert('Hello world!')");
//
建立新函數——避免
!!
var sayHi = new Function("alert('Hello world!')");
//
設置超時——避免
!!
setTimeout("alert('Hello world!')", 500);
在以上這些例子中,都要解析包含了JavaScript代碼的字符串。這個操做是不能在初始的解析過程當中完成的,由於代碼是包含在字符串中的,也就是說在JavaScript代碼運行的同時必須新啓動一個解析器來解析新的代碼。實例化一個新的解析器有不容忽視的開銷,因此這種代碼要直接解析慢得多。
對於這幾個例子都有另外的辦法。只有極少的狀況下eval()是絕對必須的,因此儘量避免使用。在這個例子中,代碼其實能夠直接內嵌在原代碼中。對於Function構造函數,徹底能夠直接寫成通常的函數,調用setTimeout()能夠傳入函數做爲第一參數。如下是一些例子:
//
已修正
alert('Hello world!');
//
建立新函數——已修正
var sayHi = function(){
alert('Hello world!');
};
//
設置一個超時——已修正
setTimeout(function(){
alert('Hello world!');
}, 500);
若是要提升代碼性能,儘量避免出現須要按照JavaScript解釋的字符串。
性能的其餘注意事項:
當評估腳本性能的時候,還有其餘一些能夠考慮的東西。下面並不是主要的問題,不過若是使用得當也會有至關大的提高。
q
原生方法較快——只要有可能,使用原生方法而不是本身用JavaScript 重寫一個。原生方法是用
諸如C/C++之類的編譯型語言寫出來的,因此要比JavaScript 的快不少不少。JavaScript 中最容
易被忘記的就是能夠在
Math
對象中找到的複雜的數學運算;這些方法要比任何用JavaScript 寫
的一樣方法如正弦、餘弦快的多。
q
Switch 語句較快 —— 若是有一系列複雜的
if-else
語句,能夠轉換成單個
switch
語句則可
以獲得更快的代碼。還能夠經過將
case
語句按照最可能的到最不可能的順序進行組織,來進一
步優化
switch
語句。
q
位運算符較快 —— 當進行數學運算的時候,位運算操做要比任何布爾運算或者算數運算快。選
擇性地用位運算替換算數運算能夠極大提高複雜計算的性能。諸如取模,邏輯與和邏輯或均可
以考慮用位運算來替換
最小化語句數:
1.多個變量聲明:
有個地方不少開發人員都容易建立不少語句,那就是多個變量的聲明,很容易看到代碼中由多個var語句來聲明多個變量,以下所示:
//4
個語句——很浪費
var count = 5;
var color = "blue";
var values = [1,2,3];
var now = new Date();
在強類型語言中,不一樣的數據類型的變量必須在不一樣的語句中聲明。然而,在JavaScript中全部的變量均可以使用單個var語句來聲明。前面的代碼能夠以下重寫:
//
一個語句
var count = 5,
color = "blue",
values = [1,2,3],
now = new Date();
此處,變量聲明只用了一個var語句,之間由逗號隔開。在大多數狀況下這種優化都很是容易作,而且要比單個變量分別聲明快不少。
2.插入迭代值:
當使用迭代值(也就是在不一樣位置進行增長或減小的值)的時候,儘量合併語句。請看如下代碼:
var name=values[i];
i++;
前面這2句語句各只有一個目的:第一個從values數組中獲取值,而後存儲在name中;第二個給變量i增長1。這兩句能夠經過迭代值插入第一個語句組合成一個語句,以下所示:
var name=values[i++];
這一個語句能夠完成和前面兩個語句同樣的事情。由於自增操做符是後綴操做符,i的值只有在語句其餘部分結束以後纔會增長。一旦出現相似狀況,都要嘗試將迭代值插入到最後使用它的語句中去。
3.使用數組和對象字面量:
使用構造函數或者是使用字面量。使用構造函數老是要用到更多的語句來插入元素或者定義屬性,而字面量能夠將這些操做在一個語句中完成。請看如下例子:
//
用
4
個語句建立和初始化數組——浪費
var values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;
//
用
4
個語句建立和初始化對象——浪費
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.sayName = function(){
alert(this.name);
};
這段代碼中,只建立和初始化了一個數組和一個對象。各用了4個語句:一個調用構造函數,其餘3個分配數據。其實能夠很容易地轉換成使用字面量的形式,以下所示:
//
只用一條語句建立和初始化數組
var values = [123, 456, 789];
//
只用一條語句建立和初始化對象
var person = {
name : "Nicholas",
age : 29,
sayName : function(){
alert(this.name);
}
};
重寫後的代碼只包含兩條語句,一條是建立和初始化數組,另外一條建立和初始化對象。以前用了八條語句的的東西如今只用了兩條,減小了75%的語句量。在包含成千上萬行JavaScript的代碼庫中,這些優化的價值更大。只要有可能,儘可能使用數據和對象的字面量表達式來消除沒必要要的語句。
優化DOM交互:
1.最小化現場更新
一旦你須要訪問的DOM部分是已經顯示的頁面的一部分,那麼你就是在進行一個現場更新。之因此叫現場更新,是由於須要當即(現場)對頁面對用戶的顯示進行更新。每個更改,無論是插入單個字符,仍是移除整個片斷,都會有一個性能懲罰,由於瀏覽器要從新計算無數尺寸以進行更新。現場更新進行得越多,代碼完成執行所花的時間就越長;完成一個操做所需的現場更新越少,代碼就越快。
2.使用innerHTML
有兩種在頁面上建立DOM節點的方法:使用諸如createElement()和appendChild()之類的DOM方法,以及使用innerHTML。對於小的DOM更改而言,兩種方法效率都差很少。然而,對於大的DOM更改,使用innerHTML要比使用標準DOM方法建立一樣的DOM結構快得多。
當把innerHTML設置爲某個值時,後臺會建立一個HTML解析器,而後使用內部的DOM調用來建立DOM結構,而非基於JavaScript的DOM調用。因爲內部方法是編譯好的而非解釋執行的。因此執行快得多。
在使用innerHTML時候,能夠使用字符串鏈接後再調用innerHTML比較好。
3.使用事件代理
大多數Web應用在用戶交互上大量用到事件處理程序。頁面上的事件處理程序的數量和頁面相應用戶交互的速度之間有個負相關。爲了減輕這種懲罰,最好使用事件代理。
事件代理,如第13章所討論的那樣,用到了事件冒泡。任何能夠冒泡的事件都不只僅能夠在事件目標上進行處理,目標的任何祖先節點上也能處理。使用這個知識,就能夠將事件處理程序附加到更高層的地方負責多個目標的事件處理。若是可能,在文檔級別附加事件處理程序,這樣能夠處理整個頁面的事件。
4.注意HTMLCollection
HTMLCollection對象的陷阱,由於它們對於Web應用的性能而言是巨大的損害。記住,任什麼時候候要訪問HTMLCollection,無論它是一個屬性仍是一個方法,都是在文檔刪進行一個查詢,這個查詢開銷很昂貴。最小化訪問HTMLCollection的次數能夠極大地改進腳本的性能。
也許優化HTMLCollection訪問最重要的地方就是循環了。前面提到過將長度計算移入for循環的初始化部分。如今看一下這個例子:
var images = document.getElementsByTagName("img"),
i, len;
for (i=0, len=images.length; i < len; i++){
//處理
}
這裏的關鍵在於長度length存入了len變量,而不是每次都去訪問HTMLCollection的length屬性。當在循環中使用HTMLCollection的時候,下一步應該是獲取要使用的項目的引用,以下所示,以便避免在循環體內屢次調用HTMLCollection。
var images = document.getElementsByTagName("img"),
image,
i, len;
for (i=0, len=images.length; i < len; i++){
image = images[i];
//
處理
}
這段代碼添加了image變量,保存了當前的圖像。這以後,在循環內就沒有理由再訪問images的HTMLCollection了。
編寫JavaScript的時候,必定要知道什麼時候返回HTMLCollection對象,這樣你就能夠最小化對他們的訪問。發生如下狀況時會返回HTMLCollection對象:
q
進行了對
getElementsByTagName()
的調用;
q
獲取了元素的
childNodes
屬性;
q
獲取了元素的
attributes
屬性;
q
訪問了特殊的集合,如
document.forms
、
document.images
等。
要了解當使用HTMLCollection對象時,合理使用會極大提高代碼執行速度。
部署
構建過程:
完備JavaScript代碼能夠用於部署的一件很重要的事情,就是給它開發某些類型的的構建過程。軟件開發的典型模式是寫代碼-編譯-測試,即首先書寫好代碼,將其編譯經過,而後運行並確保其正常工做。因爲JavaScript並不是一個編譯型語言,模式變成了寫代碼-測試,這裏你寫的代碼就是你要在瀏覽器中測試的代碼。這個方法的問題在於他不是最優的,你寫的代碼不該該原封不動地放入瀏覽器中,理由以下所示。
q
知識產權問題 —— 若是把帶有完整註釋的代碼放到線上,那別人就更容易知道你的意圖,對它
再利用,而且可能找到安全漏洞。
q
文件大小 —— 書寫代碼要保證容易閱讀,才能更好地維護,可是這對於性能是不利的。瀏覽器
並不能從額外的空白字符或者是冗長的函數名和變量名中得到什麼好處。
q
代碼組織 —— 組織代碼要考慮到可維護性並不必定是傳送給瀏覽器的最好方式。
基於這些緣由,最好給JavaScript 文件定義一個構建過程。
構建過程始於在源控制中定義用於存儲文件的邏輯結構。最好避免使用一個文件存放全部的JavaScript,遵循如下面向對象語言中的典型模式:將每一個對象或自定義類型分別放入其單獨的文件中。這樣能夠確保每一個文件包含最少許的代碼,使其在不引入錯誤的狀況下更容易修改。另外,在使用像CVUS或Subversion這類併發源控制系統的時候,這樣作也減小了在合併操做中產生衝突的風險。
記住將代碼分離成多個文件只是爲了提升可維護性,並不是爲了部署。要進行部署的時候,須要將這些源代碼合併爲一個或幾個歸併文件。推薦Web應用中儘量使用最少的JavaScript文件。推薦Web應用中儘量使用最少的JavaScript文件,是由於HTTP請求時Web中的主要性能瓶頸之一。記住經過<script>標記引用JavaScript文件是一個阻塞操做,當代碼下載並運行的時候會中止其餘全部的下載。所以,儘可能從邏輯上將JavaScript代碼分組成部署文件。
新興的API:
requestAniamationFrame():
很長時間以來,計時器和循環間隔一直都是JavaSctipt動畫的最核心技術。雖然CSS變換及動畫爲Web開發人員提供了實現動畫的簡單手段,但JavaScript動畫開發領域的情況這些年來並無大的變化。Firefox 4最先爲JavaScript動畫添加了一個新API,即mozRequestAnimationFrame()。這個方法會告訴瀏覽器:有一個動畫開始了。進而 瀏覽器就能夠肯定重繪的最佳方式。
webkitRequestAnimationFrame與msRequestAnimationFrame:
Chrome和IE10+也都給出了本身的實現。這兩個版本與Mozilla的版本有兩個方面的微小差別。首先,不會給回調函數傳遞時間碼,所以你沒法知道下一次重繪將發生在什麼時間。其次,Chrome又增長了第二個可選的參數,即將要發生辯護的DOM元素。知道了重繪將發生在頁面中哪一個特定元素的區域內,就能夠將重繪限定在該區域內。
Page Visibility API:
不知道用戶是否是在與頁面交互,這是困擾廣大Web開發人員的一個主要問題。若是頁面最小化或者隱藏在了其餘標籤頁後面,那麼有些功能是能夠停下來的,好比輪詢服務器或者某些動畫效果。而Page Visibility API(頁面可見性API)就是爲了讓開發人員指導頁面是否對用戶可見而推出的。
這個API自己很是簡單,由如下三部分組成。
q
document.hidden
:表示頁面是否隱藏的布爾值。頁面隱藏包括頁面在後臺標籤頁中或者瀏覽
器最小化。
q
document.visibilityState
:表示下列4 個可能狀態的值。
n
頁面在後臺標籤頁中或瀏覽器最小化。
n
頁面在前臺標籤頁中。
n
實際的頁面已經隱藏,但用戶能夠看到頁面的預覽(就像在Windows 7 中,用戶把鼠標移動到
任務欄的圖標上,就能夠顯示瀏覽器中當前頁面的預覽)。
n
頁面在屏幕外執行預渲染處理。
q
visibilitychange
事件:當文檔從可見變爲不可見或從不可見變爲可見時,觸發該事件
Geolocation API:
地理定位(geolocation)是最使人興奮,並且獲得了普遍支持的一個新API。經過這套API,JavaScript代碼可以訪問到用戶的當前位置信息。固然,訪問以前必須獲得用戶的明確許可,即統一在頁面中共享其位置信息。若是頁面嘗試訪問地理定位信息,瀏覽器就會顯示一個對話框,請求用戶許可共享其位置信息。
File API:
不能直接訪問用戶計算機中的文件,一直都是Web應用開發中的一大障礙。2000年之前,處理文件的惟一方式就是再表單中加入<input type="file">字段,僅此而已。File API的宗旨是爲Web開發人員提供一種安全的方式,以便在客戶端訪問用戶計算機中的文件,並更好地對這些文件執行操做。
File API在表單中的文件輸入字段的基礎上,又添加了一些直接訪問文件信息的接口。HTML5在DOM中位文件輸入元素添加一個files集合。咋經過文件輸入字段選擇了一或多個文件時,files集合中將包含一組File對象,每一個File對象對應着一個文件。
Web計時:
頁面性能一直都是Web開發人員最關注的領域。但直到最近,度量頁面性能指標的惟一方式,就是提升代碼複雜程度和巧妙地使用瀏覽器內部的度量結果,經過直接讀取這些信息能夠作任何想作的分析。與本章介紹過的其餘API不一樣,Web Timming API實際上已經成爲了W3C的建議標準,只不過目前支持它的瀏覽器還不夠多。
Web 計時機制的核心是windo.performance對象。對頁面的全部度量信息,包括那些規範中已經定義的和未來才能肯定的,都包含在這個對象裏面。Web Timing規範一開始就爲performance對象定義了兩個屬性。
Web Workers:
隨着Web應用複雜性的與日俱增,愈來愈複雜的計算在所不免。長時間運行的JavaScript進程會致使瀏覽器凍結用戶界面,讓人感受屏幕」凍結「了。Web Workers規範經過讓JavaScript在後臺運行解決了這個問題。瀏覽器實現WebWorkers規範的方式有不少種,能夠使用線程,後臺進程或運行在其餘處理器核心上的進程,等等。具體的實現細節其實沒有那麼重要,重要的是開發人員如今能夠放心地運行JavaScript,而沒必要擔憂會影響用戶體驗了。
使用Worker:
實例化Worker對象並傳入要執行的JavaScript文件名就能夠建立一個新的Web Worker。例如:
var worker=new Worker("stufftodo.js");
這行代碼會致使瀏覽器下載stufftodo.js,但只有Worker接收到消息纔會實際執行文件中的代碼。要給Worker傳遞消息,能夠使用postMessage()方法:
worker.postMessage("start!");
消息內容能夠是任何可以被序列化的值,不過與XDM不一樣的是,在全部支持的瀏覽器中,postMessage()都能接收對象參數。所以能夠隨便傳遞任何形式的對象數據;
ECMAScript Harmony:
通常性變化:
Harmny爲ECMAScript引入了一些基本的變化。對這門語言來講,這些雖然不算是大的變化,但的確也彌補了它功能上的而一些缺憾。
常量:
沒有正式的常量是JavaScript的一個明顯缺陷。爲了彌補這個缺陷,標準制定者爲Harmony增長了用Const關鍵字聲明常量的語言。使用方式與var相似,但const聲明的變量在初始賦值後,就不能再從新賦值了。來看一個例子。
const MAX_SIZE=25;
能夠像聲明變量同樣在任何地方聲明常量。但在同一做用域中,常量名不能與其餘變量或函數名重名,所以下列聲明會致使錯誤:
const FLAG=true;
var FLAG=false; //錯誤!
除了值不能修改以外,能夠像使用任何變量同樣使用常量。修改常量的值,不會有任何效果,以下所示:
const FLAG=true;
FLAG=false;
alert(FLAG);//true
塊級做用域及其餘做用域:
本書時不時就會提醒讀者一句:JavaScript沒有塊級做用域。換句話說,在語句塊中定義的變量與在包含函數中定義的變量共享相同的做用域。Harmony新增了定義塊級做用域的語法:使用let關鍵字。
與const和var相似,能夠使用let在任何地方定義變量併爲變量賦值。區別在於,使用let定義的變量在定義它的代碼以外沒有定義。好比說吧,下面是很是常見的代碼塊:
for(var i=0;i<10;i++){
//執行某些操做
}
alert(i);//10
在上面的代碼塊中,變量i是做爲代碼塊所在函數的局部變量來聲明的,也就是說,在for循環執行完畢後,仍然可以讀取i的值。若是在這裏使用let代替var,則循環以後,變量i將不復存在。
看下面的例子:
for(let i=0;i<10;i++){
//執行某些操做
}
alert(i); //錯誤! 變量i沒有定義
以上代碼執行到最後一行的時候,就會出現錯誤,由於for循環已結束,變量i就已經沒有定義了。所以不能對沒有定義的變量執行操做,因此發生錯誤是天然的。
還有另一種使用let的方式,即建立let語句,在其中定義只能在後續代碼塊中使用的變量,像下面的例子這樣:
var num=5;
let(num=10,multiplier=2){
alert(num*multiplier); //20
}
alert(num); //5
這是由於let語句建立了本身的做用域,這個做用域裏的變量與外面的變量無關。
函數:
大多數代碼都是以函數方式編寫的,所以Harmony從幾個方面改進了函數,使其更便於使用。與Harmony中其餘部分相似,對函數的改進也集中在開發人員和實現人員共同面臨的難題上。
剩餘參數和分佈參數:
Harmony中再也不有arguments對象,所以也就沒法經過它來讀取到未聲明的參數。不過,使用剩餘參數語法,也能表示你期待給函數傳入可變數量的參數。剩餘參數的語法形式是三個點後跟一個標識符。使用這種語法能夠定義可能會傳進來的更多參數,而後把它們收集到一個數組中。
來看例子;
function num(sum,sum2,...sums){
var result=sum+sum2;
for(let i=0,len=nums.length;i<len;i++){
result+=nums[i];
}
return result;
}
var result=sum(1,2,3,4,5,6);
以上代碼定義了一個num()函數,接收至少兩個參數。這個函數還能接收更多參數,而其他參數都將保存在sunms數組中,與原來的arguments對象不一樣,剩餘參數都保存在Array的一個實例中,所以能夠使用任何數組方法來操做它們。另外,即便並無多餘的參數傳入函數,剩餘參數對象也是Array的實例。
與剩餘參數緊密相關的另外一種參數語法是分佈參數。經過分佈參數,能夠向函數中傳入一個數組,而後數組中的元素會映射到函數的每一個參數上。分佈參數的語法形式與剩餘參數的語法相同,就是再值得前面加三個點。惟一的區別是分佈參數在調用參數的時候使用,而剩餘參數在定義函數的時候使用。好比,咱們能夠不誒num函數一個一個地傳入參數,而是傳入分佈參數:
var result=num(...[1,2,3,4,5]);
在這裏,咱們將一個數組做爲分佈參數傳給了sum()函數。以上代碼在功能上與下面這行代碼等價:
var result=num.apply(this,[1,2,3,4,5,6]);
默認參數值:
ECMAScript函數中的全部參數都是可選的,由於實現不會檢查傳入的參數數量。不過,除了手工檢查傳入了哪一個參數以外,你還能夠爲參數制定默認值。若是調用函數時沒有傳入該參數,那麼該參數就會使用默認值。
要爲參數制定默認值,能夠在參數名後面直接加上等於號和默認值,就像下面這樣:
function sum(num1,num2=0){
return num1+num2;
}
var result1=sum(5);
var result2=sum(5,5);
這個sum()函數接收兩個參數,但第二個參數是可選的,由於它的默認值爲0.使用可選參數的好處是開發人員不用再去檢查是否給某個參數傳入了值,若是沒有的話就使用某個特定的值。默認參數值幫你解除了這個困擾。
生成器:
所謂生成器,其實就是一個對象,它每次都能生成一系列值中的一個。對Harmony而言,要建立生成器,可讓函數經過yield操做符返回某個特殊的值。對於使用yield操做符返回值的函數,調用它時就會建立並返回一個新的Generator實例。而後,在這個實例上調用next()方法就能取得生成器的第一個值。此時,執行的是原來的函數,但執行劉到yield語句就會中止,值返回特定的值。從這個角度看,yield與return很類似。若是再次調用next()方法,原來函數中位於yield語句後的代碼會繼續執行,直到再次碰見yield語句時中止執行,此時再返回一個新值。
function myNumbers(){
for (var i=0; i < 10; i++){
yield i * 2;
}
}
var generator = myNumbers();
try {
while(true){
document.write(generator.next() + "<br />");
}
} catch(ex){
//
有意沒有寫代碼
} finally {
generator.close();
}
function myNumbers(){
for (var i=0; i < 10; i++){
yield i * 2;
}
}
var generator = myNumbers();
try {
while(true){
document.write(generator.next() + "<br />");
}
} catch(ex){
//
有意沒有寫代碼
} finally {
generator.close();
}
調用MyNumbers()函數後,會獲得一個生成器。myNumbers()函數自己很是簡單,包含一個每次循環都產生一個值的for循環。每次調用next()方法都會執行一次for循環,而後返回下一個值。第一個值是0,第二個值是2,第三個值是4,以此類推,在myNumbers()函數完成推出而沒有執行yield語句時(最後一次循環判斷i不小於10的時候),生成器會拋出StopIteration錯誤。所以,爲了輸出生成器能產生的全部數值,這裏用了一個try-catch結構包裝了一個while循環,以免出錯時中斷代碼執行。
若是再也不須要某個生成器,最好是調用它的close()方法。這樣會執行原始函數的其餘部分,包括try-catch相關的finally語句塊。
在須要一系列值,而每個值又與前一個值存在某種關係的狀況下,能夠使用生成器。