顯示具有 JavaScript 標籤的文章。 顯示所有文章
顯示具有 JavaScript 標籤的文章。 顯示所有文章

DOM修改文字節點 - innerText或textContent傻傻分不清楚

textContent=innerText?

DOM節點樹

圖片來源:www.w3schools.com

在一段JS程式碼之中,看到一個未看過的textContent屬性,查詢了一下依照MDN對textContent的說明:Gets or sets the text content of a node and its descendents.基本上它的使用方式和innerText屬性一樣。

innerText與textContent的差異?

如果你對DOM不熟悉,可以先參考這篇DOM簡介

就功能上而言,兩者是一模一樣,主要都是針對文字節點(Text Node)來進行get與set的動作。先看一下以下程式碼:

   <!DOCTYPE html>
   <html>
   <head>
   <meta charset=utf-8 />
   <title>Text Node Test</title>
   </head>
   <body>
     <p id="help1"></p>
     <p id="help2"></p>
     <p id="help3"></p>
   </body>
   </html>
  

以上只是一個簡單包含三個空白<p>元素的HTML。然後,我們撰寫JavaScript來修改文字節點的內容:

JavaScript - createElement vs createDocumentFragment

答客問

網友 Adam Lee 在 JavaScript - 程式碼壓縮與最佳化 留言指教,說我的測試程式有問題。因為文章撰寫時間有點久了,也忘了當時有沒有進行測試,那我們就來測試看看吧。

createElement與createDocumentFragment的測試程式碼。

網友 Adam Lee 已準備好一個測試程式 http://jsfiddle.net/z3caE/ 大家可以用不同瀏覽器玩玩看分數如何?

以下是我在本機端的測試程式:

ASP.NET Web API - Entity Framework(EDMX) Navigation Property引發的JSON物件循環參考錯誤

Entity Framework(EDMX) Navigation Property

上圖是一個ASP.NET Web API專案,從範例Northwind資料庫裡加入兩張有關連性的資料表到Entity Framework(EDMX)裡,很正常的,它會加入巡覽屬性(Navigation Property),當我們什麼都不修改,直接加入API Controller,例如,ProcutsController和OrderDetailsController然後建置、啟動、測試Web API服務會立即得到一個錯誤。

JSON的錯誤訊息與產生原因

完整訊息如下

實用Sublime Text 2套件整理(2012/12)

Sublime Text 2的延伸套件

文字編輯器在接觸Sublime Text 2之前大多以Notepad++為主,筆記本為輔。不過看過Kevin TsengSublime Text 2系列教學文之後,又花了一段時間來上手,發現Sublime Text 2這個又免費又付費的編輯器,實在是用程式設計師的角度來開發,而不是像一般編輯器是用編輯器的角度來開發,慢慢的喜歡上它,而且它多元化的輔助套件,和Visual Studio的擴充套件一樣,令Sublime Text 2加分不少。

以下整理我由A選到Z的套件,分類為安裝與選用,安裝是我正在安裝使用中的套件,選用是可依個人需求安裝使用。

Sublime Text 2實用安裝套件

  • Package Control

    必啟用,沒有它你就無法安裝擴充套件,快速鍵:Ctrl+Shift+P

    啟用請參考:啟用Package Control

  • BracketHighlighter

    讓你在撰寫如HTML程式碼時,能有高亮度提醒功能。請參考以下官方圖片:

    BracketHighlighter功能說明圖
  • ColorHighlighter

    當的游標移至文字中如"#FFFFFF","rgb(255,255,255)","white"會自動顯示對應的顏色。當然,用在CSS居多。

JavaScript大量元素選取器效能大PK(含HTML5)

何謂大量元素選取器

這裡所談的JavaScript大量元素選取器是指getElementsByTagName()之類的元素選取器,此類元素選取器與getElementById()不同,getElementById()只回傳一個元素物件,而大量元素選取器會回傳一個陣列物件,陣列物件中包含所有合乎規則的元素內容。在撰寫HTML5程式碼時,心裡面突然有個念頭,這些大量元素選取器(含HTML5才提供的功能)在走訪一個又大又深DOM時,效能如何?

HTML 4主角有:getElementsByName(),getElementsByTagName()

HTML 4派出兩位主角分別是getElementsByName()與getElementsByTagName()。

  • getElementsByName(屬性名稱):以元素屬性 name="value" 的value來取得所有符合名稱的元素集合。
  • getElementsByTagName(元素名稱):以元素名稱,例如"p",來取得所有p元素集合。

HTML5主角有:getElementsByClassName(),querySelectorAll()

HTML5派出兩位主角分別是getElementsByClassName()與querySelectorAll()。

  • getElementsByClassName(類別名稱):以元素屬性 class="value" 的value來取得所有符合名稱的元素集合。
  • querySelectorAll("CSS Selector"):直接透過CSS選擇器規則來進行選取,符合CSS選擇器規則(支援CSS3的虛擬類別)的元素將被選取。

又深又長的DOM

以下JavaScript程式要建立一個又深又長的DOM,以增加走訪DOM的難度。

