JavaScript基礎

 

JavaScript是jQuery應用的基礎,掌握JavaScript這門語言是使用jQuery的基礎條件。本章不會全面細緻的講解JavaScript的所有, 而是講解其精髓,這些知識能夠提高你們的JavaScript內功。切忌,要修煉上乘的武功,必需要有深厚的內功基礎,不然只可學到其招式而發揮不了功力。JavaScript實際上包括三部分:javascript

w   ECMAScript 描述了該語言的語法和基本對象。php

w   DOM 描述了處理網頁內容的方法和接口。html

w   BOM 描述了與瀏覽器進行交互的方法和接口。java

本章將講解ECMAScript和DOM的相關知識。程序員

2.1  JavaScript基礎

一般所說的JavaScript語法,其實是指JavaScript中的ECMAScript部分。本節主要講解JavaScript的語法和語意特性。正則表達式

2.1.1 JavaScript與ECMAScript

不少人知道JavaScript,殊不知道ECMAScript爲什麼物。這就比如你知道本書的做者叫作張子秋,可是殊不知道做者也屬於人類同樣。爲何要知道二者的關係呢?由於除了JavaScript,還有微軟的JScript,以及Flash中的ActionScript, 這幾種語言寫法上有太多的類似之處。懂得ECMAScript,就可以清楚地理解這些語言爲什麼如此的類似算法

什麼是ECMAScript?下面是維基百科中對於ECMAScript的定義:編程

ECMAScript是一種由ECMA國際(前身爲歐洲計算機制造商協會)經過ECMA-262標準化的腳本程序設計語言。這種語言在萬維網上應用普遍,它每每被稱爲JavaScript或JScript,但實際上後二者是ECMA-262標準的實現和擴展。json

1995年Netscape公司發佈的Netscape Navigator 2.0中,發佈了與Sun聯合開發的JavaScript 1.0而且大獲成功, 而且隨後的3.0版本中發佈了JavaScript1.1,恰巧這時微軟進軍瀏覽器市場,IE 3.0搭載了一個JavaScript的克隆版-JScript, 再加上Cenvi的ScriptEase(也是一種客戶端腳本語言),致使了三種不一樣版本的客戶端腳本語言同時存在。爲了創建語言的標準化,1997年JavaScript 1.1做爲草案提交給歐洲計算機制造商協會(ECMA),第三十九技術委員會(TC39)被委派來「標準化一個通用的,跨平臺的,中立於廠商的腳本語言的語法和語意標準」。最後在Netscape、Sun、微軟、Borland等公司的參與下制訂了ECMA-262,該標準定義了叫作ECMAScript的全新腳本語言。數組

今後之後的Javscript,JScript,ActionScript等腳本語言都是基於ECMAScript標準實現的。

因此,ECMAScript其實是一種腳本在語法和語義上的標準。實際上JavaScript是由ECMAScript,DOM和BOM三者組成的。 因此說,在JavaScript,JScript和ActionScript中聲明變量,操做數組等語法徹底同樣,由於它們都是ECMAScript。可是在操做瀏覽器對象等方面又有各自獨特的方法,這些都是各自語言的擴展。圖2-1顯示了ECMAScript與各個語言的關係。

 

圖 2-1    各類腳本語言的關係

2.1.2 JavaScript中的值類型和引用類型

不一樣於C#,Java中多種多樣的「類」,JavaScript是一個相對單純的世界。JavasScript中也分爲值類型和引用類型兩大類,可是這兩大類中都只含有不多的幾種類型。

瞭解C#語言的都對值類型和引用類型不會陌生。JavaScript中有時也稱爲原始值(primitive value)和引用值(reference value)。

值類型:存儲在棧(stack)中,一個值類型的變量實際上是一個內存地址,地址中存儲的就是值自己。

引用類型:存儲在堆(heap)中,一個引用類型的變量的值是一個指針,指向存儲對象的內存處。

2.1.3 JavaScript中的原始類型

時刻記着「值類型」和「引用類型」的區別有助於更好的理解語言的精髓。爲了化繁爲簡, 雖然從理論上應該分爲「值類型」和「引用類型」,又能夠將JavaScript中對象分爲「本地對象」,「內置對象」和「宿主對象」,可是在實際應用中爲了讓JavaScript變得真正單純,能夠將JavaScript中的類型分爲:

undefined,null,number,string,boolean,function, 其餘object引用類型。

即便一個JavaScript初學者對這些類型也不會陌生。這種分類方法前6種都是最常使用的JavaScript類型,第7種object引用類型其實並非獨立的類型,由於function就是一種引用類型,另外JavaScript中的值類型背後其實也是一個「引用類型」,這一點和C#極其類似,就是全部的類型都是從Object中派生而來。好比number是一個「值類型」, 可是其實存在一個引用類型「Number」,咱們可使用以下的方式聲明一個變量:

var oNumberObject = new Number(55);

Number對象是ECMAScript標準中定義的。可是本文不許備深刻的講解它們,由於最經常使用的仍是使用下面的方式建立一個「值類型」的數值爲55的變量:

var iNumberObject = 55;

這就夠了不是嗎?可是要記住藏在背後的Number對象是一個引用類型!

2.1.4 undefined,null和typeof運算符

若是你對undefined和null這兩種類型常常分辨不清,那麼恭喜,由於你會找到不少的知音。其實要理解這兩種類型, 首先要知道它們設計的初衷:      

undefined:表示一個對象沒有被定義或者沒有被初始化。

null:表示一個還沒有存在的對象的佔位符。

有意思的是undefined類型是從null派生來的。因此它們是相等的:

alert(null == undefined); //輸出  「true」

對於全部的JavaScript開發人員,最常碰到的就是對象不存在錯誤。正如在C#中的空引用錯誤同樣。不少程序員習慣的覺得JavaScript中的if會自動將undefined和null對象轉化爲false,好比:

var oTemp = null;

if(oTemp){}; //false

if(undefined){}; //false

