《JavaScript DOM 編程藝術(shù)》 學(xué)習(xí)筆記
[TOC]
概念:
- 平穩(wěn)退化
- 漸進(jìn)增強(qiáng)
- 以用戶為中心
第一章 js簡史
可以使用DOM
(Document Object Model)給HTML
(HyperText Markup Language)文檔增加交互能力,就像CSS
(Cascading Style Sheet)給文檔增加樣式一樣。DOM
是一種API
(Application Programing Interface),就是一種已得到各方認(rèn)同的基本約定,作為一種標(biāo)準(zhǔn)可以使得人們更方便的進(jìn)行交流和合作,類似于化學(xué)元素周期表、莫爾斯碼,W3C聯(lián)盟推出的標(biāo)準(zhǔn)化DOM理論上可以讓任何一種程序設(shè)計(jì)語言對使用任何一種標(biāo)記語言編寫出來的任何一份文檔進(jìn)行操控。
W3C聯(lián)盟對DOM的定義:一個與系統(tǒng)平臺和編程語言無關(guān)的接口,程序和腳本可以通過這個接口動態(tài)的訪問和修改文檔的內(nèi)容、結(jié)構(gòu)和樣式。
第二章 js語法
準(zhǔn)備工作
程序語言分為解釋型和編譯型兩大類
- 編譯型
Java、C++等語言需要一個編譯器(Compiler)。編譯器是一種程序,能夠把用Java等解釋型高級語言編寫出來的源代碼翻譯為直接在計(jì)算機(jī)上執(zhí)行的文件。
編譯型的語言編寫的代碼如果有錯誤,這些錯誤會在代碼編譯階段就會被發(fā)現(xiàn)。與解釋型語言相比速度更快,可移植性更高,但學(xué)習(xí)曲線也更陡峭。 - 解釋型
解釋型語言不需要編譯器,僅需要解釋器。對于JavaScript語言,在互聯(lián)網(wǎng)環(huán)境下Web瀏覽器負(fù)責(zé)完成有關(guān)的解釋和執(zhí)行工作。瀏覽器中的JavaScript解釋器將直接讀入源代碼并執(zhí)行。瀏覽器中如果沒有解釋器,JavaScript代碼就無法執(zhí)行。
解釋型語言編寫的代碼只能等到解釋器執(zhí)行到有關(guān)代碼時(shí)才能被發(fā)現(xiàn)。
語法
每行結(jié)尾并不一定需要分號,不過為了更容易追蹤JavaScript腳本的執(zhí)行順序還是推介加上;
var不關(guān)心變量的類型,ex:
var str="happy", str1 = 50;
str = 33;
字符串可以包括在雙引號或者單引號里,如果在引號中包括引號,那么需要對這個字符進(jìn)行轉(zhuǎn)義(escaping):
var str="about 5'10\" tall";
- 數(shù)組聲明:
var beatles = ["John", "Paul", "George", "Ringo", true, 44, strA, [0, false, "my Dog", strB]];
var arr1 = Array();
var arr2 = [];
arr2["name"] = "John";
- 對象聲明:
var lennon = Object();
lennon.name = "Smith";
var lennon1 = {name: "Ringo", age: 78};
- 比較操作符:
var strC = false;
if (strC == "") {
alert("xixi"); //會輸出
}var strC = false;
if (strC == "") {
alert("xixi"); //會輸出
}
相等操作符 ==
會認(rèn)為false與空字符串含義相同,如果要進(jìn)行嚴(yán)格比較,則需要全等操作符 ===
,全等操作符會執(zhí)行嚴(yán)格的比較,不僅會比較值,還會比較變量的類型,嚴(yán)格不相等也類似 !==
。ex:
var strC = false;
if (strC === "") {
alert("xixi"); //不會輸出
}var strC = false;
if (strC === "") {
alert("xixi"); //不會輸出
}
在if()或者while()的判斷條件中,整數(shù)中0被判斷為false,除了0之外都為true包括負(fù)數(shù)。
while (-1.1) {
alert("memeda"); //會一直輸出
}
for循環(huán):
var beatles = ["John", "Paul", "George", "Ringo", true, 44, [0, false, "my Dog", strB]];
for (var strE = 0; strE < beatles.length; strE++) {
alert(beatles[strE]);
}
函數(shù):
function shout(para) {
var beatles = [para, 44];
for (var strE = 0; strE < beatles.length; strE++) {
alert(beatles[strE]);
}
}
shout("xixixi"); //彈出2條alert: xixixi , 44
變量和函數(shù)的命名(為了能一眼看出哪些是變量,哪些是函數(shù)):
- 變量使用下劃線來分割各個單詞,如
temp_celsius
; - 函數(shù)名使用首字母小寫的駝峰命名法,如
convertToCelsius
;
內(nèi)建對象(Native Object):
- Array
- Math
var num = 7.454;
num = Math.round(num);
alert(num); //輸出四舍五入結(jié)果:7
- Date
var current_date=new Date();
var today=current_date.getDate();
alert(today); //輸出當(dāng)天日期
etc...
第三章 DOM
DOM是針對XML但是經(jīng)過擴(kuò)展用于HTML的應(yīng)用程序編程接口。DOM把整個頁面映射為一個多層節(jié)點(diǎn)結(jié)構(gòu)。
DOM中的D
D指的是document,當(dāng)創(chuàng)建了一個網(wǎng)頁并將其加載到web瀏覽器中時(shí),DOM已經(jīng)被創(chuàng)建,它把編寫的網(wǎng)頁文檔轉(zhuǎn)換成一個文檔對象。
DOM中的O
O指的是對象object,js中的對象包括:
- 用戶自定義對象(user-defined object):自行創(chuàng)建的對象
- 內(nèi)建對象(native object):內(nèi)建在js中的對象,如Array、Math、Date
- 宿主對象(host object):由瀏覽器提供的對象
最基礎(chǔ)的宿主對象:window對象:
window.open("http://www.baidu.com", "","", false); //彈出百度窗口,也可以是新窗口
DOM中的M
M指的是Model,有三種DOM方法可以獲取元素節(jié)點(diǎn),分別是通過ID,通過標(biāo)簽名,和通過類名來獲取。
- getElementById
返回一個有著給定ID屬性值的元素節(jié)點(diǎn)對應(yīng)的對象,它是document對象特有的函數(shù),并且形參是要獲取的id值,放在單引號或雙引號里:
<h1 title="memeda" id="header_h1">Hello World!</h1>
<script>
alert(document.getElementById("header_h1").getAttribute("title")); //彈出 memeda
</script>
- getElementsByTagName
返回一個對象數(shù)組,每個對象對應(yīng)著文檔里有著給定標(biāo)簽的一個元素,形參是標(biāo)簽,且在引號內(nèi):
<ul>
<li>Data1</li>
<li>Data2</li>
<li>Data3</li>
</ul>
<script>
alert(document.getElementsByTagName("li").length); //彈出 3
</script>
- getElementsByClassName
返回具有Class名的對象數(shù)組,還可以查找那些帶有多個類名的元素,并不是所有瀏覽器都支持:
<ul>
<li class="data_1 data_2">Data1</li>
<li class="data_1">Data2</li>
<li class="data_2">Data3</li>
</ul>
<script>
alert(document.getElementsByClassName("data_1 data_2").length); //彈出 1 ,只有一個元素既有data_1也有data_2
</script>
如果瀏覽器不一定支持,那么可以使用:
function getElementsByClassName(node, classname) { //node是DOM樹中搜索起點(diǎn),也可以是document,不支持多個class搜索
if (node.getElementsByClassName) {
return node.getElementsByClassName(classname);
} else {
var results = [];
var elems = node.getElementsByTagName("*");
for (var i = 0; i < elems.length; i++) {
if (elems[i].className.indexOf(classname) != -1) {
results[results.length] = elems[i];
}
}
}
}
- getAttribute & setAttribute
可以用來獲取和設(shè)置節(jié)點(diǎn)的屬性:
<h1 title="memeda" id="header_h1">Hello World!</h1>
<script>
var shop=document.getElementById("header_h1");
alert(shop.getAttribute("title"));
shop.setAttribute("title","xixi");
alert(shop.getAttribute("title"));
</script>
通過setAttribute
對文檔作出修改后,通過查看源代碼發(fā)現(xiàn)文檔源代碼仍然是改變之前的值,也就是說,setAttribute
作出的修改并不會反映到文檔本身的源代碼里,原因是DOM的工作模式:先加載文檔的靜態(tài)內(nèi)容,再動態(tài)刷新,動態(tài)刷新不影響文檔的靜態(tài)內(nèi)容。
這正是DOM的真正威力:**對頁面內(nèi)容進(jìn)行刷新卻不需要在瀏覽器里刷新頁面。 **
第四章 js圖片庫
需要說明的是,若一個站點(diǎn)要用到多個js文件,為了減少對站點(diǎn)的請求次數(shù)提高性能,應(yīng)該把這些js文件合并到一個文件中。圖片同理。
實(shí)例:
<ul>
<li><a href="../imgs/aaa.jpeg" onclick="showpic(this); return false;">Data1</a></li>
<li><a href="../imgs/step.png" onclick="showpic(this); return false;">Data2</a></li>
<li><a href="../imgs/arrow-right-bold.png" onclick="showpic(this); return false;">Data3</a></li>
<li><a href="../imgs/dazongdianpin_logo.png" onclick="showpic(this); return false;">Data4</a></li>
</ul>