// 先在頁面上新增一個
元素 var test = document.getElementById("test"); for (var i = 0; i < 1000; i++) { var d = document.createElement("div"); d.setAttribute("id", "div" + i); d.setAttribute("class", "div100") for (var j = 0; j <= 1000; j++) { var para = document.createElement("p"); para.setAttribute("class", "sale"); para.setAttribute("name", "sale"); var txt = document.createTextNode(j); para.appendChild(txt); d.appendChild(para); } // 讓層數加深 if (i === 0) { test.appendChild(d); } else { var dp = document.getElementById("div" + (i - 1)); dp.appendChild(d); } }

每一個div裡有1001個p元素,第1002個是div元素,再包1001個p元素,第1002個是div元素...會有1000層的深度及1001000個p元素。

// 測試取得大量元素的時間
var star_time = new Date();
var select_para = document.getElementsByName("sale");
//var select_para = document.getElementsByTagName("p");
//var select_para = document.getElementsByClassName("sale");
//var select_para = document.querySelectorAll("p");
//var select_para = document.querySelectorAll(".sale");
var end_time = new Date();

console.log("取得p元素個數: " + select_para.length);
console.log("Start: " + star_time.getMinutes() + ":" +
                        star_time.getSeconds() + ":" +
                        star_time.getMilliseconds() +
                        ", time: " + star_time.getTime());
console.log("End: " + end_time.getMinutes() + ":" +
                      end_time.getSeconds() + ":" +
                      end_time.getMilliseconds() +
                      ", time: " + end_time.getTime());

這邊讓我偷懶一下,用偷懶的寫法。這樣就可以取得每個大量元素選取器的執行時間。

從EverNote的求職需求看未來開發人員應有的技能

EverNote的求職需求

此篇靈感來自Johnny.Net的Plurk,Johnny.NET貼了一個EverNote的求職連結,從中我們可以看到一些趨勢,我簡單做個整理,各位開發者做個Check List,其中你擁有了幾項技能。

EverNote技能需求

EverNote 職位職責

EverNote技能需求職責

這裡的關鍵點有幾個:

  1. HTML5/CSS/JavaScript的應用程式

    這裡談的是應用程式,不單只是網站網頁,我會強調在application app。

  2. prototype(原型),我猜這是指JavaScript裡的原型,通常使用到prototype都已經是JavaScript裡的物件導向(OOP)。以前JavaScript可以隨便寫寫,能動就好,現在連JavaScript都開始走物件導向(OOP)。

EverNote 技能最低需求

EverNote技能最低需求

這裡的關鍵點有幾個:

那些年,我們應該瞭解的 JAVASCRIPT 小知識 - 函式 function

JavaScript 是需要被瞭解

前一篇,我們針對了物件, 數字, 字串, 陣列, 迴圈提供了一些小知識,續前篇,我們再談一些關於函式的小知識。

函式小知識

函式(function)可說是 JavaScript 裡頑皮又最有趣的…(函式)物件。還記的「萬物皆物件,萬物皆可放」這兩句話嗎。

具名函式

function add(x, y) {
    var total = x + y;
    return x + y;
}

這是一個非常完整的函式,正式名稱叫具名函式,但一般我們簡稱的函式就是具名函式:

  1. 名稱,name
  2. 參數,x 與 y
  3. 區域變數,total
  4. 運算,x + y
  5. 回傳值,return

目前為止都是中規中舉,函式是拿來呼叫的,所以我們來不正常呼叫吧!

// 傳入兩個 undefined
// undefined + undefined = NaN (就不是整數呀)
document.writeln(add());              // NaN 
document.writeln(add(5, 9, 999));     //  14,函式忽略第三個參數

第二個才是好玩的地方,JavaScript 對於多傳入的參數給省略了,但其實不是,因為我們可以在函式之內存取一個 arguments 的變數,arguments 內有傳遞給函式的所有值。這與一般程式語言的用法不同,一般程式語言的函式,你定義幾個參數就是能傳入幾個參數,頂多是依參數的不同設計為重載,但你能傳遞的參數個數還是被函式所限制,JavaScript 透過 arguments 變數的幫忙,可以把所有函式設計成接受無限的傳入值。讓我們改寫原始函式:

function avg() {
    var sum = 0;
    // 透過 arguments.length 取得要加總的次數
    for (var i = 0, total = arguments.length; i < total; i++) {
        sum += arguments[i];
    }
    return sum / total; // 平均值
}

document.writeln(avg(1,3,5,7,9,2,4,6,8,10));    // 5.5

用起來好像很不錯,但 arguments 變數有一個問題,就是它只接受用 ,(逗點)分隔的參數值,所以如果我們還想要處理來自陣列的值,我們就要再寫一個函式來處理陣列:

// arr 為陣列
function avgArray(arr) {
    var sum = 0;
    // 透過 arr.length 取的要加總的次數
    for (var i = 0, total = arr.length; i < total; i++) {
        sum += arr[i];
    }
    return sum / total;
}

var arr = [2,4,6,8,10,1,3,5,7,9];
document.writeln(avgArray(arr));    // 5.5

明明是一樣的事,為什麼要做兩分工呢?還好,函式也是物件,要讓 arguments 變數也可以接受陣列值,最要一點小技巧,我們必須使用到一個函式物件所提供的方法 apply()

// Function.apply(thisArg, arrayArg);
// thisArg, 物件內部的 this 會等於 thisArg;
// arrayArg,傳入一個陣列的參數
var arr = [2,4,6,8,10,1,3,5,7,9];
document.writeln(avg.apply(null, arr)); // 5.5

這邊我們先關注在 ArrayArg 這個參數就好,這行 avg.apply(null, arr); 翻譯成白話就是「執行 avg 函式物件,而且幫傳入二個參數,一個給 this 使用,另一個是陣列的參數」,經過如此改良之後,arguments 變數就看得懂傳入陣列是參數。或許這樣想,經過 Function.apply() 的幫忙,它把我們要傳入的陣列 [2,4,6,8,10,1,3,5,7,9] 變成 2,4,6,8,10,1,3,5,7,9 (去除陣列實字符號[])然後傳入。

還有一個函式物件方法為 call(),用法上與 apply() 相似,差異只有第二個陣列參數,我們必須一個一個傳入,例如:avg.call(null, 1,3,5,7,9,2,4,6,8,10)

那些年,我們應該瞭解的 JavaScript 小知識 - 物件, 數字, 字串, 陣列, 迴圈

JavaScript 是需要被瞭解

JavaScript 是一種非常方便就能上手及使用的程式語言,但真正瞭解 JavaScript 就不多了。借由 HTML5 的推助,JavaScript 再一次站上主角,第一次是 XMLHttpRequest 的 AJAX 應用,而 HTML5 滿滿的 API 全部都必須使用 JavaScript 撰寫來實現。我們必須更進一瞭解 JavaScript,而不只是簡單帶過。

這一篇會介紹 JavaScript 裡一些小知識,包含物件,數字,字串,陣列,迴圈等。

萬物皆物件

在 JavaScript 的世界裡,除了非常少數核心部分,廣義而言,可以說萬物階物件(Object),我們常看和不常看的型態都是一種物件,例如:

  • Number (數字是物件)
  • String (字串是物件)
  • Boolean (布林是物件)
  • Object (物件是物件)
    • Function (函式是物件)
    • Array (陣列是物件)
    • Date (日期是物件)
    • regExp (正規表達式是物件)
  • Null (空是物件)
  • Undefined (未定義是物件)
  • Error (錯誤是物件)

好多物件,這和我們學習的正規程式語言有很大差異,但沒關係,當你在學習 JavaScript 時,如果有卡卡的,看不懂的地方,回想這句話:「萬物階物件」,用物件的角度來看事情,先不要管它是個什麼東西,它就物件,很多時候問題會簡單很多。

數字小知識

第一件事,在 JavaScript 中沒有整數這件事。在規範中 JavaScript 數字是一個「雙精確度 64位元格式 IEEE745 值(double-precision 64-bit binary format IEEE 754 value)」,所以在做小數點大數運算時必須小心。例如,「0.1 + 0.2」或「一個 16位數或超過 16位數」的數值。

第二件事,在進行 parseInt() 函式將字串轉成整數時,一定要指定第二個參數,第二個參數指定進位數

var a = 0.1 + 0.2;
document.writeln(a); // 0.30000000000000004

var b = 9999999999999998;
document.writeln(b);

var c = 9999999999999999;
document.writeln(c); // 10000000000000000 

var d = parseInt("014");
document.writeln(d); // 12,果然有問題

var e = parseInt("014", 10);
document.writeln(e); // 14,你在貪污呀,抓到了吧

var f = parseInt("014", 16);
document.writeln(f); // 20,這是洗錢手法之一

以上程式碼純屬程式,沒有其他用意。@_@

Visual Studio 2012 RC 中讓撰寫JavaScript與.NET程式碼使用相同自動完成鍵

從HTML5 API說起

近來,把許多心力放在 HTML5 的學習使用上,學過 HTML5 的人都知道,如果只是語意標籤(article, section, nav, footer),幾分鐘就可以學完,就幾個語意標籤如何造成整個網路上的大浪潮?當然不可能,重點在HTML5 背後整個大量且實用的 API,而這些 API 有非常大量的都是透過 JavaScript 實作出來,也就是說,你必須透過 JavaScript 來調用這些 HTML5 API 來使用。問題就在這裡,我在 Visual Studio 裡寫 JavaScript 的時間與機會大大增加。

撰寫 .NET Framework 的自動完成鍵

我們在 Visual Studio 寫 .NET Framework 程式碼,應該很少人會把每行每字都完整打出來,絕大部份都是透過 IntelliSense 所提供的自動完成功能,例如,【 Dim a As inte 】應該就會跳出提示:

.NET Framework 程式碼 IntelliSense 功能

然後我們會按下【空白鍵, space】,來完成整個【Integer】這個關鍵字。這是我們非常習慣的程式碼撰寫方式。

撰寫 JavaScript 的自動完成鍵

我要先給 Visual Studio 2012 RC 拍拍手,這一版本真的是我目前為止使用撰寫 JavaScript 最好的工具。不過在 Visual Studio 2012 RC 裡寫 JavaScript 一直有問題困擾我,舉例說明,我輸入【 func

JavaScript 程式碼 IntelliSense 功能

我們很順就給它按【空白鍵, space】,結果是給你一個空白!變成了【 func 】(最後面是一個空白),而正確的使用方式是按【 Enter 或 Tab 】。

這兩個使用方式的不同步,常常讓我浪費許多時間,變成在寫 .NET 按 Enter,在寫 JavaScript 按 space,時常錯亂。是我人老手眼腦不協調嗎?想說,是否可以同步一下這兩個 IntelliSense 的使用方式。看了一下Visual Studio 2012 RC 裡的設定:

看起來也沒有特別可調整的地方。想說上 connect 反應一下好了,好久沒上 connect(沒bug上來做什麼) 好像有些不一樣了,多了一個【Submit Idea】,看起來是要區分【Bug】與【建議】,很好很好,我這應該不算 Bug,所以就到 Idea 寫個建議 (各位無聊可以上去幫忙投票,每人可以投三票,小弟英文不好,如有寫錯或表達不清的地方,請指教)。

解決撰寫 JavaScript IntelliSense 的問題

HTML5 完美風暴推薦序

XHTML 騙了我好多年

直到最近,我才知道,原來 XHTML 騙了我好多年,我們寫出來的程式,從來就沒有成為 XHTML 的一天,但一直以為只要我把那非常長的 DOCTYPE 加入,然後使用良好格式(well-formated)來撰寫,並且只要通過 w3 的驗證器認證,就可以大聲說,我很厲害,而且還可以把 w3 官方 XHTML Logo 放在網頁上,以示眾人。

HTML5 完美風暴封面
來源:呂高旭作者點部落 - HTML5 完美風暴封面

JavaScript - 程式碼壓縮與最佳化

壓縮程式碼

在正式上線前,可以將JavaScript程式碼進行壓縮,減少檔案大小,讓使用者下載更快,也節省頻寬。方法有手動與自動兩種。

手動(效果不好):

  • 刪除「長變數名」、「註解」、「空格」、「換行」…
  • true改用1, false改用0

缺點,易產生錯誤。

自動(建議使用):

使用工具,自動化產生壓縮式的JavaScript檔案:


JavaScript自動化壓縮工具還有很多,各位可以自行上網找一套自己喜歡的來用。未來新的 Visual Studio 11 / .NET Framework 4.5 會將自動化壓縮最佳化 JavaScript 與 CSS 等特性加入。

JavaScript 程式碼最佳化

這個部份提出的都是自動化壓縮工具比較幫不上忙,有些是邏輯技巧,有些是程式技巧,順序上,應該先進行內部程式碼最佳化,然後再使用壓縮工具來加速提升。

宣告變數

JavaScript中未宣告的變數(函式中也一樣)預設為全域變數,全域變數只有在瀏覽器關閉後才釋放。

name = Bruce;

使用內建函數

內建函數都已編譯過,執行速度會比即時編譯的JavaScript快很多。

巢狀if

將出現率最多的情況放在前面,另外,使用switch語句替代巢狀if/else if。

整合同性質工作

指令碼中程式行數越少,執行時間越快,將同性質的指令碼集中一次處理,例如,例如宣告變數:

// 宣告方法一
var name="Bruce";
var age = 18;
var birthday = new Date(2010/10/10);

// 宣告方法二
var name="Bruce", age=18,birthday=new Date(2010/10/10);

節約使用DOM

JavaScript對DOM的處理可能是最耗時費力,盡量減少DOM的操作,例如,為ul新增10個li,我們的處理方式可以有:

var oUL = document.getElementById("ulItem");
for (var i=0; i<10; i++){
  var oLI = document.createElement("li");
  oUL.appendChild(oLI); //10次
  oLI.appendChild(document.createTextNode("Item" + i)); //10次
}

共執行20次DOM操作。其實我們可以有更好的選擇:

var oUL = document.getElementById("ulItem");
var oTemp = document.createDocumentFragment();
for (var i=0; i<10; i++){
  var oLI = document.createElement("li");
  oLI.appendChild(document.createTextNode("Item" + i));
  oTemp.appendChild(oLI);
}
oUL.appendChild(oTemp); //1次

共執行1次DOM操作。20 : 1,相信那段程式碼效率一眼就可以看出來。

[KKBruce補充]可多利用 document.createDocumentFragment 來最佳化相關程式碼。

最佳化的影響

壓縮程式碼與程式碼最佳化通常會大大影響「可讀性」,不佳的可讀性讓後續「維護」困難度升高,所以正常做法是分為上線和開發兩個版本。

  • 上線:使用壓縮及最佳化,加速下載速度,提高執行效能。
  • 開發:保持良好程式架構和註解等,提高可讀性及減低維護難度。

JavaScript - 除錯, Debug

常見錯誤有:
  1. 語法錯誤(syntax error)
  2. 邏輯錯誤(logic error)
  3. 執行錯誤(runtime error)

除蟲清單:
  • 確定「括號( )」成對出現
  • 確定程式區塊的「大括號{ }」成對出現
  • 避免打錯字,使用有關鍵字提示功能IDE開發工具
  • JavaScript有大小寫之分
  • 字串與變數連接錯誤,例如,多或少+連接符號
  • 單、雙引號,維持一致使用方式
  • 單、雙引號,沒有成對出現也是常見錯誤
  • 確定物件在使用前已經建立
  • 別對區域變數與全域變數取相同的名稱
  • 變數不存在
  • 等號(=)與比較符號(==)使用錯誤
  • 記得要結束";"

bug處理:
  • alert()與document.write()方法
  • 使用window.onerror事件
  • try ... catch
  • Firebug for Firefox

目前主流且最新版本的瀏覽器(IE9, Chrome 16)都內建不錯的除錯功能,也是另一種選擇。

JavaScript - 物件導向(Object-oriented Language, OOP)

Object-oriented Language

Object-oriented Language,物件導向程式語言,特性有:

  1. 封裝Encapsulation
  2. 繼承Inheritance
  3. 多形Polymorphism

Object(物件)

讓正真有需要的原始碼接觸資料,減少資料曝露的程度,讓程式碼再利用。物件結合資料與行為,建立一種新的資料型別,其中可以儲存(store)資料,也可以根據資料而行動(act),所以物件是個容器,其中儲存資料,並聯繫資料與依據資料行動的原始碼。物件(Object)在一個儲存容器內聯繫起變數與函式。

資料(變數)+行為(函式)=物件

物件內的資料稱「property」(屬性、特性),物件內的行為稱「method」(方法),

property + method = Object

.(點)

點號運算子('.' dot operator)用於取用物件property與method,

Object + . + property/method

例如,

// mail 物件
// . 運算子
// from, to, send 方法
mail.from = "...";
mail.to = "...";
...
mail.send();

物件可以包含其他物件,例如,

var mail = new Mail(new Date(), "from", ...);
在Mail建構式中傳入一個Date物件。

建構式(constructor) - 屬性(property)

物件具有資料,資料必須在物件建立時「初始化」,每個自訂物件都需要自己的建構式,名稱與「物件相同」。

function Mail(from,to,body,subject){
  this.from = from;
  this.to = to;
  this.body = body;
  this.subject = subject;
}

建構式使用首字母大寫,建立property需使用JavaScript的關鍵字「this」,關鍵字this區分物件property與一般變數。this指定物件property的擁有權,同時設定property的初始值建立屬於「這個(this)」物件的property,例如,

this.from = from;

將收到的參數指定給新property。沒有this,建構式不會知道你正在建立物件property。以建構式建立物件時,我們使用new運算子,它呼叫物件的建構式,開啟物件建立過程。例如,

var mail = new Mail("from","to","body","subject");

  1. var mail:新物件儲存在變數裡
  2. new Mail(...):new運算子建立新Mail物件(呼叫建構式)
  3. "from","to","body",...:傳入參數給建構式,以設定property

建構式(constructor) - 方法(method)

function Mail(from,to,body,subject){
  // Object method
  this.send = function(){
  ...
  }
}

該如何知道指令碼(函式)應放入method?

method就是根據物件的property而採取某些行動,換言之,如果指令碼需要取用物件內部資料,就非常合適放入method。

把函式轉為方法

  1. 宣告method
  2. 把現有程式碼移入method裡
  3. 修改使用物件property

宣告method,使用this建立method,與建立property類似。例如,

this.send = function(){
  // Do something
}

關於選用參數

function Mail(from,to,body,subject,smtp,port){
  ...
  // 假設smtp與port為選用功能
  this.smtp = smtp;
  this.port = port;
}

當某個參數未傳入給function、method、constructor時,在任何試圖使用參數值的原始碼裡,它的值都是null,在缺少參數相對應的property將會被設為null。確認選用參數在function參數清單的最後面。例如:當funcion有兩個參數,可能的選擇有:

  • 兩個都傳入
  • 只傳入第一個
  • 兩個都不傳
  • 無法只傳第二個

複本

Mail物件的send()方法,Mail物件的method是在建構式裡,利用關鍵字this建立,但依此建立的Mail物件都會各有一份物件方法的複本,關鍵字"this"用於設置「instance」擁有的property和method。例如,

var mail1 = new Mail(...);
var mail2 = new Mail(...);
var mail3 = new Mail(...);

每個Mail物件(mail1,mail2,mail3),都會有自己的property與method複本,等於會有三份method,這是浪費又沒效率。property為每個物件儲存獨特資料,method應該為物件所共享。

Class與instance

類別Class,是物件的描述,樣版、藍圖。例如,

function Mail(...)

實例Instance,實際物件,依Class建立。例如,

var mail = new Mail(...);
mail是依Mail來建立的物件(instance)。

每個instance的property多半都不一樣,所以每個instance才需要有自己的property複本,但method沒有必要複製到每個instance中。

Class擁有method

class-owned instance method,當method為Class所擁有時,所有該Class的instance都可以取用此method,而不需要另外複製一份。

在Class使用prototype

在JavaScript中每個物件都有一個prototype物件(以property存在),prototype用以設定Class level(類別層級)的property與method, 而非屬於instance。也就是說,我們需要把method的「擁有權」指派給Class,而不是指派小單一instance,例如,

Mail.prototype.send = function(){}

prototype使用需在建構式之外。send()方法直接附加到Mail class本身,而非隸屬於某個instance,這樣無論Mail class建立多少份instance,都只會有一份send()方法。當send()接受呼叫時,將在class內執行,例如,

mail1.send();
mail2.send();
mail3.send();

當其他Mail物件的instance也呼叫了send()方法,仍會執行Class內的同一方法。

class property
類別屬性,此property只需要在類別儲存一次,但能被所有instance存取,例如,

Mail.prototype.smtp = "smtp.kkbruce.net";
Mail.prototype.port = "25";

此property只有一個值,讓所有instance共享。想成,它是class的全域變數,但只能讓class對應的instance取用。JavaScript標準物件能也使用prototype,每個物件都有prototype,它允許在Class Level增加property與method,例如,擴充Date物件,

Date.prototype.shortFormat = function(){
  return this.getFullYear() +'/'+(this.getMonth()+1)+'/'+ this.getDate();
}

var d = new Date();
d.shortFormat();
這樣即可傳回「年/月/日」格式日期函式。

Class自己的method

類別方法(class method),它屬於Class,只能取用class property,不能取用instance property,建立class method是直接為Class設定method,而不使用prototype,例如,

Mail.showSMTP = function(){
  alert('SMTP:'+Mail.prototype.smtp+' ,\n Port:'+Mail.prototype.port);
}
...
Mail.showSMTP();
  • Mail.showSMTP:建立Mail物件的showSMTP方法
  • Mail.prototype.smtp:為了從class method取用class property,你必須下探至prototype,smtp是個Mail class property,所以Mail class method可以取用
  • Mail.showSMTP();:直接透過Class名稱Mail來呼叫方法

再回建構式

讓建構式專注在property的建立與初始化 OOP範例:AJAX OOP的寫法

function AjaxRequest() {
  if (window.XMLHttpRequest) {
    try {
      this.request = new XMLHttpRequest();
    } catch(e) {
      this.request = null;
    }
  } else if (window.ActiveXObject) {
      try {
        this.request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch(e) {
        this.request = null;
      }
  }

if (this.request == null)
    alert("建立Ajax物件錯誤.\n" + "細節:" + e.message);
}

AjaxRequest.prototype.send = function(type, url, handler, postDataType, postData) {
  //type, url, handler為get必要參數
  //postDataType, postData為post必要參數
  if (this.request != null) {
    this.request.abort();

    url += "?dummy=" + new Date().getTime();

    try {
      this.request.onreadystatechange = handler;
      this.request.open(type, url, true); 
      if (type.toLowerCase() == "get") {
        this.request.send(null);
      } else {
        this.request.setRequestHeader("Content-Type", postDataType);
        this.request.send(postData);
      }
    } catch(e) {
      alert("Ajax程式錯誤.\n" + "細節:" + e);
    }
  }
}

AjaxRequest.prototype.getReadyState = function() {
  return this.request.readyState;
}

AjaxRequest.prototype.getStatus = function() {
  return this.request.status;
}

AjaxRequest.prototype.getResponseText = function() {
  return this.request.responseText;
}

AjaxRequest.prototype.getResponseXML = function() {
  return this.request.responseXML;
}

宣告AjaxRequest物件,

var ajax = new AjaxRequest();

JavaScript - Asynchronous JavaScript And XML, AJAX

同步與非同步

同步HTTP請求:請求 → 顯示 → 互動 → 暫停 → 請求 → 顯示 → 互動 → 暫停 …

需要不斷重新刷新整個頁面,會出現一段時間的空白。

非同步HTTP請求:請求 → 顯示 → 互動 → (請求)互動 → (請求)互動 …

非同步HTTP請求在顯示、執行互動不需要再暫停操作,可以馬上執行所需的互動,只向伺服器要求一小部份資料,然後更新頁面的一小部份, 不需要重新刷新整個頁面,也不會出現一段時間的空白。

非同步好處

  1. 減輕伺服器的負擔,加快瀏覽速度(只給所需)
  2. 帶來更好的用戶體驗(更流暢的畫面)
  3. 標準化技術,主流瀏覽器都支援
  4. 促進頁面呈現與資料分離(提高可維護性)

AJAX組成

AJAX不是單一技術,是集4種技術而成,JavaScript、CSS、DOM、XMLHttpRequest。(或說5種,再加上(X)HTML)

JavaScript AJAX技術組成
名稱說明
JavaScript通用指令碼語言。是撰寫AJAX主要程式語言
CSS為Web頁面元素提供視覺化樣式的定義方法, AJAX透過修改CSS樣式,改變使用者介面
DOMAJAX可以在執行時透過DOM來改變使用者介面,或局部更新某個節點
XMLHttpRequestXMLHttpRequest物件允許Web開發人員向伺服器以非同步方式取資料, 資料格式通常是XML或是文字,目前較流行為JSON

XMLHttpRequest物件

能夠直接在Client使用Javascript來提出HTTP請求。IE內建MSXML剖析器有多個版本,其提供的XMLHttpReqest物件也有多個版本(不建議使用)。

KKBruce提醒,除非您必須處理類似IE等相容性問題,不然是不建議使用以下表格內容。

JavaScript IE內建MSXML剖析器
版本DLL名稱ProgID
2.0(IE5)msxml.dllMicrosoft.XMLHttp
2.6msxml2.dllMSXML2.XMLHttp
3.0(IE6)msxml3.dllMSXML2.XMLHttp.3.0
4.0msxml4.dllMSXML2.XMLHttp.4.0
5.0msxml5.dllMSXML2.XMLHttp.5.0
6.0msxml6.dllMSXML2.XMLHttp.6.0

建立XMLHttpRequest物件

在Mozilla,Chrome和Safaris等主流瀏覽器是內建物件,例如,

// 1. 透過 new XMLHttpRequest() 產生物件
// 2. 注意大小寫
var xmlHttp=new XMLHttpRequest();

在IE6之前是透過ActiveX控制項物件(由MSXML提供),例如,

var xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");

IE7之後,同時支援原生XMLHttpRequest物件(預設值)和ActiveX控制項物件,例如,

var xmlHttp;
function createXMLHttpRequest(){
  if (window.XMLHttpRequest) //支援原生型
    xmlHttp = new XMLHttpRequest();
  else if (window.ActiveXObject) //IE6以下
    xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}

[KKBruce補充] 建立XMLHttpRequest物件注意事項

XMLHttpRequest物件屬性

JavaScript XMLHttpRequest物件屬性
名稱說明
readyState唯讀屬性,目前狀態
responseBody唯讀屬性,取得伺服器的回應,傳回非符號位元組的陣列
responseSteam唯讀屬性,取得伺服器的回應,傳回IStream資料流物件
responseText唯讀屬性,取得伺服器的回應,傳回字串
responseXML唯讀屬性,取得伺服器的回應,傳回XML DOM物件,可作DOM處理
status唯讀屬性,取得HTTP狀態碼
statusText唯讀屬性,取得HTTP狀態訊息字串
onreadystatechange存取屬性,執行readyState屬性改變的事件處理,設定處理的程序

readyState會回傳一數值,以表示目前處理的進度。

JavaScript readyState回傳狀態值
名稱說明
0uninitalized,未初始化
1loading,載入中
2loaded,載入完成
3interactive,可互動
4completed,完成

status會回傳狀態碼,以表示目前處理的結果。

流程 -
名稱說明
200OK,請求成功
202請求被接收,但處理未完成
400錯誤的請求
404Not Found,找不到網頁
500Internal Server Error,內部錯誤
...…其他網頁錯誤碼

[KKBruce補充] 這裡有一篇整理很棒的 HTTP 狀態碼,【HTTP 狀態碼,在 IIS 7.0 和 IIS 7.5】非常建議加入我的最愛中,在查詢錯誤的 HTTP 狀態碼時非常有用。例如,我們一般只知道 404 狀態碼是找不到,但 404 下還有整整 20 項細部的定義,這對於開發人員是不可多得的資訊,可以更快速清楚問題所在,而不是用經驗法則在處理。

XMLHttpRequest物件方法

JavaScript XMLHttpRequest物件方法
名稱說明
abort()取消目前HTTP請求
getAllResponseHeaders()取得全部HTTP標頭內容
getResponseHeader("指定Header名稱")取得指定HTTP標頭名稱的內容
open("GET|POST","URL",async,userid,pwd)開啟HTTP請求
send("參數")傳送HTTP請求到伺服器
setRequestHeader("自訂Header名稱",value)自訂HTTP標頭資料

XMLHttpRequest物件open方法參數
名稱說明
GET取得伺服器回應
POST傳送資料到伺服器
URL請求的XML文件或伺服端程式(ASP.NET, PHP, JSP...)
async布林值, 預設true,false表示需要等到伺服器回應後才繼續執行Javascript
userid, pwd伺服器需要id/password,才需要使用

send("參數")說明

參數表示要向伺服器發送的資料,參數格式為查詢字串的形式,例如,

var param = "name=Bruce&age=18";
在open中指定為GET,則參數作為查詢字串傳送,如果指定為POST,則作為HTTP的POST方法傳送,對於send()方法而言,此參數為必需, 如不發送任何資料,請傳送null即可。

// GET
xmlHttp.send(null);
// POST
xmlHttp.send(param);

setRequestHeader()說明

如果使用POST方法,在發送之前必須設定HTTP標頭,例如,

xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
// 加上UTF-8是最好
xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");

AJAX下的GET與POST

function startRequest(){
    // 建立xmlHttp物件
 createXMLHttpRequest(); 
 // 向Demo.aspx,使用GET開啟請求
 xmlHttp.open("GET","Demo.aspx");
 // 狀態有改變時,就執行
 xmlHttp.onreadystatechange = function(){
     // 當狀態值為4(完成)與狀態碼為200(請求成功)
  if(xmlHttp.readyState == 4 && xmlHttp.status == 200)
      // Do something
   alert("服務器傳回訊息: " + xmlHttp.responseText);
 }
 // 傳送HTTP請求到伺服器
 // GET記得加上null
 xmlHttp.send(null);
}

IE會自動暫存非同步的結果,解決方法是每次非同步請求的URL不同,我們可以加上一個伺服器不需要的參數,讓每次的URL都不同。

// 加上一個時間戳計當亂數使用
var url = "Demo.aspx?timestamp=" & new Date().getTime();
xmlHttp.open("GET",url, true);

GET請求如果有參數,直接放入URL網址中,例如,

var queryString = "name=Bruce&age=18";
var url = "Demo.aspx?" + queryString + "&timestamp=" + new Date().getTime();

POST請求有參數,統一放在send()中來傳送,例如,

var queryString = "name=Bruce&age=18";
var url = "Demo.aspx?timestamp=" & new Date().getTime();
// url放在open()
xmlHttp.open("POST",url,true);
...
xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
// queryString放在send()
xmlHttp.send(queryString);

POST方法中文會出現亂碼

在IE8與Firefox上測試已無此問題,但為了相容性還是建議加上解決方法。

這是因為XMLHttpRequest物件在處理傳回responseText/XML時是使用UTF-8編碼,建議使用解決方法2,對回傳的資料進行decodeURIComponent()解碼即可。

解決方法一

對要傳送的資料進行兩次encodeURI()編碼,以解決中文亂碼問題,例如,

...
xmlHttp.send(encodeURI(encodeURI(queryString)));

對傳回資料responseText/XML再進行一次decodeURI()解碼,例如,

responseDiv.innerHTML = decodeURI(xmlHttp.responseText);

解決方法二

setRequestHeader直接宣告(charset)使用UTF-8,例如,

xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
xmlHttp.send(queryString);

對傳回資料responseText/XML進行decodeURIComponent()解碼,例如,

responseDiv.innerHTML = decodeURIComponent((xmlHttp.responseXML));

解決方法三

ASP,ASP.NET: 輸出之前Response.Charset = "big5 | gb2312",依取得的頁面charset設定。

onreadystatechange方法

AJAX的重點在狀態改變後的事件處理函式(handler),也可以說,這是唯一我們必須自訂(手動)設計程式碼的地方,換個方向說,我們在請求/回應,而伺服器回應資料後,我們要如何處理及顯示到網頁上(DOM)。

同時觸發非同步問題

如果我們必須同時觸發(呼叫)多個AJAX物件,因為我們通常都是把建立的AJAX物件的xmlHttp宣告為全域變數,所以可能會產生覆蓋的情況。

解決同時觸發非同步問題

就是將xmlHttp宣告為區域變數來處理,並在收到伺服器資料且處理完後,手動刪除。

function getData(){
  var xmlHttp="";
  createXMLHttpRequest();
  xmlHttp.onreadystatechange = function(){
    ...
    // 處理完立即刪除與釋放
    delete xmlHttp;
    xmlHttp = null;
  }
}

delete關鍵字

刪除物件的property。The delete operator deletes an object, an object's property, or an element at a specified index in an array.

JavaScript - XML DOM簡介

Asynchronous JavaScript And XML, AJAX

  1. HTML/XHTML與CSS:顯示UI與相關資料
  2. XML:伺服端非同步傳遞的資料
  3. XML DOM:當Browser非同步取得XML資料後,可進一步使用Javascript和XML DOM取出所需資訊
  4. XMLHttpRequest:Javascript透過XMLHttpRequest物件來建立非同步HTTP請求

Extensible Markup Language, XML

利用特殊的標示語言(Markup Language),建立XML文件(XML Document)。

XML Document

XML Document是由元素(Elements)組成。

標籤(Tags)

XML能自己定義標籤,如:<book>、<title>…。

元素(Elements)

元素是一個完整項目:<book code="K2010">Javascipt入門</book> 開始標籤、屬性、文字內容、結尾標籤。

屬性

屬性值需要使用引號(單、雙皆可)括起。

實體參考(Entity Reference)

XML本身有一些保留符號,如文件內需要使用這些符號,請使用實體參考。

JavaScript XML實體參考
實體參考代表符號
&lt;<
&gt;>
&amp;&
&apos;'
&quot;"

註解(Comment)

與HTML相同,例如,

<!-- 註解內容 -->

PCDATA

文字內容稱PCDATA,例如,

<book code="K2010">Javascipt入門</book>

PCDATA → Javascript入門。

CDATA

Character Data,表示XML在剖析文件時不用處理此區塊的內容,例如,

<script type="text/javascript">
<![CDATA[
    function a(){
    ...
    }
]]>
</script>
CDATA的註解
在HTML解析器(Browser)是不承認CDATA開始"<![CDATA["與結束"(]]>"標籤,也不認識HTML實體參考(Entity Reference), 可能導致會把問題顯示在Browser,並可能產生"跨網站腳本"問題,所以在HTML會建議註解CDATA。

範例一:JavaScript註解
<script type="text/javascript">
// <![CDATA[
  document.write("<");
// ]]>
</script>

範例二:CSS註解

<style type="text/css">
/*<![CDATA[*/
  body { background-image: url(marble.png) }     
/*]]>*/
</style>

Processing Instructions, PI

允許XML文件包含傳送給應用程式的指令,例如開頭宣告就是PI,PI如同註解,不屬於XML文件內容。

<?xml version="1.0" encoding="utf-8" ?>

Document Type Declarations, DTD

是XML文件的驗證機制,可以檢查XML標籤與文件架構是否正確。

<!DOCTYPE book [
<!ELEMENT book (code, title, author, price)>
<!ELEMENT code (#PCDATA)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT author (zh-tw_name, zh-en_name)>
<!ELEMENT zh-tw_name (#PCDATA)>
<!ELEMENT zh-en (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>

一行一行看,不會很難。

  • <!DOCTYPE book [:book之下包含以下元素
  • <!ELEMENT book (code, title, author, price)>:元素book之下包含其他子元素
  • <!ELEMENT code (#PCDATA)>:code元素含文字內容

XHTML

Well-Format(良好格式)的HTML。什麼是Well-Format?

  1. 標籤、屬性都是小寫
  2. 一定要<html>為根元素
  3. 標籤必須有結尾標籤,空元素需以 />結束標籤
  4. 巢狀標籤不能重疉
  5. 屬性必須有值
  6. 屬性值需使用引號
  7. Script需使用CDATA

第三點,標籤必須有結尾標籤,例如,

<-- 有頭有尾 -->
<b>粗體字</b>
<-- 空元素需以 />結束標籤 -->
<input id="name" type="text" />
<br />

第五點,屬性必須有值,例如,

<-- HTML -->
<input type=radio checked>

<-- XHTML -->
<input type="radio" checked="checked" />

Document Object Model, DOM

Document Object Model,XML DOM可以將XML文件視為一棵樹狀結構的節點, XML DOM就是W3C DOM針對XML文件所提供應用程式的標準程式介面, 可以透過API來存取各節點物件,將一份結構化文件(XML Document)轉換成一棵節點(Nodes)樹, 提供節點的屬性、方法來存取節點內容,或新增、修改、刪除節點內容。例如,

<book>
  <code>K2010</code>
  <title>Javascript入門</title>
  <author>
   <zh-tw_name>金剛</zh-tw_name>
   <zh-en_name>Bruce</zh-en_name>
  </author>
</book>

經過DOM剖析器之後,如圖,

DOM樹
圖一:DOM樹


MSXML

在Javascript程式可使用IE內建的MSXML元件來建立XML DOM物件,建立物件使用ProgID字串,但MSXML物件只能在IE使用,要注意相容性問題。

KKBruce提醒:實務上已經很少人使用MSXML來撰寫程式碼,以下只是簡單介紹,大多都已經使用標準AJAX方式(XMLHttpRequest物件)來處理。

JavaScript MSXML DOM版本
版本DLL名稱ProgID
2.0msxml.dllMicrosoft.XMLDOM或MSXML.DOMDocument
2.6msxml2.dllMSXML2.DOMDocument
3.0msxml3.dllMSXML2.DOMDocument.3.0
4.0msxml4.dllMSXML2.DOMDocument.4.0
5.0msxml5.dllMSXML2.DOMDocument.5.0
6.0msxml6.dllMSXML2.DOMDocument.6.0

範例程式一(IE Only):

<table border="1">
<script type="text/javascript" >
//<![CDATA[
  // 建立XML DOM物件, 使用ProgID來建立
  var xmlDom = new ActiveXObject("MSXML2.DOMDocument.6.0");
  //async=false,可以保證XML文件能完全載入
  xmlDom.async = "false";
  // 載入XML文件
  xmlDom.load("Books.xml");
  // 建立文件元素子節點樹
  var objNode = xmlDom.documentElement.childNodes;
  // 顯示所有XML節點
  for ( i=0; i < objNode.length; i++ ) {
      document.write("<tr><td>");
      document.write(objNode.item(i).nodeName + "</td><td>");
      document.write(objNode.item(i).text + "</td></tr>");
  }
//]]>
</script>
</table>

範例程式二(IE Only):

>// 字串變數
var strXML = "<book>";
strXML = strXML + "<code>K2012</code>";
...
//載入字串變數
xmlDom.load(strXML);

先建立XMLDOM物件,然後由Books.xml當成資料來源,由XMLDOM物件讀入轉換成節點樹,再將節點讀取顯示出來。

[補充]使用MSXML2.DOMDocument.6.0與MSXML2.DOMDocument.3.0就好。

JavaScript - W3C DOM簡介

HTML DOM

DOM Core(核心)提供HTML與XML文件瀏覽、處理和維護階層架構的功能。主要有二大功能:

  • 瀏覽(Navigator):走訪各節點
  • 參考(Reference):存取節點

提供一致的走訪方式,只要建立好樹狀結構,所有能走訪到的節點就是標籤物件,文字節點都可以使用相同的nodeValue屬性更改其內容。

DOM HTML

HTML專屬的DOM API,提供現成的物件模型。需使用id/name屬性或forms[]、images[]等物件集合才能取得標籤物件, 而且每種標籤物件都擁有不同的屬性。

DOM Core可使用在HTML與XML,DOM HTML則針對HTML。

每一節點都是一個物件

  • DOM HTML提供各節點物件的屬性和方法
  • DOM Core提供屬性來走訪節點

W3C DOM只能更改文字節點的內容,其它標籤物件的屬性和方法還是來自DOM HTML。

轉換為節點樹

<table>
  <tbody>
    <tr><td>HTML</td><td>CSS</td></tr>
    <tr><td>Javascript</td><td>jQuery</td></tr>
  </tbody>
</table>

瀏覽器會將由伺服器所傳遞過來的HTML轉換為一節點樹,節點樹一定由html元素為根節點,html底下一定有兩個子點節,head元素子節點與body元素子節點。我們可以把這一層一層的關係畫成圖,此圖又稱節點(樹)圖。

節點(樹)圖

以上面table結構為例,

節點(樹)
圖一:節點(樹)

節點(樹狀)結構:


  • 下一層的子節點 → Child Node → tbody是table的Child Node
  • 上一層的父節點 → Parent Node → table是tbody的Parent Node
  • 左右節點(兄弟) → Sibling Node → tr與tr或tr之下td與td,各為Sibling Node
  • 最下層葉節點 → Leaf Node → td為Leaf Node
  • 文字節點(HTML的內容) → Text Node → HTML/CSS/Javascript/jQuery

在轉換一般table時需注意,要先將table轉換成HTML 4.0版的表格,即擁有<tbody>標籤,這樣在訪問時,才不會出錯。

另注意<font>,IE在轉換時,會在<font>後多加一個文字節點#text,目前HTML標準已不建議使用<font>標籤,請改用CSS。

在DOM中有3種節點

  1. 元素節點:element node,各種標籤便是這些元素節點的名稱,元素節點可以包含其他元素,唯一沒有被包含只有根元素<html>
  2. 屬性節點:attribute node,屬性節點總是被包含在元素節點之中,例<a href="http://blog.kkbruce.net">KKBruce Blog</a>,a是元素節點名稱,href是屬性節點名稱
  3. 文字節點:text node,標籤裡具體的文字內容,網頁最終目的是向使用者展示內容

W3C DOM屬性

JavaScript W3C DOM唯讀屬性
名稱說明類型/傳回類型
firstChild傳回第一個子節點childNodes物件集合,包含此節點下所有的子節點Node
lastChild傳回最後一個子節點的childNodes物件集合,包含此節點下所有的子節點Node
parentNode傳回父節點的物件,如已是根節點,傳回nullNode
nextSibling傳回下一個兄弟節點物件,如已是最後一個節點,傳回nullNode
previousSibling傳回上一個兄弟節點物件,如已是第一個節點,傳回nullNode
nodeName傳回節點的HTML標籤(英文大寫)名稱String
nodeType傳回節點種類,1是標籤(element node)、2是屬性(attribute node)、3為文字(text node)Number
specified布林值,傳回在HTML標籤是否有設定指定的屬性值Boolean

W3C DOM讀寫屬性

JavaScript W3C DOM讀寫屬性
名稱說明類型/傳回類型
nodeValue存取文字節點(text node),其他種類的節點傳回nullString

W3C DOM物件集合

W3C DOM物件集合可以取得下一層子節點和節點的屬性。

JavaScript W3C DOM物件集合
名稱說明類型/傳回類型
attributes節點屬性的物件集合,可以直接使用名稱來存取,僅用於元素節點NameNodeMap
childNodes所有子節點的物件集合(Array),方法item(i)可訪問第i+1個節點NodeList

在IE中可以直接使用id/name屬性來取得指定的節點物件,但用此方法實作的程式碼相容性不佳, 建議先使用getElementById("id")來取得指定的節點物件,然後再實作以確保Browser相容性。例如,

// 相容性不好
myid.childNodes[0].firstChild.nodeValue = "ASP.NET MVC";

// 相容性好
var objValue = document.getElementById("myid");
objValue.childNodes[0].firstChild.nodeValue = "ASP.NET MVC";

IE與其他Browser中childNodes走訪不同問題

我們在使用childNodes走訪節點時,IE會依順序走訪各元素節點,但其他瀏覽器不光是元素節點,連它們之間的空格也被當成子節點計算。例如,

<ul id="comicList">
 <li>航海王</li>
 <li id="Naruto">火影忍者</li>
 <li>海棉寶寶</li>
<ul>

可以使用以下JavaScript在IE與其他瀏覽器測試,

var eleUL=document.getElementById("comicList");
var listName="";
if (ele.hasChildNodes()) {
 var eleLI = eleUL.childNodes;
 for (var i=0,len=eleLI.length;i<len;i++){
  listName += eleLI[i].nodeName + "\n";
 }
 alert(listName);
}

在IE的測試結果:

  1. LI
  2. LI
  3. LI

在Firefox測試結果:

  1. #text
  2. LI
  3. #text
  4. LI
  5. #text
  6. LI

解決瀏覽器走訪不同問題

function nextSib(node){
  var tempLast = node.parentNode.lastChild; //取得node的最後一個節點
  if (node ==tempLast) { //是否為最後一個節點
    return null;
  }
  var tempObj = node.nextSibling; //非最後一個,可找下一個Node
  while ( tempObj.nodeType!=1 && tempObj.nextSibling!=null) //nodeType不是元素節點且不是最後一個,即找到元素節點為止
    tempObj = tempObj.nextSibling; //往下找下一個
  return (tempObj.nodeType==1) ? tempObj:null; //如果是元素節點,傳回節點本身,否則傳回null
}

function prevSib(node){
  var tempFirst = node.parentNode.firstChild; //取得node的第一個節點
  if (node ==tempFirst) { //是否為第一個節點
    return null;
  }
  var tempObj = node.previousSibling; //非第一個,可往上找上一個Node
  while ( tempObj.nodeType!=1 && tempObj.previousSibling!=null) //nodeType不是元素節點且不是第一個,即找到元素節點為止
    tempObj = tempObj.previousSibling; //往上找上一個
  return (tempObj.nodeType==1) ? tempObj:null; //如果是元素節點,傳回節點本身,否則傳回null
}

我們可以透過這兩個函式來走訪,而不用擔心相容性問題。

var eleLI = document.getElementById("Naruto");
var nextItem = nextSib(eleLI);
var preItem = prevSib(eleLI);
if (nextItem != null) //傳回不是null,是元素節點
...

W3C DOM方法

W3C DOM提供方法能建立、刪除、複製、交換、取代節點。

流程 -
名稱範例說明
appendChildobj(父).appendChild(obj(子))在obj(父)節點新增子節點obj(子),傳回新增的節點物件
cloneNodeobjNew = objDup.cloneNode(deep)複製objDup節點的新節點物件,deep為false僅複製此節點,true連子節點的整個節點樹都複製
createElementobjNew = document.createElement("tagName")建立一個HTML節點物件
createTextNodeobjNew = document.createTextNode(string)建立文字節點
hasChildNodesobjNode.hasChildNodes()檢查節點是否擁有子節點,有為true
insertBeforeobj(父).insertBefore(obj(子),objBrother)在obj(父)節點前插入一個子節點obj(子),位置在objBrother節點前
removeChildobj.parentNode.removeChild(targetNode)先找到要刪除的節點,透過parentNode的removeChild()方法來刪除目標節點
replaceChildobj.parentNode.replaceChild(newNode,oldNode)先找到要替換的節點,透過parentNode的replaceChild()方法來替換節點
getAttributeobj.getAttribute("attr")讀取obj的attr屬性值,例如,obj.getAttribute("title");
setAttributeobj.setAttribute("attr","value")設定obj的attr屬性的值為value,例obj.setAttribute("src","bruce.png");

insertAfter()函式

DOM只有提供insertBefore()在目標元素之前插入新元素,或是appendChild()在父元素的childNodes尾新增新元素, 如果需要在特用元素後插入新元素,需自行撰寫。

function insertAfter(newElement, targetElement){
  // 取得目標元素的上層節點
  var tParent = targetElement.parentNode;
  // 檢查是否有子元素
  if (tParent.lastChild == targetElement)
    // 附加至子節點後
    tParent.appendChild(newElement);
  else
    // 新增至節點之前
    tParent.insertBefore(newElement, targetElement.nextSibling);
}

新增HTML Tag步驟

  1. 建立節點
  2. 進行處理

建立節點

// 新增HTML節點
var objNew = document.createElement("P"); 
// 新增文字節點
var objText = document.createTextNode("新增說明文字");

進行處理

// 將objNew節點加入objNode節點最後面
objNode.appendChild(objNew);
// 將objText文字節點加入objNew節點之後
objNew.appendChild(objText);

例如,在一個空的<table>可以一層一層加上<tr>、<td>來自由動態產生你所想要的<table>。

改變節點文字的安全步驟

  1. 刪除所有子節點:removeChild()
  2. 根據新內容,建立新內容節點:createElement(),createTextNode()
  3. 將新內容節點,附加在目標節點之下:appendChild()

function replaceNodeText(id, newText){
  var node=document.getElementById(id);
  while (node.firstChild)
    node.removeChild(node.firstChild);
  node.appendChild(document.createTextNode(newText));
}

DOM與CSS樣式

DOM提供了透過節點取用樣式的途徑。

className屬性:提供對節點樣式類別的存取。

alert(ele.className);
ele.className = "highlight";

style屬性:提供對單一樣式的存取。

ele.style.visibility = "hidden";
ele.style.display = "none";

JavaScript Anchor Element

Anchor,連結,也就是HTML裡最重點的一個特性,讓我們可以在不同網頁之間瀏覽。

<a href="protocal://domain/file#bookmark" target="frame_name">context</a> 

href屬性

設定超連結的目的地。

  • protocal:通訊協定,例如,http/https/ftp
  • domain:網域名稱或IP
  • file:檔案名稱
  • #bookmark:指定HTML文件中的降落點

讓我們用範例來說明降落點:

<a id="info">TOP</p>

在網頁設定一個名稱top的降落點,然後,

<a href="news.htm#info">News</a>

我可以指定要到那一個網頁的那一降落點。如果#info是在網頁的中間,你就能發現,我們一點連結過去時,網頁會自動移至#info的位置,這也就是降落點的意思。

連結其他屬性

JavaScript 連結屬性
名稱說明
name/id名稱
type存取連結資源的Content type
accessKey快速鍵
target設定超連結目的地HTML文件的frame名稱
charset連結資源的編碼方式
hreflang連結資源的語系
tabIndexTab鍵切換的順序值

target屬性設定值

JavaScript 連結target屬性設定值
名稱說明
_blank開啟新視窗來顯示HTML文件
_self在原視窗來顯示HTML文件
_top在原視窗顯示HTML文件
_parent如為frameset,且擁有父frame,就在父frame顯示HTML文件

_top如為frameset,取消frameset以全視窗顯示HTML文件。

JavaScript frame - iframe Element

Frame簡介

框架頁(Frameset)目前使用的越來越少,多會建議使用CSS來實作畫面切割排列。不過<iframe>在影音網站還是很常見。
<frameset cols="value1,value2" rows="value3,value4">
    <frame name="1" src="1.html" scrolling="yes|no" />
    <frame name="2" src="2.html" scrolling="yes|no" />
    <frame name="3" src="3.html" scrolling="yes|no" />
    <frame name="4" src="4.html" scrolling="yes|no" />
</frameset> 

Frame屬性

  1. cols屬性:定義左右分割frame尺寸
  2. rows屬性:定義上下分割frame尺寸

以前面範例為例,先切左右,畫面從中間左右分割為兩塊(cols兩個value),再切上下,再從中間橫切上下兩塊(rows兩個value),所以畫面會被切成四塊,分別載入不同HTML文件。

frameset運作

name="1"的frame,載入1.html;name="2"的frame,載入2.html,以此類推,由左而右,由上而下。

frame屬性

JavaScript frame屬性
名稱說明
src要顯示的HTML文件
nameframe名稱
scrolling是否有捲動軸,yes有

frames集合架構

Frames集合架構
圖一:Frames集合架構

frames屬性

JavaScript frames集合屬性
名稱說明
self傳回目前的window物件,相當於window屬性
parent傳回目前frame的上一層物件(父物件)
top如果是巢狀frame,top就是最上層的window物件,也因為巢狀frame,top不一定等於prent屬性

// 由後往前看,找frames[0]的上一層物件
parent.frames[0]; 
// 使用name來取得window物件,然後就可以進一步設定window物件屬性
parent.1.localtion.href = "1.html";

巢狀frame

  1. 第一層frameset的frame可以擁有第二層的frameset
  2. window和self屬性是指第二層frameset中目前<frame>
  3. parent屬性是指第二層<frameset>,下一層是self屬性的frame
  4. top屬性是最上層第一層<frameset>,下一層是parent屬性的第二層frameset

Frameset屬性

  • cols屬性:定義左右分割frame尺寸
  • rows屬性:定義上下分割frame尺寸

Frame屬性

JavaScript Frame屬性
名稱說明
name/idframe名稱
frameBorder是否顯示frame邊框
src載入的HTML文件
scrolling是否擁有捲動軸
noResize是否可以調整frame的尺寸
marginHeight存取frame邊界的高
marginWidth存取frame邊界的寬

iframe介紹

<iframe name="" src="" height="" width="" align=""></iframe>

JavaScript iframe屬性
名稱說明
name/id名稱
src顯示圖檔、HTML文件、URL網址
height
width
alignIframe與網頁其他文字對齊方式

// 將Video.htm載入到id="if1"的iframe
document.getElementById("if1").src="Video.htm"; 

JavaScript Image Element

所有圖片<img>依照出現順序都屬於images物件集合 。例如,

// 設定網頁裡第一張圖片的alt屬性
document.images[0].alt="wolf"; 

image元素屬性

JavaScript image元素屬性
名稱說明
name存取圖片名稱
src存取圖片檔案的URL
width存取圖片寬度
height存取圖片的高度
hspace/vspace存取圖片的位置
isMap設定圖片為伺服端的影像地圖
useMap設定圖片為客戶端的影像地圖
alt存取無法顯示圖片時的替代文字
complete檢查圖片是否已經完全載入,true為已載入

image元素事件

JavaScript image元素事件
名稱說明
onload當圖片完全載入時,觸發此事件
onabort當使用者中止載入時,觸發此事件
onerror當找不到圖片檔案或圖檔損壞時,觸發此事件

放大圖片時

如果<img>是含在<div>之內,當我們放大圖片時,能在<div>內使用CSS的兩個參數,讓<div>幫我們出現捲動軸。

<div id="imgFrame" style="overflow-x:scroll;overflow-y:scroll;height:350px;width:600px;">
<img src="kkbruce.png" alt="KKBruce" />
</div>

JavaScript Table Element

table物件

table物件就是<table>,不過並沒有提供集合物件存取指定的<table>,使用使用id或name屬性來存取,例如,

document.mytable.border = 3; 

summary屬性


<table summary="This is KKBruce created Table"> 

此table的說明,它對於Browser而言是不會顯示,但對於網路蜘蛛則十分重要,是加分的屬性。

scope屬性

<th scope="row | col"> 

th標題是要放在「行(row)」或「列(col)」。scope預設是row,指定th標題出現在表格上方第一行或左邊第一列。

Table元素屬性


JavaScript Table元素屬性
名稱說明
caption存取表格的標題文字(如果存在)
border存取表格框線尺寸
width存取表格的寬度
cellPadding存取儲存格文字內容和框線的距離
cellSpacing存取儲存格框線間的距離
frame存取表格外框線
rules存取表格內框線
rows傳回表格每一最TableRow物件的物件集合(Array)
tBodies傳回<tbody>物件集合
tFoot傳回<tfoot>物件集合
tHead傳回<thead>物件集合

Table元素方法

在table中,每一儲存格都有一組index編號,index編號是由TableRow物件的rowIndex屬性和TableCell物件的cellIndex屬性所組成,(rowIndex, cellIndex) 所以一個二維表格裡每一儲存格Index編號是:
(0,0)(0,1)
(1,0)(1,1)
(2,0)(2,1)


JavaScript Table元素方法
名稱說明
insertRow(index)插入一列<tr>,插入位置是在參數的列號之前
deleteRow(index)刪除index的表格列
createCaption()建立標題文字<caption>
deleteCaption()刪除標題文字
createTHead()建立標題區塊<thead>
deleteTHead()刪除標題區塊
createTFoot()建立註腳區塊<tfoot>
deleteTFoot()刪除標題區塊

document.mytable.insertRow(0).insertCell(0);
document.mytable.insertRow(0).insertCell(1); 

刪除table某一列

function deleteColumn(table, number) {
  //每行刪除對應儲存格
  for (var i=0, len=table.rows.length; i < len; i++){
    table.rows[i].deleteCell(number);
  }
} 

Table元素屬性與方法

另一種分類法:

針對table元素:
JavaScript table元素屬性與方法
名稱說明
caption指向<caption>元素(如果存在)
tBodies指向<tbody>元素的集合
tFoot指向<tfoot>元素(如果存在)
tHead指向<thead>元素(如果存在)
rows表格中所有「行」的集合
deleteRow(position)刪除指定位置上的行
insertRow(position)在rows集合中的指定位置插入一個新行
creatCaption()建立<caption>元素並放入表格中
deleteCaption()刪除<caption>元素

針對tbody元素:
avaScript tbody元素屬性與方法
名稱說明
rows<tbody>中所有行的集合
deleteRows(position)刪除指定位置上的行
insertRows(position)在rows集合中的指定位置插入一個新行

針對tr元素:
JavaScript tr元素屬性與方法
名稱說明
cells<tr>中所有儲存格的集合
deleteCell(position)刪除指定位置上的儲存格
insertCell(position)在cells集合的指定位置上插入一個新的儲存格
<tr>
  <td>儲存格1</td>
  <td>儲存格2</td>
</tr> 

<tr>...</tr>對應TableRow物件。例如,TableRow → rows[i]。<td>...</td>對應TableCell物件,例如,TableCell → cells[i]。

// rows[i],第幾列
// cells[i],第幾格 
mytable.rows[i].cells[i];

TableRow(<tr>)

JavaScript TableRow屬性
名稱說明
cells傳回表格列中儲存格TableCell物件集合(Array)
align列中儲存格的水平對齊方式
vAlign列中儲存格的垂直對齊方式
rowIndex傳回TableRow物件的列號(Array)
sectionRowIndex傳回<thead>、<tfoot>、<tbody>區塊中TableRow物件列號

JavaScript TableRow方法
名稱說明
insertCell(index)插入儲存格在傳入index編號前
deleteCell(index)刪除傳入index編號儲存格

TableCell(<td>)

JavaScript TableCell屬性
名稱說明
align儲存格的水平對齊方式
vAlign儲存格的垂直對齊方式
colSpan存取儲存格的左右合併,colspan屬性
rowSpan存取儲存格的上下合併,rowspan屬性
cellIndex傳回儲存格編號