上面的語句都是正確的,if中的條件都是false。可是若是註釋掉oTemp的聲明部分,狀況就不一樣了:

//var oTemp = null; 註釋掉變量聲明語句

if(oTemp){}; //error

會拋出錯誤。可是不管是否聲明過oTemp對象,使用typeof運算符獲取到的都是undefined而且不會報錯:

//var oTemp1; 註釋掉變量聲明語句

alert(typeof oTemp1); //輸出  「undefined」

var oTemp2;

alert(typeof oTemp2); //輸出  「undefined」

因此若是在程序中使用一個可能沒有定義過的變量,而且沒有使用typeof作判斷,那麼就會出現腳本錯誤。而若是是此變量是null或者沒有初始化的undefined對象,能夠經過if或者「==」來判斷。切記,未聲明的對象只能使用typeof運算符來判斷!

正由於如此,typeof常常和undefined變量一塊兒使用。typeof運算符返回的都是一個字符串,而時常程序員會看成類型來使用。是否你也犯過以下的錯誤呢?

//var oTemp; 註釋掉變量聲明語句

if(typeof oTemp == undefined ){…}; //false

       這裏if將永遠是false。要時刻銘記typeof返回的是字符串,應該使用字符串比較:

//var oTemp; 註釋掉變量聲明語句

if(typeof oTemp ==」undefined」){…};//true

       下面是typeof運算符對各種型的返回結果:

w   undefined:「undefined」

w   null:「object」

w   string:「string」

w   number:「number」

w   boolean:「Boolean」

w   function:「function」

w   object:「object」

結果只有null類型讓人吃驚。null類型返回object,這實際上是JavaScript最初實現的一個錯誤,而後被ECMAScript沿用了,也就成爲了如今的標準。因此須要將null類型理解爲「對象的佔位符」,就能夠解釋這一矛盾,雖然這只是一中 「辯解」。對於代碼編寫者必定要時刻警戒這個「語言特性」,由於:

alert(typeof null == 「null」);//輸出  false

永遠爲false。

       還要提醒,一個沒有返回值的function(或者直接return返回)實際上返回的是undefined。

        function voidMethod()

        {

            return;

        }

        alert(voidMethod()); //輸出  "undefined"

2.1.5 變量聲明

由於JavaScript是弱類型語言,因此在變量的聲明上體現了與C#等強類型語言的明顯不一樣。

JavaScript可使用var顯式的聲明變量:

var iNum;

var sName;

也能夠在一個var語句中聲明多個變量,用「,」分割變量:

var iNum, sName;

變量的類型是在賦值語句中肯定的,JavaScript使用「=」賦值,能夠在聲明變量的同時對其進行賦值:

var sName=」ziqiu.zhang」;

由於是弱類型語言,即便變量的類型在初始化時已經被肯定,仍然能夠在以後把它設置成其它類型,好比:

var sName = 「ziqiu.zhang」;

alert(sName); //輸出  「ziqiu.zhang」

sName = 55;

alert(sName); //輸出 輸出  「55」

       變量除了能夠顯式聲明,也能夠隱式聲明。所謂隱式聲明,就是不使用var關鍵詞聲明,而直接爲變量賦值,好比:

//var sName; 註釋掉變量聲明語句

sName = 「ziqiu.zhang」

alert(sName); //輸出 輸出  「ziqiu.zhang」

       上面的語句不會出任何的錯誤。就如同使用var聲明過變量同樣。可是不一樣之處是變量的做用域。隱式聲明的變量老是被建立爲全局變量。即便是在一個函數中建立的變量也依然是全局的。好比:

        function test()

        {

            sName = " ziqiu.zhang ";

        }

        //var sName;

        test();     

        alert(sName);    //輸出 輸出  「ziqiu.zhang」

雖然sName是在函數中建立的,可是在函數外層仍然可以訪問sName,由於sName是全局變量。

變量能夠隱式聲明,可是不能夠不聲明。若是一個變量既沒有隱式聲明,也沒有顯式聲明,那麼在使用時會發生對象未定義的錯誤。

2.1.6 JavaScript命名規範

由於JavaScript語言的靈活性,不少人會忽視變量的命名,有的公司即便制定了後段代碼如C#的命名規範,卻忽視了JavaScript變量的命名規範。

從一開始就制定完整的JavaScript命名規範是頗有必要的,尤爲是在愈來愈追求用戶體驗的今天,JavaScript將會承載愈來愈多的用戶邏輯。

先來學習三種命名方法:

w   Camel命名法:首字母小寫,接下來的每一個單詞首字母大寫。好比:
var firstName, var myColor;

w   Pascal命名法:首字母大寫,接下來的每一個單詞首字母大寫。好比:
var FirstName, var MyColor;

w   匈牙利類型命名法:在以Pascal命名法的變量前附加一個小寫字母來講明該變量的類型。例如s表示字符串,則聲明一個字符串類型的變量爲:
var sFirstName;

在JavaScript中應該使用匈牙利命名法命名變量,使用Camel命名法命名函數。

這一點是和C#、Java不一樣的。一般服務器端語言都是用Pascal命名法命名方法,即首字母要大些。可是JavaScript中的全部方法首字母都是小寫的,爲了和JavaScript中的默認命名一致,因此要採用Camel命名法命名方法。好比:

function testMethod(){};

雖然JavaScript中的變量能夠變換類型,可是熟悉強類型語言的人都知道,這是很危險的作法,頗有可能最後在使用時都沒法肯定變量的類型。因此應該儘可能使用匈牙利命名法命名變量,下面是匈牙利命名法的前綴列表:

類型

前綴

示例

Array

a

aNameList

Boolean

b

bVisible

Float

f

fMoney

Function

fn

fnMethod

Int

i

iAge

Object

o

oType

Regexp(正則表達式)

re

rePattern

string

s

sName

可變類型

v

vObj

表 2-1            JavaScript中的匈牙利命名法前綴

