文章目錄
- 1、JavaScript簡介
-
- 1.1 DOM
- 1.2 BOM
- 2、在 HTML 中使用 JavaScript
-
- 2.1 < script >元素
-
- 2.1.1 使用< script >元素的方式有兩種
-
- (1)直接在頁面中嵌入 JavaScript 代碼
- (2)包含外部 JavaScript檔案
- 2.1.2 标簽的位置
- 2.1.3 延遲腳本
- 2.1.4 異步腳本
- 3、基本概念
-
- 3.1 文法
-
- 3.1.1 嚴格模式
-
- 嚴格模式的限制
- 為什麼使用嚴格模式?
- 3.4 資料類型
-
- 3.4.1 typeof操作符
- 3.6 語句
-
- 3.6.8 with語句
- 4、變量、作用域和記憶體
-
- 4.1 基本類型和引用類型
-
- 4.1.4 instanceof檢測類型
- 4.2 執行環境和作用域
-
- 4.2.2 沒有塊級作用域
- 4.3 垃圾收集
-
- 函數中局部變量的正常生命周期
- 4.3.1 标記清除
- 4.3.2 引用計數
- 4.3.3 記憶體管理
- 5、引用類型
-
- 5.1 Object
-
- 5.1.1 建立對象的兩種方式
- 5.1.2 通路對象屬性的方法
- 5.2 Array
-
- 5.2.1 建立Array的兩種方式
- 5.2.2 數組檢測
- 5.2.3 棧方法
- 5.2.4 隊列方法
- 5.2.5 其他方法
- 5.5 Function
-
- 5.5.1 沒有重載
- 5.5.2 函數聲明與函數表達式
- 5.5.4 函數内部屬性
- 5.5.5 函數屬性和方法
- 5.6 基本包裝類型
-
- 5.6.1 Boolean
- 5.6.2 Number
- 5.6.3 String
- 5.7 單體内置對象
-
- 5.7.1 Global對象
-
- 5.7.1.1 URI編碼方法
- 5.7.1.2 eval()方法
- 5.7.1.4 window對象
- 6、面向對象的程式設計
-
- 6.1 了解對象
-
- 6.1.1 屬性類型
-
- 6.1.1.2 資料屬性
- 6.1.1.2 通路器屬性
- 6.2 建立對象
-
- 6.2.1 工廠模式
- 6.2.2 構造函數模式
- 6.2.3 原型模式
-
- 6.2.3.1 原型對象
- 6.2.3.2 原型與 in 操作符
- 6.2.3.3. 更簡單的原型文法
- 6.2.4 組合使用構造函數模式和原型模式
- 6.2.5 動态原型模式
- 6.3 繼承
- 6.3.1 原型鍊
-
- 6.3.3 組合繼承
- 7、函數表達式
-
- 7.2 閉包
- 7.3 模仿塊級作用域
- 7.4 私有變量
-
- 7.4.2 子產品模式
- 9、用戶端檢測
-
- 9.1 能力檢測
- 13、事件
-
- 13.1 事件流
-
- 13.1.3 DOM事件流
- 13.2 事件處理程式
-
- 13.2.2 DOM0 級事件處理程式
- 13.2.3 DOM2 級事件處理程式
- 13.2.5 跨浏覽器的事件處理程式
- 13.5 記憶體和性能
-
- 13.5.1 事件委托
- 13.5.2 移除事件處理程式
- 14、表單腳本
-
- 14.1 表單基礎知識
-
- 14.1.1 送出表單
- 15、使用Canvas繪圖
-
- 15.1 基本用法
- 15.2 2D 上下文
-
- 15.2.1 填充和描邊
- 15.2.2 繪制矩形
- 15.2.3 繪制路徑
- 15.2.4 繪制文本
- 15.2.5 變換
- 15.2.10 使用圖像資料
- 17、錯誤處理與調試
-
- 17.2 錯誤處理
-
- 17.2.1 try-catch語句
- 17.2.7 把錯誤記錄到伺服器
- 17.3 調試技術
-
- 17.3.3 抛出錯誤
- 20.1 文法
-
- 20.1.1 簡單值
- 20.1.2 對象
- 20.1.3 數組
- 20.2 解析與序列化
-
- 20.2.1 JSON對象
- 21、Ajax與Comet
-
- 21.1 XMLHTTPRequest對象
-
- 21.1.1 XHR的用法
- 21.1.4 POST請求
- 21.4 跨源資源共享
- 21.5 其他跨域技術
-
- 21.5.1 圖像Ping
- 21.5.3 Comet
- 21.5.5 Web Sockets
- 21.7 安全
參考Javascript進階程式設計(第3版)
1、JavaScript簡介
1.1 DOM
文檔對象模型(DOM,Document Object Model)是針對 XML 但經過擴充用于 HTML 的應用程式程式設計接口(API,Application Programming Interface)。DOM 把整個頁面映射為一個多層節點結構。
1.2 BOM
浏覽器視窗的浏覽器對象模型(BOM,Browser Object Model)。
2、在 HTML 中使用 JavaScript
2.1 < script >元素
script标簽定義了下列 6 個屬性:
屬性 | 說明 |
---|---|
async | 可選。表示應該立即下載下傳腳本,但不應妨礙頁面中的其他操作,比如下載下傳其他資源或等待加載其他腳本。隻對外部腳本檔案有效。 |
charset | 可選。表示通過 src 屬性指定的代碼的字元集。 |
defer | 可選。表示腳本可以延遲到文檔完全被解析和顯示之後再執行。隻對外部腳本檔案有效。IE7 及更早版本對嵌入腳本也支援這個屬性。 相當于告訴浏覽器立即下載下傳,但延遲執行。 腳本會被延遲到整個頁面都解析完畢後再運作。 |
language | 已廢棄。原來用于表示編寫代碼使用的腳本語言(如 JavaScript、JavaScript1.2或 VBScript)。大多數浏覽器會忽略這個屬性,是以也沒有必要再用了。 |
src | 可選。表示包含要執行代碼的外部檔案。 |
type | 可選。可以看成是 language 的替代屬性;表示編寫代碼使用的腳本語言的内容類型(也稱為 MIME 類型)。目前 type 屬性的值依舊還是text/javascript。不過,這個屬性并不是必需的,如果沒有指定這個屬性,則其預設值仍為text/javascript。 |
2.1.1 使用< script >元素的方式有兩種
(1)直接在頁面中嵌入 JavaScript 代碼
須為< script >标簽指定 type 屬性
<script type="text/javascript">
function sayHi(){
alert("Hi!");
}
</script>
包含在< script >元素内部的 Java Script 代碼将被從上至下依次解釋。
注意:在使用< script >嵌入 JavaScript 代碼時,不要在代碼中的任何地方出現"< script >"字元串。通過轉義字元“/”,例如:
<script type="text/javascript">
function sayScript(){
alert("<\/script>");
}
</script>
(2)包含外部 JavaScript檔案
通過< script >元素來包含外部 JavaScript 檔案,那麼 src 屬性就是必需的,指向外部 JavaScript 檔案的連結。
<script type="text/javascript" src="example.js"></script>
在解析外部 JavaScript 檔案(包括下載下傳該檔案)時,頁面的處理也會暫時停止。
通過< script >元素的 src 屬性還可以包含來自外部域的 JavaScript 檔案。(跨域要注意安全)
<script type="text/javascript" src="http://www.somewhere.com/afile.js"></script>
2.1.2 标簽的位置
現代 Web 應用程式一般都把全部 JavaScript 引用放在< body >元素中頁面内容的後面,為了在解析包含的 JavaScript 代碼之前,先将頁面的内容完全呈現在浏覽器中。
2.1.3 延遲腳本
為< script >标簽定義了 defer 屬性, 相當于告訴浏覽器立即下載下傳,但延遲執行。 腳本會被延遲到整個頁面都解析完畢後再運作。但是腳本會先于 DOMContentLoaded 事件執行,在現實當中,延遲腳本并不一定會按照順序執行,也不一定會在 DOMContentLoaded 事件觸發
前執行,是以最好隻包含一個延遲腳本。defer 屬性隻适用于外部腳本檔案。支援
HTML5 的實作會忽略給嵌入腳本設定的 defer 屬性。
2.1.4 異步腳本
HTML5 為< script >元素定義了 async 屬性。async 隻适用于外部腳本檔案,并告訴浏覽器立即下載下傳檔案。标記為 async 的腳本并不保證按照指定它們的先後順序執行。指定 async 屬性的目的是不讓頁面等待兩個腳本下載下傳和執行,進而異步加載頁面其他内容。為此,建議異步腳本不要在加載期間修改 DOM。
異步腳本一定會在頁面的 load 事件前執行,但可能會在 DOMContentLoaded 事件觸發之前或之後執行。
3、基本概念
3.1 文法
3.1.1 嚴格模式
嚴格模式是為 JavaScript 定義了一種不同的解析與執行模型。在嚴格模式下,ECMAScript 3 中的一些不确定的行為将得到處理,而且對某些不安全的操作也會抛出錯誤。“use strict” 指令隻允許出現在腳本或函數的開頭。要在整個腳本中啟用嚴格模式,可以在頂部添加如下代碼:
"use strict";
也可以指定函數在嚴格模式下執行。
嚴格模式的限制
- 不能使用未聲明的變量。(對象也是一個變量)
- 不允許删除變量或對象
- 不允許删除函數
- 不允許變量重名
- 不允許使用八進制
- 不允許使用轉義字元
- 不允許對隻讀屬性指派
- 不允許對一個使用getter方法讀取的屬性進行指派
- 不允許使用 with 語句
"use strict";
var obj = { get x() {return 0} };
obj.x = 3.14; // 報錯
- 不允許删除一個不允許删除的屬性
- 變量名不能使用 “eval” 字元串:eval(String) 函數可計算某個字元串,并執行其中的的 JavaScript 代碼。
- 變量名不能使用 “arguments” 字元串
- 由于一些安全原因,在作用域 eval() 建立的變量不能被調用
- 禁止this關鍵字指向全局對象
function f(){
return !this;
}
// 傳回false,因為"this"指向全局對象,"!this"就是false
function f(){
"use strict";
return !this;
}
// 傳回true,因為嚴格模式下,this的值為undefined,是以"!this"為true。
//是以,使用構造函數時,如果忘了加new,this不再指向全局對象,而是報錯。
function f(){
"use strict";
this.a = 1;
};
f();// 報錯,this未定義
為什麼使用嚴格模式?
- 消除代碼運作的一些不安全之處,保證代碼運作的安全;
- 提高編譯器效率,增加運作速度;
3.4 資料類型
5 種基本資料類型(按值通路):Undefined、Null、Boolean、Number和 String。
1種複雜資料類型(按引用通路):Object,Array,Function,Object 本質上是由一組無序的名值對組成的。
3.4.1 typeof操作符
檢測給定變量的資料類型
傳回值含義:
傳回值 | 說明 |
---|---|
undefined | 這個值未定義; |
boolean | 這個值是布爾值; |
string | 這個值是字元串; |
number | 這個值是數值; |
object | 這個值是對象或nu11; |
function | 這個值是函數。 |
說明:
- 調用 typeof null會傳回"object",因為特殊值 null 被認為是一個空的對象引用。
- Safari 5 及之前版本、Chrome 7 及之前版本在對正規表達式調用 typeof 操作符時會傳回"function",而其他浏覽器在這種情況下會傳回"object"。
- 對未初始化的變量執行 typeof 操作符會傳回 undefined 值
- 對未聲明的變量執行 typeof 操作符同樣也會傳回 undefined 值。
3.6 語句
3.6.8 with語句
with 語句的作用是将代碼的作用域設定到一個特定的對象中。with 語句的文法如下:
定義 with 語句的目的主要是為了簡化多次編寫同一個對象的工作,如:
var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;
上面幾行代碼都包含 location 對象。如果使用 with 語句,可以把上面的代碼改寫成如下所示:
with(location){
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}
注意:
嚴格模式下不允許使用 with 語句,否則将視為文法錯誤。因為大量使用 with 語句會導緻性能下降,同時也會給調試代碼造成困難,是以在開發大型應用程式時,不建議使用 with 語句。
4、變量、作用域和記憶體
4.1 基本類型和引用類型
4.1.4 instanceof檢測類型
instanceof操作符判斷對象變量是什麼類型的對象。
alert(person instanceof Object); // 變量person是Object嗎?
alert(colors instanceof Array); // 變量colors是Array嗎?
alert(pattern instanceof RegExp); // 變量pattern是RegExp嗎?
4.2 執行環境和作用域
當代碼在一個環境(全局,函數,代碼塊等)中執行時,會建立變量對象的一個作用域鍊(scope chain)。作用域鍊的用途,是保證對執行環境有權通路的所有變量和函數的有序通路。
類似于一套房子,有公共的區域:客廳,衛生間,廚房,還有私人區域:卧室A和卧室B,公共區域即為全局作用域,私人區域即為類似函數、catch塊、with語句等的作用域,私人區域可以通路大範圍的公共區域,而公共區域不能通路私人區域。私人區域之間也不能互相通路。
4.2.2 沒有塊級作用域
JavaScript沒有塊級作用域。由for語句建立的變量 i 即使在for循環執行結束後,也依舊會存在于循環外部的執行環境中。
使用var聲明的變量會自動被添加到最接近的環境中。
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //由于sum不是有效的變量,是以會導緻錯誤
4.3 垃圾收集
JavaScript具有自動垃圾收集機制,執行環境會負責管理代碼執行過程中使用的記憶體。
垃圾收集機制的原理: 找出那些不再繼續使用的變量,然後釋放其占用的記憶體。為此,垃圾收集器會按照固定的時間間隔(或代碼執行中預定的收集時間),周期性地執行這一操作。
函數中局部變量的正常生命周期
在函數執行時,會在棧(堆)空間上為變量配置設定空間。然後函數執行過程中使用,函數執行結束後,占用的記憶體就會被釋放。垃圾收集器會對變量進行标記跟蹤,用來判斷沒用的變量,對其進行回收。
4.3.1 标記清除
JavaScript中最常用的垃圾收集方式是标記清除(mark-and-sweep)。當變量進入環境(例如,在函數中聲明一個變量)時,就将這個變量标記為“進入環境”。而當變量離開環境時,則将其标記為“離開環境”。
标記政策: 可以通過翻轉某個特殊的位來記錄一個變量何時進入環境,或者使用一個“進入環境的”變量清單及一個“離開環境的”變量清單來跟蹤哪個變量發生了變化。
标記清除方式回收記憶體流程: 垃圾收集器在運作的時候會給記憶體中的所有變量打标,然後去除環境内的變量以及正在被環境内的變量引用的變量的标記,而此後再次被加上标記的變量則會被視為準備删除的變量,最後垃圾收集器完成記憶體清除工作。
4.3.2 引用計數
引用計數的含義是跟蹤記錄每個值被引用的次數。
引用計數方式回收記憶體流程: 當聲明一個變量并将一個引用類型的值指派給該變量,這個值的引用次數就加1,相反,如果這個變量又被指派另外一個引用類型的值,則減1,當引用次數為0時,垃圾收集器釋放該記憶體。
存在的問題:循環引用。對象A中包含一個指向對象B的指針,而對象B中也包含一個指向對象A的引用。
4.3.3 記憶體管理
為了提高性能,優化記憶體,将不再有用的資料值設定為null來釋放其引用——解除引用(dereferencing)。
解除一個值的引用并不意味着自動回收該值所占用的記憶體。解除引用的真正作用是讓值脫離執行環境,以便垃圾收集器下次運作時将其回收。
5、引用類型
5.1 Object
5.1.1 建立對象的兩種方式
- 使用new操作符後跟Object構造函數
- 使用對象字面量
var person = {
name : "Nicholas",
age : 29
};
使用對象字面量文法時,如果留白其花括号,則可以定義隻包含預設屬性和方法的對象,如下所示:
5.1.2 通路對象屬性的方法
- 點
- 方括号(主要優點是可以通過變量來通路屬性)
alert(person["name"]); //"Nicholas"
//方括号文法的主要優點是可以通過變量來通路屬性
var propertyName = "name";
alert(person[propertyName]); //"Nicholas"
5.2 Array
- ECMAScript數組的每一項可以儲存任何類型的資料。
- ECMAScript數組的大小是可以動态調整的,即可以随着資料的添加自動增長以容納新增資料。
5.2.1 建立Array的兩種方式
數組最多可以包含4 294 967 295個項。
- 使用Array構造函數(可以省略new操作符,建議使用這種方式)
var colors = new Array();
var colors = new Array(20);
var colors = new Array("red", "blue", "green");
- 使用數組字面量表示法
var colors = ["red", "blue", "green"]; // 建立一個包含3個字元串的數組var names = []; // 建立一個空數組
var values = [1,2,]; // 不要這樣!這樣會建立一個包含2或3項的數組
var options = [,,,,,]; // 不要這樣!這樣會建立一個包含5或6項的數組
注意:數組的length屬性不是隻讀的,可以通過設定length的值來從數組的末尾移除項或向數組中添加新項。
5.2.2 數組檢測
-
instanceof
缺陷:不同的全局執行環境,進而存在兩個以上不同版本的Array構造函數,因為其檢測的是由構造函數構造的執行個體對象,不同的全局環境構造函數可能不同。
- Array.isArray()方法
5.2.3 棧方法
ECMAScript 為數組專門提供了 push()和 pop()方法
方法 | 說明 |
---|---|
push() | 可以接收任意數量的參數,把它們逐個添加到數組末尾,并傳回修改後數組的長度。 |
pop() | 從數組末尾移除最後一項,減少數組的 length 值,然後傳回移除的項。 |
5.2.4 隊列方法
棧資料結構的通路規則是 LIFO(後進先出)
隊列資料結構的通路規則是 FIFO(First-In-First-Out,先進先出)。
隊列在清單的末端添加項,從清單的前端移除項。
方法 | 說明 |
---|---|
push() | 向數組末端添加項 |
shift() | 移除數組中的第一個項并傳回該項,同時将數組長度減 1 |
unshift() | 在數組前端添加任意個項并傳回新數組的長度 |
5.2.5 其他方法
方法 | 說明 |
---|---|
reverse() | 反轉 |
sort() | 按升序排列數組項 |
sort(compare) | 通過比較函數産生排序,需要實作一個compare函數,比較函數接收兩個參數,如果第一個參數應該位于第二個之前則傳回一個負數,如果兩個參數相等則傳回0,如果第一個參數應該位于第二個之後則傳回一個正數。function compare(value1, value2){ return value2 - value1; } |
concat() | 基于目前數組中的所有項建立一個新數組。在沒有給concat()方法傳遞參數的情況下,它隻是複制目前數組并傳回副本。如果傳遞給concat()方法的是一或多個數組,則該方法會将這些數組中的每一項都添加到結果數組中。如果傳遞的值不是數組,這些值就會被簡單地添加到結果數組的末尾。 |
slice() | 基于目前數組中的一或多個項建立一個新數組。slice()方法可以接受一或兩個參數,即要傳回項的起始和結束位置。在隻有一個參數的情況下,slice()方法傳回從該參數指定位置開始到目前數組末尾的所有項。如果有兩個參數,該方法傳回起始和結束位置之間的項——但不包括結束位置的項。注意,slice()方法不會影響原始數組。 如果slice()方法的參數中有一個負數,則用數組長度加上該數來确定相應的位置。 |
splice() | 主要用途是向數組的中部插入項,splice()方法始終都會傳回一個數組,該數組中包含從原始數組中删除的項(如果沒有删除任何項,則傳回一個空數組)。 |
indexOf() | 接收兩個參數:查找項和查找起點索引(可選),從數組開頭開始向後查找,傳回要查找的項在數組中的位置,或者在沒找到的情況下傳回-1 |
lastIndexOf() | 接收兩個參數:查找項和查找起點索引(可選),從數組末尾開始向前查找,傳回要查找的項在數組中的位置,或者在沒找到的情況下傳回-1 |
every() | 對數組中的每一項運作給定函數,如果該函數對每一項都傳回true,則傳回true。 |
filter() | 對數組中的每一項運作給定函數,傳回該函數會傳回true的項組成的數組。 |
forEach() | 對數組中的每一項運作給定函數。這個方法沒有傳回值。 |
map() | 對數組中的每一項運作給定函數,傳回每次函數調用的結果組成的數組。 |
some() | 對數組中的每一項運作給定函數,如果該函數對任一項傳回true,則傳回true。 |
reduce() | 從數組的第一項開始,逐個周遊到最後,疊代數組的所有項,然後建構一個最終傳回的值。接收兩個參數:一個在每一項上調用的函數和(可選的)作為歸并基礎的初始值。傳給reduce()和reduceRight()的函數接收4個參數:前一個值、目前值、項的索引和數組對象。這個函數傳回的任何值都會作為第一個參數自動傳給下一項。第一次疊代發生在數組的第二項上,是以第一個參數是數組的第一項,第二個參數就是數組的第二項。 |
reduceRight() | 從數組的最後一項開始,向前周遊到第一項。,疊代數組的所有項,然後建構一個最終傳回的值。接收兩個參數:一個在每一項上調用的函數和(可選的)作為歸并基礎的初始值。傳給reduce()和reduceRight()的函數接收4個參數:前一個值、目前值、項的索引和數組對象。這個函數傳回的任何值都會作為第一個參數自動傳給下一項。第一次疊代發生在數組的第二項上,是以第一個參數是數組的第一項,第二個參數就是數組的第二項。 |
splice()方法使用說明:
示例 | 說明 |
---|---|
splice(0,2) | 删除數組中的前兩項。删除: 可以删除任意數量的項,隻需指定2個參數:要删除的第一項的位置和要删除的項數。 |
splice(2,0,“red”,“green”) | 從目前數組的位置2開始插入字元串"red"和"green"。插入: 可以向指定位置插入任意數量的項,隻需提供3個參數:起始位置、0(要删除的項數)和要插入的項。如果要插入多個項,可以再傳入第四、第五,以至任意多個項。 |
splice (2,1,“red”,“green”) | 删除目前數組位置2的項,然後再從位置2開始插入字元串"red"和"green"。替換: 可以向指定位置插入任意數量的項,且同時删除任意數量的項,隻需指定3個參數:起始位置、要删除的項數和要插入的任意數量的項。插入的項數不必與删除的項數相等。 |
5.5 Function
5.5.1 沒有重載
如果出現兩個同名函數,後面的函數會覆寫前面的函數。
5.5.2 函數聲明與函數表達式
解析器在向執行環境中加載資料時,會率先讀取函數聲明,并使其在執行任何代碼之前可用(可以通路);至于函數表達式,則必須等到解析器執行到它所在的代碼才會真正被解釋執行。
函數聲明提升:對代碼求值時,JavaScript 引擎在第一遍會聲明函數并将它們放到源代碼樹的頂部。
不僅可以像傳遞參數一樣把一個函數傳遞給另一個函數,而且可以将一個函數作為另一個函數的結果傳回。
通路函數的指針而不執行函數的話,必須去掉函數名後面的那對圓括号。
5.5.4 函數内部屬性
函數内部,有兩個特殊的對象:arguments和this。
-
arguments對象有一個callee屬性,該屬性是一個指針,指向擁有這個arguments對象的函數。arguments的主要用途是儲存函數參數。
當函數在嚴格模式下運作時,通路arguments.callee會導緻錯誤。
-
另一個函數對象的屬性:caller。儲存着調用目前函數的函數的引用,如果是在全局作用域中調用目前函數,它的值為null。通過arguments.callee.caller 來通路
為了厘清arguments.caller和函數的caller屬性,定義了arguments.caller屬性。當函數在嚴格模式下運作時,通路arguments.caller會導緻錯誤,且不能為函數的caller屬性指派,否則會導緻錯誤。
5.5.5 函數屬性和方法
每個函數都包含兩個屬性:length和prototype。
- length屬性表示函數希望接收的命名參數的個數
- prototype是儲存它們所有執行個體方法的真正所在,prototype屬性是不可枚舉的,使用for-in無法發現。
每個函數都包含兩個非繼承而來的方法:apply()和call()。在特定的作用域中調用函數,實際上等于設定函數體内this對象的值。
- apply() 方法接收兩個參數:一個是在其中運作函數的作用域,另一個是參數數組。
- call() 方法,第一個參數是this值沒有變化,變化的是其餘參數都直接傳遞給函數。在使用call()方法時,傳遞給函數的參數必須逐個列舉出來,而不是使用數組
- bind() 會建立一個函數的執行個體,其this值會被綁定到傳給bind()函數的值。
apply()和call()真正強大的地方是能夠擴充函數賴以運作的作用域。使用call()(或apply())來擴充作用域的最大好處,就是對象不需要與方法有任何耦合關系。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //red
sayColor.call(this); //red
sayColor.call(window); //red
sayColor.call(o); //blue
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
5.6 基本包裝類型
引用類型與基本包裝類型的主要差別就是對象的生存期。使用new操作符建立的引用類型的執行個體,在執行流離開目前作用域之前都一直儲存在記憶體中。而自動建立的基本包裝類型的對象,則隻存在于一行代碼的執行瞬間,然後立即被銷毀。這意味着不能在運作時為基本類型值添加屬性和方法。
5.6.1 Boolean
基本類型與引用類型的布爾值還有兩個差別。首先,typeof操作符對基本類型傳回"boolean",而對引用類型傳回"object"。其次,由于Boolean對象是Boolean類型的執行個體,是以使用instanceof 操作符測試Boolean對象會傳回true,而測試基本類型的布爾值則傳回false。
5.6.2 Number
可以為toString()方法傳遞一個表示基數的參數,告訴它傳回幾進制數值的字元串形式
var num = 10;
alert(num.toString()); //"10"
alert(num.toString(2)); //"1010"
alert(num.toString(8)); //"12"
alert(num.toString(10)); //"10"
alert(num.toString(16)); //"a"
toFixed()方法會按照指定的小數位傳回數值的字元串
var num = 10;
alert(num.toFixed(2)); //"10.00"
5.6.3 String
5.7 單體内置對象
5.7.1 Global對象
5.7.1.1 URI編碼方法
對URI(Uniform Resource Identifiers,通用資源辨別符)進行編碼,以便發送給浏覽器。有效的URI中不能包含某些字元,例如空格。而這兩個URI編碼方法就可以對URI進行編碼,它們用特殊的UTF-8編碼替換所有無效的字元,進而讓浏覽器能夠接受和了解。
方法 | 說明 |
---|---|
encodeURI() | 主要用于整個URI(例如,http://www.wrox.com/illegal value.htm),不會對本身屬于URI的特殊字元進行編碼,例如冒号、正斜杠、問号和井字号; |
encodeURIComponent() | 主要用于對URI中的某一段(例如前面URI中的illegal value.htm)進行編碼。會對它發現的任何非标準字元進行編碼。 |
decodeURI() | 隻能對使用encodeURI()替換的字元進行解碼。 |
decodeURIComponent() | 能夠解碼使用encodeURIComponent()編碼,的所有字元,即它可以解碼任何特殊字元的編碼。 |
var uri = "http://www.wrox.com/illegal value.htm#start";
//"http://www.wrox.com/illegal%20value.htm#start"
alert(encodeURI(uri));
//"http%3A%2F%2Fwww.wrox.com%2Fillegal%20v
alert(encodeURIComponent(uri));alue.htm%23start"
使用encodeURI()編碼後的結果是除了空格之外的其他字元都原封不動,隻有空格被替換成了%20。而encodeURIComponent()方法則會使用對應的編碼替換所有非字母數字字元。這也正是可以對整個URI使用encodeURI(),而隻能對附加在現有URI後面的字元串使用encodeURIComponent()的原因所在。
5.7.1.2 eval()方法
eval()方法就像是一個完整的ECMAScript解析器,它隻接受一個參數,即要執行的ECMAScrip(t或JavaScript)字元串。
當解析器發現代碼中調用eval()方法時,它會将傳入的參數當作實際的ECMAScript語句來解析,然後把執行結果插入到原位置。通過eval()執行的代碼被認為是包含該次調用的執行環境的一部分,是以被執行的代碼具有與該執行環境相同的作用域鍊。這意味着通過eval()執行的代碼可以引用在包含環境中定義的變量,舉個例子:
var msg = "hello world";
eval("alert(msg)"); //"hello world"
eval("function sayHi() { alert('hi'); }");
sayHi();
嚴格模式下,在外部通路不到eval()中建立的任何變量或函數,是以前面兩個例子都會導緻錯誤。同樣,在嚴格模式下,為eval指派也會導緻錯誤:
注意:eval()中建立的任何變量或函數都不會被提升,因為在解析代碼的時候,它們被包含在一個字元串中;它們隻在eval()執行的時候建立。
能夠解釋代碼字元串的能力非常強大,但也非常危險。是以在使用eval()時必須極為謹慎,特别是在用它執行使用者輸入資料的情況下。否則,可能會有惡意使用者輸入威脅你的站點或應用程式安全的代碼(即所謂的代碼注入)。
5.7.1.4 window對象
另一種取得Global對象的方法是使用以下代碼:
var global = function(){
return this;
}();
6、面向對象的程式設計
6.1 了解對象
6.1.1 屬性類型
ECMAScript 中有兩種屬性:資料屬性和通路器屬性。
6.1.1.2 資料屬性
資料屬性包含一個資料值的位置。在這個位置可以讀取和寫入值。資料屬性有 4 個描述其行為的特性。
特性 | 說明 |
---|---|
[[Configurable]] | 表示能否通過 delete 删除屬性進而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為通路器屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性預設值為 true。 |
[[Enumerable]] | 表示能否通過 for-in 循環傳回屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性預設值為 true。 |
[[Writable]] | 表示能否修改屬性的值。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性預設值為 true。 |
[[Value]] | 包含這個屬性的資料值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值儲存在這個位置。這個特性的預設值為 undefined。 |
直接在對象上定義的屬性,它們的[[Configurable]]、[[Enumerable]]和[[Writable]]特性都被設定為 true,而[[Value]]特性被設定為指定的值。
Object.defineProperty()方法修改屬性預設的特性值。接收三個參數:屬性所在的對象、屬性的名字和一個描述符對象。其中,描述符(descriptor)對象的屬性必須是:configurable、enumerable、writable 和 value。設定其中的一或多個值,可以修改
對應的特性值。例如:
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
6.1.1.2 通路器屬性
它們包含一對兒 getter 和 setter 函數(不過,這兩個函數都不是必需的)。在讀取通路器屬性時,會調用 getter 函數,這個函數負責傳回有效的值;在寫入通路器屬性時,會調用setter 函數并傳入新值,這個函數負責決定如何處理資料。通路器屬性有如下 4 個特性。
特性 | 說明 |
---|---|
[[Configurable]] | 表示能否通過 delete 删除屬性進而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為資料屬性。對于直接在對象上定義的屬性,這個特性的預設值為true。 |
[[Enumerable]] | 表示能否通過 for-in 循環傳回屬性。對于直接在對象上定義的屬性,這個特性的預設值為 true。 |
[[Get]] | 在讀取屬性時調用的函數。預設值為 undefined。 |
[[Set]] | 在寫入屬性時調用的函數。預設值為 undefined。 |
通路器屬性不能直接定義,必須使用 Object.defineProperty()來定義。
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); //2
_year 前面
的下劃線是一種常用的記号,用于表示隻能通過對象方法通路的屬性。
6.2 建立對象
6.2.1 工廠模式
用函數來封裝以特定接口建立對象的細節,例如:
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
但卻沒有解決對象識别的問題(即怎樣知道一個對象的類型)。
6.2.2 構造函數模式
任何函數,隻要通過 new 操作符來調用,那它就可以作為構造函數;而
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
使用構造函數的主要問題,就是每個方法都要在每個執行個體上重新建立一遍。而實際上每個執行個體上的方法沒必要都重新建立。通過把函數定義轉移到構造函數外部來解決這個問題。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
新問題又來了:在全局作用域中定義的函數實際上隻能被某個對象調用,這讓全局作用域有點名不副實。而更讓人無法接受的是:如果對象需要定義很多方法,那麼就要定義很多個全局函數,于是我們這個自定義的引用類型就絲毫沒有封裝性可言了。好在,這些問題可以通過使用原型模式來解決。
6.2.3 原型模式
6.2.3.1 原型對象
每個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有執行個體共享的屬性和方法。prototype 就是通過調用構造函數而建立的那個對象執行個體的原型對象。使用原型對象的好處是可以讓所有對象執行個體共享它所包含的屬性和方法。 換句話說,不必在構造函數中定義對象執行個體的資訊,而是可以将這些資訊直接添加到原型對象中,如下面的例子所示:
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
雖然在腳本中沒有标準的方式通路[[Prototype]],但 Firefox、Safari 和 Chrome 在每個對象上都支援一個屬性__proto__;而在其他實作中,這個屬性對腳本則是完全不可見的。這個連接配接存在于執行個體與構造函數的原型對象之間,而不是存在于執行個體與構造函數之間。
當為對象執行個體添加一個屬性時,這個屬性就會屏蔽原型對象中儲存的同名屬性;使用 delete 操作符可以完全删除執行個體屬性,進而讓我們能夠重新通路原型中的屬性。
6.2.3.2 原型與 in 操作符
用于判斷給定屬性是否存在于對象中,無論該屬性存在于執行個體中還是原型中。
“屬性名” in 對象執行個體名
同時使用 hasOwnProperty()方法和 in 操作符,就可以确定該屬性到底是存在于對象中,還是存在于原型中。由于 in 操作符隻要通過對象能夠通路到屬性就傳回 true,hasOwnProperty()隻在屬性存在于執行個體中時才傳回 true,是以隻要 in 操作符傳回 true 而 hasOwnProperty()傳回 false,就可以确定屬性是原型中的屬性。
6.2.3.3. 更簡單的原型文法
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
注意:constructor 屬性不再指向 Person 了,constructor 屬性就變成了新對象的 constructor 屬性(指向 Object 構造函數),不再指向 Person 函數。
6.2.4 組合使用構造函數模式和原型模式
建立自定義類型的最常見方式,就是組合使用構造函數模式與原型模式。構造函數模式用于定義執行個體屬性,而原型模式用于定義方法和共享的屬性。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
6.2.5 動态原型模式
function Person(name, age, job){
//屬性
this.name = name;
this.age = age;
this.job = job;
//方法
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){ alert(this.name); };
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();
6.3 繼承
實作繼承主要是依靠原型鍊。
6.3.1 原型鍊
基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。讓原型對象等于另一個類型的執行個體。
原型鍊的問題(實踐中很少會單獨使用原型鍊。):
- 包含引用類型值的原型在通過原型來實作繼承時,原型屬性會被所有執行個體共享。
- 在建立子類型的執行個體時,不能向超類型的構造函數中傳遞參數。
6.3.3 組合繼承
使用原型鍊實作對原型屬性和方法的繼承,而通過借用構造函數來實作對執行個體屬性的繼承。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//繼承屬性
SuperType.call(this, name);
this.age = age;
}
//繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
7、函數表達式
定義函數的方式有兩種:一種是函數聲明,另一種就是函數表達式。
函數聲明的文法:
function functionName(arg0, arg1, arg2) {
//函數體
}
函數表達式的文法:
var functionName = function(arg0, arg1, arg2){
//函數體
};
這種情況下建立的函數叫做匿名函數(anonymous function),因為 function 關鍵字後面沒有辨別符。函數表達式在使用前必須先指派。
7.2 閉包
閉包就是函數裡的函數,能讓外部的變量通路到函數内部的變量,函數作為傳回值。閉包中的變量沒有被銷毀。
閉包是指有權通路另一個函數作用域中的變量的函數。建立閉包的常見方式,就是在一個函數内部建立另一個函數。
使用閉包建立計數器:
function count(count){
return function(){
count ++;
}
};
閉包的作用域鍊包含着它自己的作用域、包含函數的作用域和全局作用域。當函數傳回了一個閉包時,這個函數的作用域将會一直在記憶體中儲存到閉包不存在為止。
即使 JavaScript 中沒有正式的私有對象屬性的概念,但可以使用閉包來實作公有方法,而通過公有方法可以通路在包含作用域中定義的變量。
由于閉包會攜帶包含它的函數的作用域,是以會比其他函數占用更多的記憶體。過
度使用閉包可能會導緻記憶體占用過多,我們建議讀者隻在絕對必要時再考慮使用閉
包。
7.3 模仿塊級作用域
匿名函數模仿塊級作用域,定義并立即調用了一個匿名函數。将函數聲明包含在一對圓括号中,表示它實際上是一個函數表達式。而緊随其後的另一對圓括号會立即調用這個函數。
(function(){
//這裡是塊級作用域
})();
//下面這段代碼會導緻文法錯誤,是因為 JavaScript 将 function 關鍵字當作一個函數聲明的開始,而函數聲明後面不能跟圓括号。然而,函數表達式的後面可以跟圓括号。
function(){
//這裡是塊級作用域
}(); //出錯!
7.4 私有變量
7.4.2 子產品模式
JavaScript 是以對象字面量的方式來建立單例對象的。
9、用戶端檢測
9.1 能力檢測
能力檢測的目标不是識别特定的浏覽器,而是識别浏覽器的能力。采用這種方式不必顧及特定的浏覽器如何如何,隻要确定浏覽器支援特定的能力,就可以給出解決方案。
13、事件
13.1 事件流
13.1.3 DOM事件流
“DOM2級事件”規定的事件流包括三個階段:事件捕獲階段、處于目标階段和事件冒泡階段。首先發生的是事件捕獲,為截獲事件提供了機會。然後是實際的目标接收到事件。最後一個階段是冒泡階段,可以在這個階段對事件做出響應。
13.2 事件處理程式
13.2.2 DOM0 級事件處理程式
每個元素(包括 window 和 document)都有自己的事件處理程式屬性,這些屬性通常全部小寫,例如 onclick。将這種屬性的值設定為一個函數,就可以指定事件處理程式,如下所示:
var btn = document.getElementById("myBtn");
btn.onclick = function(){
alert("Clicked");
};
删除通過 DOM0 級方法指定的事件處理程式:
DOM0 級對每個事件隻支援一個事件處理程式。
13.2.3 DOM2 級事件處理程式
用于處理指定和删除事件處理程式的操作:addEventListener()和 removeEventListener()。
它們都接受 3 個參數:要處理的事件名、作為事件處理程式的函數和一個布爾值。最後這個布爾值參數如果是 true,表示在捕獲階段調用事件處理程式;如果是 false,表示在冒泡階段調用事件處理程式。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
使用 DOM2 級方法添加事件處理程式的主要好處是可以添加多個事件處理程式。
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
alert(this.id);
}, false);
btn.addEventListener("click", function(){
alert("Hello world!");
}, false);
這裡為按鈕添加了兩個事件處理程式。這兩個事件處理程式會按照添加它們的順序觸發,是以首先會顯示元素的 ID,其次會顯示"Hello world!"消息。
通過 addEventListener()添加的事件處理程式隻能使用 removeEventListener()來移除;移除時傳入的參數與添加處理程式時使用的參數相同。通過 addEventListener()添加的匿名函數将無法移除。
13.2.5 跨浏覽器的事件處理程式
第一個要建立的方法是 addHandler(),它的職責是視情況分别使用 DOM0 級方法、DOM2 級方法或 IE 方法來添加事件。這個方法屬于一個名叫 EventUtil 的對象,本書将使用這個對象來處理浏覽器間的差異。addHandler()方法接受 3 個參數:要操作的元素、事件名稱和事件處理程式函數。
移除之前添加的事件處理程式對應的方法是 removeHandler(),它也接受相同的參數。
EventUtil 的用法如下所示:
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener){ //如果存在 DOM2 級方法
element.addEventListener(type, handler, false);
} else if (element.attachEvent){ //如果存在的是 IE 的
方法
element.attachEvent("on" + type, handler); //為了在 IE8 及更早版本中運作,此時的事件類型必須加上"on"字首。
} else { //是使用 DOM0 級方法
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler){
if (element.removeEventListener){ //如果存在 DOM2 級方法
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){ //如果存在的是 IE 的
方法
element.detachEvent("on" + type, handler); //為了在 IE8 及更早版本中運作,此時的事件類型必須加上"on"字首。
} else { //是使用 DOM0 級方法
element["on" + type] = null;
}
}
};
使用 EventUtil 對象:
var btn = document.getElementById("myBtn");
var handler = function(){
alert("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
//這裡省略了其他代碼
EventUtil.removeHandler(btn, "click", handler);
13.5 記憶體和性能
13.5.1 事件委托
事件委托利用了事件冒泡,隻指定一個事件處理程式,就可以管理某一類型的所有事件。例如,click 事件會一直冒泡到 document 層次。也就是說,我們可以為整個頁面指定一個onclick 事件處理程式,而不必給每個可單擊的元素分别添加事件處理程式。
使用事件委托,隻需在DOM 樹中盡量最高的層次上添加一個事件處理程式,如下面的例子所示:
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>
var list = document.getElementById("myLinks");
EventUtil.addHandler(list, "click", function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch(target.id){
case "doSomething":
document.title = "I changed the document's title";
break;
case "goSomewhere":
location.href = "http://www.wrox.com";
break;
case "sayHi":
alert("hi");
break;
}
});
最适合采用事件委托技術的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。雖然 mouseover 和 mouseout 事件也冒泡,但要适當處理它們并不容易,而且經常需要計算元素的位置。(因為當滑鼠從一個元素移到其子節點時,或者當滑鼠移出該元素時,都會觸發 mouseout 事件。)
13.5.2 移除事件處理程式
設定< div >的 innerHTML 屬性之前,先移除按鈕的事件處理程式。
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
//先執行某些操作
btn.onclick = null; //移除事件處理程式
document.getElementById("myDiv").innerHTML = "Processing...";
};
</script>
14、表單腳本
14.1 表單基礎知識
14.1.1 送出表單
送出表單時可能出現的最大問題,就是重複送出表單。在第一次送出表單後,如果長時間沒有反應,使用者可能會變得不耐煩。解決這一問題的辦法有兩個:
- 在第一次送出表單後就禁用送出按鈕
- 利用 onsubmit 事件處理程式取消後續的表單送出操作。
15、使用Canvas繪圖
15.1 基本用法
- 建立< canvas >标簽
<canvas id="drawing" width=" 200" height="200">A drawing of something.</canvas>
-
取得繪圖上下文
在使用元素之前,首先要檢測 getContext()方法是否存在,這一步非常重要。
var drawing = document.getElementById("drawing");
//确定浏覽器支援<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
//更多代碼
}
15.2 2D 上下文
2D 上下文的坐标開始于元素的左上角,原點坐标是(0,0)。所有坐标值都基于這個原點計算,x 值越大表示越靠右,y 值越大表示越靠下。
15.2.1 填充和描邊
fillStyle 和 strokeStyle
var drawing = document.getElementById("drawing");
//确定浏覽器支援<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
context.strokeStyle = "red";
context.fillStyle = "#0000ff";
}
15.2.2 繪制矩形
矩形是唯一一種可以直接在 2D 上下文中繪制的形狀。
與矩形有關的方法包括 :
fillRect()、strokeRect()和 clearRect()。
15.2.3 繪制路徑
要繪制路徑,首先必須調用 beginPath()方法,表示要開始繪制新路徑。
方法 | 說明 |
---|---|
arc(x, y, radius, startAngle, endAngle, counterclockwise) | 以(x,y)為圓心繪制一條弧線,弧線半徑為 radius,起始和結束角度(用弧度表示)分别為 startAngle 和endAngle。最後一個參數表示 startAngle 和 endAngle 是否按逆時針方向計算,值為 false表示按順時針方向計算。 |
arcTo(x1, y1, x2, y2, radius) | 從上一點開始繪制一條弧線,到(x2,y2)為止,并且以給定的半徑 radius 穿過(x1,y1)。 bezierCurveTo(c1x, c1y, c2x, c2y, x, y):從上一點開始繪制一條曲線,到(x,y)為止,并且以(c1x,c1y)和(c2x,c2y)為控制點。 |
lineTo(x, y) | 從上一點開始繪制一條直線,到(x,y)為止。 |
moveTo(x, y) | 将繪圖遊标移動到(x,y),不畫線。 |
quadraticCurveTo(cx, cy, x, y) | 從上一點開始繪制一條二次曲線,到(x,y)為止,并且以(cx,cy)作為控制點。 |
rect(x, y, width, height) | 從點(x,y)開始繪制一個矩形,寬度和高度分别由 width 和height 指定。這個方法繪制的是矩形路徑,而不是 strokeRect()和 fillRect()所繪制的獨立的形狀。 |
closePath() | 繪制一條連接配接到路徑起點的線條 |
fill() | 用 fillStyle 填充它,先指定fillStyle |
stroke() | 對路徑描邊,先指定fillStyle |
clip() | 在路徑上建立一個剪切區域 |
15.2.4 繪制文本
fillText()和 strokeText(),都可以接收 4 個參數:要繪制的文本字元串、x 坐标、y 坐标和可選的最大像素寬度。這兩個方法都以下列 3 個屬性為基礎。
方法 | 說明 |
---|---|
font | 表示文本樣式、大小及字型,用 CSS 中指定字型的格式來指定,例如"10px Arial"。 |
textAlign | 表示文本對齊方式。可能的值有"start"、“end”、“left”、“right"和"center”。建議使用"start"和"end",不要使用"left"和"right",因為前兩者的意思更穩妥,能同時适合從左到右和從右到左顯示(閱讀)的語言。 |
textBaseline | 表示文本的基線。可能的值有"top"、“hanging”、“middle”、“alphabetic”、“ideographic"和"bottom”。 |
15.2.5 變換
如下方法來修改變換矩陣。
方法 | 說明 |
---|---|
rotate(angle) | 圍繞原點旋轉圖像 angle 弧度。 |
scale(scaleX, scaleY) | 縮放圖像,在 x 方向乘以 scaleX,在 y 方向乘以 scaleY。scaleX和 scaleY 的預設值都是 1.0。 |
translate(x, y) | 将坐标原點移動到(x,y)。執行這個變換之後,坐标(0,0)會變成之前由(x,y)表示的點。 |
setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy) | 将變換矩陣重置為預設狀态,然後再調用 transform()。 |
transform(m1_1, m1_2, m2_1, m2_2, dx, dy) | 直接修改變換矩陣,方式是乘以如下矩陣。 |
m1_1 m1_2 dx
m2_1 m2_2 dy
0 0 1
有兩個方法可以跟蹤上下文的狀态變化:
方法 | 說明 |
---|---|
save() | 把設定儲存到棧結構,save()方法儲存的隻是對繪圖上下文的設定和變換,不會儲存繪圖上下文的内容。 |
restore() | 調用 restore()則可以一級一級傳回。 |
15.2.10 使用圖像資料
通過 getImageData()取得原始圖像資料。這個方法接收4 個參數:要取得其資料的畫面區域的 x 和 y 坐标以及該區域的像素寬度和高度。例如,要取得左上角坐标為(10,5)、大小為 50×50 像素的區域的圖像資料,可以使用以下代碼:
傳回的對象是 ImageData 的執行個體。每個 ImageData 對象都有三個屬性:width、height 和
data。其中 data 屬性是一個數組,儲存着圖像中每一個像素的資料。在 data 數組中,每一個像素用4 個元素來儲存,分别表示紅、綠、藍和透明度值。
17、錯誤處理與調試
17.2 錯誤處理
17.2.1 try-catch語句
try-catch 語句,作為 JavaScript 中處理異常的一種标準方式。
try{
// 可能會導緻錯誤的代碼
} catch(error){
// 在錯誤發生時怎麼處理
}
如果 try 塊中的任何代碼發生了錯誤,就會立即退出代碼執行過程,然後接着執行 catch 塊。
在發生錯誤時,就可以像下面這樣實事求是地顯示浏覽器給出的消息。
try {
window.someNonexistentFunction();
} catch (error){
alert(error.message);
}
隻要代碼中包含 finally 子句,則無論 try 或 catch 語句塊中包含什麼代碼——甚至 return 語句,都不會阻止 finally 子句的執行。
function testFinally(){
try {
return 2;
} catch (error){
return 1;
} finally {
return 0;
}
}
//傳回 0
17.2.7 把錯誤記錄到伺服器
參考:21.5.1 圖像Ping
首先需要在伺服器上建立一個頁面(或者一個伺服器入口點),用于處理錯誤資料。這個頁面的作用無非就是從查詢字元串中取得資料,然後再将資料寫入錯誤日志中。這個頁面可能會使用如下所示的函數:
function logError(sev, msg){
var img = new Image();
img.src = "log.php?sev=" + encodeURIComponent(sev) + "&msg=" +
encodeURIComponent(msg);
}
這個 logError()函數接收兩個參數:表示嚴重程度的數值或字元串(視所用系統而異)及錯誤消息。其中,使用了 Image 對象來發送請求,這樣做非常靈活,主要表現如下幾方面:
- 所有浏覽器都支援 Image 對象,包括那些不支援 XMLHttpRequest 對象的浏覽器。
- 可以避免跨域限制。通常都是一台伺服器要負責處理多台伺服器的錯誤,而這種情況下使用XMLHttpRequest 是不行的。
- 在記錄錯誤的過程中出問題的機率比較低。大多數 Ajax 通信都是由 JavaScript 庫提供的包裝函數來處理的,如果庫代碼本身有問題,而你還在依賴該庫記錄錯誤,可想而知,錯誤消息是不可能得到記錄的。
隻要是使用 try-catch 語句,就應該把相應錯誤記錄到日志中。來看下面的例子:
for (var i=0, len=mods.length; i < len; i++){
try {
mods[i].init();
} catch (ex){
logError("nonfatal", "Module init failed: " + ex.message);
}
}
17.3 調試技術
17.3.3 抛出錯誤
這個簡單的函數計算兩個數的除法,但如果有一個參數不是數值,它會傳回 NaN。類似這樣簡單的計算如果傳回 NaN,就會在 Web 應用程式中導緻問題。對此,可以在計算之前,先檢測每個參數是否都是數值。例如:
function divide(num1, num2){
if (typeof num1 != "number" || typeof num2 != "number"){
throw new Error("divide(): Both arguments must be numbers.");
}
return num1 / num2;
}
對于大型應用程式來說,自定義的錯誤通常都使用 assert()函數抛出。這個函數接受兩個參數,一個是求值結果應該為 true 的條件,另一個是條件為 false 時要抛出的錯誤。以下就是一個非常基本的 assert()函數。
function assert(condition, message){
if (!condition){
throw new Error(message);
}
}
可以用這個 assert()函數代替某些函數中需要調試的 if 語句,以便輸出錯誤消息。下面是使用
這個函數的例子:
function divide(num1, num2){
assert(typeof num1 == "number" && typeof num2 == "number",
"divide(): Both arguments must be numbers.");
return num1 / num2;
}
20、JSON
20.1 文法
JSON 的文法可以表示以下三種類型的值:
- 簡單值:可以在 JSON 中表示字元串、數值、布爾值和 null。 但 JSON 不支援 JavaScript 中的特殊值 undefined。
- 對象:對象作為一種複雜資料類型,表示的是一組無序的鍵值對兒。而每個鍵值對兒中的值可以是簡單值,也可以是複雜資料類型的值。
- 數組:數組也是一種複雜資料類型,表示一組有序的值的清單,可以通過數值索引來通路其中的值。數組的值也可以是任意類型——簡單值、對象或數組。
20.1.1 簡單值
JavaScript 字元串與 JSON 字元串的最大差別在于,JSON 字元串必須使用雙引号(單引号會導緻文法錯誤)。
布爾值和 null 也是有效的 JSON 形式。
20.1.2 對象
JSON 中的對象與 JavaScript 字面量稍微有一些不同。
JavaScript 中建立對象字面量的标準方式,但 JSON 中的對象要求給屬性加引号。(對象字面量也可以給屬性加引号)
對象字面量:
var person = {
name: "Nicholas",
age: 29
};
JSON 表示上述對象的方式如下:
{
"name": "Nicholas",
"age": 29
}
JSON 對象 JavaScript 的對象字面量差別:
- 沒有聲明變量(JSON 中沒有變量的概念)
- 沒有末尾的分号(因為這不是 JavaScript 語句,是以不需要分号)
手工編寫 JSON 時,忘了給對象屬性名加雙引号或者把雙引号寫成單引号都是常見的錯誤。
20.1.3 數組
JSON 數組采用的就是 JavaScript 中的數組字面量形式。對象和數組通常是 JSON 資料結構的最外層形式。
[
{
"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
}
]
20.2 解析與序列化
20.2.1 JSON對象
JSON 對象有兩個方法:
方法 | 說明 |
---|---|
stringify() | 把JavaScript 對象序列化為 JSON 字元串 |
parse() | 和把 JSON 字元串解析為原生 JavaScript 值 |
在序列化 JavaScript 對象時,所有函數及原型成員都會被有意忽略,不展現在結果中。此外,值為undefined 的任何屬性也都會被跳過。結果中最終都是值為有效 JSON 資料類型的執行個體屬性。
21、Ajax與Comet
21.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.");
}
}
建立 XHR 對象:
21.1.1 XHR的用法
- 調用open(),它接受 3 個參數:要發送的請求的類型(“get”、"post"等)、請求的 URL 和表示是否異步發送請求的布爾值。需要說明兩點:一是 URL相對于執行代碼的目前頁面(當然也可以使用絕對路徑);二是調用 open()方法并不會真正發送請求,而隻是啟動一個請求以備發送。
- 要發送特定的請求,必須像下面這樣調用 send()方法,send()方法接收一個參數,即要作為請求主體發送的資料。如果不需要通過請求主體發送資料,則必須傳入 null,因為這個參數對有些浏覽器來說是必需的。調用 send()之後,請求就會被分派到伺服器。
xhr.open("get", "example.txt", false);
xhr.send(null);
由于這次請求是同步的,JavaScript 代碼會等到伺服器響應之後再繼續執行。在收到響應後,響應的資料會自動填充 XHR 對象的屬性,相關的屬性簡介如下。
屬性 | 說明 |
---|---|
responseText | 作為響應主體被傳回的文本。 |
responseXML | 如果響應的内容類型是"text/xml"或"application/xml",這個屬性中将儲存包含着響應資料的 XML DOM 文檔。 |
status | 響應的 HTTP 狀态。 |
statusText | HTTP 狀态的說明。 |
多數情況下,我們還是要發送異步請求,才能讓JavaScript 繼續執行而不必等待響應。此時,可以檢測 XHR 對象的 readyState 屬性,該屬性表示請求/響應過程的目前活動階段。這個屬性可取的值如下:
取值 | 說明 |
---|---|
未初始化。尚未調用 open()方法。 | |
1 | 啟動。已經調用 open()方法,但尚未調用 send()方法。 |
2 | 發送。已經調用 send()方法,但尚未接收到響應。 |
3 | 接收。已經接收到部分響應資料。 |
4 | 完成。已經接收到全部響應資料,而且已經可以在用戶端使用了。 |
隻要 readyState 屬性的值由一個值變成另一個值,都會觸發一次 readystatechange 事件。
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);
21.1.4 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));
}
這個函數可以将 ID 為"user-info"的表單中的資料序列化之後發送給伺服器。而下面的示例 PHP檔案 postexample.php 就可以通過$_POST 取得送出的資料了:
<?php
header("Content-Type: text/plain");
echo <<<EOF
Name: {$_POST[‘user-name’]}
Email: {$_POST[‘user-email’]}
EOF;
?>
21.4 跨源資源共享
CORS(Cross-Origin Resource Sharing,跨源資源共享)實作方法:
一個簡單的使用 GET 或 POST 發送的請求,發送該請求時,需要給它附加一個額外的 Origin 頭部,其中包含請求頁面的源資訊(協定、域名和端口),以便伺服器根據這個頭部資訊來決定是否給予響應。下面是 Origin 頭部的一個示例:
Origin: http://www.nczonline.net
如果伺服器認為這個請求可以接受,就在 Access-Control-Allow-Origin 頭部中回發相同的源
資訊(如果是公共資源,可以回發"*")。例如:
Access-Control-Allow-Origin: http://www.nczonline.net
如果沒有這個頭部,或者有這個頭部但源資訊不比對,浏覽器就會駁回請求。正常情況下,浏覽器會處理請求。注意,請求和響應都不包含 cookie 資訊。
由于無論同源請求還是跨源請求都使用相同的接口,是以對于本地資源,最好使用相對 URL,在通路遠端資源時再使用絕對 URL。這樣做能消除歧義,避免出現限制通路頭部或本地 cookie 資訊等問題。
21.5 其他跨域技術
21.5.1 圖像Ping
第一種跨域請求技術是使用标簽。我們知道,一個網頁可以從任何網頁中加載圖像,不用擔心跨域不跨域。圖像 Ping 是與伺服器進行簡單、單向的跨域通信的一種方式。請求的資料是通過查詢字元串形式發送的,而響應可以是任意内容,但通常是像素圖或 204 響應。通過圖像 Ping,浏覽器得不到任何具體的資料,但通過偵聽 load 和 error 事件,它能知道響應是什麼時候接收到的。
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
這裡建立了一個 Image 的執行個體,然後将 onload 和 onerror 事件處理程式指定為同一個函數。這樣無論是什麼響應,隻要請求完成,就能得到通知。請求從設定 src 屬性那一刻開始,而這個例子在請求中發送了一個 name 參數。
圖像 Ping 有兩個主要的缺點:
- 一是隻能發送 GET 請求,
- 二是無法通路伺服器的響應文本。
是以,圖像 Ping 隻能用于浏覽器與伺服器間的單向通信。
21.5.3 Comet
Ajax 是一種從頁面向伺服器請求資料的技術,而 Comet 則是一種伺服器向頁面推送資料的技
術。
有兩種實作 Comet 的方式:長輪詢和流。長輪詢是傳統輪詢(也稱為短輪詢)的一個翻版,即浏覽器定時向伺服器發送請求,看有沒有更新的資料。
- 長輪詢把短輪詢颠倒了一下。頁面發起一個到伺服器的請求,然後伺服器一直保持連接配接打開,直到有資料可發送。發送完資料之後,浏覽器關閉連接配接,随即又發起一個到伺服器的新請求。
- 第二種流行的 Comet 實作是 HTTP 流,它在頁面的整個生命周期内隻使用一個 HTTP 連接配接。浏覽器向伺服器發送一個請求,而伺服器保持連接配接打開,然後周期性地向浏覽器發送資料。
Firefox、Safari、Opera 和 Chrome 中,通過偵聽 readystatechange 事件及檢測 readyState
的值是否為 3,就可以利用 XHR 對象實作 HTTP 流。随着不斷從伺服器接收資料,eadyState 的值會周期性地變為 3。當 readyState 值變為 3 時,responseText 屬性中就會儲存接收到的所有資料。此時,就需要比較此前接收到的資料,決定從什麼位置開始取得最新的資料。
使用 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、在接收到資料時調用的函
數以及關閉連接配接時調用的函數。
21.5.5 Web Sockets
在 JavaScript 中建立了 Web Socket 之後,會有一個 HTTP 請求發送到浏覽器以發起連接配接。在取得伺服器響應後,建立的連接配接會使用 HTTP 更新從 HTTP 協定交換為 Web Socket 協定。
由于 Web Sockets 使用了自定義的協定,是以 URL 模式也略有不同。未加密的連接配接不再是 http://,而是 ws://;加密的連接配接也不是 https://,而是 wss://。
好處:能夠在用戶端和伺服器之間發送非常少量的資料,而不必擔心 HTTP 那樣位元組級的開銷。
問題:存在一緻性和安全性的問題。
21.7 安全
為確定通過 XHR 通路的 URL 安全,通行的做法就是驗證發送請求者是否有權限通路相應的資源。
- 要求以 SSL 連接配接來通路可以通過 XHR 請求的資源。
- 要求每一次請求都要附帶經過相應算法計算得到的驗證碼。
請注意,下列措施對防範 CSRF 攻擊不起作用。
- 要求發送 POST 而不是 GET 請求——很容易改變。
- 檢查來源 URL 以确定是否可信——來源記錄很容易僞造。
- 基于 cookie 資訊進行驗證——同樣很容易僞造。