<script>
function showpic(whichpic) {
document.getElementById("placeholder").setAttribute("src", whichpic.getAttribute("href"));
}
</script>
實(shí)現(xiàn)一個在點(diǎn)擊a標(biāo)簽時(shí)把placeholder占位圖片替換成a標(biāo)簽對應(yīng)的圖片,并攔截點(diǎn)擊a時(shí)網(wǎng)頁的默認(rèn)行為。
另外,setAttribute方法是DOM Level1 的內(nèi)容,它可以設(shè)置任意元素節(jié)點(diǎn)的任意屬性,在DOM Level1出現(xiàn)之前的HTML-DOM還可以通過:element.value="value"
來直接設(shè)置元素的屬性,不過不推介使用這種方式,這種方式只適用于web文檔,而DOM則適用于任何一種標(biāo)記語言,因?yàn)椤癉OM是一種適用于多種環(huán)境和多種程序設(shè)計(jì)語言的通用型API”,比如xml,為了更好的移植性,嚴(yán)格遵守DOM Level1可以避免很多問題。
nodeType屬性可以得到任何節(jié)點(diǎn)的節(jié)點(diǎn)屬性:
元素節(jié)點(diǎn)的nodeType值為1
屬性節(jié)點(diǎn)的nodeType值為2
文本節(jié)點(diǎn)的nodeType值為3
nodeValue屬性:
可以用來檢索節(jié)點(diǎn)的值,也可以用來設(shè)置節(jié)點(diǎn)的值。
<ul>
<li><a href="#" onclick="showa(this);return false;">xixi1</a></li>
<li><a href="#" onclick="showa(this);return false;">xixi2</a></li>
<li><a href="#" onclick="showa(this);return false;">xixi3</a></li>
</ul>
<p id="description">Hello</p>
<script>
function showa(whicha) {
document.getElementById("description").firstChild.nodeValue = whicha.firstChild.nodeValue;
}
</script>
第五章 最佳實(shí)踐
平穩(wěn)退化
平穩(wěn)退化(graceful degradation)確保網(wǎng)頁在沒有js的情況下也能正常工作;就是說,雖然某些功能無法使用,但最基本的操作仍能順利實(shí)現(xiàn)。
<a onclick="openw(this.href);return false;">Example</a>
<script>
function openw(urlStr) {
window.open(urlStr, "ppp", "width=400,height=600");
}
</script>
這種方式比JavaScript偽協(xié)議:href="javascript:..."
和href="#" onclick=“...”
,方式效果要好得多,即使在js功能已被禁用或者遇到爬蟲的情況下,鏈接也是可用的,雖然功能上打了折扣,但是并沒有徹底失效。
漸進(jìn)增強(qiáng)
漸進(jìn)增強(qiáng)(progressive enhancement)原則基于這樣一種思想:實(shí)現(xiàn)良好的結(jié)構(gòu)應(yīng)該從最核心的部分,也就是從內(nèi)容開始。根據(jù)內(nèi)容使用標(biāo)記實(shí)現(xiàn)良好的結(jié)構(gòu);然后再逐步加強(qiáng)這些內(nèi)容,這些增強(qiáng)工作可以是通過css改進(jìn)呈現(xiàn)效果,也可以通過DOM添加各種行為,如果使用DOM來添加核心內(nèi)容,那么這是不推介的,并且也不利于網(wǎng)站SEO,js也沒有任何空間去平穩(wěn)退化,那些缺乏js支持的訪問者就無法看到其內(nèi)容。
向后兼容
確保老版本的瀏覽器不會因?yàn)槟愕膉s而掛掉;
可以使用對象檢測(object detection):
window.onload = function () {
if (!document.getElementsByTagName()) return false;
}
檢測瀏覽器是否支持這一方法,來避免老版本瀏覽器不會因?yàn)樾录尤氲膉s出錯。
分離js
把網(wǎng)頁的結(jié)構(gòu)和內(nèi)容與js腳本的動作行為分開;
性能考慮
確保腳本執(zhí)行的性能最優(yōu);
- 避免搜索浪費(fèi)
if(document.getElementsByTagName("a").length>0){ //不推介
var links=document.getElementsByTagName("a");
for(var i=0;i<links.length;i++){
}
}
這樣會進(jìn)行兩次.getElementsByTagName()
搜索,造成性能浪費(fèi),因此不推介。如果有多個函數(shù)重復(fù)做一件事,那么可以把搜索結(jié)果放在全局變量中,或者把一組元素直接以參數(shù)形式傳遞給函數(shù),以免造成搜索浪費(fèi)。
合并與放置腳本
在<head>
部分的腳本會導(dǎo)致瀏覽器無法并行加載其他文件,因此推介在文檔末尾</body>
之前引入script
元素,可以讓頁面變得更快;根據(jù)HTTP規(guī)范,瀏覽器每次從同一個域名最多只能同時(shí)下載兩個文件,而在下載腳本期間,瀏覽器不會下載其他任何文件,即使是來自不同域名的文件也不會下載,所有其他資源要等腳本加載完畢后才能下載。壓縮腳本
可以使用壓縮工具來刪除腳本文件中不必要的字節(jié),比如空格和注釋,有的壓縮工具設(shè)置會重寫部分代碼,使用更短的變量名,從而減少整體文件大小。精簡后的代碼不易看懂,但會大幅減小文件大小,一般會在壓縮后的文件名后加上.min.
,比如scriptName.min.js
。
第六章 案例:圖片庫改進(jìn)
ex:
<ul id="imagegallery">
<!--平穩(wěn)退化-->
<li><a href="../imgs/aaa.jpeg">Data1</a></li>
<li><a href="../imgs/step.png">Data2</a></li>
<li><a href="../imgs/arrow-right-bold.png">Data3</a></li>
<li><a href="../imgs/dazongdianpin_logo.png">Data4</a></li>
</ul>