使用匈牙利命名法是推薦的作法。可是不少程序由於歷史遺留緣由都使用Camel命名法。記住匈牙利命名法纔是正確的方法。寫代碼也是一門藝術,只要有機會就應該作正確的事。

2.1.7 變量的做用域與閉包

變量的做用域就是變量做用的範圍,只有在變量的做用域內才能夠訪問該變量,不然是沒法訪問此變量的。好比:

        function test()

        {

            var sName = " ziqiu.zhang ";

        }

        alert(sName); //輸出  "sName未定義";

會提示錯誤」sName」未定義。

JavaScript變量的做用域基本上與C#、Java等語言相同。可是由於JavaScript語言的特殊性,有些須要特殊理解的地方。

首先要了解,全局變量是Window對象的屬性。

前面已經提到過隱式聲明的變量都是全局變量。說是「全局」但實際上仍是有做用域的,那就是當前窗口window對象。一個全局變量就是window對象的一個屬性:

        function test()

        {

            sName = " ziqiu.zhang ";

        }

        //var sName;

        test();

        alert(sName);

        alert(window.sName);

隱式聲明瞭一個全局變量sName後,既能夠直接訪問sName,也能夠經過window.sName訪問,二者的效果是相同的。

若是說全局變量仍是傳統的做用域模型,那麼閉包(closure)的概念是會讓初學者迷惑的。閉包的概念比較難以理解, 先看閉包的定義:

閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分。

簡單表述:

閉包就是function實例以及執行function實例時來自環境的變量。

不管是定義仍是簡單表述,都讓人難以理解。由於這都是理論的抽象。經過實例就能夠快速的理解閉包的含義:

【代碼路徑:jQueryStorm.Web/chapter2/closure.aspx】

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title> jQuery storm - 閉包舉例</title>

</head>

<body>

    <div id="divResult">

    </div>

    <script type="text/JavaScript">

        function start()

        {

            var count = 10;

                 //設置定時器,每隔3秒鐘執行一次

            window.setInterval(function()

            {

                       //設置定時器,每隔3秒鐘執行一次

                document.getElementById("divResult").innerHTML += count + "<br/>";

                count++;

            }, 3000);

        };

        start();

    </script>

</body>

上面的實例使用setInterval函數,設置了一個定時器,每3秒鐘會向divResult容器中,添加count變量的值,而且count變量自增。

count是start函數體內的變量,按照一般的理解count的做用域是在start()函數內,在調用start()函數結束後應該也會消失。可是此示例的結果是count變量會一直存在,而且每次被加1,數據結果是:

10

11

12

13

這是由於count變量是setInterval中建立的匿名函數(也就是包含count++的函數)的閉包的一部分!

再通俗的講, 閉包首先就是函數自己,好比上面這個匿名函數自己,同時加上在這個函數運行時須要用到的count變量。

JavaScript中的閉包是隱式的建立的,而不像其餘支持閉包的語言那樣須要顯式建立。在C#語言中不多碰到是由於C#中沒法在方法中再次聲明方法. 而在一個方法中調用另外一個方法一般使用參數傳遞數據。

JavaScript中的閉包是很是強大的,能夠用於執行復雜的邏輯。可是在使用時要時刻當心,由於閉包的強大也致使了它的複雜,使用閉包會讓程序難以理解和維護。

2.2 悟透JavaScript中的function

function類型是JavaScript的靈魂,由於程序是由數據和邏輯兩部分組成的,基本的數據類型能夠存儲各類數據,而function則用來存儲邏輯。固然這只是將function看成「方法」來看待而已。實際上function類型有着更增強大的生命力。

2.2.1 使用function聲明方法和類型

可使用function聲明一個方法,好比:

        function testMethod()

        {

            alert("Hello world!");

        }

        testMethod(); //輸出  "Hello world!"

調用該方法將顯示「Hello world!」。然而除了方法,function還能夠用來聲明「類型」。JavaScript中本沒有「類型」的概念,也就是C#中的Class的概念,可是可使用function來假裝一個類型。好比:

        function Car()

        {

            this.color = "none";

            if (typeof Car._initialized == "undefined")

            {

                Car.prototype.showColor = function()

                {

                    alert(this.color);

                }

            }

            Car._initialized = true;

        };

上面的代碼聲明瞭一個Car類,而且帶有一個color屬性和一個showColor方法。這裏使用的是「動態原型」方法,此方法將在下一節中作詳細講解。

好了,如今可使用「new」運算符建立一個類的實例,並使用它:

        var car = new Car();

        car.showColor(); //輸出  "none"

        car.color = "blue";

        car.showColor(); //輸出  "blue"

2.2.2 function的本質

雖然function有這麼多的能力,可是其本質卻很簡單:

function變量是引用類型,內容存儲的是指向function內容的指針,function的內容就是函數體。

JavaScript中的對象有一個特色,裏面存儲的都是name/value(名/值)。用name/value存儲屬性是比較容易理解的,一個屬性的名稱就是name,好比color屬性。屬性的值就是value,好比color的屬性值red。這與C#語言是同樣的。可是JavaScript中的function也是一個name/value,這一點十分獨特。可使用alert方法輸出function變量的內容:

alert(testMethod);

輸出:

 

圖 2-2    函數變量輸出結果

 

發現輸出的內容就是testMethod的方法體自己。如同一個屬性輸出的是其屬性值。

認清這一點有助於更好的理解function的本質。

2.2.3 new 運算符

function類型的對象,配合new運算法,能夠建立一個實例。依然是上面建立一個Car類實例的例子:

var car = new Car();

首字母大寫的Car是一個函數,可是使用new運算符生成的是一個object:

alert(typeof car); //輸出  object

而Car函數自己則更像是一個類的構造函數。實際上,new與運算符的操做,等價於下面的語句:

    var car2 = {}; //建議一個空對象   

    //將car2的原型設置爲Car.prototype,這一部是經過javascript內部的Object.create實現的,可是此函數是內部函數沒法直接訪問。

    Car.call(car2); //修改函數調用的上下文

    alert(car2.color);

