JavaScript基礎——瀏覽器物件模型(BOM)
簡介
ECMAScript是JavaScript的核心,但如果要在Web中使用JavaScript,那麼BOM(瀏覽器物件模型)則無疑才是真正的核心。BOM提供了很多物件,用於訪問瀏覽器的功能,這些功能與任何網頁內容無關。多年來,缺少事實上的規範導致BOM及有意思又有問題,因為瀏覽器提供商會按照各自的想法隨意去擴充套件它。於是,瀏覽器之間公有的物件就成為了事實上的標準。這些物件在瀏覽器中得以存在,很大程度上是由於它們提供了與瀏覽器的互操作性。W3C為了把瀏覽器中JavaScript最基本的部分標準化,已經將BOM的主要方面乃如了HTML5的規範中。
window物件
BOM的核心物件是window,它表示瀏覽器的一個例項,在瀏覽器中,window物件有雙重角色,它既是通過JavaScript訪問瀏覽器視窗的一個介面,有時ECMAScript規定的Global物件。這意味著在網頁中定義的任何一個物件、變數和函式,都以window作為其Global物件,因此有權訪問parseInt()等方法。
全域性作用域
由於window物件同時扮演著ECMAScript中Global物件的角色,因此所有在全域性作用域中宣告的變數、函式都會變成window物件的屬性和方法。來看下面的例子。
var age = 29;
functionsayAge(){
alert(this.age);
}
alert(window.age);//29
sayAge();//29
window.sayAge();//29
我們在全域性作用域中定義了一個變數age和一個函式sayAge(),它們被自動歸在了window物件名下。於是,可以通過window.age訪問變數age,也可以通過window.sayAge()訪問函式sayAge()。由於sayAge()存在於全域性作用域中,因此this.age被對映到window.age,最終顯示的仍然是正確的結果。
拋開全域性變數會成為window物件的屬性不談,定義全域性變數與在window物件上直接定義屬性還是有一點差別:全域性變數不能通過delete操作符刪除,而直接在window物件上的定義的屬性可以。例如:
var age = 29;
window.color= "red";
//在IE<9時丟擲錯誤,在其他瀏覽器都返回false
alert(delete window.age);
//在IE<9時丟擲錯誤,在其他瀏覽器都返回true
alert(delete window.color);
alert(window.age);
alert(window.color);
剛才使用var語句新增的window屬性有一個名為[[Configurable]]的特性,這個特性的值被設定為false,因此這樣定義的屬性不可以通過delete操作符刪除。IE8及更早版本在遇到使用delete刪除window屬性的語句時,不管該屬性最初是如何建立的,都會丟擲錯誤,以示警告。IE9及更高版本不會丟擲錯誤。
另外,還要記住一件事:嘗試訪問未宣告的變數會丟擲錯誤,但是通過查詢window物件,可以知道某個可能未宣告的變數是否存在。例如:
//這裡會丟擲錯誤,因為oldValue未定義
var newValue =oldValue;
//這裡不會丟擲錯誤,因為這是一次屬性查詢
var newValue = window.oldValue;
視窗關係及框架
如果頁面中包含框架,則每個框架都擁有自己的window物件,並且儲存在frames集合中。在frames集合中,可以通過數值索引(從0開始,從左至右,從上到下)或者框架名稱來訪問相應的window物件。每個window物件都有一個name屬性,其中包含框架的名稱。下面是一個包含框架的頁面:
<html>
<head>
<meta charset="UTF-8">
<title>FrameSet Example</title>
</head>
<frameset rows="160,*">
<frame src="frame.html" name="topFrame"></frame>
<frameset cols="50%,50%">
<frame src="anotherframe.html" name="leftFrame">
<frame src="yetanotherframe.html" 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)。如:
<html>
<head>
<meta charset="UTF-8">
<title>FrameSet Example</title>
</head>
<frameset rows="160,*">
<frame src="frame.html" name="topFrame"></frame>
<frameset cols="50%,50%">
<frame src="anotherframe.html" name="leftFrame">
<frame src="anotherframeset.html" name="RightFrame">
</frameset>
</frameset>
</html>
這個框架集中的一個框架包含了另一個框架集,該框架集的程式碼如下所示。
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<frameset cols="50%,50%">
<frame src="red.html" name="rdFrame">
<frame src="blue.html" name="blueFrame">
</frameset>
</html>
瀏覽器在載入完第一個框架集以後,會繼續將第二個框架載入到rightFrame中。如果程式碼位於redFrame(或blueFrame)中,那麼parent物件指向的就是rightFrame。可是,如果程式碼位於topFrame中,parent則指向的是top,因為topFrame的直接上層框架就是最外層框架。
注意,除非最高層視窗是通過window.open()開啟的,否則其window物件的name屬性不會包含任何值。
與框架有關的最後一個物件是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物件位置的屬性和方法有很多。IE、Safari、Opera和Chrome都提供了screenLeft和screenTop屬性,分別用於表示礦口相對於螢幕左邊和上邊的位置。Firefox則在screenX和screenY屬性中提供相同的視窗位置資訊,Safari和Chrome也同時支援這兩個屬性。Opera雖然也支援screenX和screeY屬性,但與screenLeft和screenTop屬性並不對應,因此建議不要在Opera中使用它們。使用下面程式碼可以跨瀏覽器取得左邊和上邊的位置。
var leftPos = (typeof window.screenLeft == "number")?
window.screenLeft:window.screenX;
var topPos = (typeof window.screenLeft == "number")?
window.screenTop:window.screenY;
這個例子運用二元操作符首先確定screenLeft和screenTop屬性是否存在,如果是(在IE、Safari、Opera和Chrome中),則取得這兩個屬性的值。如果不存在(在Firefox中),則取得screenX和screenY的值。
在使用這些值的過程中,還必須注意一些小問題。在IE、Opera和Chrome中,screenLeft和screenTop中儲存的是從螢幕左邊和上邊由window物件表示的頁面可見區域的距離。換句話說,如果window物件是最外層物件,而且瀏覽器視窗緊貼螢幕最上端——即y軸座標為0,那麼screenTop的值就是位於頁面可見區域上方的瀏覽器工具欄的畫素高度。但是,在Firefox和Safari中,screenX或screenTop中儲存的是整個瀏覽器視窗相對於螢幕的座標值,即在視窗的y軸座標為0時返回0。
更讓人捉摸不透的是,Firefox、Safari和Chrome始終返回頁面中每個框架的top.screenX和top.screenY值。即使在頁面由於被設定了外編劇而發生便宜的情況下,相對於window物件使用screenX和screenY每次也都會返回相同的值。而IE和Opera則會給出框架相對於螢幕邊界的精確座標值。
最終結果,就是無法在跨瀏覽器的條件下取得視窗左邊和上邊的精確座標值。然而使用moveTo()和moveBy()方法倒是有可能將視窗精確地移動到一個新位置。這兩個方法都接收兩個引數,其中moveTo()接收的是新位置的x和y座標值,而moveBy()接收的是在水平和垂直方向上移動的畫素數。如:
//將視窗移動到螢幕左上角
window.moveTo(0, 0);
//將視窗向下移動100畫素
window.moveBy(0, 100);
//將視窗移動到(200,300)
window.moveTo(200, 300);
//將視窗向左移動50畫素
window.moveBy(-50, 0)
需要注意的是,這兩個方法可能會被瀏覽器禁用;而且,在Opera和IE7(及更高版本)中預設就是禁用的。另外,這兩個方法都不適用於框架,只能對最外層的window物件使用。
視窗大小
跨瀏覽器確定一個視窗的大小不是一件簡單的事。IE9+、Firefox、Safari、Opera和Chrome均為此提供了4個屬性:innerWidth、innerHeight、outerWidth和outerHeight。在IE9+、Safari和Firefox中,outerWidth和outerHeight返回瀏覽器視窗本身的尺寸(無論是從最外層的window物件還是從某個框架訪問)。在Opera中,這兩個屬性的值表示頁面內檢視容器的大小。而innerWidth和innerHeight則表示該容器中頁面檢視區的大小(減去邊框寬度)。在Chrome中,outerWidth、outerHeight與innerWidth、innerHeight返回相同的值,即視口大小而非瀏覽器視窗大小。
IE8及更早版本沒有提供取得當前瀏覽器視窗尺寸的屬性,不過,它通過DOM提供了頁面可見區域的相關資訊。
在IE、Firefox、Safari、Opera和Chrome中,document.documentElement.clientWidth和document.documentElement. clientHeight中儲存了頁面視口的資訊。在IE6中,這些屬性必須在標準模式下才有效;如果是混雜模式,就必須通過document.body.clientWidth和document.body.clientHeight取得相同資訊。而對於混雜模式下的Chrome,則無論通過document.documentElement還是document.body中的clientWidth和clientHeight屬性,都可以獲得視口的大小。
雖然最終無法確定瀏覽器視窗本身的大小,但卻可以取得視口的大小,如:
var pageWidth = window.innerWidth,
pageHeight = window.innerHeight;
if(typeof pageWidth != "number"){
if(document.compatMode == "SCC1Compat"){
pageWidth =document.documentElement.clientWidth;
pageHeight =document.documentElement.clientHeight;
}else{
pageWidth = document.body.clientWidth;
pageHeight = document.body.clientHeight;
}
}
在以上程式碼中,首先將window.innerWidth和window.innerHeight的值分別賦給了pageWidth和pageHeight。然後檢查pageWidth中儲存的是不是一個數值;如果不是,則通過檢查document.compatMode來確定頁面是否處於標準模式。如果是,則分別使用document.documentElement.clientWidth和document.documentElement.clientHeight的值。否則,就使用document.body.clientWidth和document.body.clientHeight的值。
對於移動裝置,window.innerWidth和window.innerHeight儲存著可見視口,也就是螢幕上可見頁面區域的大小。移動IE瀏覽器不支援這些屬性,但通過document.documentElement.clientWidth和document.documentElement.clientHeight提供了相同的資訊。隨著頁面的縮放,這些值也會相應變化。
在其他移動瀏覽器中,document.documentElement度量是佈局視口,即渲染後頁面的實際大小(與視口不同,可見視口只是整個頁面中的一小部分)。移動IE瀏覽器把佈局視口的資訊儲存在document.body.clientWidth和document.body.clientHeight中。這些值不會隨著頁面的縮放變化。
由於與桌面瀏覽器間存在這些差異,最好是先檢測一下使用者是否早使用移動裝置,然後再決定使用哪個屬性。
另外使用resizeTo()和resizeBy()方法可以調整瀏覽器視窗的大小。這兩個方法都接收兩個引數,其中resizeTo()接收瀏覽器視窗的新寬度和新高度,而resizeBy()接收新視窗與原視窗的寬度和高度之差,如:
//調整到100x100
window.resizeTo(100,100);
//調整到200x150
window.resizeBy(100,50);
//調整到300x300
window.resizeTo(300, 300);
需要注意的是,這兩個方法與移動視窗位置的方法類似,也有可能被瀏覽器禁用;而且,在Opera和IE7(及更高版本)中預設就是禁用的。另外,這兩個方法同樣不適用於框架,而只能對最外層的window物件使用。
導航和開啟視窗
使用window.open()方法既可以導航到一個特定的URL,也可以開啟一個新的瀏覽器視窗。這個方法可以接收4個引數:要載入的URL、視窗目標、一個特性字串以及一個表示新頁面是否取代瀏覽器歷史記錄中當前載入頁面的布林值。通常只須傳遞第一個引數,最後一個引數只在不開啟新視窗的情況下使用。
如果為window.open()傳遞了第二個引數,而且該引數是已有視窗或框架的名稱,那麼就會在具有該名稱的視窗或框架中載入第一個引數指定的URL。如:
//等同於<a href = "http://blog.csdn.net/goskalrie"target="topFrame"></a>
window.open("http://blog.csdn.net/goskalrie" , "topFrame");
呼叫這行程式碼,就如同使用者點選了href屬性為http://blog.csdn.net/goskalrie,target屬性為“topFrame”的連線。如果有一個名叫“topFrame”的視窗或框架,就會在該視窗或框架中載入這個URL;否則,就會建立一個新視窗並將其命名為“topFrame”。此外,第二個引數也可以是下列任何一個特殊的視窗名稱:_self、_parent、_top或_blank。
彈出視窗
如果給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 |
表中所列的全部分或全部設定選項,都可以通過逗號分隔的明智隊列表來指定。其中,名值對以等號表示(注意,整個特性字串中不允許出現空格),如下面的例子所示:
window.open("http://blog.csdn.net/goskalrie","topFrame",
"height=400,width=400,top=10,left=10,resizable=yes");
這行程式碼會開啟一個新的可以調整大小的視窗,視窗初始大小為400x400畫素,並且距螢幕上沿和左邊各10畫素。
window.open()方法會返回一個指向新視窗的引用。引用的物件與其他window物件大致相似,但可以對其進行更多控制。例如,有些瀏覽器在預設情況下可能不允許我們針對主瀏覽器視窗調整大小或移動位置,但卻允許我們針對通過window.open()建立的視窗調整大小或移動位置。通過這個返回的物件,可以像操作其他視窗一樣操作新開啟的視窗,如:
var goser =window.open("http://blog.csdn.net/goskalrie" , "topFrame" , "height=400,width=400,top=10,left=10,resizable=yes");
//調整大小
goser.resizeTo(500,500);
//移動位置
goser.moveTo(100, 100);
呼叫close()方法還可以關閉新開啟的視窗。
goser.close();
但是,這個方法僅適用於通過window.open()開啟的彈出視窗。對於瀏覽器的主視窗,如果沒有得到使用者允許的情況下是不能關閉它的。不過,彈出視窗倒是可以呼叫top.close()在不經過使用者允許的情況下關閉自己。彈出視窗關閉之後,視窗的引用仍然還在,但除了像下面這樣檢測其closed屬性之外,已經沒有其他用處了。
goser.close();
alert(goser.closed);//true
新建立的window物件有一個opener屬性,其中儲存著開啟它的原始視窗物件。這個屬性只在彈出視窗的最外層window物件(top)中有定義,而且指向呼叫window,open()的視窗或框架。如:
var goser =window.open("http://blog.csdn.net/goskalrie" ,"topFrame" , "height=400,width=400,top=10,left=10,resizable=yes");
alert(goser.opener== window);//true
雖然彈出視窗有一個指向開啟它的原始視窗,但原始視窗中並沒有這樣的指標指向彈出視窗。視窗並不跟蹤記錄它們開啟的彈出視窗,因此只能在必要的時候自己來手動實現跟蹤。
有些瀏覽器(如IE8和Chrome)會在獨立的程序中執行每個標籤頁。當一個標籤頁開啟另一個標籤頁時,如果兩個window物件之間需要彼此通訊,那麼新標籤頁就不能執行在獨立的程序中。在Chrome中,將新建立的標籤頁的opener屬性設定為null,即表示在單獨的程序中執行新標籤頁,如:
var goser =window.open("http://blog.csdn.net/goskalrie" ,"topFrame" , "height=400,width=400,top=10,left=10,resizable=yes");
goser.opener = null;
將opener屬性設定為null就是告訴瀏覽器新建立的標籤頁不需要與開啟它的標籤頁通訊,因此可以在獨立的程序中執行。標籤頁之間的聯絡一旦切斷,將沒辦法恢復。
安全限制
曾經有一段時間,廣告商在網頁上使用彈出視窗達到了肆無忌憚的程度。他們經常把彈出視窗打扮成系統對話方塊的模樣,引誘使用者去點選其中的廣告。由於看起來像是系統對話方塊,一般使用者很難分辨是真是假,為了解決這個問題,有些瀏覽器開始在彈出視窗配置方面增加限制。
Windows XP SP2中的IE6對彈出視窗施加了多方面的安全限制,包括不允許在螢幕之外建立彈出視窗、不允許將彈出視窗移動到螢幕以外、不允許關閉狀態列等。IE7則增加了更多的安全限制,如不允許關閉位址列、預設情況下允許移動彈出視窗或調整其大小。Firefox1從一開始就不支援修改狀態列,因此無論給window.open()傳入什麼樣的特性字串,彈出視窗中都會無一例外地顯示狀態列。後來的Firefox3又強制始終在彈窗視窗中顯示位址列。Opera只會在祝瀏覽器視窗中開啟彈出視窗,但不允許它們同時出現在可能與系統對話方塊混淆的地方。
此外,有的瀏覽器只根據使用者操作來彈出視窗,這樣一來,在頁面尚未載入完成時呼叫window.open()的語句根本不會執行,而且還可能會將錯誤訊息顯示給使用者。換句話說,只能通過單擊或者擊鍵來開啟彈出視窗。
對於那些不是使用者右移開啟的彈出視窗,Chrome採取了不同的處理方式。它不會像其他瀏覽器那樣簡單地遮蔽這些彈出視窗,而是隻顯示它們的標題欄,並把它們放在瀏覽器視窗的右下角。
在開啟計算機硬碟中的網頁時,IE會解除對彈出視窗的某些限制。但是在伺服器上執行這些程式碼會受到對彈出視窗的限制。
彈出視窗遮蔽程式
大多數瀏覽器都內建有彈出視窗遮蔽程式,而沒有內建此類程式的瀏覽器,也可以安裝Yahoo!ToolBar等帶有內建遮蔽程式的實用工具。結果就是使用者可以將絕大多數不想看到彈出視窗遮蔽掉。於是,在彈出視窗被遮蔽時,就應該考慮兩種可能性。如果是瀏覽器內建的遮蔽程式阻止的彈出視窗,那麼window.open()很可能會返回null。此時,只要檢測這個返回的值就可以確定彈出視窗是否被遮蔽了,如:
var goser =window.open("http://blog.csdn.net/goskalrie" , "_blank");
if(goser == null){
alert("The popup was blocked!");
}
如果是瀏覽器擴充套件或其他程式阻止的彈出框,那麼window.open()通常會丟擲一個錯誤。因此,要想準確地檢測出彈出視窗是否被遮蔽,必須在檢測返回值的同時,將對window.open()的呼叫封裝在一個try-catch塊中,如:
var blocked = false;
try{
var goser = window.open("http://blog.csdn.net/goskalrie" , "_blank");
if(goser == null){
blocked = true;
}
}catch(ex){
blocked = true;
}
if(blocked){
alert("The popup was blocked!");
}
在任何情況下,以上程式碼都可以檢測出呼叫window.open()開啟的彈出視窗是不是被遮蔽了。但要注意的是,檢測彈出視窗是否被遮蔽只是一方面,它並不會阻止瀏覽器顯示與被遮蔽的彈出視窗有關的訊息。
間歇呼叫和超時呼叫
JavaScript是單執行緒語言,但它允許通過設定超時值和間歇值來排程程式碼在特定的時刻執行。前者是在指定的時間過後執行程式碼,而後者則是每隔指定的時間就執行一次程式碼。
超時呼叫需要使用window物件的setTimeout()方法,它接受兩個引數:要執行的程式碼和以毫秒錶示的時間(即在執行程式碼前需要等待多少毫秒)。其中,第一個引數可以是一個包含JavaScript程式碼的字串(就和在eval()函式中使用的字串一樣),也可以是一個函式。例如,下面對setTimeout()的兩次呼叫都會在一秒鐘後顯示一個警告框。
//不建議傳遞字串
setTimeout("alert('Hello World!')" , 1000);
//推薦的呼叫方法
setTimeout(function(){
alert("Hello World!");
} , 1000);
雖然這兩種呼叫方式都沒有問題,但由於傳遞字串可能導致效能損失,因此不建議以字串作為第一個引數。
第二個引數是一個表示等待多長時間的毫秒數,但經過該時間後指定的程式碼不一定會執行。JavaScript是一個單執行緒的直譯器,因此一定時間內只能執行一段程式碼。為了控制要執行的程式碼,就有一個JavaScript任務佇列。這些任務會按照將它們新增到佇列的順序執行。setTimeout()的第二個引數告訴JavaScript再過多長時間把當前任務新增到佇列中。如果佇列是空的,那麼新增的程式碼會立即執行;如果佇列不是空的,那麼它就要等前面的程式碼執行完了以後再執行。
呼叫setTimeout()之後,該方法會返回一個數值ID,表示超時呼叫。這個超時呼叫ID是計劃執行程式碼的唯一識別符號,可以通過它來取消超時呼叫。要取消尚未執行的超時呼叫計劃,可以呼叫clearTimeout()方法並將相應的超時呼叫ID作為引數傳遞給它,如:
//設定超時呼叫
var timeoutId =setTimeout(function(){
alert("Hello World!");
} ,1000);
//注意:把它取消
clearTimeout(timeoutId);
只要是在指定的時間尚未過去之前呼叫clearTimeout(),就可以完全取消超時呼叫。前面的程式碼在設定超時呼叫之後馬上又呼叫了clearTimeout(),結果就跟什麼也沒發生一樣。
超時呼叫的程式碼都是在全域性作用域中執行的,因此函式中this的值在非嚴格模式下指向window物件,在嚴格模式下是undefined。
間歇呼叫與超時呼叫類似,只不過它會按照指定的時間間隔重複執行程式碼,直至間歇呼叫被取消或者頁面被解除安裝。設定間歇呼叫的方法是setInterval(),它接受的引數與setTimeout()相同:要執行的程式碼(字串或函式)和每次執行之前需要等待的毫秒數,如:
setInterval(function(){
document.getElementById("nowTime").innerHTML = new Date().toLocaleString();
},500);
上面程式碼簡單的在頁面中顯示了一個動態的時鐘,與setTimeout()方法一樣,不建議setInterval()的第一個引數為字串,且setInterval()方法同樣也會返回一個間歇呼叫Id,該Id可用於在將來某個時刻取消間歇呼叫。要取消尚未執行的呼叫,可以使用clearInterval()方法並傳入相應的間歇呼叫Id。取消間歇呼叫的重要性要遠遠高於取消超時呼叫,因為在不加干涉的情況下,間歇呼叫將會一直執行到頁面解除安裝。如:
functiongetCheckNo(){
var seconds = 121;
var s = 60;
var intervalId = setInterval(function(){
var disabled = true;
s = Math.floor((seconds--)/2);
document.getElementById("btnGetCheckNo").innerHTML="( " + s +" )" + "秒後重新發送";
if(seconds <= 0){
disabled = false;
document.getElementById("btnGetCheckNo").innerHTML="獲取驗證碼";
}
if(disabled){
document.getElementById("btnGetCheckNo").setAttribute("disabled", "disabled");
}else{
document.getElementById("btnGetCheckNo").removeAttribute("disabled");
}
} , 500);
}<body>
<div id="content">
<input type="text"/>
<button id="btnGetCheckNo"onclick="getCheckNo()">獲取驗證碼</button>
</div>
</body>
上面的例子模擬了大多數註冊頁面中的傳送驗證碼的功能,點選“獲取驗證碼”後,倒計時將一直進行,這樣是不符合要求的,這就需要在適合的時機將間歇呼叫給停掉,如下面的程式碼:
functiongetCheckNo(){
var seconds = 121;
var s = 60;
var intervalId = setInterval(function(){
var disabled = true;
s = Math.floor((seconds--)/2);
document.getElementById("btnGetCheckNo").innerHTML="( " + s +" )" + "秒後重新發送";
if(seconds <= 0){
disabled = false;
clearInterval(intervalId);
document.getElementById("btnGetCheckNo").innerHTML="獲取驗證碼";
}
if(disabled){
document.getElementById("btnGetCheckNo").setAttribute("disabled", "disabled");
}else{
document.getElementById("btnGetCheckNo").removeAttribute("disabled");
}
} , 500);
}
上面的例子中,程式碼行#10,在倒計時到0秒的時候停止間歇呼叫。一般認為,使用超時呼叫來模擬間歇呼叫是一種最佳模式。在開發環境下,很少使用真正的間歇呼叫,原因是後一個間歇呼叫可能會在前一個間歇呼叫結束之前開啟。可以將上面例子中禁用按鈕的程式碼去掉,開啟頁面後多次連續點選“獲取驗證碼”按鈕,很明顯的看出上面描述的間歇呼叫的缺點。但是,像下面這樣使用超時呼叫,則完全可以避免這一點,所以,最好不要使用間歇呼叫。
var seconds = 121;
var s = 60;
functioncount(){
s = Math.floor((seconds--)/2);
document.getElementById("btnGetCheckNo").innerHTML="( " + s +" )" + "秒後重新發送";
if(seconds <= 0){
document.getElementById("btnGetCheckNo").innerHTML="獲取驗證碼";
document.getElementById("btnGetCheckNo").removeAttribute("disabled");
}else{
setTimeout(count, 500);
}
}
functiongetCheckNo(){
document.getElementById("btnGetCheckNo").setAttribute("disabled", "disabled");
setTimeout( count, 500);
}
系統對話方塊
瀏覽器通過alert()、confirm()和prompt()方法可以呼叫系統對話方塊向用戶顯示訊息。系統對話方塊與瀏覽器中顯示的網頁沒有關係,也不包含HTML。它們的外觀由作業系統及(或)瀏覽器設定決定,而不是由CSS決定。此外,通過這幾個方法開啟的對話方塊都是同步和模態的。也就是說,顯示這些對話方塊的時候程式碼會停止執行,而關掉這些對話方塊後代碼會恢復執行。
經常使用的是alert()方法,這個方法接受一個字串 並將其顯示給使用者。具體來說,呼叫alert()方法的結果就是向用戶顯示一個系統對話方塊,其中包含指定的文字和一個OK(“確定”)按鈕。
confirm()方法生成的對話方塊包含兩個按鈕Ok(“確定”)和Cancel(“取消”),該方法返回一個布林值,使用者點選Ok時返回true,點選Cancel或直接關閉對話方塊返回false:
var flag =confirm("Are Usure?");
if(flag){
alert("U click Ok.");
}else{
alert("U click cancel or close.");
}
prompt()也會彈出一個對話方塊,用於使用者輸入一些文字。對話方塊中除了顯示確定和取消鍵之外,還會顯示一個文字輸入域,以供使用者在其中輸入內容。prompt()方法接受兩個引數:要顯示給使用者的文字提示和文字輸入域的預設值(可以是一個空字串)。如果使用者點選了確定按鈕,則prompt()返回文字輸入域的值;如果使用者點選了取消按鈕或直接關閉對話方塊則返回null,如:
var result =prompt("What'syour name?" , "");
if(result != null){
alert("Welcome " + result);
}
綜上,這些系統對話方塊很適合向用戶顯示訊息並請使用者做出決定。由於不涉及HTML、CSS或JavaScript,因此它們是增強Web應用程式的一種便捷方式。
除了上述三種對話方塊外,GoogleChrome瀏覽器還引用了一種新特性。如果當前指令碼在執行過程中會開啟兩個或多個對話方塊,那麼從第二個對話方塊開始,每個對話方塊中都會顯示一個複選框,以便使用者阻止後續的對話方塊顯示,除非使用者重新整理頁面。
還有兩個可以on各國JavaScript開啟的對話方塊,即“查詢”和“列印”。這兩個對話方塊都是非同步顯示的,能夠將控制權立即交還給指令碼。這兩個對話方塊與使用者通過瀏覽器選單的“查詢”和“列印”命令開啟的對話方塊相同。而在JavaScript中可以像下面這樣通過window物件的find()和print()方法開啟它們:
//顯示“列印”對話方塊
window.print();
//顯示“查詢”對話方塊
window.find();
這兩個方法同樣不會就使用者在對話方塊中的操作給出任何資訊,因此它們的用處有限。另外,既然這兩個對話方塊是非同步顯示的,那麼Chrome的對話方塊計數器就不會將它們計算在內,所以它們也不會受使用者禁用後續對話方塊顯示的影響。
location物件
location是最有用的BOM物件之一,它提供了與當前視窗中載入的文件有關的資訊,還提供了一些導航功能。事實上,location物件是很特別的一個物件,因為它既是window物件的屬性,也是document物件的屬性;換句話說,window.location和document.location引用的是同一個物件。location物件的用處不知表現在它儲存著當前文件的資訊,還表現在它將URL解析為獨立的片段,讓開發人員可以通過不同的屬性訪問這些片段。下表列出了location物件的所有屬性(注:省略了每個屬性前面的location字首)。
屬性名 |
例子 |
說明 |
hash |
“#contents” |
返回URL中的hash(#號後跟零或多個字元),如果URL中不包含雜湊,則返回空字串。 |
host |
“www.wrox.com:80” |
返回伺服器名稱和埠號(如果有) |
hostname |
“www.wrox.com” |
返回不帶埠號的伺服器名稱 |
href |
“http:/www.wrox.com” |
返回當前載入頁面的完整URL。而location物件的toString()方法也返回這個值 |
pathname |
“”/WileyCDA/ |
返回URL中的目錄和(或)檔名 |
port |
“8080” |
返回URL中指定的埠號。如果URL中不包含埠號,則這個屬性返回空字串 |
protocol |
“http:” |
返回頁面使用的協議。通常是“http”或“https” |
search |
“?q=javascript” |
返回URL的查詢字串。這個字串以問號開頭 |
查詢字串引數
雖然通過上面的屬性可以訪問到location物件的大多數資訊,但其中訪問URL包含的查詢字串的屬性並不方便。儘管location.search返回從問號到URL末尾的所有內容,但卻沒有辦法逐個訪問其中的每個查詢字串引數。為此,可以像下面這樣建立一個函式,用以解析查詢字串,然後返回包含所有引數的一個物件:
functiongetQueryStringArgs(){
//取得查詢字串並去掉開頭的問號
var qs = (location.search.length > 0?location.search.substring(1):""),
//儲存資料的物件
args = {},
//取得每一項
items = qs.length?qs.split("&"):[],
item = null,
name = null,
value = null,
//在for迴圈中使用
i = 0,
len = items.length;
//這個將每一項新增到args物件中
for(i=0; i<len; i++){
item = items[i].split("=");
name = decodeURIComponent(item[0]);
value = decodeURIComponent(item[1]);
if(name.length){
args[name] = value;
}
}
return args;
}
位置操作
使用location物件可以通過很多方式來改變瀏覽器的位置。首先,也是最常用的方式,就是使用assign()方法併為其傳遞一個URL,如:
location.assign("http://blog.csdn.net/goskalrie");
這樣,就可以立即開啟新URL並在瀏覽器的歷史記錄中生成一條記錄。如果是將location.href或window.location設定為一個URL值,也會以該值呼叫assign()方法。例如,下列兩行程式碼與顯示呼叫assign()方法的效果完全一樣:
window.location = "http://blog.csdn.net/goskalrie";
location.href = "http://blog.csdn.net/goskalrie";
在這些改變瀏覽器位置的方法中,最常用的是設定location.href屬性。
另外,修改location物件的其他屬性也可以改變當前載入的頁面。下面的例子展示了通過將hash、search、hostname、pathname和port屬性設定為新值來改變URL。
//假設初始URL為 http://my.csdn.net/
//將URL修改為 http://my.csdn.net/#start
location.hash= "#start";
//將URL修改為 http://my.csdn.net/?q=javascript#start
location.search = "?q=javascript";
//將URL修改為 http://blog.csdn.net/?q=javascript#start
location.hostname = "blog.csdn.net";
//將URL修改為http://blog.csdn.net/Goskalrie?q=javascript#start
location.pathname = "Goskalrie";
//將URL修改為http://blog.csdn.net:8080/Goskalrie?q=javascript#start(實際上是沒有該URL的)
location.port = 8080;
每次修改location的屬性(hash除外),頁面都會以新URL重新載入。當通過上述任何一種方式修改URL之後,瀏覽器的歷史記錄中就會生成一條新記錄,因此使用者通過單擊“後退”按妞妞都會導航到前一個頁面。要禁用這種行為,可以使用replace()方法。這個方法只接受一個引數,即要導航到的URL;結果雖然會導致瀏覽器位置改變,但不會在歷史記錄中生成新記錄。在呼叫replace()方法之後,使用者不能回到前一個頁面。
與位置有關的最後一個方法是reload(),作用是重新載入當前顯示的頁面。如果呼叫reload()時不傳遞任何引數,也買年就會以最有效的方式重新載入。也就是說,如果頁面自上次請求以來並沒有改變過,頁面就會從瀏覽器快取中重新載入。如果要強制從伺服器載入,則需要為該方法傳遞引數true。
位於reload()呼叫之後的程式碼可能會也可能不會執行,這取決於網路延遲或系統資源等因素。為此,最好將reload()放在程式碼的最後一行。
navigator物件
最早由Netscape Navigator 2.0引入的navigator物件,現在已經稱為識別客戶端瀏覽器的事實標準。雖然其他瀏覽器也通過其他方式提供了相同或相似的資訊(例如,IE中的window.clientInfomation和Opera中的window.opera),但navigator物件卻是所有支援JavaScript的瀏覽器所公有的。與其他BOM物件的情況一樣,每個瀏覽器中的navigator物件也有一套自己的屬性,下表列出了存在於所有瀏覽器中的屬性和方法。
屬性或方法 |
說明 |
appCodeName |
瀏覽器的名稱。通常都是Mozilla,即使在非Mozilla瀏覽器中也是如此 |
appMinorVersion |
次版本資訊 |
appName |
完整的瀏覽器名稱 |
appVersion |
瀏覽器的版本。一般不與實際的瀏覽器版本對應 |
buildID |
瀏覽器編譯版本 |
cookieEnabled |
表示cookie是否啟用 |
cpuClass |
客戶端計算機中使用的CPU型別(x86、68K、Alpha、PPC或Other) |
javaEnabled() |
表示當前瀏覽器是否啟用了java |
language |
瀏覽器的主語言 |
mimeTypes |
在瀏覽器中註冊的MIME型別陣列 |
onLine |
表示瀏覽器是否連線到了因特網 |
opsProfile |
似乎早就不用了,早不到相關文件 |
oscpu |
客戶端計算機的作業系統或使用的CPU |
Platform |
瀏覽器所在的系統平臺 |
plugins |
瀏覽器中安裝的外掛資訊的陣列 |
preferencs() |
設定使用者的首選項 |
product |
產品名稱 |
productSub |
關於產品的次要資訊 |
registerContentHandler |
針對特定的MIME型別將一個站點註冊為處理程式 |
registerProtocolHandler() |
針對特定的協議將一個站點註冊為處理程式 |
securityPolicy |
已經廢棄 |
systemLanguage |
作業系統的語言 |
taintEnabled() |
已經廢棄 |
userAgent |
瀏覽器的使用者代理字串 |
userLanguage |
作業系統的預設語言 |
userProfile |
藉以訪問使用者個人資訊的物件 |
vendor |
瀏覽器的品牌 |
vendorSub |
有關供應商的次要資訊 |
檢測外掛
檢測瀏覽器中是否安裝了特定的外掛是一種最常見的檢測例程。對於非IE瀏覽器,可以使用plugins陣列來達到這個目的。該陣列中的每一項都包含下列屬性。
name:外掛的名字。
description:外掛的描述。
filename:外掛的檔名。
length:外掛所處理的MIME型別數量。
一般來說,name屬性中會包含檢測外掛必須的所有資訊,但有時候也不完全如此。在檢測外掛時,需要像下面這樣迴圈迭代每個外掛並將外掛的name與給定的名字進行比較。
//檢測外掛(在IE中無效)
functionhasNotIEPlugin(name){
name = name.toLowerCase();
for(var i = 0 ; i <navigator.plugins.length; i++){
if(navigator.plugins[i].name.toLowerCase().indexOf(name)> -1){
return true;
}
}
return false;
}
//檢測IE中的外掛
functionhasIEPlugin(name){
try{
new ActiveXObject(name);
return true;
}catch(e){
return false;
}
}
functionisIE(){
return navigator.userAgent.toLowerCase().match(/rv:([\d.]+)\) like gecko/)?true:false;
}
functionhasPlugin(name){
if(isIE){
return hasIEPlugin(name);
}else{
return hasNotIEPlugin(name);
}
}
alert(hasPlugin("ShockwaveFlash.shockwaveFlash"));
檢測IE中的外掛比較麻煩,因為IE不支援Netscape式的外掛.在IE中檢測外掛的唯一方式就是使用專有的ActiveXObject型別,並嘗試建立一個特定外掛的例項.IE是以COM物件的方式實現外掛的,而COM物件使用唯一識別符號來標識.因此,要想檢查特定的外掛,就必須知道其COM識別符號.例如,Flash的識別符號是ShockwaveFlash.shockwaveFlash.知道其唯一識別符號之後,就可以編寫類似上面的函式檢測IE中是否安裝相應外掛了。
在Web程式設計中,瀏覽器相容是必不可少的,判斷瀏覽器型別和版本的方法也各有各的實現,下面是一種:
$(function () {
var Sys = {};
var ua = navigator.userAgent.toLowerCase();
var s;
(s = ua.match(/rv:([\d.]+)\) like gecko/))? Sys.ie = s[1] :
(s = ua.match(/msie ([\d.]+)/)) ? Sys.ie =s[1] :
(s = ua.match(/firefox\/([\d.]+)/)) ?Sys.firefox = s[1] :
(s = ua.match(/chrome\/([\d.]+)/)) ?Sys.chrome = s[1] :
(s = ua.match(/opera.([\d.]+)/)) ?Sys.opera = s[1] :
(s = ua.match(/version\/([\d.]+).*safari/))? Sys.safari = s[1] : 0;
if (Sys.ie) $('span').text('IE: ' + Sys.ie);
if (Sys.firefox) $('span').text('Firefox: ' + Sys.firefox);
if (Sys.chrome) $('span').text('Chrome: ' + Sys.chrome);
if (Sys.opera) $('span').text('Opera: ' + Sys.opera);
if (Sys.safari) $('span').text('Safari: ' + Sys.safari);
});
plugins集合有一個名叫refresh()的方法,用於重新整理plugins以反映最新安裝的外掛。這個方法接收一個引數:表示是否應該重新載入頁面的一個布林值。如果將這個值設定為true,則會重新載入包含外掛的所有頁面;否則,只更新plugins集合,不重新載入頁面。
註冊處理程式
Firefox2為navigator物件新增了registerContentHandler()和registerProtocolHandler()方法(這兩個方法是在HTML5中定義的)。這兩個方法可以讓一個站點指明它可以處理特定型別的資訊。隨著RSS閱讀器和線上電子郵件程式的興起,註冊處理程式就為像使用桌面應用程式一樣預設使用這些線上應用程式提供了一種方式。
其中,registerContentHandler()方法接收三個引數:要處理的MIME型別、可以處理該MIME型別的頁面的URL以及應用程式的名稱。舉個例子,要將一個站點註冊為處理RSS源的處理程式,可以使用如下程式碼:
navigator.registerContentHandler(“application/rss+xml”,http://www.somereader.com?feed=%s,”SomeReader”);
第一個引數是RSS源的MIME型別。第二個引數是應該接收RSS源URL的URL,其中%s表示RSS源URL,由瀏覽器自動插入。當下一次請求RSS源時,瀏覽器就會開啟指定的URL,而相應的Web程式將以適當方式來處理該請求。
類似的呼叫方式也適用於registerProtocolHandler()方法,它也接收三個引數:要處理的協議(如,mailto或ftp)、處理該協議的頁面的URL和應用程式的名稱。例如,要想將一個應用程式註冊為預設的郵件客戶端,可以使用如下程式碼。
navigator.registerProtocolHandler(“mailto”,http://www.somemailclient.com?cmd=%s,”SomeMail Client”);
這個例子註冊了一個mailto協議的處理程式,該程式指向一個基於Web的電子郵件客戶端。同樣第二個引數仍然是處理相應請求的URL,而%s則表示原始的請求。
screen物件
JavaScript中有幾個物件在程式設計中用處不大,而screen物件就是其中之一。screen兌現基本上只用來表名客戶端的能力,其中包括瀏覽器視窗外部的顯示器的資訊,如畫素寬度和高度等。每個瀏覽器中的screen兌現格鬥包含著各不相同的屬性,下表列出了所有屬性及支援相應屬性的瀏覽器。
屬性 |
說明 |
IE |
Firefox |
Safari/ Chrome |
Opera |
availHeight |
螢幕的畫素高度減系統部件高度之後的值(只讀) |
√ |
√ |
√ |
√ |
availLeft |
未被系統部件佔用的最左側的畫素值(只讀) |
√ |
√ |
||
availTop |
螢幕的畫素寬度減系統部件寬度之後的值(只讀) |
√ |
√ |
||
availWidth |
螢幕的畫素寬度減系統部件寬度之後的值(只讀) |
√ |
√ |
√ |
√ |
bufferDepth |
讀、寫用於呈現屏外點陣圖的位數 |
√ |
|||
colorDepth |
用於表現顏色的位數,多數系統都是32(只讀) |
√ |
√ |
√ |
√ |
deviceXDPI |
螢幕實際的水平DPI(只讀) |
√ |
|||
deviceYDPI |
螢幕實際的垂直DPI(只讀) |
√ |
|||
fontSmoothingEnabled |
表示是否啟用了字型平滑(只讀) |
√ |
|||
height |
螢幕的畫素高度 |
√ |
√ |
√ |
√ |
left |
當前螢幕距左邊的畫素距離 |
√ |
|||
logicalXDPI |
螢幕邏輯的水平DPI(只讀) |
√ |
|||
logicalYDPI |
螢幕邏輯的垂直DPI(只讀) |
√ |
|||
pixelDepth |
螢幕的位深(只讀) |
√ |
√ |
√ |
|
top |
當前螢幕距上邊的畫素距離 |
√ |
|||
updateInterval |
讀、寫以毫秒錶示的螢幕重新整理時間間隔 |
√ |
|||
width |
螢幕的畫素寬度 |
√ |
√ |
√ |
√ |
這些資訊經常出現在測定客戶端能力的站點跟中工具中,但通常不會用於影響功能。不過,有時候也可能會用到其中的資訊來調整瀏覽器視窗大小,使其佔據螢幕的可用空間,例如:
window.resizeTo(screen.availWidth,screen.availHeight);
前面曾經提到過,許多瀏覽器都會禁用調整瀏覽器視窗大小的能力,因此上面這行程式碼不一定在所有環境下都有效。
設計移動裝置的螢幕大小時,情況有點不一樣。執行iOS的裝置始終會像是把裝置豎著拿在手裡一樣,因此返回的值是768x1024。而Android裝置則會相應呼叫screen.width和screen.height的值。
history物件
history物件儲存著使用者上網的歷史記錄,從視窗被開啟的那一刻算起。因為histor是window物件的屬性,因此每個瀏覽器視窗、每個標籤頁乃至每個框架,都有自己的history物件與特定的window物件關聯。出於安全方面的考慮,開發人員無法得知使用者瀏覽過的URL。不過,藉由使用者訪問過的頁面列表,同樣可以在不知道實際情況URL的情況下實現後退和前進。
使用go()方法可以在使用者的歷史記錄中任意跳轉,可以向後也可以向前。這個方法接受一個引數,表示向後或向前的頁面數的一個整數值。負數表示向後跳轉(類似於單擊瀏覽器的“後退”按鈕),正數表示向前跳轉(類似於單擊瀏覽器的“後退”按鈕)。如:
//後退一頁
history.go(-1);
//前進一頁
history.go(1);
//後退兩頁
history.go(-2);
也可以給go()方法傳遞一個字串引數,此時瀏覽器會跳轉到歷史記錄中包含該字串的第一個位置——可能後退也可能前進,具體要看那個位置最近。如果歷史記錄中不包含