前言
歸根結(jié)底,代碼都是思想和概念的體現(xiàn)。沒人能把一種程序設(shè)計語言的所有語法和關(guān)鍵字都記住,可以查閱參考書來解決。平穩(wěn)退化、漸進(jìn)增強(qiáng)、以用戶為中心的設(shè)計對任何前端Web開發(fā)工作都非常重要。
第1章 JavaScript簡史
1 JavaScript的起源
Netscape公司和Sun公司合作開發(fā)。第一個版本出現(xiàn)在1995年推出的Netscape Navigator2瀏覽器中。IE也以JScript為名發(fā)布了一個版本,面對IE的競爭,Netscape和Sun聯(lián)合ECMA對JavaScript進(jìn)行了標(biāo)準(zhǔn)化——ECMAScript語言,就是現(xiàn)在談?wù)摰腏avaScript(以下均簡稱JS)。與Sun公司開發(fā)的Java程序語言沒有任何關(guān)系。
2 DOM
DOM是一套對文檔內(nèi)容進(jìn)行抽象和概念化的方法。JS的早期版本向程序員提供了查詢和操控web文檔某些實際內(nèi)容的手段,主要是圖像和表單,因為JS預(yù)先定義了images和forms等術(shù)語,通常把這種試驗性質(zhì)的初級DOM成為0級DOM。
3 瀏覽器戰(zhàn)爭
Netscape Navigator 4發(fā)布于1997年6月,IE4發(fā)布于同年10月,兩者都大幅擴(kuò)展了DOM(但彼此不兼容),使JS功能大大增加,出現(xiàn)一個新名詞:DHTML,即動態(tài)HTML,是描述HTML、CSS、JS技術(shù)組合的術(shù)語。
4 制定標(biāo)準(zhǔn)
瀏覽器制造商們攜手W3C于1998年制定完成了新的標(biāo)準(zhǔn),即第1級DOM。標(biāo)準(zhǔn)化的DOM有遠(yuǎn)大的抱負(fù)。
- 瀏覽器以外的考慮。DOM是一種API(應(yīng)用編程接口),API是一組已經(jīng)得到有關(guān)各方共同認(rèn)可的基本約定,如國際時區(qū)、化學(xué)元素周期表等。W3C對DOM的定義是:一個與系統(tǒng)平臺和編程語言無關(guān)的接口,程序和腳本可以通過這個接口動態(tài)地訪問和修改文檔的內(nèi)容、結(jié)構(gòu)和樣式。
- 瀏覽器戰(zhàn)爭的結(jié)局。市場份額大戰(zhàn)中,微軟戰(zhàn)勝了Netscape。下一代瀏覽器產(chǎn)品對Web標(biāo)準(zhǔn)的支持得到了極大的改善。
- 嶄新的起點。如今,safari(WebKit)、Chrome(WebKit)、Firefox(Gecko)、IE(Trident)和一些智能手機(jī)都對DOM有良好的支持。
第2章 JS語法
1 準(zhǔn)備工作
編寫JS腳本:文本編輯器+腳本,必須通過HTML/XHTML文檔執(zhí)行。
兩種方式:
- 將JS代碼放到文檔<head>標(biāo)簽中的<script>標(biāo)簽之間
- 把JS代碼存為一個擴(kuò)展名為.js的獨立文件,在<head>部分放入<script>標(biāo)簽,將其src屬性指向該文件
- 最好的做法是把<script>標(biāo)簽放在HTML文檔的最后,</body>之前,這樣能使瀏覽器更快地加載頁面
程序設(shè)計語言分為解釋型和編譯型兩大類。Java或C++等語言需要一個編譯器,能把源代碼翻譯為直接在計算機(jī)上執(zhí)行的文件。解釋型語言不需要編譯器,僅需要解釋器。對于JS解釋器,瀏覽器負(fù)責(zé)完成有關(guān)的解釋和執(zhí)行工作。
2 語法
(1)語句。建議在每條語句后面加分號,這是一種良好的編程習(xí)慣。
(2)注釋。有效幫助了解代碼流程,多種注釋方法。建議用“//”來注釋單行,用“/*”注釋多行。
(3)變量。將值存入變量的操作,叫賦值。提前聲明變量是一種良好的編程習(xí)慣,雖然JS沒有強(qiáng)制要求。最有效率的方法是聲明和賦值一次完成:
var mood="happy",age=26;
相當(dāng)于:
var mood,age;
mood="happy";
age=33;
JS中變量和其他語法元素的名字都區(qū)分字母大小寫,不允許變量名中包括空格或標(biāo)點符號(美元符號除外),允許包括字母、數(shù)字、美元符號和下劃線,第一個字符不允許是數(shù)字。如變量名my mood無效,可用my_mood,或myMood,這種駝峰格式是函數(shù)名、方法名和對象屬性名命名的首選格式。
(4)數(shù)據(jù)類型。上述代碼中,變量mood的值是字符串,變量age的值是數(shù)字,類型不同但聲明和賦值語法完全相同。有些其他語言還要求聲明數(shù)據(jù)類型,稱為強(qiáng)類型語言,而JS屬于弱類型語言。JS中的幾種數(shù)據(jù)類型:
- 字符串。包括(但不限于)字母、數(shù)字、標(biāo)點符號和空格,必須包在引號里,單雙均可,但最好在整個文本里保持一致。如果字符串里本身含引號造成一定干擾,可使用反斜線\進(jìn)行轉(zhuǎn)義,如‘don't ask'。
- 數(shù)值。允許任意位小數(shù),稱為浮點數(shù),也可以是負(fù)數(shù)。
- 布爾值。布爾數(shù)據(jù)只有兩個可選值:true 或者false。布爾值不是字符串,不要用引號括起來,false和“false”是兩碼事。
(5)數(shù)組。字符串、數(shù)值和布爾值都是標(biāo)量(scalar),它只能有一個值。而數(shù)組是指用一個變量表示一個值的集合,集合中的每個值都是這個數(shù)組的一個元素。
JS數(shù)組可以用關(guān)鍵字Array聲明,聲明的同時還可以指定初始元素個數(shù),即數(shù)組長度。
var beatles=Array(4);或 var beatles=Array()
向數(shù)組添加元素的操作稱為填充(populating)。填充時,需要給出新元素的值及其在數(shù)組中的存放位置,即元素的下標(biāo)(index),下標(biāo)必須用方括號括起來:
array[index]=element;
如,var beatles=Array(4);
beatles[0]="John";(JS規(guī)定0作為第一個下標(biāo))
beatles[1]="Paul";
beatles[2]="George";
beatles[3]="Ringo";
簡便寫法:
var beatles=Array("John","Paul","George","Ringo");
或者:
var beatles=["John","Paul","George","Ringo"];
數(shù)組元素可以是字符、數(shù)字、布爾值:
var lennon=["John",1940,false];
還可以是變量:
var name="John";
var beatles[0]=name;
還可以是另一個數(shù)組的元素:
var names=["Ringo","John","George","Paul"];
beatles[1]=names[3];
還可以包含其他的數(shù)組:
var lennon=["John",1940,false];
var beatles=[];
beatles[0]lennon;
則beatles[0][1]的值是1940.
但如果要記住每個下標(biāo)數(shù)字的話,編程工作將十分麻煩,幸好還有其他方法可以填充數(shù)組:關(guān)聯(lián)數(shù)組;將數(shù)組保存為對象。
關(guān)聯(lián)數(shù)組:
如果在填充數(shù)組時只給出了元素的值,這個數(shù)組將是一個傳統(tǒng)數(shù)組,它的各個元素的下標(biāo)將被自動創(chuàng)建和刷新。在為新元素給出下標(biāo)時,不必局限于使用整數(shù)數(shù)字,還可以用字符串:
var lennon=Array();
lennon["name"]="John";
lennon["year"]=1940;
lennon["living"]=false;
這樣的數(shù)組叫關(guān)聯(lián)數(shù)組,代碼更具有可讀性,但這種用法不是一個好習(xí)慣,不推薦使用。本質(zhì)上,在創(chuàng)建關(guān)聯(lián)數(shù)組時,創(chuàng)建的是Array對象的屬性。在JS中所有變量實際上都是某種類型的對象。理想情況下,不應(yīng)該修改Array對象的屬性,而應(yīng)該使用通用的對象(Object)。
(6)對象
與數(shù)組類似,對象也是使用一個名字表示一組值。對象的每個值都是對象的一個屬性,前一節(jié)的lennon數(shù)組也可以創(chuàng)建成下面這個對象:
var lennon=Object();
lennon.name="John";
lennon.year=1940;
lennon.living=false;
它不使用方括號和下標(biāo)來獲取元素,而是像任何JS對象一樣,使用點號來獲取屬性。創(chuàng)建對象還有一種更簡潔的語法,即花括號語法:
var lennon={name:"John",year:1940,living:false};
用對象來代替?zhèn)鹘y(tǒng)數(shù)組的做法意味著可以通過元素的名字而不是下標(biāo)數(shù)字來引用它們,這大大提高了腳本的可讀性。
3 操作
進(jìn)行計算和處理數(shù)據(jù),即操作(operation)。
算術(shù)操作符
變量可以包含操作:var total=(1+4)*5;
還可以對變量進(jìn)行操作:var temp_fahrenheit=95;var temp_celsius=(temp_fahrenheit-32)/1.8;
加號(+)是一個比較特殊的操作符,既可用于數(shù)值,也可用于字符串:
var message="I am feeling"+"happy";
這種把多個字符串首尾相連的操作叫拼接(concatenation)。
拼接可通過變量完成:
var mood="happy";
var message="I am feeling"+mood;
還可以把數(shù)值和字符串拼接
var year=2005;
var message="The year is"+year;
把字符串和數(shù)字拼接在一起,結(jié)果是一個更長的字符串,兩個數(shù)值拼接在一起,結(jié)果是兩個數(shù)值的算術(shù)和。
快捷操作符:
++相當(dāng)于+1;
+=則是一次完成“拼接和賦值”:
var year=2010;
var message="The year is";
message+=year;
結(jié)果是變量message的值為“The year is 2005”。
4 條件語句
最常見的條件語句是if語句,基本語法:if (condition) {statements;},其中condition的求值結(jié)果永遠(yuǎn)是一個布爾值。if語句可以有一個else子句,其語句會在給定condition為false時執(zhí)行。
(1)比較操作符。
- 等于。注意(=)是賦值,(==)才是等于。(==)不表示嚴(yán)格相等,而(===)表示全等操作符,不僅比較值,還會比較變量類型。
var my_mood="happy";
var your_mood="sad";
if (my_mood==your_mood){
alert("we both feel the same");
}
- 不等于。(!=),嚴(yán)格不相等需使用(!==)
if (my_mood != your_mood){
alert("We are feeling different moods.");
}
(2)邏輯操作符。把條件語句里的操作組合在一起,需使用邏輯操作符。
- 邏輯與&&。兩個操作數(shù)都true才是true。
if (num>=5 && num<=10){
alert("The number is in the right range.");
}
- 邏輯或||。一個true就是true。
- 邏輯非!。
if (!(5>10||5<1)){
alert("The number is in the right range.");
}
5 循環(huán)語句
if 語句的不足是無法完成重復(fù)性操作,如需多次執(zhí)行同一個代碼塊,必須使用循環(huán)語句,其工作原理是只要給定條件仍能滿足,包含在循環(huán)語句里的代碼就會重復(fù)執(zhí)行,一旦給定條件的求值結(jié)果不再是true,循環(huán)終止。
(1)while 循環(huán)。與if語句的語法幾乎完全一樣,唯一的區(qū)別是只要給定條件求值結(jié)果是true,花括號里的代碼就將反復(fù)執(zhí)行。
var count=1;
while(count<11){
alert(count);
count++;
}
對循環(huán)控制條件的求值發(fā)生在每次循環(huán)開始之前,如果控制條件首次求值結(jié)果是false,那么花括號里的代碼將一個也不會被執(zhí)行。在某些場合,為保證代碼至少執(zhí)行一次,用到do...while循環(huán),其對循環(huán)控制條件的求值發(fā)生在每次循環(huán)結(jié)束之后。
var count=1;
do{
alert(count);
count++;
}while(count<11);
(2)for循環(huán)。是while循環(huán)的一種變體。
for(initial condition;test condition;alter condition){
statements;
} 用for循環(huán)來重復(fù)執(zhí)行代碼的好處是循環(huán)控制結(jié)構(gòu)更加清晰。上例:
for (var count=1; count<11;count++){
alert(count);
}
for循環(huán)最常見的用途之一是對某個數(shù)組里的全體元素進(jìn)行遍歷處理:
var beatles=Array("John","Paul","George","Ringo");
for(var count=0;count<beatles.length;count++){
alert(beatles[count]);
}
6 函數(shù)
每當(dāng)需要反復(fù)做一件事時,都可以利用函數(shù)來避免重復(fù)鍵入大量相同內(nèi)容,不過函數(shù)的真正威力體現(xiàn)在,你可以把不同數(shù)據(jù)傳遞給它們,完成預(yù)定操作。把傳遞給函數(shù)的數(shù)據(jù)稱為參數(shù)(argument)。JS提供了很多內(nèi)建函數(shù),如前面的alert。
定義一個函數(shù)的語法:
function name(arguments){
statements;
}
如傳遞兩個參數(shù)的函數(shù):
function multiply(num1,num2){
var total=num1*num2;
alert(total);
}
定義了這個函數(shù)的腳本,可從任意位置調(diào)用
multiply(10,2);
函數(shù)不僅能夠以參數(shù)的形式接收數(shù)據(jù),還能夠返回數(shù)據(jù),需用到return語句。
函數(shù)的真正價值體現(xiàn)在,可以把它們當(dāng)做一種數(shù)據(jù)類型來使用,變量用下劃線,函數(shù)用駝峰命名法,可予以區(qū)分。
變量的作用域(scope)
- 全局變量global variable,可以在腳本中的任何位置被引用。
- 局部變量local variable,只存在于聲明它的函數(shù)內(nèi)部。
如果在某個函數(shù)中使用了var,那個變量就是局部變量,否則視為全局變量,如果腳本里已經(jīng)存在一個與之同名的全局變量,這個函數(shù)就會改變那個全局變量的值。所以定義一個函數(shù)時,一定要把它內(nèi)部的變量全都明確地聲明為局部變量,避免隱患。
7 對象
包含在對象里的數(shù)據(jù)可以通過兩種形式訪問——屬性property和方法method。屬性是隸屬于某個特定對象的變量,方法是只有某個特定對象才能調(diào)用的函數(shù)。
Person.mood
Person.age
Person.walk()
使用new關(guān)鍵字,為對象創(chuàng)建實例instance:
var jeremy=new Person;
就可以用Person對象的屬性來檢索關(guān)于jeremy的信息了:
jeremy.age
jeremy.mood
(1)內(nèi)建對象。
數(shù)組就是一種,還有Meth、Date等。
(2)宿主對象。
這些對象不是由JS語言本身而是由它的運(yùn)行環(huán)境提供的。具體到Web應(yīng)用,這個環(huán)境就是瀏覽器,由瀏覽器提供的預(yù)定義對象被稱為宿主對象,如Form、Image等。
第3章 DOM
1 文檔:DOM中的D
當(dāng)創(chuàng)建了一個網(wǎng)頁并把它加載到Web瀏覽器中時,你編寫的網(wǎng)頁文檔就轉(zhuǎn)換為一個文檔對象了。
2 對象:DOM中的O
JS對象分三種類型:用戶自定義對象、內(nèi)建對象、宿主對象。
3 模型:DOM中的M
就像一個模型火車代表著一列真正的火車,DOM代表著加載到瀏覽器窗口的當(dāng)前網(wǎng)頁。DOM把文檔表示為一棵家譜樹,稱為“節(jié)點樹”更準(zhǔn)確。
4 節(jié)點
節(jié)點node表示網(wǎng)絡(luò)中的一個連接點,一個網(wǎng)絡(luò)就是由一些節(jié)點構(gòu)成的集合。
DOM里有三種不同類型的節(jié)點:元素節(jié)點、文本節(jié)點、屬性節(jié)點。
獲取元素
- getElementById.這個方法將返回一個與有著給定id屬性值的元素節(jié)點對應(yīng)的對象。如document.getElementById("purchases")。
- getElementsByTagName.這個方法將返回一個對象數(shù)組,每個對象分別對應(yīng)著文檔里有著給定標(biāo)簽的一個元素。為了減少不必要的打字量并改善代碼可讀性,可把document.getElementsByTagName("")賦值給一個變量。
getElementsByTagName還允許把通配符作為參數(shù),如alert(document.getElementsByTagName("*").length);
還可以跟getElementById結(jié)合起來運(yùn)用。 - getElementsByClassName.
5 獲取和設(shè)置屬性
(1)getAttribute.是只有一個參數(shù)的函數(shù),但它不屬于document對象,所以不能通過document對象調(diào)用,只能通過元素節(jié)點對象調(diào)用。
(2)setAttribute.它允許我們隊屬性節(jié)點值做出修改,也是只能用于元素節(jié)點。
object.setAttribute(attribute,value)
var shopping = document.getElementById("purchases");
alert(shopping.getAttribute("title"));
shopping.setAttribute("title","a list of goods");
alert(shopping.getAttribute("title"));
第一個alert顯示null,第二個alert顯示a list of goods。這表明setAttribute實際上完成了兩項操作:先創(chuàng)建屬性,然后設(shè)置它的值。
一個細(xì)節(jié):通過setAttribute對文檔做出修改后,查看源代碼時看到的仍是改變前的屬性值。這種現(xiàn)象源自DOM的工作模式:先加載文檔的靜態(tài)內(nèi)容,再動態(tài)刷新,動態(tài)刷新不影響文檔的靜態(tài)內(nèi)容。
第4章 案例研究:JS圖片庫
1 標(biāo)記
第一項工作是為這些圖片創(chuàng)建一個鏈接清單。如果圖片有排序,用<ol>,無排序,用 <ul>。增加一個占位符圖片為圖片預(yù)留一個瀏覽區(qū)域。
<ul>
<li>
<a href="images/1.jpg" onclick="showPic(this);return false;" title="gray">gray</a>
</li>
<li>
<a href="images/2.jpg" onclick="showPic(this);return false;" title="ziont1">ziont</a>
</li>
</ul>
<img id="placeholder" src="" alt="my image gallery">
2 JS
function showPic(whichpic){
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
placeholder.setAttribute("src",source);
}
函數(shù)取名為showPic,其參數(shù)取名為whichpic.通過getAttribute獲取whichpic對象的href屬性,通過getElementById獲取占位符圖片,通過setAttribute更改占位符圖片的src屬性。
3 應(yīng)用這個函數(shù)
需要在html文件中添加事件處理函數(shù)(event handler).
事件處理函數(shù)的作用是,在特定事件發(fā)生時調(diào)用特定的JS代碼。如onmouseover、onmouseout、onclick函數(shù)。
當(dāng)把onclick函數(shù)嵌入到一個鏈接中時,需要把這個鏈接本身用作showPic函數(shù)的參數(shù),可使用this關(guān)鍵字:onclick="showPic(this);"
但是,點擊這個鏈接時,不僅showPic函數(shù)被調(diào)用,鏈接被點擊的默認(rèn)行為也會被調(diào)用,如何阻止這個默認(rèn)行為被調(diào)用。事件處理函數(shù)的工作機(jī)制是,一旦事件發(fā)生,相應(yīng)的JS代碼就會被執(zhí)行。被調(diào)用的JS代碼可以返回給事件函數(shù)一個值。如果讓返回的是一個布爾值,如該例中return false,則onclick就認(rèn)為“這個鏈接沒有被點擊”。
4 擴(kuò)展這個函數(shù)
(1)childNodes屬性。
在一棵節(jié)點樹上,childNodes屬性可以用來獲取任何一個元素的所有子元素,它是一個包含這個元素全部子元素的數(shù)組:element.childNodes.
如需精確查出body元素一共有多少個子元素:
function countBodyChildren(){
var body_element = document.getElementsByTagName("body")[0];
alert(body_element.childNodes.length);
}
window.onload = countBodyChildren;
(2)nodeType屬性。
nodeType共有12種可取值,但其中僅有3種具有實用價值。
- 元素節(jié)點的nodeType屬性值是1
- 屬性節(jié)點的nodeType屬性值是2
- 文本節(jié)點的nodeType屬性值是3
(3)為標(biāo)記里增加一段描述。
首先,為目標(biāo)文本安排顯示位置,設(shè)置id值。
<p id="description">Choose an picture.</p>
目的是圖片鏈接被點擊時,不僅把占位符圖片替換為那個href屬性指向的圖片,還要把這段文本同時替換為那個圖片鏈接的title屬性值。
(4)用JS改變這段描述。
修改showPic()函數(shù):
function showPic(whichpic){
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
placeholder.setAttribute("src",source);
var text = whichpic.getAttribute("title");
var description = document.getElementById("description");
}
(5)nodeValue屬性。
如果想改變一個文本節(jié)點的值,要使用DOM提供的nodeValue屬性,它用來得到(和設(shè)置)一個節(jié)點的值。注意:<p>元素本身的nodeValue屬性是一個空值,包含在<p>元素里的文本是另一種節(jié)點,它是p元素的第一個子節(jié)點。要修改p元素的文本值,需要獲取的是文本而不是p,因此下面兩條語句,第一條返回null,第二條才是文本值。
alert(description.nodeValue);
alert(description.childNodes[0].nodeValue);
(6)firstChild和lastChild屬性。
數(shù)組元素childNodes[0]有個更直觀易讀的同義詞:firstChild.與之對應(yīng)的是lastChild.
(7)利用nodeValue屬性刷新這段描述。
function showPic(whichpic){
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
placeholder.setAttribute("src",source);
var text = whichpic.getAttribute("title");
var description = document.getElementById("description");
description.firstChild.nodeValue = text;
}
第5章 最佳實踐
1 過去的錯誤
(1)JS
易學(xué)易用的技術(shù)是一把雙刃劍,容易被廣泛應(yīng)用,但往往缺乏高水平的質(zhì)量控制。一些現(xiàn)成的JS函數(shù)里有很多問題考慮不周全。一旦瀏覽器不支持或禁用了JS解釋功能,那些質(zhì)量低劣的腳本就會導(dǎo)致用戶無法瀏覽相應(yīng)的網(wǎng)頁甚至整個網(wǎng)站。
(2)flash
(3)質(zhì)疑
網(wǎng)站對JS的濫用已經(jīng)持續(xù)了相當(dāng)長的時間。如果要使用JS,就要確認(rèn):這么做會對用戶瀏覽體驗產(chǎn)生什么影響?用戶瀏覽器不支持JS該怎么辦?
2 平穩(wěn)退化
如果正確地使用了JS腳本,就可以讓訪問者在他們?yōu)g覽器不支持JS的情況下仍能順利地瀏覽你的網(wǎng)站,這就是所謂的平穩(wěn)退化,就是說雖然某些功能無法使用,但最基本的操作仍能順利完成。舉例:創(chuàng)建新的瀏覽器窗口 window.open(url,name,features)
(1)JS偽協(xié)議
“真”協(xié)議用來在因特網(wǎng)上的計算機(jī)之間傳輸數(shù)據(jù)包,如HTTP協(xié)議、FTP協(xié)議等,偽協(xié)議則是一種非標(biāo)準(zhǔn)化的協(xié)議,偽協(xié)議讓我們通過一個鏈接來調(diào)用JS函數(shù)。
如調(diào)用popUp()函數(shù):
<a hret="javascript:popUp('http://www.example.com/');">Example</a>
這條語句在支持“javascript:”偽協(xié)議的瀏覽器中運(yùn)行正常,較老的瀏覽器會失敗,支持這種偽協(xié)議但禁用了JS功能的瀏覽器什么也不會做??傊贖TML文檔中通過“javascript:”偽協(xié)議調(diào)用JS代碼的做法非常不好。
(2)內(nèi)嵌的事件處理函數(shù)
<a href="#" onclick="popUp('http://www.example.com/');
return false;">Example</a>
把href值設(shè)置為“#”只是為了創(chuàng)建一個空鏈接,實際工作全部由onclick屬性負(fù)責(zé)完成。這個方法同樣不能平穩(wěn)退化。
(3)平穩(wěn)退化的重要性
一個重要的訪問者:搜索機(jī)器人(searchbot)。目前只有極少數(shù)搜索機(jī)器人能理解JS代碼(?)。如果你的JS網(wǎng)頁不能平穩(wěn)退化,它們在搜索引擎上的排名就可能大受損害。一個解決辦法,具體到popUp()函數(shù),把href屬性設(shè)置為真實存在的URL地址:
<a
onclick="popUp('http://www.example.com');return false;">Example</a>
上述代碼可簡化為
<a
onclick="popUp(this.href);return false;">Example</a>
3 向CSS學(xué)習(xí)
(1)結(jié)構(gòu)與樣式的分離
具備CSS支持的瀏覽器可以把網(wǎng)頁呈現(xiàn)得美輪美奐,不支持或禁用了CSS功能的瀏覽器同樣可以把網(wǎng)頁的內(nèi)容按照正確的結(jié)構(gòu)顯示出來。
(2)漸進(jìn)增強(qiáng)
所謂“漸進(jìn)增強(qiáng)”就是用一些額外的信息層去包裹原始數(shù)據(jù),按照漸進(jìn)增強(qiáng)原則創(chuàng)建出來的網(wǎng)頁幾乎都符合平穩(wěn)退化原則。如果說CSS是提供“表示”,則JS是提供“行為”。把CSS代碼從HTML文檔里分離出來可以讓CSS工作得更好,這同樣適用于JS行為層。
4 分離JS
<a class="popup">Example</a>
如何實現(xiàn)當(dāng)這個鏈接被點擊時,它將調(diào)用popUp()函數(shù):JS語言不要求事件必須在HTML文檔里處理,可以在外部JS文件里把一個事件添加到HTML文檔中的某個元素上,可以利用class或id屬性來解決。具體步驟:
(1)把文檔中所有鏈接全放入一個數(shù)組里
(2)遍歷數(shù)組
(3)如果某個鏈接的class屬性等于popup,就表示這個鏈接在被點擊時應(yīng)調(diào)用popUp()函數(shù)。
window.onload = prepareLinks;
function prepareLinks(){
var links = document.getElementsByTagName("a");
for (var i=0;i<links.length;i++){
if (links[i].getAttribute("class")=="popup"){
links[i].onclick=function(){
popUp(this.getAttribute("href"));
return false;
}
}
}
}
function popUp(winURL){
window.open(winURL,"popup",width=320,height=480");
}
以上代碼將調(diào)用popUp()函數(shù)的onclick事件添加到有關(guān)鏈接上,等于把這些操作從HTML文檔里分離出來,這就是“分離JavaScript”。另外,為保證HTML文檔加載完再加載腳本,可將代碼打包到preparelinks()函數(shù),并將其添加到windows對象的onload事件上。
5向后兼容
(1)對象檢測
檢測瀏覽器對JS的支持程度。只要把某個方法打包在一個if語句里,就可以根據(jù)這條if語句的條件表達(dá)式求值結(jié)果是true還是false來決定采取怎樣的行動。這種檢測稱為對象檢測。
if(method){
statements
}
但如此編寫出來的函數(shù)會增加一對花括號,如果需要在函數(shù)里檢測多個DOM方法和/或?qū)傩允欠翊嬖?,這個函數(shù)最重要的語句就會深埋在一層又一層的花括號里,這樣的代碼往往很難閱讀和理解。把測試條件改為“如果你不理解這個方法,就離開”則更簡單。如:
if(!getElementById || !getElementsByTagName)return false;
(2)瀏覽器嗅探技術(shù)
這是一種風(fēng)險很大的技術(shù)。一是瀏覽器有時會撒謊,二是為適用于多種瀏覽器,嗅探腳本會越來越復(fù)雜,三是許多嗅探腳本在進(jìn)行此類測試時,要求瀏覽器版本號必須得到精確匹配,因此需要一直修改。這種技術(shù)正在被更簡單更健壯的對象檢測技術(shù)所取代。
6 性能考慮
(1)盡量少訪問DOM和盡量減少標(biāo)記。在多個函數(shù)都會取得一組類似元素的情況下,可以考慮重構(gòu)代碼,把搜索結(jié)果保存在一個全局變量里,或者把一組元素直接以參數(shù)形式傳遞給函數(shù)。并且要盡量減少文檔中的標(biāo)記數(shù)量。
(2)合并和放置腳本。包含腳本的最佳方式就是使用外部文件。減少請求數(shù)量通常都是在性能優(yōu)化時首先要考慮的。腳本在標(biāo)記中的位置對頁面的初次加載時間也有很大影響。傳統(tǒng)上放在<head>里,但會導(dǎo)致瀏覽器無法并行加載其他文件。一般來說根據(jù)HTTP規(guī)范,瀏覽器每次從同一域名中最多只能同時下載兩個文件。把所有<script>標(biāo)簽都放到文檔的末尾,</body>標(biāo)記前,可以讓頁面變得更快(?)
(3)壓縮腳本。是指把腳本文件中不必要的字節(jié),如空格和注釋統(tǒng)統(tǒng)刪除。有許多工具可以用來精簡代碼。多數(shù)情況下你應(yīng)該有兩個版本,一個是工作副本,可以修改代碼并添加注釋,另一個是精簡副本,用于放在站點上,為了與非精簡版本區(qū)分開,可在文件名中加上min字樣。
第6章 案例研究:圖片庫改進(jìn)
1 圖片庫代碼回顧
2 平穩(wěn)退化
此代碼(見第5章)即使在JS功能被禁用,用戶也可以瀏覽圖片庫里的圖片,所有鏈接也都正常工作。但如果選用“javascript:”偽協(xié)議,或把鏈接寫成#,禁用JS的用戶將無法瀏覽圖片。
3 JS與HTML是分離的嗎
因為在html里插入了onclick事件,所以JS和HTML是混在在一起的。把JS移出HTML有多種方法,如給每個鏈接添加class屬性。但注意圖片清單里各個鏈接有一個共同點,他們都包含在同一個列表清單元素里,因此給列表清單設(shè)置一個id比較簡單。
(1)添加事件處理函數(shù)。
想要完成的工作:
- 檢查當(dāng)前瀏覽器是否理解getElementsByTagName
- 檢查當(dāng)前瀏覽器是否理解getElementById
- 檢查當(dāng)前網(wǎng)頁是否有id為imagegallery的元素
- 遍歷imagegallery中的所有鏈接
- 設(shè)置onclick事件,讓它在有關(guān)鏈接被點擊時完成以下操作:把這個鏈接作為參數(shù)傳給showPic()函數(shù),取消鏈接被點擊時的默認(rèn)行為。
①檢查點
if(!document.getElementsByTagName||!document.getElementById)return false;
if(!document.getElementById(“imagegallery”))return false;
出于JS與HTML分離的原則,如果想用JS給某個網(wǎng)頁添加一些行為,就不應(yīng)該讓JS代碼對這個網(wǎng)頁的結(jié)構(gòu)有任何依賴。
②變量名里有什么
創(chuàng)建一個“gallery”變量,選擇一些有意義的單詞來命名可以讓代碼更容易閱讀和理解。但要注意有些單詞在JS語言中有特殊的含義和用途,這些統(tǒng)稱為“保留字”的單詞不能用作變量名,另外,現(xiàn)有JS函數(shù)或方法的名字,如alert、var、if,也不能用來命名變量。
var galley=document.getElementById("imagegallery");
var links=gallery.getElementsByTagName("a");
③遍歷
把充當(dāng)循環(huán)計數(shù)器的變量命名為“i”是一種傳統(tǒng)做法,含義是increment(遞增)。
for(var i=0;i<links.length;i++){
④改變行為
links[i].onclick=function(){ /*定義匿名函數(shù)*/
showPic(this); /*this代表links[i]*/
return false; /*禁用鏈接默認(rèn)行為*/
}
⑤完成JS函數(shù)
function prepareGallery(){
if(!document.getElementsByTagName||!document.getElementById)return false;
if(!document.getElementById(“imagegallery”))return false;
var galley=document.getElementById("imagegallery");
var links=gallery.getElementsByTagName("a");
for(var i=0;i<links.length;i++){
links[i].onclick=function(){
showPic(this);
return false;
}
}
}
(2)共享onload事件。
如果DOM不完整,則測試的準(zhǔn)確性就無從談起。因此應(yīng)該讓這個函數(shù)在網(wǎng)頁加載完畢之后立刻執(zhí)行。網(wǎng)頁加載完畢會觸發(fā)onload事件,必須把prepareGallery函數(shù)綁定在這個事件上:window.onload=prepareGallery;
但如果想讓兩個函數(shù)都在頁面加載完成是執(zhí)行,分別與onload綁定,只有最后那個函數(shù)會執(zhí)行。一個最簡單的解決方法:創(chuàng)建一個匿名函數(shù)來容納這兩個函數(shù),再將該匿名函數(shù)與onload綁定。一個彈性最佳的解決方法:利用addLoadEvent函數(shù)。
function addLoadEvent(func){
var oldonload=window.onload;
if(typeof window.onload!='function'{
window.onload=func;
}else{
window.onload=function(){
oldonload();
func();
}
}
}
4 不要做太多假設(shè)
之前的代碼里用到了id屬性值等于placeholder和description的元素,但未對這些元素是否存在做任何檢查。假如我們要實現(xiàn),只要placeholder圖片存在,即使description元素不存在,切換顯示新圖片的操作也照常進(jìn)行。
檢查placeholder:
if(!document.getElementById("placeholder"))return false;
description:
if(document.getElementById("description"));這樣是可選的,有則執(zhí)行,無則忽略。
添加這兩條代碼后,即使HTML中沒有id=placeholder也不會出現(xiàn)JS錯誤,但是會出現(xiàn)點擊鏈接沒任何響應(yīng),這意味著腳本不能平穩(wěn)退化。問題在于prepareGallery函數(shù)做出了這樣的假設(shè):showPic函數(shù)肯定會正常返回?;谶@一假設(shè),prepareGallery函數(shù)取消了onclick事件的默認(rèn)行為。是否返回false值以取消onclick事件的默認(rèn)行為,應(yīng)該由showPic函數(shù)決定。如果圖片切換成功,返回true;如果圖片切換不成功,返回false。應(yīng)該在返回前驗證showPic的返回值,以決定是否組織默認(rèn)行為。如果showPic返回true,則返回false阻止默認(rèn)行為;如果showPic返回false,則返回true允許默認(rèn)行為。
function prepareGallery(){
if(!document.getElementsByTagName||!document.getElementById)return false;
if(!document.getElementById("imagegallery"))return false;
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);
}
}
}
5 優(yōu)化
showPic函數(shù)里仍存在一些需要處理的假設(shè),如假設(shè)每個鏈接都有title屬性。
if(whichpic.getAttribute("title") !=null)
作為一種簡單的視覺反饋,把title不存在時的text設(shè)置為空字符串:
if(whichpic.getAttribute("title") !=null){
var text=whichpic.getAttribute("title");
} else {
var text="";
}
還可以寫成:
var text=whichpic.getAttribute("title")?whichpic.getAttribute("title"):"";
這里的問號叫三元操作符,其后是text的兩種取值:
variable=condition?if true:if false;
還可驗證placeholder是否為圖片:
if(placeholder.nodeName!="IMG") return false;
注意nodeName總是返回一個大寫字母的值,即使元素在HTML中是小寫。
還可驗證description的第一個子元素是文本:
if(description.firstChild.nodeType==3){
description.firstChild.nodeValue=text;
}
在實際工作中,你要自己決定是否需要這些檢查,它們針對的是HTML中可能不在你控制范圍的內(nèi)情況。理想情況下不應(yīng)該對HTML的內(nèi)容和結(jié)構(gòu)做太多假設(shè)。
6 鍵盤訪問
有些用戶不使用鼠標(biāo),使用鍵盤,需調(diào)用onkeypress事件處理函數(shù)。要讓onkeypress和onclick觸發(fā)同樣的行為,可復(fù)制onclick代碼,或者links[i].onkeypress=links[i].onclick;但onkeypress很容易出問題。不過在幾乎所有瀏覽器里,用Tab鍵移動到某個鏈接后按回車,也可觸發(fā)onclick。最好不要使用onkeypress,onclick對鍵盤訪問的支持已很完美。最終代碼:
window.onload=prepareGallery;
function prepareGallery(){
if(!document.getElementsByTagName||!document.getElementById) return false;
if(!document.getElementById("imagegallery"))return false;
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)?false:true;
}
}
}
function showPic(whichpic){
if(!document.getElementById("placeholder")) return false;
var source = whichpic.getAttribute("href");
var placeholder = document.getElementById("placeholder");
if(placeholder.nodeName !="IMG") return false;
placeholder.setAttribute("src",source);
if(document.getElementById("description")){
var text = whichpic.getAttribute("title")? whichpic.getAttribute("title"):"";
var description = document.getElementById("description");
if (description.firstChild.nodeType==3){
description.firstChild.nodeValue = text;
}
}
return true;
}
7 把JS和CSS結(jié)合起來##
DOM Core和HTML-DOM##
至此用到的DOM方法:getElementsByTagName、getElementById、getAttribute、setAttribute都是DOM Core的組成部分,并不專屬于JS,支持DOM的任何一種程序語言都可以使用它們。使用JS和DOM為HTML文件編寫腳本時,還有許多屬性可供選用,如onclick,這些屬性屬于HTML-DOM。例如HTML-DOM提供了一個forms對象,它可以把document.getElementsByTagName("form")簡化成document.forms。HTML-DOM代碼通常比DOM Core代碼簡短,但它們只能用來處理Web文檔??筛鶕?jù)個人喜好和具體情況進(jìn)行選擇。
第7章 動態(tài)創(chuàng)建標(biāo)記
網(wǎng)頁的結(jié)構(gòu)由標(biāo)記負(fù)責(zé)創(chuàng)建,絕大多數(shù)JS函數(shù)只用來改變某些細(xì)節(jié)而不改變其底層結(jié)構(gòu)。但JS也可以用來改變網(wǎng)頁的結(jié)構(gòu)和內(nèi)容。
1 傳統(tǒng)方法
(1)document.write
HTML:
<body>
<script>
document.write("<p>This is inserted.</p>");
</script>
</body>
其最大缺點是違背了“行為應(yīng)該與表現(xiàn)分離”的原則。即使把語句挪到外部函數(shù)里,也仍然要在<body>部分添加<script>來調(diào)用。最好用外部JS文件去控制網(wǎng)頁行為,避免在<body>部分亂用<script>,避免使用document.write方法。
(2)innerHTML屬性
類似于document.write方法,innerHTML屬性也是HTML專有屬性,不能用于任何其他標(biāo)記語言文檔。任何時候,標(biāo)準(zhǔn)的DOM都可以替代innerHTML,雖然往往需要多編寫一些代碼,但DOM提供了更高的精確性和更強(qiáng)大的功能。
2 DOM方法
DOM不僅可以獲取文檔內(nèi)容,還可以更新文檔內(nèi)容。如setAttibute,注意它并未改變文檔的物理內(nèi)容,只有用瀏覽器打開文檔是才會看到效果變化,這是因為瀏覽器實際顯示的是那棵DOM節(jié)點樹,在瀏覽器看來,DOM節(jié)點樹才是文檔。所以你不是在創(chuàng)建標(biāo)記,而是在改變DOM節(jié)點樹。
(1)createElement方法
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="testdiv">
</div>
</body>
</html>
想把一段文本插入到testdiv中,用DOM語言說,就是要創(chuàng)建一個p節(jié)點,并將其作為div節(jié)點的一個子節(jié)點。用createElement創(chuàng)建:
var para = document.createElement("p");
任何時候,只要使用了createElement,把新創(chuàng)建的元素賦給一個變量總是個好主意。雖然p已經(jīng)存在,但它還不是任何DOM節(jié)點樹的組成部門,這種情況稱為文檔碎片,但它已經(jīng)像其他節(jié)點一樣,有自己的DOM屬性了。
(2)appendChild方法
parent.appendChild(child) /*child不上引號*/
具體到上面的例子,讓p稱為testdiv的子節(jié)點:
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);
雖然使用appendChild方法時,不必非得使用一些變量來引用父節(jié)點和子節(jié)點,但這樣會提高代碼的可讀性。
(3)createTextNode方法
現(xiàn)在想把一些文本放入p元素,需要用createTextNode,語法與createElement類似。
var para = document.createElement("p");
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);
var txt=document.createTextNode("Hello World");
para.createTextNode(txt);
appendChild方法還可以用來連接那些尚未成為文檔樹的節(jié)點,所以可以先創(chuàng)建節(jié)點,再使用appendChild連接。
(4)一個更復(fù)雜的組合
如果要插入<p>This is <em>my</em> content.</p>,先分析節(jié)點樹再寫代碼:
window.onload=function(){
var para=document.createElement("p");
var txt1=document.createTextNode("This is ");
var emphasis=document.createElement("em");
var txt2=document.createTextNode("my ");
var txt3=document.createTextNode("content.");
para.appendChild(txt1);
para.appendChild(emphasis);
para.appendChild(txt3);
emphasis.appendChild(txt2);
var testdiv=document.getElementById("testdiv");
testdiv.appendChild(para);
}
3 重回圖片庫
之前圖片庫的HTML中有一個圖片和一段文字的存在只是為了讓DOM處理,那么用DOM方法來創(chuàng)建它們是最合適的選擇。
需要完成的任務(wù):
- 創(chuàng)建一個img元素節(jié)點
- 設(shè)置這個節(jié)點的id屬性
- 設(shè)置這個節(jié)點的src屬性
- 設(shè)置這個節(jié)點的alt屬性
- 創(chuàng)建一個p元素節(jié)點
- 設(shè)置這個節(jié)點的id屬性
- 創(chuàng)建一個文本節(jié)點
- 把這個文本節(jié)點追加到p元素上
- 把p元素和img元素插入到gallery.html文檔
var placeholder=document.createElement("img");
placeholder.setAttribute("id","placeholder");
placeholder.setAttribute("src","images/show.jpg");
placeholder.setAttribute("alt","my image gallery");
var description=document.createElement("p");
description.setAttribure("id","description");
var desctext=document.createTextNode("Choose an image");
description.appendChild(desctext);
document.body.appendChild(placeholder);
document.body.appendChild(description);
(1)在已有元素前插入一個新元素
原HTML文檔中圖片清單剛好在文檔最后,所以把placeholder和description追加到body節(jié)點上,它們會出現(xiàn)在清單后面。如果想把一個新元素插入到一個現(xiàn)有元素的前面,可用insertBefore()方法實現(xiàn):
parentElement.insertBefore(newElement,targetElement)
其實不用搞清楚parentElement是哪個,因為targetElement的parentNode屬性值就是它。
gallery.parentNode.insertBefore(description,gallery);
(2)在現(xiàn)有方法后插入一個新元素
DOM沒有提供insertAfter方法,但完全可以用DOM方法編寫一個insertAfter方法。思路,查看目標(biāo)元素是不是parent的lastChild,如果是,直接appendChild;如果不是,就插入到目標(biāo)元素下一個兄弟元素的前面。
function insertAfter(newElement,targetElement){
var parent=targetElement.parentNode;
if(parent.lastChild==targetElement){
parent.appendChild(newElement);
}else{
parent.insertBefore(newElement,targetElement.nextSibling);
}
(3)圖片庫二次改進(jìn)
4 Ajax
2005年發(fā)明,用于概括異步加載頁面內(nèi)容的技術(shù)。使用Ajax可以做到只更新頁面中的一小部分,不必再次加載整個頁面。對頁面的請求以異步方式發(fā)送到服務(wù)器,服務(wù)器不會用整個頁面來響應(yīng)請求,會在后來處理請求,用戶此時仍能繼續(xù)瀏覽頁面并與頁面交互。
(1)XMLHttpRequest對象
它是Ajax技術(shù)的核心,充當(dāng)瀏覽器的腳本與服務(wù)器之間的中間人。JS通過這個對象可以自己發(fā)送請求和處理響應(yīng)。由于不同瀏覽器實現(xiàn)該對象的方式不太一樣,因此需要為同一事情寫不同的代碼分支。