其中第二步沒法直接用語句替代,是由於實現這一步是在javascript引擎內部,方法沒法直接調用。

2.2.4 function的arguments參數對象

JavaScript不支持方法的重載,緣由是在JavaScript中同名的function只能有一個,而且function函數的參數個數可使任意的。

雖然沒法直接支持「重載」,可是能夠經過arguments對象來假裝重載,讓一個function根據參數的不一樣實現不一樣的功能。

arguments是在function中的特殊對象,經過arguments對象能夠訪問到function調用者傳遞的全部參數信息,好比獲取到傳入的參數個數:

        function testMethod()

        {

            alert(arguments.length);

        }

        testMethod(); //輸出  "0"

        testMethod("abc"); //輸出  "1"  

能夠經過arguments的索引arguments[index]獲取到每個傳入的參數值:

        function myMethod()

        {

            alert(arguments instanceof Array);

            if (arguments.length == 0)

            {

                alert("no arguments");

            }

            else if (arguments.length == 1)

            {

                alert("Hello:" + arguments[0].toString())

            }

        }

        myMethod();

        myMethod("ziqiu.zhang");

顯然,上面的方法經過判斷參數的個數實現了假裝重載。可是這種實現並很差,由於全部的重載邏輯都集中在一個方法中,而且有使人厭煩的多個if-else分支。

可是使用arguments能夠開發出功能強大靈活的函數。在開發jQuery的插件時就要常用arguments對象實現函數的僞重載。

2.2.5 理解this指針

在C#中,this變量一般指類的當前實例。JavaScript則不一樣,JavaScript中的「this」是函數上下文,,不是在聲明時決定的,而是在調用時決定的。由於全局函數其實就是window的屬性,因此在頂層調用全局函數時的this是指window對象,下面的例子能夠很好的說明這一切:

    <script type="text/JavaScript">

        var oName = { name: "ziqiu.zhang" };

        window.name = "I am window";

           //showName是一個函數,用來顯示對象的name屬性

        function showName()

        {

            alert(this.name);

        }

 

        oName.show = showName;

        window.show = showName;

 

        showName(); //輸出 輸出爲 "I am window"

        oName.show(); //輸出 輸出爲 "ziqiu.zhang"

        window.show(); //輸出 輸出爲 "I am window"      

    </script>

showName是一個function,使用alert語句輸出this.Name,。由於showName是一個全局的函數,因此其實showName是window 的一個屬性:window.showName,調用全局的showName就是調用window.showName, 由於window上的name屬性爲」I am window」,因此直接調用showName和調用window.showName都會輸出」I am window」。

oName是一個對象,前面已經講過函數也是一個屬性,能夠像屬性同樣賦值,因此將showName方法複製給oName的show方法,當調用oName.show方法時,也會觸發alert(this.name)語句,可是此時方法的調用者是oName,因此this.name輸出的是oName的name屬性。

利用「this指向函數調用者」的特性,能夠實現鏈式調用。jQuery中大部分都是鏈式調用。首先實現一個鏈式調用的例子:

        var oName = { name: "ziqiu.zhang", age:999 };

        oName.showName = function()

        {

            alert(this.name);

            return this;

        };

        oName.showAge = function()

        {

            alert(this.age);

            return this;

        };

        oName.showName().showAge();    

上面的代碼首先輸出」ziqiu.zhang」,而後輸出」999」。oName使用鏈式調用方法分別調用了兩個方法,等效於:

oName.showName();

oName.showAge(); 

使用鏈式調用的關鍵點就是要返回調用者自己,也就是this指針。

在使用this時,也經常由於this指向函數上下文的特性,致使引用的錯誤,好比:

        var myClass =

        {

            checkName: function() { return true; },

            test: function() {

                if (this.checkName()) {

                    alert("ZZQ");

                }

            }

        }

$("#btnTest").click(myClass.test);

 

上面的例子中試圖使用面向對象的方式將一些方法封裝在myClass中,併爲btnTest對象添加單擊事件的事件處理函數。如今若是單擊btnTest調用myClass.test方法,會發生錯誤,提示找不到checkName方法。jQuery.proxy函數解決此問題。參見「jQuery工具函數」一章中的「修改函數上下文」一節。

2.3 JavaScript中的原型

JavaScript中的原型(Prototype)是JavaScript最特別的地方之一。不管是實現JavaScript的面向對象仍是繼承,使用prototype都必不可少。

2.3.1 使用原型實現JavaScript的面向對象

「原型」表示對象的原始狀態,JavaScript中的每一個對象都有一個prototype屬性,可是隻有Function類型的prototype屬性可使用腳本直接操做。Object的prototype屬於內部屬性,沒法直接操做。prototype屬性自己是一個object類型。一個函數的prototype上全部定義的屬性和方法,都會在其實例對象上存在。因此說prototype就是C#類中的實例方法和實例屬性。實例方法和靜態方法是不一樣的,靜態方法是指不須要聲明類的實例就可使用的方法,實例方法是指必需要先使用"new"關鍵字聲明一個類的實例, 而後才能夠經過此實例訪問的方法。

下面是兩種方法的聲明方式:

function staticClass() { }; //聲明一個類

staticClass.staticMethod = function() { alert("static method") }; //建立一個靜態方法

staticClass.prototype.instanceMethod = function() { "instance method" }; //建立一個實例方法

上面首先聲明瞭一個類staticClass, 接着爲其添加了一個靜態方法staticMethod 和一個動態方法instanceMethod,區別就在於添加動態方法要使用prototype原型屬性。

對於靜態方法能夠直接調用:

staticClass.staticMethod();

可是實例方法不能直接調用:

staticClass.instanceMethod(); //語句錯誤, 沒法運行.

實例方法須要首先實例化後才能調用:

var instance = new staticClass();//首先實例化

