|
一、關(guān)于類型
什么叫做類型?簡單地說,類型就是把內(nèi)存中的一個二進制序列賦予某種意義。比如,二進制序列0100 0000 0111 0000 0001 0101 0100 1011 1100 0110 1010 0111 1110 1111 1001 1110如果看作是64位無符號整數(shù)類型就是4643234631018606494 而按照IEEE 754規(guī)定的浮點數(shù)二進制表示規(guī)則(見附1)雙精度浮點類型則是257.331。
變量類型
大部分計算機語言使用變量來存儲和表示數(shù)據(jù),一些語言會給變量規(guī)定一個類型,在整個程序中(不論是編譯時還是運行時),這個類型都不能被改變。與此相對,JavaScript和一些其它語言的變量可以存儲任何類型,它們使用無類型的變量。變量類型是否存在,是跟語法無關(guān)的,例如C#中也提供了var類型的變量,但是,下面的語句在C#中會出錯:
var a=1;
a=”string”;
原因是C#的var關(guān)鍵字只是省略了變量類型聲明,而根據(jù)初始化表達(dá)式自動推斷變量類型,所以C#的var變量仍然是有類型的。而JavaScript中,任何時刻你都可以把任何值賦值給特定變量,所以JavaScript變量是無類型的。
強類型和弱類型
按照計算機語言的類型系統(tǒng)的設(shè)計方式,可以分為強類型和弱類型兩種。二者之間的區(qū)別,就在于計算時是否可以不同類型之間對使用者透明地隱式轉(zhuǎn)換。從使用者的角度來看,如果一個語言可以隱式轉(zhuǎn)換它的所有類型,那么它的變量、表達(dá)式等在參與運算時,即使類型不正確,也能通過隱式轉(zhuǎn)換來得到正確地類型,這對使用者而言,就好像所有類型都能進行所有運算一樣,所以這樣的語言被稱作弱類型。與此相對,強類型語言的類型之間不一定有隱式轉(zhuǎn)換(比如C++是一門強類型語言,但C++中double和int可以互相轉(zhuǎn)換,但double和任何類型的指針之間都需要強制轉(zhuǎn)換)
為什么要有類型
類型可以幫助程序員編寫正確的程序,它在實際編寫程序的過程中體現(xiàn)為一種約束。一般規(guī)律是,約束越強越不容易出錯,但編寫程序時也越麻煩。變量有類型的強類型語言約束最強,典型代表是C++,變量無類型的弱類型語言約束最弱,典型代表是JavaScript。在JavaScript中,因為約束比較弱,所以容易出現(xiàn)這種錯誤:
var a =200;
var b ="1";
var c= a + b;
你可能期望c是201,但實際上它是"2001",這個錯誤在強類型語言中決不會出現(xiàn)。然而正是因為JavaScript沒有這些約束,所以可以很方便地拼接數(shù)字和字符串類型。所以,約束和靈活性對語言的設(shè)計者而言,永遠(yuǎn)是需要平衡的一組特性。
靜態(tài)類型和動態(tài)類型
類型是一種約束,這種約束是通過類型檢查來發(fā)生作用的。在不同語言中,類型檢查在不同的階段發(fā)生作用,這樣又可以分為編譯時檢查和運行時檢查。對于JavaScript這樣的解釋型語言,也有跟編譯過程比較相似的階段,即詞法分析和語法分析,解釋型語言的類型檢查若在語法分析或者之前的階段完成,也可以認(rèn)為類似于編譯時檢查。所以更合理的說法是靜態(tài)類型檢查和動態(tài)類型檢查。
有趣的是,很多語言雖然編譯時檢查類型,但是它的類型信息仍可以在運行時獲得,如C#中使用元數(shù)據(jù)來保存類型信息,在運行階段,使用者可以通過反射來獲取和使用類型的信息。
JavaScript在設(shè)計的各個方面都以靈活性優(yōu)先,所以它使用動態(tài)類型檢查,并且除了在進行極少數(shù)特定操作時,JavaScript不會主動檢查類型。你可以在運行時獲得任何一個變量或者表達(dá)式的類型信息并且通過程序邏輯檢查它的正確性。
二、JavaScript標(biāo)準(zhǔn)規(guī)定的類型
JavaScript標(biāo)準(zhǔn)中規(guī)定了9種類型:Undefined Null Boolean String Number Object Reference List Completion
其中,Reference List Completion三種類型僅供語言解析運行時使用,無法從程序中直接訪問,這里就暫不做介紹。下面我們可以了解下這六種類型:
Undefined類型
Undefined類型只有一個值undefined,它是變量未被賦值時的值,在JS中全局對象有一個undefined屬性表示undefined,事實上undefined并非JavaScript的關(guān)鍵字,可以給全局的undefined屬性賦值來改變它的值。
Null類型
Null類型也只有一個值null,但是JavaScript為它提供了一個關(guān)鍵字null來表示這個唯一的值。Null類型的語義是“一個空的對象引用”。
Boolean類型
Boolean有兩種取值true和false
String類型
String類型的的正式解釋是一個16位無符號整數(shù)類型的序列,它實際上用來表示以UTF-16編碼的文本信息。
Number類型
JavaScript的Number共有18437736874454810627 (就是 264-253 +3)個值。JavaScript的Number以雙精度浮點類型存儲,除了9007199254740990表示NaN,它遵守IEEE 754(見附1)規(guī)定,占用64位8字節(jié)。
Object類型
JavaScript中最為復(fù)雜的類型就是Object,它是一系列屬性的無序集合,F(xiàn)unction是實現(xiàn)了私有屬性[[call]]的Object,JavaScript的宿主也可以提供一些特別的對象。
三、JavaScript使用者眼中的類型:
前面講了JS標(biāo)準(zhǔn)中規(guī)定的類型,然而一個不能忽略的問題是JS標(biāo)準(zhǔn)是寫給JS實現(xiàn)者看的,對JS使用者而言,類型并不一定要按照標(biāo)準(zhǔn)來定義,比如,因為JS在進行.運算的時候,會自動把非Object類型轉(zhuǎn)換為與其對應(yīng)的對象,所以"str".length其實和(new String("str")).length是等效的,從這個角度而言,認(rèn)為二者屬于同一類型也未嘗不可。我們利用JS中的一些語言特性,可以進行運行時的類型判別,但是這些方法判斷的結(jié)果各不相同,孰好孰壞還需要您自己決定。
typeof——看上去很官方
typeof是JS語言中的一個運算符,從它的字面來看,顯然它是用來獲取類型的,按JavaScript標(biāo)準(zhǔn)的規(guī)定,typeof獲取變量類型名稱的字符串表示,他可能得到的結(jié)果有6種:string、bool、number、undefined、object、function,而且JavaScript標(biāo)準(zhǔn)允許其實現(xiàn)者自定義一些對象的typeof值。
在JS標(biāo)準(zhǔn)中有這樣一個描述列表:
Type | Result |
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
String | "string" |
Object (native and doesn't implement [[call]]) | "object" |
Object (native and implements [[call]]) | "function" |
Object (host) | Implementation-dependent |
下面一個例子來自51js的Rimifon,它展示了IE中typeof的結(jié)果產(chǎn)生"date"和"unknown"的情況:
var xml=document.createElement("xml");
var rs=xml.recordset;
rs.Fields.Append("date", 7, 1);
rs.Fields.Append("bin", 205, 1);
rs.Open();
rs.AddNew();
rs.Fields.Item("date").Value = 0;
rs.Fields.Item("bin").Value = 21704;
rs.Update();
var date = rs.Fields.Item("date").Value;
var bin = rs.Fields.Item("bin").Value;
rs.Close();
alert(date);
alert(bin);
alert([typeof date, typeof bin]);
try{alert(date.getDate())}catch(err){alert(err.message)}
關(guān)于這個最為接近"類型"語義的判斷方式,實際上有不少的批評,其中之一是它無法分辨不同的object,new String("abc")和new Number(123)使用typeof無法區(qū)分,由于JS編程中,往往會大量使用各種對象,而typeof對所有對象都只能給出一個模糊的結(jié)果"object",這使得它的實用性大大降低。
instanceof——原型還是類型?
instanceof的意思翻譯成中文就是"是……的實例",從字面意思理解它是一個基于類面向?qū)ο缶幊痰男g(shù)語,而JS實際上沒有在語言級別對基于類的編程提供支持。JavaScript標(biāo)準(zhǔn)雖然只字未提,但其實一些內(nèi)置對象的設(shè)計和運算符設(shè)置都暗示了一個"官方的"實現(xiàn)類的方式,即從把函數(shù)當(dāng)作類使用,new運算符作用于函數(shù)時,將函數(shù)的prototype屬性設(shè)置為新構(gòu)造對象的原型,并且將函數(shù)本身作為構(gòu)造函數(shù)。
所以從同一個函數(shù)的new運算構(gòu)造出的對象,被認(rèn)為是一個類的實例,這些對象的共同點是:1.有同一個原型 2.經(jīng)過同一個構(gòu)造函數(shù)處理。而instanceof正是配合這種實現(xiàn)類的方式檢查"實例是否屬于一個類"的一種運算符。猜一猜也可以知道,若要檢查一個對象是否經(jīng)過了一個構(gòu)造函數(shù)處理千難萬難,但是檢查它的原型是什么就容易多了,所以instanceof的實現(xiàn)從原型角度理解,就是檢查一個對象的[[prototype]]屬性是否跟特定函數(shù)的prototype一致。注意這里[[prototype]]是私有屬性,在SpiderMonkey(就是Firefox的JS引擎)中它可以用__proto__來訪問。
原型只對于標(biāo)準(zhǔn)所描述的Object類型有意義,所以instanceof對于所有非Object對象都會得到false,而且instanceof只能判斷是否屬于某一類型,無法得到類型,但是instanceof的優(yōu)勢也是顯而易見的,它能夠分辨自定義的"類"構(gòu)造出的對象。
instanceof實際上是可以被欺騙的,它用到的對象私有屬性[[prototype]]固然不能更改,但函數(shù)的prototype是個共有屬性,下面代碼展示了如何欺騙instanceof
function ClassA(){};
function ClassB(){};
var o = new ClassA();//構(gòu)造一個A類的對象
ClassB.prototype = ClassA.prototype; //ClassB.prototype替換掉
alert(o instanceof ClassB)//true 欺騙成功 - -!
Object.prototype.toString——是個好方法?
Object.prototype.toString原本很難被調(diào)用到,所有的JavaScript內(nèi)置類都覆蓋了toString這個方法,而對于非內(nèi)置類構(gòu)造出的對象,Object.prototype.toString又只能得到毫無意義的[object Object]這種結(jié)果。所以相當(dāng)長的一段時間內(nèi),這個函數(shù)的神奇功效都沒有被發(fā)掘出來。
在標(biāo)準(zhǔn)中,Object.prototype.toString的描述只有3句
1. 獲取this對象的[[class]]屬性
2. 通過連接三個字符串"[object ", 結(jié)果(1), 和 "]"算出一個字符串
3. 返回 結(jié)果(2).
顯而易見,Object.prototype.toString其實只是獲取對象的[[class]]屬性而已,不過不知道是不是有意為之,所有JS內(nèi)置函數(shù)對象String Number Array RegExp……在用于new構(gòu)造對象時,全都會設(shè)定[[class]]屬性,這樣[[class]]屬性就可以作為很好的判斷類型的依據(jù)。
因為Object.prototype.toString是取this對象屬性,所以只要用Object.prototype.toString.call或者Object.prototype.toString.apply就可以指定this對象,然后獲取類型了。
Object.prototype.toString盡管巧妙,但是卻無法獲取自定義函數(shù)構(gòu)造出對象的類型,因為自定義函數(shù)不會設(shè)[[class]],而且這個私有屬性是無法在程序中訪問的。Object.prototype.toString最大的優(yōu)點是可以讓1和new Number(1)成為同一類型的對象,大部分時候二者的使用方式是相同的。
然而值得注意的是 new Boolean(false)在參與bool運算時與false結(jié)果剛好相反,如果這個時候把二者視為同一類型,容易導(dǎo)致難以檢查的錯誤。
總結(jié):
為了比較上面三種類型判斷方法,我做了一張表格,大家可以由此對幾種方法有個整體比較。為了方便比較,我把幾種判斷方式得到的結(jié)果統(tǒng)一了寫法:
對象 | typeof | instanceof | Object.prototype.toString | 標(biāo)準(zhǔn) |
"abc" | String | —— | String | String |
new String("abc") | Object | String | String | Object |
function hello(){} | Function | Function | Function | Object |
123 | Number | —— | Number | Number |
new Number(123) | Object | Number | Number | Object |
new Array(1,2,3) | Object | Array | Array | Object |
new MyType() | Object | MyType | Object | Object |
null | Object | —— | Object | Null |
undefined | Undefined | —— | Object | Undefined |
事實上,很難說上面哪一種方法是更加合理的,即使是標(biāo)準(zhǔn)中的規(guī)定,也只是體現(xiàn)了JS的運行時機制而不是最佳使用實踐。我個人觀點是淡化"類型"這一概念,而更多關(guān)注"我想如何使用這個對象"這種約束,使用typeof配合instanceof來檢查完全可以在需要的地方達(dá)到和強類型語言相同的效果。
附1 IEEE 754 規(guī)定的雙精度浮點數(shù)表示(來自中文wikipedia):
sign bit(符號): 用來表示正負(fù)號
exponent(指數(shù)): 用來表示次方數(shù)
mantissa(尾數(shù)): 用來表示精確度
it知識庫:JavaScript中的類型,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。