2操作視窗
瀏覽器視窗功能強大且不可少,並能呈現任何複雜度的豐富使用者介面,JavaScript 是透過 window 物件與瀏覽器互動,其中包括 window 物件的關鍵屬性和方法。
window 全域物件
在瀏覽器使用 JavaScript 時,視窗是最上層的物件,因此在任何其他物件外部定義的函數都會成為 window 全域物件的方法,window 物件本身往往是隱含的,不需明確引用:
function doNothing(){
}
doNothing(); //呼叫 function
window.doNothing(); //同上
var someVar = true;
someVar; //true
window.somVar; //同上,對函數外部定義的任何變數也一樣
按照常規,有時需要明確使用 window 物件,大多時候不需要,上述兩個例子不常見,實際上三個對話方塊方法和 Math、Date 物件都屬於 window。
其中 window 物件的三個屬性
反應瀏覽器視窗的特定部分是否可見:menubar、statubar、toolbar,此舉是將個人願望強加在使用者瀏覽器上,實際上不應該試圖改變,約十幾年前流行的概念之一是改變瀏覽器視窗的狀態列文字,但基於安全考量,許多瀏覽器禁用了這項功能,預設情況下不顯示狀態列。
還有
window.navigator 這個屬性有多個子屬性,儲存瀏覽器的相關資訊,常用來透過
window.navigator.userAgent 進行瀏覽器嗅探(browser sniffing),目的是
根據瀏覽器類型和版本撰寫條件式敘述,但現今有更好的替代方案取代──物件檢測。
if(window.menubar.visible){ //查看選單列是否可見
...
}
window.status = 'Hello. I am browser.'; //顯示狀態列的文字
window.menubar.visible; //選單列
window.toolbar.visible; //工具列
window.locationbar.visible; //網址列
window.personalbar.visible; //個人欄
window.scrollbars.visible; //捲軸
window.statusbar.visible; //狀態列
操作視窗的大小和位置
曾經由網頁改變瀏覽器視窗大小或移動視窗一度非常流行,雖技術上可行,但不建議使用。
除了 Firefox 以外,這兩個屬性無法修改視窗位置,但在絕對必要的時候,這些屬性會告訴我們是否有必要移動:
- window. screenX & window. screenY:視窗在螢幕上的位置(瀏覽器視窗左上角與使用者螢幕左上角的相對位置)
- window .screenLeft & window.screenTop:同上(for IE)
除了 Firefox 以外,這些屬性無法改變視窗大小,但若有必要,他們可以當成動態修改佈局的基礎,例如視窗縮小,可用 JavaScript 使用較小的圖片:
- window.innerHeight & window.innerWidth: 視窗中內容大小,包括捲軸(如果存在)
- window.outerHeight & window.outerWidth: 整個瀏覽器視窗大小
- document.body.clientHeight & document.body.clientWidth: 同上,針對 IE8 以下
因某種理由必須移動瀏覽器視窗,可呼叫 window 物件兩種方法:
-
moveTo( X 像素 , Y 像素 ): 移至絕對位置,(0,0) 表螢幕的左上角:
window.moveTo(100,200); //將視窗左上角放在螢幕左邊向右 100px、頂端向下 200px 的位置
-
moveBy( X 像素 , Y 像素 ): 移至目前位置的相對位置,正數代表視窗移向右下方;負數表移向左上方:
window.moveBy(25,50); //將視窗從目前位置向右移動 25px、向下移動 50px
myWindow = window.open('', 'myWindow', 'width=200, height=200');
myWindow.moveTo(0, 0); //重新定位
myWindow.document.write('移動視窗');
myWindow.moveTo(500, 100); //移至座標 (500,100)
myWindow.focus(); //focus 視窗
myWindow.moveBy(500, 100); //相對移至 (500,100)
myWindow.focus(); //focus 視窗
螢幕屬性
與使用者實體螢幕的資訊,可利用這些資訊客製化瀏覽器視窗和使用者體驗,不過現代瀏覽器支援以 CSS 媒體查詢來修改這些設定。
- window.screen.height: 返回螢幕高度(像素)
- window.screen.width: 返回螢幕寬度(像素)
- window.screen.availHeight: 返回螢幕可用工作區高度像素值,不包含所有 OS 特性(例如任務列)
- window.screen.availWidth: 返回螢幕可用工作區高度像素值,不包含所有 OS 特性(例如任務列)
- window.screen.colorDepth: 返回螢幕色深(例如 16 代表 16 位元)
window.screen.height //螢幕高度
window.screen.width //螢幕寬度
window.screen.availHeight //螢幕可用工作區高度
window.screen.availWidth //螢幕可用工作區寬度
window.screen.colorDepth //螢幕色深
網頁屬性(補充)
- document.body.clientHeight: 返回網頁可見區域寬(像素)
- document.body.clientWidth: 返回網頁可見區域高(像素)
- document.body.offsetHeight: 返回網頁可見區域寬(像素),包括邊線的寬
- document.body.offsetWidth: 返回網頁可見區域高(像素),包括邊線和捲軸的寬
- document.body.scrollHeight: 返回網頁正文全文高(像素)
- document.body.scrollWidth: 返回網頁正文全文寬(像素)
- document.body.scrollTop: 返回網頁被捲去的高(像素)
- document.body.scrollLeft: 返回網頁被捲去的左(像素)
document.body.clientHeight //網頁可見區域寬
document.body.clientWidth //返回網頁可見區域高
document.body.offsetHeight //返回網頁可見區域寬
document.body.offsetWidth //返回網頁可見區域高
document.body.scrollHeight //返回網頁正文全文高
document.body.scrollWidth //返回網頁正文全文寬
document.body.scrollTop //返回網頁被捲去的高
document.body.scrollLeft //返回網頁被捲去的左
建立新視窗
這裡建立的新視窗,與對話方塊不同的是,是真正瀏覽器視窗,非強制回應,使用者可在必要時切換到原始視窗,現今建立新視窗的情況越來越少,但運用得當可增加正面效果。
- window.open( 開啟網頁 URL , (視窗名稱) , (應有屬性的字串) ):建立新視窗呼叫 window 物件的
open() 方法,提供打算開啟的 URL,執行該方法的結果指派給一個變數,之後腳本就可以引用其他視窗,實際上,如果不把 window.open() 結果賦值給變數,有些瀏覽器會出現問題。自訂彈出視窗,應有屬性字串語法為「屬性 = 值」,多個屬性由逗號分開,有些值為數字、yes/no(1/0),且以上設定無法在開啟視窗之後修改。
上述語法不能在屬性字串中使用任何空格或歸位鍵,此外要注意,無法在開啟視窗之後修改這些設定
var popup = window.open('somefile.html'); //URL 可以是絕對或相對路徑
var popup = window.open('somepage.html', 'DefinitionsWindows', 'height=200, width=200, location=no, resizable=yes, scrollbars=yes');
| 屬性 |
註解 |
預設值 |
| height |
用於內容區域,大部份瀏覽器預設值為父視窗或最近建立視窗的高度,必須至少為 100px |
|
| width |
用於內容區域,大部份瀏覽器預設值為父視窗或最近建立視窗的寬度,必須至少為 100px |
|
| outerHeight |
整個視窗高度,至少為 100px |
|
| outerWidth |
整個視窗寬度,至少為 100px |
|
| left |
預設值大約是在父視窗左側向右 20px |
|
| top |
預設大約在父視窗頂端向下 20px |
|
| location |
位址列是否可見 |
no |
| resizable |
應始終設為 yes,在某些瀏覽器中不管該值為何,視窗大小都可以改變 |
no |
| status |
在某些瀏覽器上始終顯示 |
no |
| channelmode |
劇院模式 |
no |
| fullscreen |
全螢幕檢視(only for IE) |
no |
| menubar |
Mac 上無法隱藏 |
no |
| scrollbars |
應始終設為 yes |
no |
| toolbar |
工具列是否可見 |
no |
| titlebar |
網頁名稱標籤是否可見 |
yes |
window.open('browser_2_2.html');
window.open('browser_2_2.html', 'open');
window.open('browser_2_2.html', 'jump', 'height=500, width=500');
window.open('browser_2_2.html', 'jump', 'outerHeight=400, outerWidth=200');
window.open('browser_2_2.html', 'jump', 'top=200px, left=100px');
window.open('browser_2_2.html', 'jump', 'menubar=yes');
window.open('browser_2_2.html', 'jump', 'resizable=yes');
window.open('browser_2_2.html', 'jump', 'scrollbars=yes');
window.open('browser_2_2.html', 'jump', 'status=yes');
window.open('browser_2_2.html', 'jump', 'toolbar=yes');
window.open('browser_2_2.html', 'jump', 'location=yes');
因曾大量用於彈出式廣告,為此現代瀏覽器擁有內建彈出視窗攔截功能,在此情況下常常返回 null 值:
if(popup === null){ //如果敘述為真,將整個瀏覽器視窗重新導向到彈出式廣告顯示頁面。
...
}
關閉新視窗
- window.close(): 關閉視窗,此方法僅限於透過
window.open() 建立的視窗。
//檢查 closed 屬性可確定尚未關閉 JavaScript 建立的視窗
if((popup !== null) && !popup.closed){
...
}
改變焦點
- 視窗變數.focus():開啟超過一個以上的視窗時,focus 事件觸發於視窗成為作用中的視窗時;blur 事件則在另一個視窗成為作用中時觸發
if((popup !== null) && !popup.closed){ //可以在執行前檢查現有視窗
popup.focus();
}
可存取的解決方案
這些輔助視窗透過 JavaScript 建立,沒有可存取的替代方案,而且搜尋引擎無法看到輔助視窗的內容,另外也可能被彈出視窗攔截軟體阻止視窗發生,從整體考量上,應始終採用標準方法提供輔助視窗存取──在連結上增加一個視窗名稱:
另開新視窗
U.addEvent(U.$('close'), 'click', closeFunc);
function closeFunc(){
window.close();
}
視窗之間的溝通
當開啟多個視窗時,這時透過 JavaScript 可以讓視窗之間互相溝通:
- 利用視窗 A 打開新視窗 B,並指派給變數 windowB,就可以使用
windowB.window 存取視窗 B 的 window 物件。
-
視窗 B 使用
window.opener 取得視窗 A,就能透過 windowA.window.propertyName 和 windowA.window.functionName() 引用視窗 A 的全域變數和函數。
- window.opener: 返回目前視窗上層物件的參照,如果不是由其他視窗開啟目前的視窗,opener 屬性將返回 null
/* 視窗 A */
var windowB = window.open('browser_7_2.html', 'windowB');
windowB.window.addTo(12);
windowB.window.something;
function getFunc(getName){
return 'Hello! ' + getName + '. 回應視窗 A!';
}
/* 視窗 B */
var windowA = window.opener;
var something = 23;
function addTo(what){
something += what;
windowA.window.getFunc('Domo');
}
同源策略
瀏覽器內建一種安全措施─「同源策略(Same origin policy)」,根據涉及資源的來源限制可以進行的操作。
例如,http://example.com 上的兩個頁面:http://example.com/page1.html 和 http://example.com/file/page2.html,即使不同子目錄,也屬同一來源。
如果是以下狀況,那麼頁面來源就不相同:
- HTTP 通訊協定不同,例如:https://example.com
- 子網域之下,例如:http://blog.example.com
- 完全不同網域,例如:http://example2.com
- 埠號考量,此問題較不常見,也較為進階
對 JavaScript 來說,在一個視窗或框架中的程式碼只能存取相同來源、另一個視窗中的 JavaScript,而根據瀏覽器的差別,可能在載入另一個網域的資源時出錯(拋出錯誤或通知使用者),在任何一種情況下,JavaScript
與來自其他網域資源之間的溝通都將被禁止。
處理瀏覽器歷史
window 物件的另一個實用屬性
history 是用來查看目前視窗的歷史,這個物件有三種方法:
- back(): 與點擊「上一頁(Back)」一樣
- forward(): 與點擊「下一頁(Forward)」一樣
- go( 數字 ): 前往歷史中的特定點,唯一的參數是相對於瀏覽器歷史中的目前位置,索引為 0,因此 go(1) 等同於 forward();go(-1) 等同於 back(),go(-2) 等於呼叫兩次 back()
當使用者點擊後退鍵時,瀏覽器將返回到快取的前一個頁面,當點擊前往一個頁面的連結時,可能會找到並載入快取的版本,如無快取,瀏覽器可能會從伺服器重新請求該頁面,因此沒有真正進行過漸進式改善。
參考 W3School The History Object:http://www.w3schools.com/jsref/obj_history.asp
使用框架
針對 JavaScript,主 HTML 文件及其包含的 iframe 之間的關係,類似於瀏覽器視窗和它所開啟的另一個視窗之間的關係。
iframe 頁面可以透過 parent 物件(或更正式的 window.parent)存取父文件的全域變數和方法;如果 iframe 中未嵌入文件,window.parent 屬性等同於 window.self (是視窗識別自身的一種手段):
if(window.parent != window.self) { // This is a child!
在父文件中,window.frames 屬性是一個陣列,代表文件中的每個框架和 iframe(window.length 儲存框架的數量),因此 window.frames[0] 是第一個框架,window.frames['frameName'] 是引用框架的名稱(name),也可使用 document.getElementById() 取得 iframe 元素。
根據不同瀏覽器,可透過 contentWindow 或 contentDocument 屬性存取框架的全域屬性和函數:
var frame = document.getElementById('theFRame');
var frameWindow = frame.contentWindow || frame.contentDocument;
//可使用 frameWindow.property 和 frameWindow.function()
如果打算使用 HTML 和 iframe,還應該研究規範增加的新屬性:sandbox 和 srcdoc 屬性提供附加的安全性制定方法,seamless 屬性則能更容易將 iframe 整合到 HTML 文件的其餘部分。
參考 W3School IFrame contentWindow Property:http://www.w3schools.com/jsref/prop_frame_contentwindow.asp
參考 W3School <iframe> Attributes:http://www.w3schools.com/tags/tag_iframe.asp
重新導向瀏覽器
通常最好使用 Web 伺服器(如 Apache)進行重新導向另一頁面的動作,例如修改頁面的 URL,就希望 Web 伺服器能將瀏覽器重新導向到新目的地,而不是讓瀏覽器載入錯誤的頁面,然後才進行重新導向。有些情況下,可能無法使用 Web 伺服器(例如無權存取組態),或者此時改用 JavaScript 更有意義。
- window.location: 反應瀏覽器正在查看的目前頁面,修改此屬性,便能將瀏覽器重新導向另一個頁面(此法較外行)
- window.location.href: 反應瀏覽器正在查看的目前頁面,修改此屬性,變能將瀏覽器重新導向另一個頁面(較精確的做法),上述兩種做法意味著前一個頁面──帶有重新導向程式碼的頁面──仍然出現在瀏覽器的歷史中,可用後退鍵取出
- window.location.replace( 'URL路徑' ): 代替
window.location 和 window.location.href 的賦值方法,這個方法是用新的位置取代目前位置,因此前一個頁面不會出現在瀏覽器的歷史中,即使藉助後退件也無法存取,建議在使用者沒有必要查看前一個頁面使用
window.home(): 重新導向瀏覽器到使用者的首頁,大部分瀏覽器不支援
- window.location.hash: 表示瀏覽器 URL 從 # 開始的部分,也就是頁面中使用者將被定位到的特定位置(Anchor)或 ID
- window.location.search: 表示瀏覽器 URL 從 ? 開始的部分,也就是 URL 傳遞給頁面的參數,例如關鍵字
window.location.href = 'browser_8_4.html';
window.location.replace('browser_8_4.html');
window.home();
var hash = window.location.hash;
hash = hash.slice(1);
var getString = window.location.search; //抓參數字串
var delQMark = getString.slice(1); //去掉 ?
var perPara = delQMark.split("&"); //& 分開各參數與值
var splitPara=[], paraName=[], paraVal=[];
for (i = 0; i < perPara.length; i++) {
splitPara = perPara[i].split("="); //= 分開第 i 個參數名稱與值
paraName.push(splitPara[0]); //將分開的參數名稱放入 paraName
paraVal.push(splitPara[1]); //將分開的參數值放入 paraVal
}
for(var i=0; i< paraName.length; i++){
document.write(paraName[i] + ' => ' + paraVal[i]);
}
建立代表性的 URL
像是分頁 tab 頁籤,這類型的動態是由 JavaScript 顯示,但是分頁本身無法被瀏覽器存取為一個頁面,固定一開始顯示會是第一個頁面,若要控制一開始進入其他分頁,可利用 # 標籤標示頁籤名稱,並在進入的 URL 中建立行為:
var hash = window.location.hash;
var content = hash.charAt(1);
switch(content){
case 'tab2': //show tab2
break;
case 'tab3': //show tab3
break;
case 'tab1': //show tab1
default:
break;
}
為了使上述方法生效,必須在頁面撞態改變時更新 URL,假設點擊頁籤呼叫 setTab() 函數,該函數將事件作為一個參數:
function setTab(e){
if(typeof e == 'underfined')
var e = window.event;
}
使用事件的 target 或 srcElement 屬性,該函數就能知道顯示的是哪一個頁籤,如果函數取得資訊後,將其儲存在 tab 變數中,便可相對應動態改變 URL。
瀏覽器上下頁會瀏覽這些 URL,因為瀏覽器歷史會記錄所瀏覽的任何 URL,但是不會強迫載入頁面,意思是當 URL 改變時,不會忽叫 JavaScript 更新狀態。解決方法是:建立一個計時器檢查 hash 變化。
參考 W3School The Location Object:http://www.w3schools.com/jsref/obj_location.asp
列印本頁
- window.print(): 呼叫瀏覽器的列印選項,類似選擇「檔案(File)」>「列印(Print)」
//在許多行動和非桌面裝置無法使用,可以檢測確認 window 物件是否有 print() 函數
if(typeof window.print == 'function'){
window.print();
}
document.getElementById('printLink').onclick = function(){
if(typeof window.print == 'function'){
window.print();
}
}
文件物件
- (window.)document: 此物件表示視窗中載入的 HTML,包含許多關鍵方法,(winodw 是隱含物件,可以僅使用 document,無須再加上 window)
document.write( 字串 ): 將資料存入文件,顯示於網頁上(不建議使用)
document.writeln( 字串 ): 將資料存入文件,顯示於網頁上,且自動換行(不建議使用)
write() 和
writeln() 不使用的原因如下:
- 他們無法在 XHTML 文件正常運作
- 如果載入頁面之後使用,寫入的資料可能覆蓋現有內容
- 可能擾亂頁面 DOM 的呈現
- DOM 操作方法遠強於這兩個方法
然而這兩個方法適合一種情形:在某種條件下動態含括額外的資源。
例如:廣告和第三方腳本經常以這些方法動態的將程式碼插入支援 JavaScript 頁面(也常應用於 iframe)
- document.title: 可用來儲存目前瀏覽器視窗的標題,也可用來動態修改標題,可以在修改 URL 的時候改變瀏覽器標題,例如拿來對應書籤名稱
- document.compatMode: 修改或偵測 HTML 頁面的 DOCTYPE 問題及其對瀏覽器模式(怪癖或標準模式),如果了解瀏覽器在怪癖模式中的特定問題,就能利用 JavaScript 更正,也可將此屬性當作偵測工具:確保頁面始終執行於標準模式。BackCompat(「向後相容(Backward compatible)」)代表怪癖模式,對應 quirks mode;CSS1Compat 代表標準模式,對應 strict mode
document.title = 'New Title';
if (document.compatMode == "BackCompat") {
// in Quirks mode
}
參考 W3School The Window Object:http://www.w3schools.com/jsref/obj_window.asp
參考 W3School The Document Object:http://www.w3schools.com/jsref/dom_obj_document.asp
3操作 DOM
文件物件模型(DOM)最早於 1998 年由 W3C(World Wide Web Consortium)標準化,它只是呈現和處理 XML、 XHTML 和 HTML 資料的一種方法,透過 DOM,JavaScript 可以相對簡單地尋找、存取和操作 HTML 元素,進而動態改變 HTML 頁面內容和外觀。
DOM 基礎
DOM 將資料呈現為一顆樹,從單一的主幹(稱為根)開始,在 HTML 頁面中,起始點是 HTML 標記,根之上是樹枝,每根樹枝是 HTML 頁面的另一個元素,每個 HTML 元素及其屬性的值都可交由 DOM 表示。以下為範例:
<!docutype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>This Is The Title</title>
</head>
<body>
<!-- This is a comment node! -->
<div id="one"><p>Click me!</p></div>
<div id="two"><p>This is a paragraph <a href="#" class="popup">a link</a>.</p></div>
<p id="demo"></p>
<script src="js/text.js"></script>
</body>
</html>
function myFunction() {
var c = document.body.childNodes;
var txt = "";
var i;
for (i = 0; i < c.length; i++) {
txt = txt + c[i].nodeName + "
";
}
document.getElementById("demo").innerHTML = txt;
}
元素在 DOM 的呈現為節點(Node),節點有自己的分支,這些分支也是節點。這種結構通常以家族的方式描述:父(Parents)節點有子(Child)節點,節點還有兄弟(Sibling)節點。網頁的根節點 document 沒有父節點,只有一個子節點:html。
DOM 的每個節點代表一個物件,這些物件有特殊的屬性,以便反應每個節點與其直屬家族成員之間的關係。parentNode 屬性指向目前物件的雙親,childNodes 則指向目前物件子節點的物件陣列,如上例,BODY 的 parentNode 是 HTML 節點,它有三個子節點:DIV、DIV、SCRIPT。
節點還有 firstChild 和 lastChild 屬性,以及 previousSibling 和 nextSibling 屬性,如上例,HEAD 的 firstChild 是 META 節點,該節點是 TITLE 節點的 previousSibling。
除此之外,還有文字節點,例如,TITLE 有一個內含字串「This Is The Title」的文字子節點,第二個 DIV 內的段落有一個子節點 A 和一個文字子節點(A 也有自己的文字子節點)。children 屬性代表 HTML 元素的子節點,排除了 childNode 屬性中與 HTML 節點一起返回的文字節點。
每個節點都有提供自身資訊的屬性:
- nodeName:對 HTML 元素來說,nodeName 是全大寫的 HTML 標記,以文字節點來說,nodeName 則是 #text;document 節點的 nodeName 是 #document
- nodeValue:回傳值
- nodeType:此屬性是一個數字,HTML 元素是 1;文字是 2;註解是 8;文件是 9
理論上,可以使用這幾種樹形的組合存取 DOM 元素,針對前述 HTML 標記,在 JavaScript 中可以從 document.documentElement 開始,它會返回根元素(這裡是 HTML)。意味著
document.documentElement.lastChild 引用 BODY,
document.documentElement.lastChild.children[0] 引用第一個 DIV。
繼續上述方式,就能瀏覽整個 DOM,但這個過程很快就變得乏味,而且 HTML 中的空白會導致不可預知的問題,因為在 DOM 中它代表帶有空字串的文字節點。因此
document.documentElement.lastChild.children[0] 引用第一個 DIV,但是
document.documentElement.lastChild.childNodes[0] 實際上引用了一個空的文字節點(因為 children 僅返回 HTML 元素,而 childNodes 返回所有元素)。
幸運的是,瀏覽 DOM 有多比較簡單的代替方案。
參考 W3School HTML DOM childNodes Property:http://www.w3schools.com/jsref/prop_node_childnodes.asp
參考 W3School HTML DOM nodeName Property:http://www.w3schools.com/jsref/prop_node_nodename.asp
參考 W3School HTML DOM nodeValue Property:http://www.w3schools.com/jsref/prop_node_nodevalue.asp
參考 W3School HTML DOM nodeType Property:http://www.w3schools.com/jsref/prop_node_nodetype.asp
DOM 捷徑
document 物件有一些屬性,可以作為存取重要 DOM 元素的捷徑:
- documnet.body:引用 HTML 主體
- document.forms:是頁面中所有表單的陣列
- document.images:是頁面中所有圖形的陣列
- document.links:是頁面中所有連結的陣列(簡單概括)
例如,頁面中的第一個表單可以用
document.forms[0] 代表,也可以元素名稱代替數字索引,這樣做通常更合適:
document.images['someImageName'] 或者
document.forms['theForm']。
參考 W3School The Document Object:http://www.w3schools.com/jsref/dom_obj_document.asp
使用 DOM 操作
function createPopup(e) {
'use strict';
if (typeof e == 'undefined') var e = window.event;
var target = e.target || e.srcElement;
var popup = window.open(target.href, 'PopUp', 'height=400,width=400');
if ( (popup !== null) && !popup.closed) {
popup.focus();
return false;
} else {
return true;
}
}
window.onload = function() {
'use strict';
for (var i = 0, count = document.links.length; i < count; i++) {
document.links[i].onclick = createPopup;
}
};
DOM 方法
使用各種 document 屬性可能是尋找頁面元素的好方法,但內建於 document 物件的兩個卓越方法更加常用:
- .getElementById( id 名稱 ): 用來找特定 id 的單一元素,只要存在指定 id 的元素,就能正常運作;找不到對應元素返回 null
- .getElementsByTagName( tag 名稱 ): 用來找特定標籤的所有元素,返回選中元素的一個類似陣列的列表(留意方法名稱中的複數形式 Elements),和
getElementById() 不同的是,getElementsByTagName() 可於任何元素上呼叫,因此可使用特定的起始點限制搜尋的範圍
- .getElementsByClassName( class 名稱 ): 用來找特定 class 的所有元素,是一個較新的方法,返回指定類別名稱所有元素的一個類似陣列列表,IE 8 以前的版本中不存在
- .getElementsByName( name 名稱 ):用來找特定的 name 的所有元素,返回選中元素的一個類似陣列的列表
var header = document.getElementById('header');
var hLink = header.getElementsByTagName('a');
//上述合併成一行
var hLinks = document.getElementById('header').getElementsByTagName('a');
//getElementsByTagName()返回的變數可視為陣列,利用陣列語法參照個別元素
document.getElementById('header').getElementsByTagName('a')[0];
參考 W3School getElementById():http://www.w3schools.com/jsref/met_document_getelementbyid.asp
參考 W3School getElementsByTagName():http://www.w3schools.com/jsref/met_document_getelementsbytagname.asp
參考 W3School getElementsByClassName():http://www.w3schools.com/jsref/met_document_getelementsbyclassname.asp
參考 W3School getElementsByName():http://www.w3schools.com/jsref/met_doc_getelementsbyname.asp
CSS 選擇器
在 JavaScript 中,相對新穎的頁面元素尋找方法是透過 CSS 選擇器(Selector)引用 CSS 確定頁面元素的語法,例如依照 HTML 標記、id 或者類別,隨著 CSS 新標準的推出(目前為 CSS3),選擇器的威力越來越大。
為了在 JavaScript 中使用 CSS 選擇器,可以藉助以下兩種方法,兩者都可以在 document 物件或特定元素上呼叫,如果在特定元素執行這兩個方法,那
CSS 選擇器僅適用於該元素的子元素:
- .querySelector( CSS ): 最多返回一個元素(第一個符合條件的元素)
- .querySelectorAll( CSS ): 回傳滿足條件的所有元素
document.querySelectorAll('img.thumbnail');
document.getElementById('nav').querySelector('a.selected');
這兩個方法瀏覽器支援度很好,唯獨 IE 8 (IE 8 僅支援 CSS2.1 選擇器)之前的版本不支援。
XPath 運算式
CSS 選擇器的替代方案 XPath 運算式更快且更進階,正如 DOM 是 XML 文件(包括 HTML)的呈現形式,XPath 則是瀏覽 XML 文件的一種手段,XPath 能夠依照標記、類別、與其他元素的關係等尋找元素。在支援 XPath 的瀏覽器上,它的性能非常優越。
但支援度不一致,主流瀏覽器目前的版本支援 XPath 1.0,更新版的 XPath 標準的支援較少,且學習新語法也需勇氣。
修改元素屬性
一旦取得 HTML 元素的參照,就可以操作 DOM 修改元素的屬性。
- .nodeName: 回傳節點名稱
- .nodeType: 回傳節點類型
- .nodeValue: 回傳節點值
- .ownerDocument: 回傳節點的根節點,也就是 document
- .parentNode: 回傳節點的父節點
- .firstChild: 回傳節點內的第一個子節點
- .lastChild: 回傳節點內的最後一個子節點
- .nextSibling: 回傳節點的下一個節點集合物件
- .previousSibling: 回傳節點的上一個節點集合物件
- .childNodes: 回傳節點子節點的集合物件
- .namespaceURI: 回傳文件節點的 namespce
- .attributes: 回傳元素中的所有屬性集合
- .children: 回傳元素中的子元素集合物件
- .childElementCount: 回傳該元素內的子元素數量(IE9 以上)
- .parentElement: 回傳元素的父元素
- .firstElementChild: 回傳元素內的第一個子元素(IE9 以上)
- .lastElementChild: 回傳元素內的第一個子元素(IE9 以上)
- .nextElementSibling: 回傳元素內的下一個子元素(IE9 以上)
- .previousElementSibling: 回傳元素內的上一個子元素(IE9 以上)
- .tagName: 回傳元素的標籤名稱
- .className: 回傳 class 屬性名稱
- .classList: 回傳 class 屬性中標記 DOMTokenList 的集合物件,常用來新增移除 class
- .clientHeight: 回傳元素所佔瀏覽器視窗範圍中的高,不含 padding 與 margin
- .clientWidth: 回傳元素所佔瀏覽器視窗範圍中的寬,不含 padding 與 margin
- .clientTop: 回傳元素上邊與邊框上邊的距離,=「borderTopWidth」,不含 padding 與 margin
- .clientLeft: 回傳元素左邊與邊框左邊的距離,包含左邊捲軸寬度,=「borderLeftWidth + 捲軸寬度」,不含 padding 與 margin
- .contentEditable: 設定 true|false,可以編輯該元素的內容
- .isContentEditable: 回傳 contenteditable 屬性的值
- .dir: 賦值 ltr|rtl|auto ,改變文字方向
- .disable: 回傳 disable 屬性的值
- .href: 回傳 href 屬性的值
- .htmlFor: 回傳 for 屬性名稱
- .lang: 回傳 lang 屬性的值
- .title: 回傳 title 屬性的值
- .id: 回傳 id 屬性的值
- .tabIndex: 回傳/設定 Tab 鍵順序
- .innerHTML: 回傳/設定元素的 HTML 內容
- .textContent: 回傳/設定元素的純文字內容
- .offsetHeight: 回傳元素的高度,包含 borderWidth 和 padding,不含 margin
- .offsetWidth: 回傳元素的寬度,包含 borderWidth 和 padding,不含 margin
- .offsetLeft: 回傳元素的與左邊相對距離,包含 borderWidth 和 padding,不含 margin
- .offsetTop: 回傳元素的與上邊相對距離,包含 borderWidth 和 padding,不含 margin
- .scrollHeight: 回傳元素捲軸中完整的高度,包含 padding,不含 margin、border、scrollbar
- .scrollWidth: 回傳元素捲軸中完整的寬度,包含 padding,不含 margin、border、scrollbar
- .scrollLeft: 回傳捲軸中向左位移的距離
- .scrollTop: 回傳捲軸中向上位移的距離
- .style: 回傳 style 的物件集合
window.load = init();
function init(){
var a1Html = '';
var a1 = document.getElementById('a1');
for(var i=0; i< a1.attributes.length; i++){
a1Html += a1.attributes[i].name + ' = ' + a1.attributes[i].value + ', ';
}
a1.innerHTML = '.attributes => ' + a1Html;
document.getElementById('a2').href;
document.getElementById('checkbox1').disabled + ', ' + document.getElementById('label1').htmlFor;
document.getElementById('checkbox2').disabled + ', ' + document.getElementById('label2').htmlFor;
var toggle1 = 0;
document.getElementById('b1').onclick = function(){
if(toggle1 == 0){
document.getElementById('span1').classList.add('pBlock');
toggle1 = 1;
}else{
document.getElementById('span1').classList.remove('pBlock');
toggle1 = 0;
}
document.getElementById('span1').className;
}
document.getElementById('div1').childElementCount;
document.getElementById('div1').clientHeight;
document.getElementById('div1').clientWidth;
document.getElementById('div1').clientTop;
document.getElementById('div1').clientLeft;
}
參考 W3School The HTML DOM Element Object:http://www.w3schools.com/jsref/dom_obj_all.asp
建立元素
操作 DOM 的最後一種方法是實際建立或刪除 HTML 元素,但建立元素並新增到 DOM 的手段比使用 innerHTML 慢,不過有時候卻是必要的。
直接從無到有新增元素的方法
- document.createElement(): 建立指定類型的 HTML,並將結果指派給一變數
var p = document.createElement('p');
這時我們建立了一個新的 HTML 段落元素,但它還是空的,只具備預設的屬性,還不是 DOM 的一部份,也就是最終使用者看不見。
設定不同的 HTML 和屬性,以便進行客製化:
p.innerText = 'This is some text';
p.className = 'enhanced';
最後,使用多種可能的方法,像是
insertBefore()、
appendChild()、
replaceChild() 將元素增加到 DOM,具體方法取決於所需的最終結果,任何情況下,至少需要一個元素的參照,以便容納要置放的新元素,假設新的段落出現在某個 DIV 中:
var div = document.getElementById('someDiv');
div.appendChild(p); //在目標元素上呼叫 appendChild(),就可將段落新增到 DIV 中
var p = document.createElement('p');
p.innerText = 'This is some text';
p.className = 'enhanced';
var div = document.getElementById('someDiv');
div.appendChild(p);
參考 W3School createElement():http://www.w3schools.com/jsref/met_document_createelement.asp
至少需要一個元素的參照,來新增所要置放的元素
- .appendChild( 要新增的 HTML 元素 ): 將要增加的元素作為參數,將元素新增到指定元素中的最尾端(在所有子元素之後)
- .insertBefore( 要新增的 HTML 元素 , 要放置此子元素之前 ): 將要增加的元素及指定位置作為參數,將元素新增到指定元素中
- .replaceChild( 要新增的 HTML 元素 , 所要替換的子元素 ): 將要增加的元素及指定的子元素作為參數,以替換現有元素
如果打算替換成另一種同類型的元素,則直接取代元素的內容或屬性,不建議使用上述方法。
var span1 = document.createElement('span');
span1.innerText = 'span 1';
var p1 = document.getElementById('p1');
p1.appendChild(span1, document.getElementById('strong1'));
var span2= document.createElement('span');
span2.innerText = 'span 2';
var p2 = document.getElementById('p2');
p2.insertBefore(span2, document.getElementById('strong2'));
var span3 = document.createElement('span');
span3.innerText = 'span 3';
var p3 = document.getElementById('p3');
p3.replaceChild(span3, document.getElementById('strong3'));
參考 W3School appendChild():http://www.w3schools.com/jsref/met_node_appendchild.asp
參考 W3School insertBefore():http://www.w3schools.com/jsref/met_node_insertbefore.asp
參考 W3School replaceChild():http://www.w3schools.com/jsref/met_node_replacechild.asp
其他建立元素的用法
- .innerHTML: 設置或返回元素內的 HTML
- .innerText: 設置或返回元素內的純文字,innerText 是 IE 8 之前的 IE 專用屬性,並不是國際規範的一部分,是只早期在 IE 擁有高市佔率時,大家都已經習慣使用它,Chrome 是採用兩者都支援的方式,但 Firefox 比較嚴謹,不是規範的內容就不支援
- .textContent: 設置或返回元素內的純文字
-
.createTextNode( 要新增的文字 ): 純粹新增文字,而不需建立整個元素,將文字增加到具有其他子元素的元素內時必須採用的手法,因為用
innerText 和 textContent 會刪除其他內容
- .cloneNode( (true/false) ): 複製現有元素,藉以建立一個新元素,表示更新頁面的替代方法之一是複製整個元素,再更新其屬性和內容(也就是子元素),然後用修改過的複製品替換原來的元素。如果需要進行許多異動,這種方法只強制瀏覽器重新繪製(也就是更新 DOM 的視覺表現)頁面一次
注意,複製節點不包括原始節點的任何事件處理器
var txt = document.createTextNode("->Hello World!"); //建立新文字
//將此元素搭配上述三個插入元素的方法放置相關位置
document.getElementById("p1").appendChild(txt); //插入文字
var cln = document.getElementById("p1").cloneNode(true); //複製 p1 元素
document.getElementById("p2").appendChild(cln); //插入元素
參考 W3School createTextNode():http://www.w3schools.com/jsref/met_document_createtextnode.asp
參考 W3School cloneNode():http://www.w3schools.com/jsref/met_node_clonenode.asp
刪除元素
- .removeChild( 要刪除的子元素 ): 刪除頁面的 HTML 元素,在父元素上呼叫,參數是需要刪除的子元素,引用兩個物件都是 HTML 元素,例如 getElementById() 返回的值,不能僅提供元素的 id
var div = document.getElementById('someDiv');
div.removeChild(p);
var p = document.getElementById('someP');
var parent = p.parentNode; //如果知道需要刪除的元素,但不確定其父元素,可透過 parentNode 屬性搜尋
parent.removeChild(p);
/*上述程式碼簡化*/
var p = document.getElementById('someP');
p.parentNode.removeChild(p);
參考 W3School removeChild():http://www.w3schools.com/jsref/met_node_removechild.asp
4JavaScript 與 CSS
「主瀏覽器」和「漸進式改善」的定義都設及三個組成部分:JavaScript、DOM 操作、CSS。儘管 JavaScript 和 CSS 是兩種不同的技術,在 Web 瀏覽器扮演不同的角色,但可組合兩者,以便改進使用者體驗。
引用個別樣式
取得瀏覽器元素之後,便可透過 style 屬性拿到元素目前的 CSS 樣式。
- .style: style 屬性對於
height、width、backgroundColor 等各種樣式有單獨的屬性,為特定樣式賦予新值,就能改變任何元素樣式
注意針對所有屬性,必須使用「駝峰式大小寫」,包括帶有 dash 符號的屬性也是如此,其次,以 JavaScript 設定大小時,每次都必須指定大小的單位。
var elem = document.getElementById('someElement');
elem.style.fontSize = '10em';
style 屬性指派的直被當作「內嵌樣式」(inline style),它將優先於其他地方定義的樣式規則,這表示當讀取 style 屬性值時,它反應的只是內嵌樣式,而非所有適用的樣式。
- .currentStyle: 只有 IE 和 Opera 支援
- .getComputedStyle( 指定元素 , 偽元素 ): 對於不支援
currentStyle 的瀏覽器,可以改用 window 物件的 getComputedStyle() 方法
var elem = document.getElementById('someElement');
var elementStyle = elem.currentStyle; //只支援 IE 和 Opera
var elementStyle = window.getComputedStyle(elem); //針對不支援 currentStyle 的瀏覽器
elementStyle['display']; //getComputedStyle()返回值作關聯陣列,回傳 inline
elementStyle.display; //getComputedStyle()返回值作物件,回傳 inline
getComputedStyle() 方法的參數是元素的參照,返回值可以當做關聯陣列或物件來處理
注意,這兩種方法只能讀取,不能修改樣式,而且,返回的大小帶有單位:10em,而不是 10
var p1 = document.getElementById('p1');
p1.style.width = '100px'
document.getElementById('span1').innerText = p1.style.width
var p2 = document.getElementById('p2');
if (p2.currentStyle) {
document.getElementById('span2').innerText = p2.currentStyle.width
}
var p3 = document.getElementById('p3');
if (window.getComputedStyle) {
var p3Style = window.getComputedStyle(p3);
document.getElementById('span3').innerText = p3Style.width
}
引用樣式表
一種和使用個別樣式不同的替代方案是
操作整份頁面的樣式表,
document.styleSheets 屬性存放頁面所用的樣式表清單,包括外部的 CSS 檔案和內部定義的樣式。如果 style HTML 元素使用 id 屬性,那麼還可以
document.getElmentById() 引用單獨樣式表:
<link rel="stylesheet" id="mainStyleSheet" href="css/style.css">
var mainCSS = document.getElementById('mainStyleSheet');
一旦取得樣式表,能使用以下屬性取得或改變樣式表內容:
mainCSS.src; //回應 css/style.css
mainCSS.disable = 'disabled'; //停用樣式表
以下方法改變樣式表規則:
- .insertRule( 新規則 , 索引值 ): 建立新規則,並指出新規則插入樣式表的位置(for IE 以外的瀏覽器)
- .deleteRule( 索引值 ): 提供規則地索引位置,變能刪除樣式表規則(for IE 以外的瀏覽器)
- .addRule( 選擇器 , 新規則 , 索引值 ): 建立新規則,並指出新規則插入樣式表的位置(for IE)
- .removeRule( 索引值 ): 提供規則地索引位置,變能刪除樣式表規則(for IE)
因 CSS 規則的應用根據順序而有差別,所以索引位置非常重要
mainCSS.insertRule('.hide: {visibility: none;}', 50);
mainCSS.deleteRule(5);
mainCSS.addRule('.hide', '{visibility: none;}', 50);
一般來說,建議固定 CSS 規則並保持恆定,但是對於進階、以 CSS 驅動的網站來說,使用 JavaScript 操作規則是合理的。
舉一個漸進式改善的例子,可先建立一個頁面,預設禁用樣式表,僅在支援 JavaScript 時啟用,或者可透過
createElement() 建立 style 類型的元素,動態產生一組新的樣式規則:
var s = document.createElement('style');
s.innerText = 'body { font-size: 90%; font-family: arial, sans-serif;}'; //建立樣式內容
document.body.appendChild(s); //樣式元素作為頁面主體的子元素,新增到頁面上
5使用 Cookie
Cookie 是 Netscape 發明的另一項技術,已經成為動態網站不可或缺的一部分。通常用於瀏覽器請求 Web 頁面的超本文傳輸協定(HyperText Transfer Protocol, HTTP),其為一種無狀態的協定,也就是說,當從頁面 A 轉到相同網站的頁面 B 時,伺服器無法分辨兩個頁面得使用者是同一人。這是一個問題,沒有維護狀態的機制,也就無法管理使用者(亦即登入和註冊地能力)以及自訂內容顯示,因此電子商務上的用途變受到限制。幸運的是,有兩種方法能夠維護狀態:使用會話(Session)或者 Cookie。會話只能透過伺服器技術完成,因為其資料儲存在伺服器上。而 Cookie 則可利用伺服端技術或瀏覽器的 JavaScript 來管理。
Cookie 就是存放在瀏覽器與特定網站相關的資訊「即時貼」。當訪客連結一個網站時,
網站伺服器會發送一個 Cookie 至使用者的瀏覽器。瀏覽器儲存 Cookie 之後,後續的網頁存取都會回傳給伺服器。重要的是,Cookie 僅從原來接收它的機器上發回給相同的伺服器,即便在同一個伺服器,但不同網域,也無法讀取,因此這種限制有著安全和性能的雙重好處。
Cookie 包含多個互不相關的資訊片段:
- 名稱
- 值
- 到期日期和時間
- 有效路徑(預設為目前路徑)
- 網域(預設為目前主機)
到期日期和時間必須格式化為 UTC 字串。路徑是 Cookie 在伺服器上的有效目錄,例如,如果網址為 www.example.com,「/」路徑使 Cookie 可以從根目錄和任何子目錄讀取。倘若路徑為 /forums,則伺服器只能再使用者存取 www.example.com/forums(包括其子目錄)下的頁面時,才能讀到此 Cookie。
使用 Cookie 所需考慮的要點
不管用於 JavaScript 還是伺服端技術,Cookie 都非常方便,但也有其侷限性。首先,使用者電腦上很容易看到 Cookie,所以絕不能存放敏感資訊。其次,Cookie 很容易被終端使用者操作,所以伺服器 ── 網站 ── 應該始終驗證 Cookie 值,並謹慎使用。
綜合上述,建議在 JavaScript 中像對 JavaScript 本身那樣對待 Cookie:視為向使用者提供強化功能的一種方式。注重安全的應用程式應該始終使用由伺服端技術實作的會話。
建立 Cookie
JavaScript 的 Cookie 透過 document 物件的 cookie 屬性取得。下面的程式能夠建立一個 Cookie:
document.cookie = value;
Cookie 值必須使用特定的語法,這種語法以 cookieName=cookieValue 開始。例如,底下的 Cookie 用來儲存使用者偏愛的字體大小:
(這種發送的 Cookie 有預設的到期時間、路徑和網域)
document.cookie = 'fontSize=14';
在 Cookie 值內新增一個分號,後面加上 Cookie 屬性 expires、path、domain 及內容,便可修改這些值:
document.cookie = 'fontSize=14;expires=' + someDate.toGMTString() + ';path=/subdirectory;domain=*.example.com';
可以使用不同的 Cookie 名稱,設定多個 Cookie,將其指派給相同的 document.cookie 屬性:
document.cookie = 'fontSize=14';
document.cookie = 'color=3C9';
上述做法是有效的,因為 document.cookie 是特殊的屬性,每一次的賦值都新增到 Cookie 列表中,而不會替換之前的 Cookie。
window.navigator.cookieEnabled 屬性則說明瀏覽器是否啟用 Cookie。
讀取 Cookie
一旦在 JavaScript 設定 Cookie,就可以開始讀取。無論設定幾個 Cookie,document.cookie 屬性都代表所有可用的 Cookie。
多個 Cookie 之間用分號隔開,格式為
name=value,為了讀取每個 Cookie,必須先拆解字串,接著以一個 for 迴圈遍覽 Cookie:
var cookie = document.cookie.split(';');
for(var i=0, count = cookie.length; i< count; i++){
//cookie[i] 就是 name = value
}
for 迴圈先根據等號,將個別的 Cookie 值(name=value 組合)分解為單獨的部分。注意,無法存取單獨的 Cookie 屬性 ── 到期時間、路徑和網域,因為存取 Cookie 本身就說明 Cookie 尚未到期,可用於目前路徑和網域中的頁面。
PHP 腳本發送的 Cookie,在載入另一個頁面或重新載入同一頁面之前無法存取,與此不同,可立即讀取 JavaScript 的 cookie。
刪除 Cookie
在 JavaScript 中刪除 Cookie 只需要設定一個同名 Cookie,但不提供 Cookie 值,並將到期時間填入過去時刻 ── 如 UNIX 時間戳記:
document.cookie = 'fontSize=;expires=Thu, 01-Jan-1970 00:00:01 GMT';
為了達到最佳的可靠性,路徑和網域的內容應該與建立 Cookie 時相同。
建立 Cookie 程式庫
var COOKIE = { //建立名為 COOKIE 的物件,腳本中唯一的全域變數
//定義 setCookie() 方法,三個參數分別是 Cookie 名稱、值、到期時間
setCookie: function(name, value, expire) {
'use strict';
//首先建立一個用於完整值的字串,並指派給 document.cookie(實際上需加上驗證步驟)
//encodeURIComponent() 函數避開可能有問題的字元,並預防伺服器問題,用來將值安全儲存在 Cookie
var str = encodeURIComponent(name) + '=' + encodeURIComponent(value);
str += ';expires=' + expire.toGMTString();
document.cookie = str;
},
//定義 getCookie() 方法,一個參數為 Cookie 名稱,函數返回該 Cookie 的值
getCookie: function(name) {
'use strict';
var len = name.length;
//按照分號拆解 Cookie 值
var cookies = document.cookie.split(';');
//遍覽 Cookie
for (var i = 0, count = cookies.length; i < count; i++) {
//有些瀏覽器會在 document.cookie 中的兩個 Cookie 之間加上一個空格,必須避免這個空格
var value = (cookies[i].slice(0,1) == ' ') ? cookies[i].slice(1) : cookies[i];
//在發送時將會進行編碼,所以需使用對應的 decodeURIComponent() 函數解碼 value 變數
value = decodeURIComponent(value);
//如果是目前的 Cookie 是腳本所需的 Cookie 值相同,則返回 Cookie 值
if (value.slice(0,len) == name) {
return value.split('=')[1];
}
}
return false;
},
//定義 deleteCookie() 方法
deleteCookie: function(name) {
'use strict';
//刪除 Cookie 名稱
document.cookie = encodeURIComponent(name) + '=;expires=Thu, 01-Jan-1970 00:00:01 GMT';
}
};
參考 MSDN encodeURIComponent 函式:https://msdn.microsoft.com/zh-tw/library/aeh9cef7(v=vs.94).aspx
參考 MSDN decodeURIComponent 函式:https://msdn.microsoft.com/zh-tw/library/91b80x6x(v=vs.94).aspx
使用 Cookie 程式庫
實作例子,建立一套系統,讓使用者挑選頁面的主題,這個實例是使用者剛進入頁面時不套用任何 CSS,而是當點擊某個連結時,不但改變目前頁面主題,還發送一個 Cookie,後續的存取才能使用相同的 CSS,結合了事件處理、阻止瀏覽器預設行為、Cookie 和 DOM 的操作:
//定義 setTheme() 函數,一個參數代表使用者選擇的主題(樣式表)
function setTheme(theme) {
'use strict';
var css = null;
if (document.getElementById('cssTheme')) { //如果已經存在 link 元素,則直接更新
css = document.getElementById('cssTheme');
css.href = '../css/' + theme + '.css';
} else { //如果不存在 link 元素,則自行建立
css = document.createElement('link');
css.rel = 'stylesheet';
css.href = '../css/' + theme + '.css';
css.id = 'cssTheme';
document.head.appendChild(css);
}
}
//定義 setThemeCookie() 函數,將於使用者點擊連結時呼叫,在 Cookie 中儲存選中的主題
//該 Cookie 將被更新為從目前開始的一週內到期,然後更新頁面,一週內將保存使用者偏好設定
function setThemeCookie(e) {
'use strict';
//為了解點擊的連結,需一個針對事件和事件目標的參照
if (typeof e == 'undefined') e = window.event;
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
var target = e.target || e.srcElement;
//設定 Cookie
var expire = new Date(); //今天
expire.setDate(expire.getDate() + 7); //一週後
COOKIE.setCookie('theme', target.id, expire);
setTheme(target.id); //更新主題
return false; //阻止預設行為
}
//建立一個函數處理視窗的載入事件
window.onload = function() {
'use strict';
//新增點擊事件處理器
document.getElementById('aTheme').onclick = setThemeCookie;
document.getElementById('bTheme').onclick = setThemeCookie;
//讀取 Cookie 並設定相關的主題
var theme = COOKIE.getCookie('theme'); //取得主題的 cookie 值
if (theme) { //如果之前已儲存一個主題,頁面必須更新為所選主題
setThemeCookie(theme);
}
};
參考 W3School JavaScript Cookies:http://www.w3schools.com/js/js_cookies.asp
6使用計時器
瀏覽器提供另一種功能是「建立計時器」,此處指的是「倒數計時器」,透過它便能告知 JavaScript
在特定時間間隔之後執行某些程式。
- setTimeout( 函數 , 毫秒數 ): 建立一次性的計時器
- setInterval( 函數 , 毫秒數 ): 建立重複計時器,為
setTimeout() 的變形,指定的時間間隔重複呼叫函數
setTimeout(function(){
alert('It has been 2000 milliseconds');
}, 2000);
setInterval(function(){
alert('It has been 2000 milliseconds');
}, 2000);
任何時候都應該有最終停止計時器的程式碼,首先需要將
setInterval() 或
setTimeout() 執行結果指派給一個變數:
- clearTimeout( 變數 ): 終止
setTimeout() 計時器
- clearInterval( 變數 ): 終止
setInterval() 計時器
以
setTimeout() 建立的計時器,也可以透過將呼叫動作賦值給一個變數,然後以該變數作為參數執行
clearInterval() 函數來停止。
var interval = setInterval(doThis, 3000);
var n = 0;
function doThis(){
document.write('This is alert #' + n + '
');
n++;
if(n == 5) clearInterval(interval); //總共會跑 5 次,共 15 秒
}
參考 W3School setTimeout():http://www.w3schools.com/jsref/met_win_settimeout.asp
參考 W3School setInterval():http://www.w3schools.com/jsref/met_win_setinterval.asp
參考 W3School clearTimeout():http://www.w3schools.com/jsref/met_win_cleartimeout.asp
參考 W3School clearInterval():http://www.w3schools.com/jsref/met_win_clearinterval.asp
舉例:淡出效果
設定計時器時有一個問題:函數
不能保證在精確的時間間隔中執行,原因是 JavaScript 使用單執行緒,也就是說一次只能做一件工作。在計時器的時間消逝的過程中,使用者或瀏覽器也可能觸發必須由其他 JavaScript 程式碼處理的事件。如果時間間隔到達時,JavaScript 正忙於處理其他事件,就必須等到空閒時才能呼叫相關的函數。任何情況下,函數都不會在時間間隔抵達之前執行,而在之後也可能無法立刻執行(意指
所有事件(包括計時器事件)都進入一個佇列,並儘快得以處理)。
計時器可用於多種不同目的,例如動畫和特效,
但是複雜的動畫不應該由計時器來完成,以下為將 DIV 的不透明度從 100% 下降到 0%,以便產生淡出效果:
var div = document.getElementById('someDiv');
var opacity = 1;
var fader = setInterval(function(){ //建立計時器
opacity -= .1; //每隔 0.1 秒,透明度降低 0.1
if(opacity >= 0){ //當透明度不低於 0 時
if(typeof div.style.opacity == 'string'){ //確認是否有 opacity 屬性
div.style.opacity = opacity; //改變透明度
}else{
div.style.filters = 'alpha(opacity=' + (opacity * 100) + ')'; //for IE
}
}else{ //當透明度等於 0 時
clearInterval(fader); //停止計時器
}
}, 100);
舉例:變更 URL 不再次執行 JavaScript
計時器的另一個用處是
自動更新內容,例如目前時間、天氣或股票價格等,此例為:後退和前進按鈕會改變 URL,但不會導致 JavaScript 再次執行。解決方案就是
採用一個計時器,再監控 hash 值的改變:
var hash = window.location.hash;
var hashWatcher = setInterval(function(){
if(window.location.hash != hash){
updatePage();
}
}, 1000);
(不)使用 eval()
eval() 函數實際上用來執行一段編寫腳本時未知的程式碼字串,只有一個字串參數,並將 JavaScript 程式碼執行,以下兩行執行結果相同:
alert('This is an alert.');
eval("alert('This is an alert.')");
絕不應該像上例一般將 eval() 用在文字形式的字串上,因直接使用 JavaScript 程式碼的效能更好,而這也是不應該使用 eval() 的原因:盲目地執行未知的程式碼極其危險。這種做法使腳本暴露在注入攻擊的危險之下:在這種攻擊中,惡意的使用者有意提供「壞」資料,希望了解某些細節或者導致損害。歷史經驗告訴我們,透過 eval() 執行的程式碼性能太差,而且難以偵錯。
不過 eval() 也有合理的用途,傳統的用法之一是將 Ajax 返回的資料轉換為 JavaScript 物件,但現今的 JavaScript 中,幾乎任何方法都比 eval() 更好,應該對呼叫它的程式碼保持懷疑態度。
參考 MSDN eval 函式:https://msdn.microsoft.com/zh-tw/library/12k71sw7(v=vs.94).aspx
DEMO MEMO