instance.instanceMethod(); //在實例上能夠調用實例方法

       使用prototype除了能夠聲明實例方法,也能夠聲明實例屬性。正由於原型有着如此強大的功能,因此可以使用原型來實現JavaScript的面向對象。目前存在着不少以面向對象的形式建立對象的方法,本書不一一詳細講解,只介紹一種最經常使用,最容易理解和使用的方法:動態原型方法。

       假設要定義一個汽車Car類型, 具備屬性color和方法showColor(),則可使用下面的方式聲明和使用:

        function Car()

        {

                 //聲明屬性

            this.color = "none";

                 //聲明方法

            if (typeof Car._initialized == "undefined")

            {

                Car.prototype.showColor = function()

                {

                    alert(this.color);

                }

            }

            Car._initialized = true;

        };

        var car = new Car();

        car.showColor(); //輸出 輸出爲 "none"

        car.color = "blue";

        car.showColor(); //輸出 輸出爲 "blue"

動態原型方法的精髓在於使用prototype聲明實例方法,使用this聲明實例屬性。除了更加接近面向對象的編程方式,這種方式特色就是簡單易懂。注意充當「類定義」的function並無帶有任何參數,雖然也能夠傳遞參數進去,可是不要這麼作。這是爲了下一節講解的使用原型鏈實現繼承機制而做的準備。

能夠在創立了對象之後再爲其屬性賦值。雖然麻煩了一點,可是代碼簡單,易於理解。

2.3.2 使用原型鏈實現繼承

除了面向對象的聲明方式,在面向對象的世界中最常使用的就是對象繼承。在JavaScript中能夠經過prototype來實現對象的繼承。繼續上一節Car的例子。假設Car有兩個派生類,一個是GoodCar,特色是跑得快。一個是BadCar,特色是跑得慢。可是它們和Car同樣都具備color屬性和showColor方法。

       以GoodCar類爲例,只要讓GoodCar的prototype屬性爲Car類的一個實例,便可實現類型的繼承:

GoodCar.prototype = new Car();

如今,GoodCar類已經有了Car的全部屬性和方法:

 var goodCar = new GoodCar();

 goodCar.showColor(); //輸出  "none"

實現了繼承後,GoodCar還要實現本身的run()方法,一樣使用prototype實現,下面是GoodCard類的完整定義:

        //建立GoodCar類

        function GoodCar()

        {        }

        GoodCar.prototype = new Car();

        GoodCar.prototype.run = function() { alert("run fast"); }

須要注意GoodCar類自身的方法必定要在實現繼承語句以後定義。

爲什麼稱這種方式爲原型鏈繼承呢?由於GoodCar的prototype是Car,Car的prototype是object,也就是說GoodCar也具備object對象全部的屬性和方法。這是一個「鏈」的結構。

使用原型能夠簡單快捷的實現JavaScript的面向對象和繼承。

2.4  DOM

DOM太經常使用了,以致於不少人操做了DOM殊不知道使用的是DOM。從概念上區分JavaScript核心語法,DOM和BOM是頗有必要的,「學院派」做風可以讓Web的知識體系結構更加清晰。

2.4.1 什麼是DOM

DOM(文檔對象模型)是 HTML 和 XML 的應用程序接口(API)。

上面是DOM的定義。有人將操做HTML理解爲操做DOM,這沒有錯。可是HTML並非DOM。操做HTML也不是DOM的惟一功能。DOM是一種模型以及操做這些模型的API。DOM分爲不一樣的部分和級別。按照部分來劃分,DOM包括:

w   Core DOM
定義了一套標準的針對任何結構化文檔的對象

w   XML DOM
定義了一套標準的針對 XML 文檔的對象

w   HTML DOM
定義了一套標準的針對 HTML 文檔的對象。

因此Web開發人員常說的DOM實際上是指HTML DOM。這其實只是DOM的一部分。DOM標準分爲level 一、二、3,每一個標準都規定了不一樣的功能,並非全部的瀏覽器都能實現最高級別的DOM標準。Firefox是目前實現DOM標準最優秀的瀏覽器,但仍在爲了實現所有的DOM標準而努力。本書不對DOM作更細緻的講解,本節的目的讓開發人員正確的理解DOM與 HTML DOM的關係。

2.4.2 操做HTML DOM對象

經過JavaScript,能夠經過DOM操做HTML的各個元素。現在的頁面標準已是XHTML時代。可是頁面的本質依然是XML,一種特殊的用於頁面顯示的XML。不管是ASP.NET,JavaScriptp仍是php,任何的服務器頁面最後都要輸出爲HTML頁面—瀏覽器只認識HTML。

一個頁面會首先被瀏覽器解析成DOM,好比下面就是一個頁面的DOM結構:

 

圖 2-3    頁面的文檔對象模型

頁面被瀏覽器解析爲DOM模型後,經過JavaScript操做頁面元素其實是經過瀏覽器提供的基於DOM的API方法實現的。理解了這些就不會提出相似「DOM元素是JavaScript變量嗎?」等疑問了。

       既然DOM是一個文檔樹,就必定會有根節點:html元素。頁面的document對象表明文檔,documentElement屬性就表示html對象:

var oHtml = document.documentElement;

DOM核心的API方法能夠獲取到一個節點的第一個元素和最後一個元素,由於一個html對象一般只有head和body兩個元素,因此能夠經過下面的方式獲取:

var oHead = oHtml.firstChild;

var oBody = oHtml.lastChild;

理解了樹狀結構,使用DOM核心的方法就能夠獲取到頁面上任意的一個元素。下面是最經常使用的獲取元素節點的方法:

(1)getElementById

根據元素id獲取元素。好比獲取id爲divMsg的元素:

document.getElementById("divMsg")

這是最常使用的獲取元素的方法,一個頁面首先應該保證每一個元素的id是惟一的。由於任何元素均可以添加id屬性,因此經過id可以獲取到頁面上面的任何一個元素。這也是效率最高的一個方法。即便使用jQuery功能強大的選擇器,也應該首選使用id選擇器。

可是須要注意getElementById方法只能經過document對象調用,使用oBody. getElementById會引起錯誤。

(2)getElementsByName

