jQuery片段:
1 var
2 // Will speed up references to window, and allows munging its name.
3 window = this,
4 // Will speed up references to #ff0000, and allows munging its name.
5 undefined,
6 // Map over jQuery in case of overwrite
7 _jQuery = window.jQuery,
8 // Map over the $ in case of overwrite
9 _$ = window.$,
10
11 jQuery = window.jQuery = window.$ = function( selector, context ) {
12 // The jQuery object is actually just the init constructor 'enhanced'
13 return new jQuery.fn.init( selector, context );
14 },
15
16 // A simple way to check for HTML strings or ID strings
17 // (both of which we optimize for)
18 quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
19 // Is it a simple selector
20 isSimple = /^.[^:#\[\.,]*$/;
21
在這一節,我們將討論同一段jQuery片段的另一個知識點:數據類型和對象。為了讓我們更好地理解代碼,我們必須對這一部分內容深入了解。沒有牢固的基礎,是不可能構筑起堅實的堡壘的。
內置數據類型
內置數據類型,也稱作固有數據類型,也就是JS的基本的數據類型。首先,讓我們的大腦熱一下身:回想一下,我們所有編程語言中實際可能運用到的數據都有些什么?
基本如你所想,但實質上我們需要的只是有意義的文字而已。但對于電腦來說,它能認識的不是文字,而是邏輯電路中電平的高低。為此,我們在程序語言中,將這些高低電平轉換成0和1,并使用這些二進制的數字串構造成人類更加好理解的數字邏輯。這些數字邏輯實際上就是所謂的數據類型了。(我承認我在胡說八道……)
現在讓我們看看JS是怎么理解這些數字邏輯,來讓我們更好地使用它的(至少JS的設計者初衷是這樣)。
undefined
我們第一個看到的數據類型是undefined。或許很多人都會懷疑到——你沒有搞錯吧?undefined也是一種數據類型?!然后,我可以很鎮定的告訴你,我沒搞錯……
1 alert(undefined);// "undefined"
2 alert(typeof undefined);// "undefined"
3 alert(undefined instanceof Object);// "false"
4 alert(undefined instanceof undefined);// 語法錯誤:instanceof不能對undefined運算
5 alert(undefined instanceof Undefined);// 錯誤:"Undefined"未定義
看到上面的例子后,你還有疑問嗎?
聯系一下我們前面所說的內容,如果調用一個沒有聲明的變量被直接調用,應該是會報錯的,但上例沒有,這就證明了undefined是JS里固有的變量。而且,根據typeof的結果,這個變量的數據類型是undefined。但它不是一個Object。而且不能用instanceof來判斷他是不是undefined實例。
那么,讓我們來小結一下:undefined是一種類似單態的數據類型,他在全局作用域中以變量標識undefined存在,并且擁有唯一值undefined。
除了上面所說的以外,還有一些需要注意的地方,請看下例:
1 // 如何判斷一個變量是否聲明
2 alert(typeof x == "undefined");// true
3 alert(x==undefined);// 報錯:"x"未定義
所以,如果你需要判斷一個變量是否已聲明,請使用typeof運算符,再和"undefined"這個字符串比較。如果你看得仔細,你或許會產生一個疑問:在上一節中不是說,如果在作用域鏈中找不到變量標識的時候,不是會創建空值標識嗎?如果你有此一問,那么我得贊你一下,你真的用心思考了!這個問題將在稍后解答。
1 var x;
2 alert(undefined == "undefined");// false
3 alert(x==undefined);// true
4 alert(x=="undefined");// false
另外,別以為undefined和字符串"undefined"是相等的,undefined不是字符串,而是另一種數據類型。
null
相信看完undefined后,你已經不再懷疑null也是一種數據類型了吧。可是null又有何特別呢?請看:
1 alert(null);// "null"
2 alert(typeof null);// "object"
3 alert(null instanceof Object);// false
4 alert(null instanceof null);// 報錯:instanceof不能對null運算
你有感到奇怪了么?奇怪在哪里呢?嗯。我也覺得奇怪:為啥typeof的運算結果會是“object”,但instanceof判斷又為false呢?但對于這個問題,能找到的唯一解答是如此描述的:
“這點潛在的混淆是為了向下兼容。”
但怎么向下兼容,我已經不能考究也不想考究下去了。我能告訴大家的是typeof null的返回值“object”是ECMA-262中規定的內容。并且這規定只是為了兼容性,實際上null并不是一個Object的實例。
至此,我們再小結一下:null和undefined很相似,只是我們從根本上還是屬于兩種不同的數據類型。但是他們真的“不同”嗎?讓我們再來看看下面的例子:
1 alert(null==undefined);// "true"
2 alert(null===undefined);// "false"
那么,看來JS解釋器一般認為null和undefined是相等的了,雖然在嚴格等于運算時結果是false。(據說這是為了兼容以往的瀏覽器中沒有undefined而設的。)
由此引起了我們另一個思考:他們到底有何異同呢?實際上,導致他們嚴格等于運算返回false的是typeof的運算——在運算符節再詳細解釋吧。但他們其實還有一個語言規范層級的區別:undefined是作為Global對象的屬性存在的,而null則是ECMA規范中設定的一個字面值。換句話說,undefined是存放在Global中的屬性(所以像單態),而null是解釋執行時產生的值。然而對他們的值作強制轉換還是相同的,如:
1 alert(!undefined);// "true"
2 alert(!!undefined);// "true"
3 alert(!null);// "true"
4 alert(!!null);// "true"
// 因此我們在使用if作判斷的時候能直接判斷變量是否為空或未定義
1 alert(parseInt(undefined));// "NaN"
2 alert(parseInt(null));// "NaN"
// 注意NaN連自己也是不相等
1 alert(!undefined);// "true"
2 alert(!!undefined);// "true"
3 alert(!null);// "true"
4 alert(!!null);// "true"
// 因此我們在使用if作判斷的時候能直接判斷變量是否為空或未定義
1 alert(parseInt(undefined));// "NaN"
2 alert(parseInt(null));// "NaN"
// 注意NaN連自己也是不相等
還值得注意的是,和其他語言不同,他們和0值是不相等的,除非轉換成布爾值:
1 alert(undefined==0);// "false"
2 alert(null==0);// "false"
3 alert(!undefined==!0);// "true"
4 alert(!null==!0);// "true"
5 alert(undefined==0);// "false"
6 alert(null==0);// "false"
7 alert(!undefined==!0);// "true"
8 alert(!null==!0);// "true"
另外,它們都不帶屬性的:
1 view plaincopy to clipboardprint?
2 alert(null.a);// "錯誤: null has no properties"
3 alert(undefined.a);// "錯誤: undefined has no properties"
4 alert(null.a);// "錯誤: null has no properties"
5 alert(undefined.a);// "錯誤: undefined has no properties"
最后,提一點我在項目中經常看到的,某些程序員喜歡將一些控件的屬性是否定義的判定交給字符串"undefined"和null,但實際上只有null在起作用呢。
boolean
下一個我們看到的類型是boolean,也就是我們常用來判斷真假的布爾型。它的值只有兩個:true和false。讓我們來看看它的特點:
1 var x=true;
2 alert(x);// "true"
3 alert(typeof x);// "boolean"
4 alert(!x==0);// "true"
5 alert(x==1);// "true"
6 alert(x==2);// "false"
7 alert(parseInt(x));// "NaN"
8 alert(parseInt(!x));// "NaN"
9 x=null;
10 alert(x);// "null"
11 alert(typeof x);// "object"
12 x=undefined;
13 alert(x);// "undefined"
14 alert(typeof x);// "undefined"
從上例我們可以看到,true和1相等,false和0相等,但是他們不能轉換成1和0。并且,因為JS的弱數據類型,所以當x被賦予true和false以外的值后,他們的數據類型將會改變。
布爾型的使用通常是在分支控制上,是JS中很關鍵的數據類型之一。
number
接著我們遇到的數據類型是number。顧名思義,number型就是指存放數字的類型。在其他語言中,我們遇到的數字型變量可能很多,分得很細,例如:short,int,long,float,double等等。但在JS中,所有的數字都被歸納成數字型的數據。回想一下如果我們對整型數據作不能整除的運算后會有什么結果?嗯,不能整除的部分將被截斷。但在JS,結果會變得不一樣:
1 var x=5;
2 alert(x/2);// "2.5"
3 var x=5;
4 alert(x/2);// "2.5"
為什么會這樣呢?因為JS實際上會把所有數字型的數據解釋成浮點數。也就是無論你使用的數字是不是整數也好,JS的解釋器都會把他看成浮點數的特例。
雖然在數據類型上沒有其他語言的復雜,但是我們仍然需要面對的是他能解釋的數值范圍。請看:
1 var x=2;
2 alert(Math.pow(x,1023));// "8.98846567431158e+307
3 alert(Math.pow(x,1023)+Math.pow(x,1022));// "1.348269851146737e+308
4 alert(Math.pow(x,1024));// "Infinity"
5 // PS:JS 中以"e+n"表示10的n次方
可以看到,當數字超過2的1024次方減1后,該值就變為無限大(Infinity)了。我們可以用類似的方法再測出JS可以判定的數值范圍,但我在這里就先省去了(其實也就是正負的21024)。我們跟著規范走:JS遵守的浮點標準是IEEE 754,換成具體的數字來說,如果把科學計數法算上,JS的數字運算能力最大可以到±1.7976931348623157x10308,最小到±5x10-324。然而,在我們實際運算中,大多運算符都不能支持這么高位的運算,請看:
1 var x=2;
2 alert(999999999999999+x);// "1000000000000001"
3 alert(999999999999999-x);// "999999999999997"
4 alert(9999999999999999+x);// "10000000000000002"
5 alert(9999999999999999-x);// "9999999999999998"
上例很明顯可以看出,當數位超過15位時,計算精度就可以丟失。那么,究竟這個有效精度范圍是怎樣的呢?
1 var x=2;
2 alert(Math.pow(x,53)-2);// "9007199254740990"
3 alert(Math.pow(x,53)-1);// "9007199254740991"
4 alert(Math.pow(x,53));// "9007199254740992"
5 alert(Math.pow(x,53)+1);// "9007199254740992"
6 alert(Math.pow(x,53)+2);// "9007199254740994"
這次我們就可以明確了,加減運算精度是控制在253內的。也就是說,如果我們要處理253以外的數字運算的話,我們必須使用另外的方法了——例如數位拆分運算(名字我亂起的,實際意思就是將精度不可控的數位拆分成兩個精度可控的小數去計算,最后由兩個小數生成計算結果)。雖然要使用這種大數運算的機會幾乎為零。
雖然JS并不細分數字的數據類型,但是它對各種進制的數字還是有一定支持的。但就初始化用的字面值來說,數字型能支持的有八進制,十進制和十六進制。先看一下示例:
1 alert(10);// "10"
2 alert(010);// "8"
3 alert(0x10);// "16"
4 alert(10);// "10"
5 alert(010);// "8"
6 alert(0x10);// "16"
字面值中表示十進制的數字無須任何修飾,而且能定義小數。
表示八進制的數字需以0開頭,但必須注意的是,后續的數字不能大于7,不然還是會被判斷為十進制數字面值的。八進制數能表示負值,但不能表示小數。論壇中曾經有朋友遇到的問題就是因為八進制字面值引起的。因為他拿到的文本值是固定二位數,而單一數位大于7的字面值將被判斷為十進制而正常運作,但進位后則變為8進制數,所以實際值與預期值就不相等了。
表示十六進制的數字需要以0x或0X開頭,后續數字為符合十六進制表示法的0-9或a-f的大小寫字母。同樣地,十六進制數能表示負值,但不能表示小數。
從上例中,我們還能看到,無論我們用哪種進制的字面值,經過JS解釋后,還是將會返回十進值值的 ——雖然內部存儲必定還是二進制。我們也可以用方法轉換其的進制顯示。
下面,我們看一下JS中的一些典型的字面值和可能產生誤會的字面值:

另外,對于數字型的數據,有以下幾個特殊值:
a)NaN(not a number),意思不是一個數,但它卻是數字型的一個值,當對不適當的數據進行數學運算時,例如字符串或未定義值,它將會作為運算結果返回。NaN不與任何值相等,包括它自己,但我們可以使用方法isNaN來判定一個值得是否NaN。
b)Infinity,意思是無窮大,相對地,有負無窮大-Infinity。當一個數大于等于21024或小于等于21024,會分別用他們的正負形式來表示。所有Infinity是相等的,當然,區分正負地相等。
PS:或許在規范中還有一個負零,字面值為-0。但是實際上,當其輸出時,只是簡單的0而已。不過我沒對低版本的瀏覽器測試過。猜差要不就是低版本中會有所區分,要不就是存儲時的二進制碼有所區分。
在這里說一個實用技巧:我們常會碰到需要把文本框中的數字的小數部分多余的0去掉。這個時候,我們可以使用數字型的特性:
1 alert(5.0000);// "5"
2 alert("5.0000"-0);// "5"
3 alert(typeof("5.0000"-0));// "number"
4 alert(5.0000);// "5"
5 alert("5.0000"-0);// "5"
6 alert(typeof("5.0000"-0));// "number"
利用減0把字符強制轉換成數字型后,尾數的0自然就去掉了。
最后,要提醒一下,JS的浮點運算真的不怎么樣,很容易就會有精度丟失的:
1 alert(0.1+0.2);// "0.30000000000000004"
2 alert(0.1+0.2);// "0.30000000000000004"
解決方法有二:1)放大到整數來運算;2)對計算結果進行精度控制。
string
string,也就是我們最需要的字符串類型。任何其他類型,其實都是為了最終轉換成字符串中人類可以理解的語言而服務的。那么這個令人著迷的數據類型又有何特別呢?我們先來看看如何定義一個字符串:
1 var test="this is a test";
2 var test="this is a test";
是不是很簡單呢?我也覺得也是。那么我們再來看點復雜的:
1 alert('test again');
2 alert("Is it a 'test'?");
3 alert('How "test" things go on?');
4 alert("\"test\" is all right now!");
5 alert('That \'test\' sounds great!');
6 alert("\u0059\u0065\u0073");// "Yes"
7 alert('test again');
8 alert("Is it a 'test'?");
9 alert('How "test" things go on?');
10 alert("\"test\" is all right now!");
11 alert('That \'test\' sounds great!');
12 alert("\u0059\u0065\u0073");// "Yes"
這次是不是看到眼花繚亂呢?字符串里最“復雜”的應用莫過于引號的使用和轉義符了。
由于JS沒有單字符的數據類型——如Java中的char,所以單雙引號的成對使用時基本不存在區別——Java中單引號里的是單字符。而且單雙引號能互相嵌套使用,但單(雙)引號必須包含所有雙(單)引號,并且雙(單)引號中不能再出現單(雙)引號。另一種可以使單(雙)引號都能嵌套自身的方法是使用轉義符“\”。
另外,string支持unicode字面值,他們以并且只能以“\u”開頭,后面跟四位的unicode編碼(不區分大小寫)。在定義后,所有的string都將按UTF16儲存,所以不用擔心輸出的時候JS沒有為你轉碼——當然,可以轉碼的是unicode字面值。
最后提一下,string實際上是存放在有序數列中的。
object
呼呼!總算來到最后一個內置數據類型了。它的大名就是object!在Java中,Object類是所有類的基類,那么聲稱Java~Script的JS到底把object做了怎樣的定位呢?
由于JS中沒有類的概念,所以就沒有了繼承。啊!等等,JS是存在繼承的,而它是依賴prototype而存在的。至于這一部分的內容,我們放到prototype節里去討論。我們現在先來關心一下object這個數據類型有何特別。
object實際上是屬性與方法的集合。在ECMA規范對其類型進行劃分如下:
本地對象(Native Object):指獨立于宿主環境存在的對象,它可能是內置對象,也可能是在運行時由構造函數創建的對象,標準的本地對象是Global,Math,Object,Function,Number,Boolean,String,Array,Date,Math,RegExp和Error,非標準的本地對象則是指自建對象;
內置對象(Build-in Object):指獨立于宿主環境存在的對象,它們從JS進入運行時就能訪問,標準的內置對象包括Global,Math,Object,Function,Number,Boolean,String,Array,Date,Math,RegExp和Error,也就是和標準的本地對象是一樣的。非標準的內置對象則如JScript實現中的Enumerator等。但無論它是否標準的內置對象,所有內置對象都是本地對象;
宿主對象(Host Object):指依賴于宿主環境存在的對象,例如所有瀏覽器中的window(BOM)和document(DOM)對象。所有不是本地對象的對象都是宿主對象。
下面我們來看一下如何才能得到一個object:
1 var a = new Object();
2 function B(){
3 this.b = 1;
4 }
5 var b = new B();
6 alert(a);// "[object Object]"
7 alert(b);// "[object Object]"
8 alert(typeof a);// "object"
9 alert(typeof b);// "object"
10 alert(typeof B);// "function"
11 alert(b.b);// "1"
雖然這個例子可能看不出區別,但是可以說得到一個Object的關鍵是關鍵字new。但在typeof運算中,遇到Function對象還是會返回"function"的。這個在稍后運算符節再討論。
從上面的例子,我們可以看到,JS中獲取對象的方式和Java很相象。但實際上,它們的差別可大呢。這個留待對象中一一解說。
內部數據類型
除了在代碼中我們可以使用的數據類型以外,JS中實際上還存在為了內部運行機制而設的三種數據類型:Reference、List和Completion。
Reference
Reference是一種內部數據類型,它是作為運算中間值存在的。它的內部有兩個屬性:base object和property name。而它的內部也有一些對應的方法去獲取這兩個屬性。一般來說,base object里存放的是值,而property name里存放的是需要返回的標識。
前面曾經提及作用域鏈找不到標識時,返回的是一個空值標識,其返回形式就是以Reference為數據類型傳輸的。所賦予的null值是存放于base object中的。而因為typeof的特性,在運算返回結果的時候,會判定base object值為null的Reference的最終typeof運算結果為"undefined"。而直接判定null的時候,因為null為一種數據類型,不需要經過Reference,所以返回的值是規范中定義的"object"。
List
List也是一種內部數據類型,也是作為運算中間值存在的。它具體作用于new運算符或函數調用時的參數列表。這些參數列表是簡單的有序數列,而且可以是任意長的。PS:它和ArrayList是兩種東西——一種是數據類型,另一種是對象。
Completion
和前面兩個內部數據類型一樣,Completion也是作為中間值存在的。它是運作于終結返回操作的,例如return, break, continue和throw。它的內部由三個要素構成:type, value, target。每一個要素都分別有其可以指定的值,這些值如下表: