《JavaScript 闖關記》之 BOM

劼哥stone發表於2019-02-27

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;實際上,selfwindow 物件可以互換使用。引入 self 物件的目的只是為了與 topparent 物件對應起來,因此它不格外包含其他值。

所有這些物件都是 window 物件的屬性,可以通過 window.parentwindow.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.locationdocument.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.hrefwindow.location 設定為一個URL值,也會以該值呼叫 assign() 方法。例如,下列兩行程式碼與顯式呼叫 assign() 方法的效果完全一樣。

window.location = "http://shijiajie.com";
location.href = "http://shijiajie.com";複製程式碼

在這些改變瀏覽器位置的方法中,最常用的是設定 location.href 屬性。

另外,修改 location 物件的其他屬性也可以改變當前載入的頁面。下面的例子展示了通過將 hashsearchhostnamepathnameport 屬性設定為新值來改變 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 物件儲存著使用者上網的歷史記錄,從視窗被開啟的那一刻算起。因為 historywindow 物件的屬性,因此每個瀏覽器視窗、每個標籤頁乃至每個框架,都有自己的 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中還有兩個物件:screenhistory,但它們的功能有限。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();    // ???複製程式碼

更多

關注微信公眾號「劼哥舍」回覆「答案」,獲取關卡詳解。
關注 github.com/stone0090/j…,獲取最新動態。

相關文章