經過name屬性獲取一組元素。方法的名字中是複數形式的「Elements」,說明了獲取到的是一組元素,經過Element的單複數形式區分獲取到的是單個對象仍是多個對象,是一種便於記憶的方法。getElementsByName不常使用,由於一般只有表單元素才帶有name屬性。

(3)getElementsByTagName

       經過html元素的名稱獲取一組元素。好比頁面上全部的div:

var aDiv = document.getElementsByTagName("div");

       也能夠在一個元素上調用getElementsByTagName方法,獲取到的是這個元素內的元素:

var oHtml = document.documentElement;               

 var oBody = oHtml.lastChild;

 var aMyDiv = oBody.getElementsByTagName("div");

上面的語句會得到body元素下全部的div元素。由於全部div都是在body中的,因此其實和document.getElementsByTagName("div") 的結果是相同的。

除了獲取,還有設置屬性,建立元素,附加元素等各類DOM方法。DOM的系統學習不在本書的範圍內,由於隨後的章節將介紹使用jQuery來控制頁面元素,jQuery的內部也是使用DOM實現的,理解了DOM的原理有助於更好的學習jQuery,甚至發現jQuery內部未被發現的錯誤。

2.4.3 DOM元素與HTML元素

經過DOM接口操做HTML元素,實際上並非直接改變HTML代碼,中間有瀏覽器的幫助。在JavaScript中操做的都是DOM元素,可使用JavaScript動態的建立一個div,而後附加到body中:

【代碼路徑:jQueryStorm.Web/chapter2/DOM.aspx】

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"

 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>jQuery Storm - DOM</title>

    <script type="text/JavaScript">

        function onLoad()

        {

                //獲取到HTML對象

            var oHtml = document.documentElement;

                 //獲取到Body對象

            var oBody = oHtml.lastChild;

                 //建立一個div對象

            var oDiv = document.createElement("div");

                 //建立一個文本節點

            var oText = document.createTextNode("Hello DOM");

                 //將文本節點放到div對象中

            oDiv.appendChild(oText);

                 //將div對象(帶有文本節點)放到body對象中

            oBody.appendChild(oDiv);

        }  

    </script>   

</head>

<body onload="onLoad()">

</body>

</html>

上面的例子使用了標準的DOM方法建立了div元素節點oDiv,以及一個內容節點oText。在DOM中內容也是一個節點。

注意,若是要改變頁面的DOM結構,應該在瀏覽器加載完頁面的DOM結構以後進行。不然在IE6中會常常出現瀏覽器操做終止的錯誤。本實例將添加元素的工做放在了body的onload事件中。使用jQuery則會有更好更簡單的處理方式,使用$(function(){…})這種形式就能夠在DOM加載完畢後執行函數內的語句。在jQuery核心一節會詳細講解。

動態的添加了一個DOM元素,實際上改變了頁面的DOM模型,隨後瀏覽器會從新解析DOM模型並轉換成頁面顯示,因而頁面上出現了「Hello DOM」,就好像一開始就包含在HTML代碼中同樣。

不少時候就是在操做HTML元素,爲什麼本節要刻意的強調DOM元素與HTML元素呢?這是爲了引出關於DOM屬性與HTML屬性的區別。顧名思義,DOM屬性就是DOM元素的屬性,HTML屬性就是HTML元素的屬性。DOM是沒法直接經過源代碼看到的。可是HTML元素是實實在在存在於頁面的HTML代碼中的,好比:

<img src="images/image.1.jpg" id="hibiscus" alt="Hibiscus" class="classA" />

這是一個HTML元素,圖片的地址就是img元素的src屬性,值爲「images/image.1.jpg」,由於HTML元素最後都要轉化成DOM元素供瀏覽器顯示,因此瀏覽器最後將這部分代碼解析成一個DOM元素,而且也具備一個src屬性,只是屬性值變成了「http://localhost/images/image.1.jpg」(假設網頁在localhost站點下)。HTML中的元素最後會映射成DOM元素,並且中間的轉化並非徹底一致的,img元素的圖片地址會轉化爲絕對路徑,還有些屬性好比「class」轉化爲DOM會後會改變屬性名稱,變成了「className」。

牢記, 在JavaScript中能夠直接獲取或設置"DOM屬性",因此若是要設置元素的CSS樣式類, 要使用的是DOM屬性「className」而不是HTML元素「class」:

document.getElementById("img1").className = "classB";

在執行完上面的語句後,id爲img1的元素已經應用了classB樣式,可是查看網頁源代碼,發現HTML元素的class仍然是classA。

有了jQuery,就不須要Web開發人員刻意的理解class和className的區別。有關使用jQuery操做樣式和屬性將在後面的章節中詳細講解。

2.5 其餘JavaScript祕籍

要講解JavaScript的所有,須要厚厚的一本書。既然沒法全面講解,就只能抽取一些武功祕籍傳授給你們。除了上面講解的JavaScript知識,本節再介紹一些JavaScript的關鍵知識點。

2.5.1 數據通訊格式JSON

JSON是指JavaScript Object Notation, 即JavaScript對象表示法. 因此說JSON實際上是一種數據格式,由於JavaScript原生的支持因此賦予了JSON強大的生命力。好比可使用下面的語句建立一個對象:

        var oPerson = {

            name: "ziqiu.zhang",

            age: 999,

            school:

            {

                college: "BITI",

                "high school" : "No.18"

            },

            like: ["mm"]

        };

JSON的語法格式是使用"{"和"}"表示一個對象,  使用"屬性名稱:值"的格式來建立屬性, 多個屬性用","隔開.

上例中school屬性又是一個對象. like屬性是一個數組. 使用JSON格式的字符串建立完對象後, 就能夠用"."或者索引的形式訪問屬性:

objectA.school["high school"];

objectA.like[1];

JSON常常在Ajax中使用。讓服務器端頁面只返回JSON格式的數據,使用JavaScript的eval方法將JSON格式的數據轉換成對象,以便使用JavaScript已經操做。

