[TOC]
ECMAScript 是 JavaScript 的核心,但如果要在 Web 中使用 JavaScript,那么 BOM(瀏覽器對象模型)則無疑才是真正的核心。BOM 提供了很多對象,用于訪問瀏覽器的功能,這些功能與任何網頁內容無關。多年來,缺少事實上的規范導致 BOM 有很多問題,因為瀏覽器提供商會按照各自的想法隨意去擴展它。W3C 為了把瀏覽器中 JavaScript 最基本的部分標準化,已經將 BOM 的主要方面納入了 HTML5 的規范中。
## window 對象
BOM 的核心對象是 window,它表示瀏覽器的一個實例。在瀏覽器中,window 對象有雙重角色,它既是通過 JavaScript 訪問瀏覽器窗口的一個接口,又是 ECMAScript 規定的 Global 對象。這意味著在網頁中定義的任何一個對象、變量和函數,都以 window 作為其 Global 對象,因此有權訪問 isNaN()、isFinite()、parseInt()、parseFloat() 等方法。
### 全局作用域
由于 window 對象同時扮演著 ECMAScript 中 Global 對象的角色,因此所有在全局作用域中聲明的變量、函數都會變成 window 對象的屬性和方法。來看下面的例子。
```
var age = 29;
function sayAge(){
console.log(this.age);
}
console.log(window.age); // 29
sayAge(); // 29
window.sayAge(); // 29
```
拋開全局變量會成為 window 對象的屬性不談,定義全局變量與在 window 對象上直接定義屬性還是有一點差別:全局變量不能通過 delete 運算符刪除,而直接在 window 對象上的定義的屬性可以。例如:
```
var age = 29;
window.color = "red";
// 在 IE < 9 時拋出錯誤,在其他所有瀏覽器中都返回 false
delete window.age;
// 在 IE < 9 時拋出錯誤,在其他所有瀏覽器中都返回 true
delete window.color; // return true
console.log(window.age); // 29
console.log(window.color); // undefined
```
使用 var 語句添加的 window 屬性有一個名為 Configurable 的特性,這個特性的值被默認設置為 false,因此這樣定義的屬性不可以通過 delete 運算符刪除。IE8 及更早版本在遇到使用 delete 刪除 window 屬性的語句時,不管該屬性最初是如何創建的,都會拋出錯誤,以示警告。IE9 及更高版本不會拋出錯誤。
另外,還要記住一件事:嘗試訪問未聲明的變量會拋出錯誤,但是通過查詢 window 對象,可以知道某個可能未聲明的變量是否存在。例如:
```
// 這里會拋出錯誤,因為 oldValue 未定義
var newValue = oldValue;
// 這里不會拋出錯誤,因為這是一次屬性查詢
// newValue 的值是 undefined
var newValue = window.oldValue;
```
### 窗口關系及框架
如果頁面中包含框架,則每個框架都擁有自己的 window 對象,并且保存在 frames 集合中。在 frames 集合中,可以通過數值索引(從0開始,從左至右,從上到下)或者框架名稱來訪問相應的 window 對象。每個 window 對象都有一個 name 屬性,其中包含框架的名稱。下面是一個包含框架的頁面:
```
<html>
<head>
<title>Frameset Example</title>
</head>
<frameset rows="160,*">
<frame src="frame.htm" name="topFrame">
<frameset cols="50%,50%">
<frame src="anotherframe.htm" name="leftFrame">
<frame src="yetanotherframe.htm" name="rightFrame">
</frameset>
</frameset>
</html>
```
對這個例子而言,可以通過 window.frames[0] 或者 window.frames["topFrame"] 來引用上方的框架。不過最好使用 top 而非 window 來引用這些框架(例如 top.frames[0]),因為 top 對象始終指向最高(最外)層的框架,也就是瀏覽器窗口。使用它可以確保在一個框架中正確地訪問另一個框架。因為對于在一個框架中編寫的任何代碼來說,其中的 window 對象指向的都是那個框架的特定實例,而非最高層的框架。
與 top 相對的另一個 window 對象是 parent。顧名思義,parent(父)對象始終指向當前框架的直接上層框架。在某些情況下,parent 有可能等于 top;但在沒有框架的情況下,parent 一定等于 top(此時它們都等于 window)。
與框架有關的最后一個對象是 self,它始終指向 window;實際上,self 和 window 對象可以互換使用。引入 self 對象的目的只是為了與 top 和 parent 對象對應起來,因此它不格外包含其他值。
所有這些對象都是 window 對象的屬性,可以通過 window.parent、window.top 等形式來訪問。同時,這也意味著可以將不同層次的 window 對象連綴起來,例如 window.parent.parent.frames[0]。
在使用框架的情況下,瀏覽器中會存在多個 Global 對象。在每個框架中定義的全局變量會自動成為框架中 window 對象的屬性。由于每個 window 對象都包含原生類型的構造函數,因此每個框架都有一套自己的構造函數,這些構造函數一一對應,但并不相等。例如,top.Object 并不等于 top.frames[0].Object。這個問題會影響到對跨框架傳遞的對象使用 instanceof 運算符。
### 導航和打開窗口
使用 window.open() 方法既可以導航到一個特定的 URL,也可以打開一個新的瀏覽器窗口。這個方法可以接收4個參數:要加載的URL、窗口目標、一個特性字符串以及一個表示新頁面是否取代瀏覽器歷史記錄中當前加載頁面的布爾值。通常只須傳遞第一個參數,最后一個參數只在不打開新窗口的情況下使用。
如果為 window.open() 傳遞了第二個參數,而且該參數是已有窗口或框架的名稱,那么就會在具有該名稱的窗口或框架中加載第一個參數指定的 URL。看下面的例子。
```
// 等同于 <a href="http://shijiajie.com" target="newWindow"></a>
window.open("http://shijiajie.com/", "newWindow");
```
#### 彈出窗口
如果給 window.open() 傳遞的第二個參數并不是一個已經存在的窗口或框架,那么該方法就會根據在第三個參數位置上傳入的字符串創建一個新窗口或新標簽頁。如果沒有傳入第三個參數,那么就會打開一個帶有全部默認設置(工具欄、地址欄和狀態欄等)的新瀏覽器窗口(或者打開一個新標簽頁)。在不打開新窗口的情況下,會忽略第三個參數。
第三個參數是一個逗號分隔的設置字符串,表示在新窗口中都顯示哪些特性。下表列出了可以出現在這個字符串中的設置選項。
| 設置 | 值 | 說明 |
| --- | --- | --- |
|fullscreen |yes或no |表示瀏覽器窗口是否最大化。僅限IE |
|height |數值 |表示新窗口的高度。不能小于100 |
|left |數值 | 表示新窗口的左坐標。不能是負值 |
|location |yes或no |表示是否在瀏覽器窗口中顯示地址欄。不同瀏覽器的默認值不同。如果設置為no,地址欄可能會隱藏,也可能會被禁用(取決于瀏覽器) |
|menubar |yes或no |表示是否在瀏覽器窗口中顯示菜單欄。默認值為no |
|resizable | yes或no |表示是否可以通過拖動瀏覽器窗口的邊框改變其大小。默認值為no |
|scrollbars |yes或no |表示如果內容在視口中顯示不下,是否允許滾動。默認值為no |
|status | yes或no |表示是否在瀏覽器窗口中顯示狀態欄。默認值為no |
|toolbar | yes或no |表示是否在瀏覽器窗口中顯示工具欄。默認值為no |
|top |數值 | 表示新窗口的上坐標。不能是負值 |
|width |數值 | 表示新窗口的寬度。不能小于100 |
這行代碼會打開一個新的可以調整大小的窗口,窗口初始大小為400×400像素,并且距屏幕上沿和左邊各10像素。
```
window.open("http://shijiajie.com/","newWindow","height=400,width=400,top=10,left=10,resizable=yes");
```
window.open() 方法會返回一個指向新窗口的引用。引用的對象與其他 window 對象大致相似,但我們可以對其進行更多控制。例如,有些瀏覽器在默認情況下可能不允許我們針對主瀏覽器窗口調整大小或移動位置,但卻允許我們針對通過window.open()創建的窗口調整大小或移動位置。通過這個返回的對象,可以像操作其他窗口一樣操作新打開的窗口,如下所示。
```
var win = window.open("http://shijiajie.com/","newWindow","height=400,width=400,top=10,left=10,resizable=yes");
// 調整大小
win.resizeTo(500,500);
// 移動位置
win.moveTo(100,100);
// 關閉窗口
win.close();
```
但是,close() 方法僅適用于通過 window.open() 打開的彈出窗口。對于瀏覽器的主窗口,如果沒有得到用戶的允許是不能關閉它的。
新創建的 window 對象有一個 opener 屬性,其中保存著打開它的原始窗口對象。這個屬性只在彈出窗口中的最外層 window 對象(top)中有定義,而且指向調用 window.open() 的窗口或框架。例如:
```
var win = window.open("http://shijiajie.com/","newWindow","height=400,width=400,top=10,left=10,resizable=yes");
console.log(win.opener === window); // true
```
雖然彈出窗口中有一個指針指向打開它的原始窗口,但原始窗口中并沒有這樣的指針指向彈出窗口。窗口并不跟蹤記錄它們打開的彈出窗口,因此我們只能在必要的時候自己來手動實現跟蹤。
#### 彈出窗口屏蔽程序
曾經有一段時間,廣告商在網上使用彈出窗口達到了肆無忌憚的程度。他們經常把彈出窗口打扮成系統對話框的模樣,引誘用戶去點擊其中的廣告。由于看起來像是系統對話框,一般用戶很難分辨是真是假。為了解決這個問題,大多數瀏覽器內置有彈出窗口屏蔽程序,將絕大多數用戶不想看到彈出窗口屏蔽掉。
于是,在彈出窗口被屏蔽時,就應該考慮兩種可能性。如果是瀏覽器內置的屏蔽程序阻止的彈出窗口,那么 window.open() 很可能會返回 null,如果是瀏覽器擴展或其他程序阻止的彈出窗口,那么 window.open() 通常會拋出一個錯誤。因此,要想準確地檢測出彈出窗口是否被屏蔽,必須在檢測返回值的同時,將對 window.open() 的調用封裝在一個 try-catch 塊中,如下所示。
```
var blocked = false;
try {
var win = window.open("http://shijiajie.com", "_blank");
if (win == null){
blocked = true;
}
} catch (ex){
blocked = true;
}
if (blocked){
console.log("The popup was blocked!");
}
```
### 間歇調用和超時調用
JavaScript 是單線程語言,但它允許通過設置超時值和間歇時間值來調度代碼在特定的時刻執行。前者是在指定的時間過后執行代碼,而后者則是每隔指定的時間就執行一次代碼。
超時調用需要使用 window 對象的 setTimeout() 方法,它接受兩個參數:要執行的代碼和以毫秒表示的時間(即在執行代碼前需要等待多少毫秒)。其中,第一個參數可以是一個包含 JavaScript 代碼的字符串(就和在 eval() 函數中使用的字符串一樣),也可以是一個函數。例如,下面對 setTimeout() 的兩次調用都會在一秒鐘后顯示一個警告框。
```
// 不建議傳遞字符串
setTimeout("console.log('Hello world!') ", 1000);
// 推薦的調用方式
setTimeout(function() {
console.log("Hello world!");
}, 1000);
```
雖然這兩種調用方式都沒有問題,但由于傳遞字符串可能導致性能損失,因此不建議以字符串作為第一個參數。
第二個參數是一個表示等待多長時間的毫秒數,但經過該時間后指定的代碼不一定會執行。JavaScript 是一個單線程序的解釋器,因此一定時間內只能執行一段代碼。為了控制要執行的代碼,就有一個 JavaScript 任務隊列。這些任務會按照將它們添加到隊列的順序執行。setTimeout() 的第二個參數告訴 JavaScript 再過多長時間把當前任務添加到隊列中。如果隊列是空的,那么添加的代碼會立即執行;如果隊列不是空的,那么它就要等前面的代碼執行完了以后再執行。
調用 setTimeout() 之后,該方法會返回一個數值 ID,表示超時調用。這個超時調用 ID 是計劃執行代碼的唯一標識符,可以通過它來取消超時調用。要取消尚未執行的超時調用計劃,可以調用 clearTimeout() 方法并將相應的超時調用 ID 作為參數傳遞給它,如下所示。
```
// 設置超時調用
var timeoutId = setTimeout(function() {
console.log("Hello world!");
}, 1000);
// 注意:把它取消
clearTimeout(timeoutId);
```
只要是在指定的時間尚未過去之前調用 clearTimeout(),就可以完全取消超時調用。前面的代碼在設置超時調用之后馬上又調用了 clearTimeout(),結果就跟什么也沒有發生一樣。
間歇調用與超時調用類似,只不過它會按照指定的時間間隔重復執行代碼,直至間歇調用被取消或者頁面被卸載。設置間歇調用的方法是 setInterval(),它接受的參數與 setTimeout() 相同:要執行的代碼(字符串或函數)和每次執行之前需要等待的毫秒數。下面來看一個例子。
```
// 不建議傳遞字符串
setInterval ("console.log('Hello world!') ", 10000);
// 推薦的調用方式
setInterval (function() {
console.log("Hello world!");
}, 10000);
```
調用 setInterval() 方法同樣也會返回一個間歇調用 ID,該 ID 可用于在將來某個時刻取消間歇調用。要取消尚未執行的間歇調用,可以使用 clearInterval() 方法并傳入相應的間歇調用 ID。取消間歇調用的重要性要遠遠高于取消超時調用,因為在不加干涉的情況下,間歇調用將會一直執行到頁面卸載。以下是一個常見的使用間歇調用的例子。
```
var num = 0;
var max = 10;
var intervalId = null;
function incrementNumber() {
num++;
// 如果執行次數達到了max設定的值,則取消后續尚未執行的調用
if (num == max) {
clearInterval(intervalId);
console.log("Done");
}
}
intervalId = setInterval(incrementNumber, 500);
```
在這個例子中,變量num每半秒鐘遞增一次,當遞增到最大值時就會取消先前設定的間歇調用。這個模式也可以使用超時調用來實現,如下所示。
```
var num = 0;
var max = 10;
function incrementNumber() {
num++;
// 如果執行次數未達到max設定的值,則設置另一次超時調用
if (num < max) {
setTimeout(incrementNumber, 500);
} else {
console.log("Done");
}
}
setTimeout(incrementNumber, 500);
```
可見,在使用超時調用時,沒有必要跟蹤超時調用 ID,因為每次執行代碼之后,如果不再設置另一次超時調用,調用就會自行停止。一般認為,使用超時調用來模擬間歇調用的是一種最佳模式。在開發環境下,很少使用真正的間歇調用,原因是后一個間歇調用可能會在前一個間歇調用結束之前啟動。而像前面示例中那樣使用超時調用,則完全可以避免這一點。所以,最好不要使用間歇調用。
### 系統對話框
瀏覽器通過 alert()、confirm() 和 prompt() 方法可以調用系統對話框向用戶顯示消息。系統對話框與在瀏覽器中顯示的網頁沒有關系,也不包含 HTML。它們的外觀由操作系統及(或)瀏覽器設置決定,而不是由 CSS 決定。此外,通過這幾個方法打開的對話框都是同步和模態的。也就是說,顯示這些對話框的時候代碼會停止執行,而關掉這些對話框后代碼又會恢復執行。
第一種對話框是調用 alert() 方法生成的。它向用戶顯示一個系統對話框,其中包含指定的文本和一個 OK(“確定”)按鈕。通常使用 alert() 生成的“警告”對話框向用戶顯示一些他們無法控制的消息,例如錯誤消息。而用戶只能在看完消息后關閉對話框。
第二種對話框是調用 confirm() 方法生成的。從向用戶顯示消息的方面來看,這種“確認”對話框很像是一個“警告”對話框。但二者的主要區別在于“確認”對話框除了顯示OK按鈕外,還會顯示一個 Cancel(“取消”)按鈕,兩個按鈕可以讓用戶決定是否執行給定的操作。
為了確定用戶是單擊了OK還是Cancel,可以檢查 confirm() 方法返回的布爾值:true 表示單擊了OK,false 表示單擊了Cancel或單擊了右上角的 X 按鈕。確認對話框的典型用法如下。
```
if (confirm("Are you sure?")) {
alert("I'm so glad you're sure! ");
} else {
alert("I'm sorry to hear you're not sure.");
}
```
最后一種對話框是通過調用 prompt() 方法生成的,這是一個“提示”框,用于提示用戶輸入一些文本。提示框中除了顯示 OK 和 Cancel 按鈕之外,還會顯示一個文本輸入域,以供用戶在其中輸入內容。prompt() 方法接受兩個參數:要顯示給用戶的文本提示和文本輸入域的默認值(可以是一個空字符串)。
如果用戶單擊了 OK 按鈕,則 promp() 返回文本輸入域的值;如果用戶單擊了 Cancel 或沒有單擊 OK 而是通過其他方式關閉了對話框,則該方法返回 null。下面是一個例子。
```
var result = prompt("What is your name? ", "");
if (result !== null) {
alert("Welcome, " + result);
}
```
綜上所述,這些系統對話框很適合向用戶顯示消息并請用戶作出決定。由于不涉及 HTML、CSS 或 JavaScript,因此它們是增強 Web 應用程序的一種便捷方式。
## location 對象
location 對象提供了與當前窗口中加載的文檔有關的信息,還提供了一些導航功能。事實上,location 對象是很特別的一個對象,因為它既是 window 對象的屬性,也是 document 對象的屬性;換句話說,window.location 和 document.location 引用的是同一個對象。location 對象的用處不只表現在它保存著當前文檔的信息,還表現在它將 URL 解析為獨立的片段,讓開發人員可以通過不同的屬性訪問這些片段。下表列出了 location 對象的所有屬性。
| 屬性名 | 例子 | 說明 |
| --- | --- | --- |
|hash |"#contents" |返回 URL 中的 hash(#號后跟零或多個字符),如果 URL 中不包含散列,則返回空字符串|
|host |"shijiajie.com:80" |返回服務器名稱和端口號(如果有)|
|hostname |"shijiajie.com" |返回不帶端口號的服務器名稱|
|href |"http:/shijiajie.com" |返回當前加載頁面的完整URL。而 location 對象的 toString() 方法也返回這個值|
|pathname |"/WileyCDA/" |返回URL中的目錄和(或)文件名|
|port| "8080" |返回 URL 中指定的端口號。如果 URL 中不包含端口號,則這個屬性返回空字符串|
|protocol| "http:" |返回頁面使用的協議。通常是 http: 或 https:|
|search |"?q=javascript" |返回URL的查詢字符串。這個字符串以問號開頭||
### 查詢字符串參數
雖然通過上面的屬性可以訪問到 location 對象的大多數信息,但其中訪問URL包含的查詢字符串的屬性并不方便。盡管 location.search 返回從問號到 URL 末尾的所有內容,但卻沒有辦法逐個訪問其中的每個查詢字符串參數。為此,可以像下面這樣創建一個函數,用以解析查詢字符串,然后返回包含所有參數的一個對象:
```
/*
* 這個函數用來解析來自URL的查詢串中的name=value參數對
* 它將name=value對存儲在一個對象的屬性中,并返回該對象
* 這樣來使用它
*
* var args = urlArgs(); // 從URL中解析參數
* var q = args.q || ""; // 如果參數定義了的話就使用參數;否則使用一個默認值
* var n = args.n ? parseInt(args.n) : 10;
*/
function urlArgs() {
var args = {}; // 定義一個空對象
var query = location.search.substring(1); // 查找到查詢串,并去掉'? '
var pairs = query.split("&"); // 根據"&"符號將查詢字符串分隔開
for (var i = 0; i < pairs.length; i++) { // 對于每個片段
var pos = pairs[i].indexOf('='); // 查找"name=value"
if (pos == -1) continue; // 如果沒有找到的話,就跳過
var name = pairs[i].substring(0, pos); // 提取name
var value = pairs[i].substring(pos + 1); // 提取value
value = decodeURIComponent(value); // 對value進行解碼
args[name] = value; // 存儲為屬性
}
return args; // 返回解析后的參數
}
```
### 位置操作
使用 location 對象可以通過很多方式來改變瀏覽器的位置。首先,也是最常用的方式,就是使用 assign()方法并為其傳遞一個 URL,如下所示。
```
location.assign("http://shijiajie.com");
```
這樣,就可以立即打開新URL并在瀏覽器的歷史記錄中生成一條記錄。如果是將 location.href 或 window.location 設置為一個URL值,也會以該值調用 assign() 方法。例如,下列兩行代碼與顯式調用 assign() 方法的效果完全一樣。
```
window.location = "http://shijiajie.com";
location.href = "http://shijiajie.com";
```
在這些改變瀏覽器位置的方法中,最常用的是設置 location.href 屬性。
另外,修改 location 對象的其他屬性也可以改變當前加載的頁面。下面的例子展示了通過將 hash、search、hostname、pathname 和 port 屬性設置為新值來改變 URL。
```
// 假設初始 URL 為 http://shijiajie.com/about/
location.href = "http://shijiajie.com/about/"
// 將 URL 修改為 "http://shijiajie.com/about/#ds-thread"
location.hash = "#ds-thread";
// 將 URL 修改為 "http://shijiajie.com/about/?args=123"
location.search = "?args=123";
// 將 URL 修改為 "https://segmentfault.com/"
location.hostname = "segmentfault.com";
// 將 URL 修改為 "http://segmentfault.com/u/stone0090/"
location.pathname = "u/stone0090";
// 將 URL 修改為 "https://segmentfault.com:8080/"
location.port = 8080;
```
當通過上述任何一種方式修改URL之后,瀏覽器的歷史記錄中就會生成一條新記錄,因此用戶通過單擊“后退”按鈕都會導航到前一個頁面。要禁用這種行為,可以使用 replace() 方法。這個方法只接受一個參數,即要導航到的 URL;結果雖然會導致瀏覽器位置改變,但不會在歷史記錄中生成新記錄。在調用 replace() 方法之后,用戶不能回到前一個頁面,來看下面的例子:
```
<!DOCTYPE html>
<html>
<head>
<title>You won't be able to get back here</title>
</head>
<body>
<p>Enjoy this page for a second, because you won't be coming back here.</p>
<script type="text/javascript">
setTimeout(function () {
location.replace("http://shijiajie.com/");
}, 1000);
</script>
</body>
</html>
```
如果將這個頁面加載到瀏覽器中,瀏覽器就會在1秒鐘后重新定向到 shijiajie.com。然后,“后退”按鈕將處于禁用狀態,如果不重新輸入完整的 URL,則無法返回示例頁面。
與位置有關的最后一個方法是 reload(),作用是重新加載當前顯示的頁面。如果調用 reload() 時不傳遞任何參數,頁面就會以最有效的方式重新加載。也就是說,如果頁面自上次請求以來并沒有改變過,頁面就會從瀏覽器緩存中重新加載。如果要強制從服務器重新加載,則需要像下面這樣為該方法傳遞參數 true。
```
location.reload(); // 重新加載(有可能從緩存中加載)
location.reload(true); // 重新加載(從服務器重新加載)
```
位于 reload() 調用之后的代碼可能會也可能不會執行,這要取決于網絡延遲或系統資源等因素。為此,最好將 reload() 放在代碼的最后一行。
## history 對象
history 對象保存著用戶上網的歷史記錄,從窗口被打開的那一刻算起。因為 history 是 window 對象的屬性,因此每個瀏覽器窗口、每個標簽頁乃至每個框架,都有自己的 history 對象與特定的 window 對象關聯。出于安全方面的考慮,開發人員無法得知用戶瀏覽過的 URL。不過,借由用戶訪問過的頁面列表,同樣可以在不知道實際 URL 的情況下實現后退和前進。
使用 go() 方法可以在用戶的歷史記錄中任意跳轉,可以向后也可以向前。這個方法接受一個參數,表示向后或向前跳轉的頁面數的一個整數值。負數表示向后跳轉(類似于單擊瀏覽器的“后退”按鈕),正數表示向前跳轉(類似于單擊瀏覽器的“前進”按鈕)。來看下面的例子。
```
// 后退一頁
history.go(-1);
// 前進一頁
history.go(1);
// 前進兩頁
history.go(2);
```
也可以給 go() 方法傳遞一個字符串參數,此時瀏覽器會跳轉到歷史記錄中包含該字符串的第一個位置——可能后退,也可能前進,具體要看哪個位置最近。如果歷史記錄中不包含該字符串,那么這個方法什么也不做,例如:
```
// 跳轉到最近的 shijiajie.com 頁面
history.go("shijiajie.com");
另外,還可以使用兩個簡寫方法 back() 和 forward() 來代替 go()。顧名思義,這兩個方法可以模仿瀏覽器的“后退”和“前進”按鈕。
// 后退一頁
history.back();
// 前進一頁
history.forward();
```
除了上述幾個方法外,history 對象還有一個 length 屬性,保存著歷史記錄的數量。這個數量包括所有歷史記錄,即所有向后和向前的記錄。對于加載到窗口、標簽頁或框架中的第一個頁面而言,history.length 等于0。通過像下面這樣測試該屬性的值,可以確定用戶是否一開始就打開了你的頁面。
```
if (history.length == 0){
//這應該是用戶打開窗口后的第一個頁面
}
```
雖然 history 并不常用,但在創建自定義的“后退”和“前進”按鈕,以及檢測當前頁面是不是用戶歷史記錄中的第一個頁面時,還是必須使用它。
## 小結
BOM(瀏覽器對象模型)以 window 對象為依托,表示瀏覽器窗口以及頁面可見區域。同時,window 對象還是 ECMAScript 中的 Global 對象,因而所有全局變量和函數都是它的屬性,且所有原生的構造函數及其他函數也都存在于它的命名空間下。本章討論了下列 BOM 的組成部分。
在使用框架時,每個框架都有自己的 window 對象以及所有原生構造函數及其他函數的副本。每個框架都保存在 frames 集合中,可以通過位置或通過名稱來訪問。
有一些窗口指針,可以用來引用其他框架,包括父框架。
top 對象始終指向最外圍的框架,也就是整個瀏覽器窗口。
parent 對象表示包含當前框架的框架,而 self 對象則回指 window。
使用 location 對象可以通過編程方式來訪問瀏覽器的導航系統。設置相應的屬性,可以逐段或整體性地修改瀏覽器的 URL。
調用 replace() 方法可以導航到一個新 URL,同時該 URL 會替換瀏覽器歷史記錄中當前顯示的頁面。
navigator 對象提供了與瀏覽器有關的信息。到底提供哪些信息,很大程度上取決于用戶的瀏覽器;不過,也有一些公共的屬性(如 userAgent)存在于所有瀏覽器中。
BOM中還有兩個對象:screen 和 history,但它們的功能有限。screen 對象中保存著與客戶端顯示器有關的信息,這些信息一般只用于站點分析。history 對象為訪問瀏覽器的歷史記錄開了一個小縫隙,開發人員可以據此判斷歷史記錄的數量,也可以在歷史記錄中向后或向前導航到任意頁面。
## 關卡
```
// 挑戰一
setTimeout(function () {
console.log("1");
}, 0)
console.log("2"); // ???
// 挑戰二
for (var i = 0;i<5;i++) {
setTimeout(function () {
console.log(i); // ???
}, 0)
};
// 挑戰三
var a = 1;
var obj = {
a : 2,
b : function(){
setTimeout(function () {
console.log(this.a);
}, 0)
}
}
obj.b(); // ???
// 挑戰四
var a = 1;
var obj = {
a : 2,
b : function(){
setTimeout(function () {
console.log(this.a);
}.call(this), 0);
}
}
obj.b(); // ???
```