<script>
addLoadEvent(prepareGallery);
function prepareGallery() {
if (!document.getElementById) alert("1");
if (!document.getElementsByTagName) alert("2"); //檢查瀏覽器是否支持這個DOM方法
if (!document.getElementById("imagegallery")) alert("3"); //即使在網(wǎng)頁上刪除了這個img或者id,js也不會出錯
var gallery = document.getElementById("imagegallery");
var links = gallery.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
links[i].onclick = function () {
return !showpic(this); //由showpic返回值決定是否取消瀏覽器執(zhí)行鏈接被點(diǎn)擊時(shí)的默認(rèn)操作
}
}
}
function showpic(whichpic) {
if (!document.getElementById("placeholder")) return false;
whichpic.getAttribute("href").setAttribute("src", whichpic.getAttribute("href"));
return true; //setAttribute成功才會返回true
}
function addLoadEvent(func) { //將函數(shù)添加到頁面加載時(shí)執(zhí)行
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function () {
oldonload();
func();
}
}
}
</script>
第七章 動態(tài)創(chuàng)建標(biāo)記
傳統(tǒng)方法
document.write
可以方便的把字符串插入文檔(不推介)
<div id="test-div"></div>
<script>
document.write("<p>This is a <em>content</em> script!;</p>");
</script>
innerHTML
方法會替換元素內(nèi)的所有內(nèi)容。
<div id="test-div"></div>
<script>
window.onload = function () {
var testdiv = document.getElementById("test-div");
testdiv.innerHTML = "<p>This is a <em>content</em> script;</p>"
}
</script>
DOM方法
插入節(jié)點(diǎn)的子元素后
<div id="test-div"></div>
<script>
var newE = document.createElement("p"); //創(chuàng)建新元素節(jié)點(diǎn),不可以使用.firstChild.nodeValue賦值,因?yàn)閯?chuàng)建的標(biāo)簽的nodeValue是null
var newT = document.createTextNode("hello world~"); //創(chuàng)建新文本節(jié)點(diǎn)
newE.appendChild(newT); //把文本節(jié)點(diǎn)插入元素節(jié)點(diǎn)的最后面
document.getElementById("test-div").appendChild(newE); //把元素節(jié)點(diǎn)插入原有節(jié)點(diǎn)的最后面
</script>
插入元素后:
var place = document.createElement("p");
place.appendChild(document.createTextNode("百度"));
var tar = document.getElementById("tar");
tar.parentNode.insertBefore(place, tar);
插入元素前:
var place = document.createElement("p");
place.appendChild(document.createTextNode("百度"));
var tar = document.getElementById("tar");
insertAfter(place, hhh);
function insertAfter(newElement, targetElement) { //插入目標(biāo)元素后
var parent = targetElement.parentNode;
if (parent.lastChild === targetElement) {
parent.appendChild(newElement);
} else {
parent.insertBefore(newElement, targetElement.nextElementSibling);
}
}
Ajax
Ajax的主要優(yōu)勢是對頁面的請求以異步的方式發(fā)送到服務(wù)器。而服務(wù)器不會用整個頁面來響應(yīng)請求,它會在后臺處理請求,與此同時(shí)用戶還能繼續(xù)瀏覽頁面并與頁面交互腳本則可以按需加載和創(chuàng)建頁面內(nèi)容,而不會打斷用戶的瀏覽體驗(yàn)。
Ajax的核心是XMLHttpRequest對象,這個對象充當(dāng)瀏覽器中的客戶端與服務(wù)器之間的橋梁角色,以往的請求由瀏覽器發(fā)出,而js通過這個對象可以自己發(fā)送請求,同事也自己處理響應(yīng)。
<div id="new"></div>
<script>
function getNewContent() {
var request = new getHTTPObject();
if (request) {
request.open("GET", "test.txt", true);
request.onreadystatechange = function () {
if (request.readyState === 4) {
var para = document.createElement("p");
var txt = document.createTextNode(request.responseText);
para.appendChild(txt);
document.getElementById("new").appendChild(para);
}
};
request.send(null);
} else {
alert('sorry, your browser doesn\'t support XMLHttpRequest');
}
alert("func done!");
}
addLoadEvent(getNewContent);
function addLoadEvent(func) { // 將函數(shù)添加到頁面加載時(shí)執(zhí)行
var oldonload = window.onload;
if (typeof window.onload !== 'function') {
window.onload = func;
} else {
window.onload = function () {
oldonload();
func();
};
}
}
function getHTTPObject() {
if (typeof XMLHttpRequest === "undefined")
XMLHttpRequest = function () {
try {return new ActiveXObject("Msxml2.XMLHTTP.6.0");}
catch (e) {}
try {return new ActiveXObject("Msxml2.XMLHTTP.3.0");}
catch (e) {}
try {return new ActiveXObject("Msxml2.XMLHTTP");}
catch (e) {}
return false;
};
return new XMLHttpRequest();
}
</script>
第八章 充實(shí)文檔內(nèi)容
js腳本只應(yīng)該用來充實(shí)文檔的內(nèi)容,而避免使用DOM來創(chuàng)建核心內(nèi)容;
遍歷快捷鍵,ex:
<ul id="navigation">
<li><a href="#" accesskey="1">home</a></li>
<li><a href="#" accesskey="2">contact</a></li>
<li><a href="#" accesskey="3">search</a></li>
</ul>
<script>
function displayAccesskeys() {
if (!document.getElementsByTagName) return false;
var links = document.getElementsByTagName("a");
var tags = [];
if (links.length < 1) return false;
for (var i = 0; i < links.length; i++) {
if (!links[i].getAttribute("accesskey")) continue;
var source = links[i].lastChild.nodeValue;
var key = links[i].getAttribute("accesskey");
tags[key] = source;
}
var list = document.createElement("ul");
for (key in tags) {
var txt = key + " : " + tags[key];
var li = document.createElement("li");
var li_txt = document.createTextNode(txt);
li.appendChild(li_txt);
list.appendChild(li);
}
insertAfter(list, document.getElementById("navigation"));
}
addLoadEvent(displayAccesskeys);
function addLoadEvent(func) { //將函數(shù)添加到頁面加載時(shí)執(zhí)行
var oldonload = window.onload;
if (typeof window.onload !== 'function') {
window.onload = func;
} else {
window.onload = function () {
oldonload();
func();
};
}
}
function insertAfter(newElement, targetElement) { //插入目標(biāo)元素后
var parent = targetElement.parentNode;
if (parent.lastChild === targetElement) {
parent.appendChild(newElement);
} else {
parent.insertBefore(newElement, targetElement.nextElementSibling);
}
}
</script>