JavaScript原生的支持了JSON格式,而各類語言也都有支持JSON格式的類庫。在.net framework 3.5中微軟已經提供了原生的JSON序列化器。JSON是目前客戶端與服務器端交互數據的最好的數據格式。下面提供一個以.NetFramework3.5版本中自帶的JSON序列化器爲基礎開發的JSON幫助類,代碼也能夠在本書光盤的代碼中或者網站上找到:

【代碼路徑:jQueryStorm.Common.Utility/JsonHelper.cs】

/****************************************************************************************************

 * *

 * *        File Name       : JsonHelper.cs

 * *        Creator         : ziqiu.zhang

 * *        Create Time     : 2008-10-8

 * *        Functional Description  :  JSON幫助類。用於將對象轉換爲Json格式的字符串,或者將Json的字符串轉化爲對象。

 *   使用限制:

 *       (1)只能在.NET Framework3.5及以上版本使用。

 *       (2)對象類型須要支持序列化,類上添加[DataContract],屬性上添加[DataMember]

 *      使用舉例請參見單元測試項目。

 *

 * *        Remark          :

 * *

 * *  Copyright (c) eLong Corporation.  All rights reserved.

 * ****************************************************************************************************/

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Runtime.Serialization.Json;

using System.IO;

 

namespace jQueryStorm.Common.Utility

{

    /// <summary>

    /// JSON幫助類。用於將對象轉換爲Json格式的字符串,或者將Json的字符串轉化爲對象。

    /// 只能在.NET Framework3.5及以上版本使用。

    /// </summary>

    public class JsonHelper

    {

        /// <summary>

        /// 將對象轉化爲Json字符串

        /// </summary>

        /// <typeparam name="T">源類型</typeparam>

        /// <param name="instance">源類型實例</param>

        /// <returns>Json字符串</returns>

        public static string ObjectToJson<T>(T instance)

        {

            string result = string.Empty;

 

            //建立指定類型的Json序列化器

            DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(T));

 

            //將對象的序列化爲Json格式的Stream

            MemoryStream stream = new MemoryStream();

            jsonSerializer.WriteObject(stream, instance);

 

            //讀取Stream

            stream.Position = 0;

            StreamReader sr = new StreamReader(stream);

            result = sr.ReadToEnd();

 

            return result;

        }

 

        /// <summary>

        /// 將Json字符串轉化爲對象

        /// </summary>

        /// <typeparam name="T">目標類型</typeparam>

        /// <param name="jsonString">Json字符串</param>

        /// <returns>目標類型的一個實例</returns>

        public static T JsonToObject<T>(string jsonString)

        {

            T result;

 

            //建立指定類型的Json序列化器

            DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(T));

 

            //將Json字符串放入Stream中

            byte[] jsonBytes = new UTF8Encoding().GetBytes(jsonString);

            MemoryStream stream = new MemoryStream(jsonBytes);

 

            //使用Json序列化器將Stream轉化爲對象

            result = (T)jsonSerializer.ReadObject(stream);

 

            return result;

        }

    }

}

在使用時,假設有一個MyClass的類:

        public class MyClass

        {

            public string MyName

            { get; set; }

            public int Age

            { get; set; }

        }

使用JsonHelper的兩個泛型方法ObjectToJson和JsonToObject,就能夠在對象實例和JSON字符串之間序列化和反序列化:

            //建立一個類實例

            MyClass myClass = new MyClass() { MyName = "ziqiu.zhang", Age = 99 };

            //將對象實例轉化爲JSON字符串

            string jsonString = JsonHelper.ObjectToJson < MyClass > (myClass);

            //將JSON字符串轉化爲對象實例

            myClass = JsonHelper.JsonToObject<MyClass>(jsonString);

有關服務器端程序的序列化和反序列化,還有許多種實現方式。JsonHelper類的實現僅供參考。

2.5.2 動態語言-eval

使用eval方法能夠將JSON格式的字符轉化爲JavaScript對象:

        var sJson = "{ name:'ziqiu.zhang' }";

        eval(" var oName  =" + sJson);

        alert(oName.name); //輸出  「ziqiu.zhang」

注意這裏的sJson對象存儲的是JSON格式的字符串,這個時侯字符串的內容尚未被解析成對象。使用eval方法能夠將sJson字符串轉化爲對象存儲在oName對象中。

eval 函數可計算某個字符串,並執行其中的的 JavaScript 代碼。這讓JavaScript搖身一變成爲了動態語言,能夠在運行時構造語句,經過eval執行,就像上面的解析JSON字符同樣。

eval函數是有返回值的:

        var iNum = eval("5+2");

        alert(iNum); //輸出  "7"

eval強大的功能讓JavaScript開發人員能夠發揮無窮的想象力。實如今一些高級語言中沒法實現或者實現起來很困難的功能。要知道在C#中使用表達式目錄樹實現動態功能是多麼「深奧」的一件事情。

2.5.3 JavaScript中的邏輯運算符

由於邏輯運算符太經常使用太普通了,因此不少的程序員會認爲無非就是NOT、AND、OR三個運算符,返回布爾值。可是在JavaScript中卻不只僅如此,AND和OR還會返回對象。

(1)NOT運算符

NOT運算符用「!」表示,與邏輯 OR 和邏輯 AND 運算符不一樣的是,邏輯 NOT 運算符返回的必定是 Boolean 值。與不少程序不一樣之處在於,NOT運算符不只僅能夠運算Boolean類型的對象,任何的定義了的對象均可以進行「!」運算。這裏的定義了的對象主要是爲了排除「未定義的undifined」對象,由於即便對象的類型是undifined,也有兩種狀況,一種是未定義,一種是未初始化。未初始化的undifined類型的對象是能夠參與OR、AND和NOT邏輯運算的,只有未定義的undifined對象在邏輯運算中會出現腳本錯誤。這一點目前在一些權威教程的網站上面的解釋都是有錯誤的,請你們尤爲注意。

NOT運算符的規則以下:

w   若是運算數是對象,返回 false

