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