w   若是運算數是數字 0,返回 true

w   若是運算數是 0 之外的任何數字,返回 false

w   若是運算數是 null,返回 true

w   若是運算數是 NaN,返回 true

w   若是運算數是 未初始化的undefined,返回true

w   若是運算數是未定義的undefined,發生錯誤

NOT運算符其實和if條件語句的行爲是同樣的,只是結果相反。

(2)AND運算符

AND 運算符用雙和號(&&)表示。AND運算符的運算數若是都是Boolean類型的對象,那麼運算規則就是若是有一個運算對象是false,則返回false。

JavaScript中的AND與NOT運算符最特別的地方是運算數不必定是Boolean類型,返回的也不必定是 Boolean 值,可能返回對象。

AND運算符的規則以下:

若是一個運算數是對象,另外一個是 Boolean 值,返回該對象。

w   若是兩個運算數都是對象,返回第二個對象。

w   若是某個運算數是 null,返回 null。

w   若是某個運算數是 NaN,返回 NaN。

w   若是某個運算數是未初始化的 undefined,返回undefined。

w   若是運算數是未定義的undefined,發生錯誤

雖然上面描述了AND運算符的行爲,可是在應用時經常會碰到下面的疑惑:

            alert(false && null); //輸出  "false"

            alert(true && null); //輸出  "null"

            alert(null && false);//輸出  "null"

            alert(null && true);//輸出  "null"

 由於有一個對象是「null」,因此天然想到要應用規則「若是某個元素是null,返回null」,可是第一行語句卻返回false。認爲這些規則還具備「優先級」,實際上是由於AND運算符最本質邏輯規則:

若是第一個運算數決定告終果,就再也不計算第二個運算數。對於邏輯 AND 運算來講,若是第一個運算數是 false,那麼不管第二個運算數的值是什麼,結果都不可能等於 true。

也就是說AND運算符遇到第一個運算數「false」就中止計算返回false了。而若是第一個運算數是「true」則還要計算第二個運算數,第二個運算數爲「null」,因此根據規則返回null。

注意看規則中說明是「返回」的,說明運算到此運算數則已經返回結果,不會再繼續計算其餘運算數。因此當運算數是null,就已經返回告終果「null」,即便第二個運算數是false也不會參與運算。

(3)OR運算符

OR 運算符與C#中的相同,都由雙豎線(||)表示。若是兩個運算數都是boolean類型,OR運算符的運算邏輯和在幾乎全部的語言中都是相同的,即有一個爲「true」則返回true。

一樣在JavaScript中,OR運算符的運算數不必定是Boolean類型,返回的也不必定是 Boolean 值,可能返回對象。

OR運算符的規則以下:

w   若是一個運算數是對象,另外一個是 Boolean 值,返回該對象。

w   若是兩個運算數都是對象,返回第一個對象。

w   若是某個運算數是 null,返回 null。

w   若是某個運算數是 NaN,返回 NaN。

w   若是某個運算數是爲初始化的undefined,則忽略此操做數。

w   若是某個運算數是未定義的undefined,發生錯誤。

當兩個操做數都是對象時,OR和AND的規則是不一樣的,是否感受記憶困難?其實能夠將對象理解爲「true」,在AND中遇到「true」時須要繼續運算,因此繼續計算到第二個操做數。返回的也是第二個操做數即最後一個參與運算的操做數。在OR運算時遇到「true」即返回,因此返回第一個對象也一樣是最後一個操做數。

OR運算符對於未初始化的undefined類型的對象處理也比較特別,會忽略此操做數,也能夠理解爲看成「false」處理。若是還有第二個操做數則會繼續運算。這一點和AND不一樣,AND運算中若是碰到了未初始化的undefined,則馬上返回undefined。

再次強調,未定義的undefined在全部的邏輯運算符中都會出現錯誤。要儘可能避免使用未定義的對象。

理解了JavaScript的邏輯運算符,就能夠介紹一種在jQuery插件開發等地方常常會使用的JavaScript技巧。假設有下面的方法:

        function tsetMethod(param1)

        {

            alert(param1);

               //alert(param2); //輸出   「error」

        }

testMethod方法有一個參數param1,由於方法的簽名就帶有param1參數,因此param1和徹底未聲明過的param2是不同的,當調用testMethod方法而且不傳入參數是,會輸出「undefined」。即param1此時在函數體內屬於「未初始化的undefined」對象。

若是但願在testMethod方法中爲param1參數設置默認值「abc」,若是沒有傳入param1參數則使用默認值。有兩種思路,一是能夠經過typeof或者arguments對象判斷是否傳入了param1參數。

經過typeof判斷:

        function testMethod1(param1)

        {

            if (typeof param1 == "undefined")

            {

                param1 = "abc";

            }

            alert(param1);

        }

或者經過arguments對象判斷:

        function testMethod2(param1)

        {

 

            if (arguments.length < 1)

            {

                param1 = "abc";

            }

            alert(param1);

        }

再有能夠藉助OR運算符的規則,讓語句變得更精簡:

        function testMethod3(param1)

        {

            alert(param1 || "abc")

        }

由於param1是未初始化的undefined,而字符串「abc」是對象,因此會返回「abc」對象。而若是傳入了param1則會返回param1。這個JavaScript技巧在處理可能爲未初始化的undefined時經常用到。可是在使用可能未定義undefined對象時一樣會發生語法錯誤。有些開發人員不經過參數傳遞數據,而是在方法中經過閉包來使用外層的變量,此時就會有使用未聲明的undefined對象的危險。

2.6 小結

       本節介紹了JavaScript語言的一些精髓,指出了JavaScript開發人員的一些常見誤區和知識盲點,好比理解ECMAScript,區分JavaScript語言和DOM, 理解DOM元素和HTML元素的區別等。由於篇幅有限不可能對全部的知識進行講解。可是本章的學習將爲後續學習jQuery打下堅實的基礎,而且讓Web開發人員的JavaScript知識體系獲得昇華。

相關文章
相關標籤/搜索