英文原文:JavaScript: Types 編譯:紫雲妃
我喜歡JavaScript.它是一門集強大與靈活於一身的語言,當然前提是你得知道如何去正確的使用它.一旦你真正掌握了JavaScript,你幾乎可以用它來做任何事情,而且能做的既快又好.
如果你認為JavaScript太簡單或者太低階, 那麼你已經掉入了一個陷阱. 並且你會發現有很多人已經掉入了這樣的陷阱中了.這些所謂的JavaScript開發者也許會告訴你,一些其他的語言 “X” 更好.
他們甚至會說,如果有一個將能將X語言轉換為JavaScript的系統,那就太好了.想要逃出這個陷阱一直到真正的掌握JavaScript需要付出很多的努力和貢獻.相信我,因為從1997年開始,我就已經在學習JavaScript了.
我是通過學習官方標準文件掌握了JavaScript的所有高階知識,所以你也可以通過這種方法來掌握完整的語言知識.如果你的職稱中包含了“JavaScript開發者”,那麼你應該這樣做.
在本篇部落格中,我將會給出一個短小的JavaScript程式碼片段,然後讓你給出這段程式碼的正確輸出.如果你是一個JavaScript開發者,你會發現這樣的題目真是太簡單了.如果你仍然處在學習這門語言的過程中,你可能會遇到一些困難,不過你可以好好讀一下程式碼下面的解釋部分.
下面的JavaScript程式碼顯示了一個彈出框.彈出框中顯示什麼?
1 2 3 |
var five = 5; five.three = 3; alert(five + five.three); |
跳到本篇文章的最後檢視正確的答案.接下來是為什麼會有這樣的結果的解釋.
在JavaScript中,一共有六種資料型別: Object, Number, String, Boolean, Null, 以及 Undefined.
物件(Objects)型別包含了陣列(arrays), 函式(functions),以及其他的一般物件.數字(Numbers)型別可以是整型(integers)或者浮點數(floating point)型別以及特殊值NaN和Infinity. 字串(Strings)型別包含了空字串, “”. 布林值(Booleans)型別只有兩個值:true和false.最後兩個基本資料型別有點特殊:Null型別只有一個值:null, Undefined型別只有一個值:undefined.所有Object除外的型別都稱之為“原始值(primitive)”.JavaScript變數的型別不是在定義的時候明確指定的,還是在指令碼執行的時候自動推斷出來的.在上面的程式碼中,名為five的變數是Number型別的,因為它被賦值了一個Number字面量5.
和其他的計算機程式語言類似,JavaScript也會隱式的將某個值的型別轉換成適合對該值進行操作的運算子的型別.和其他語言不同的是, JavaScript會非常積極地做這些轉換.比如表示式”5″ – “3”的運算結果是數字2,因為減號運算子會把兩邊的運算元全部轉換為數字再進行運算.如果一個運算元不能被轉換為數字,則會使用NaN (“Not a Number”)來替代.例如表示式”5″ – “Fred”會被隱式的轉換為5 – NaN,則運算結果也會是NaN.
一套完整的隱式轉換規則並不是十分複雜,你只需要知道每個操作符需要什麼型別的運算元.
任意值轉換為原始值遵循這樣的規則“任意值轉換為原始值”. 其中物件到原始值的轉換比較複雜:如果運算元的型別必須是Number型別,這就意味著JavaScript引擎會呼叫物件的valueOf()方法,如果返回的結果仍然不是一個原始值,則會呼叫物件的toString()方法轉換成一個String型別的值.如果運算元的型別必須是String型別,則會首先呼叫物件的toString()方法,如果返回的結果不是一個原始值,再呼叫物件的valueOf()方法.不管那種情況,如果最終的轉換結果仍然不是一個原始值,則丟擲異常.
如果運算元的型別必須是一個數字,但該運算元的實際型別是:
Object:
該值首先被轉換成原始值,如果轉換的結果不是一個數字,則會進入下面的轉換分支.
String:
字串型別的值會按照通常的JavaScript規則轉換成數字型別
Boolean:
如果值為true,轉換為1,否則轉換為0
Null:
0
Undefined:
NaN
如果運算元的型別必須是一個字串,但該運算元的實際型別是:
Object:
該值首先被轉換成原始值,如果轉換的結果不是一個字串,則會進入下面的轉換分支.
Number:
數字直接轉為字串, 比如 “123” or “12.34”
Boolean:
轉為”true”或”false”
Null:
“null”
Undefined:
“undefined”
如果運算元的型別必須是一個布林值,但該運算元的實際型別是:
Object:
true
Number:
如果值為0,則轉為false,否則轉為true (譯者注:NaN也會轉為false)
String:
如果值為一個空字串””,則轉為false,否則轉為true
Null:
false
Undefined:
false
如果運算元的型別必須是一個物件值,但該運算元的實際型別是:
Number:
使用包裝物件型別Number來包裝原始值,new Number(value)
String:
使用包裝物件型別String來包裝原始值,new String(value)
Boolean:
使用包裝物件型別Boolean來包裝原始值,new Boolean(value)
Null:
丟擲異常
Undefined:
丟擲異常
現在所有的轉換規則已經很清晰了,讓我們回到最初的例子中.
1 2 3 |
var five = 5; five.three = 3; alert(five + five.three); |
正如我們前面提到的,第一行程式碼建立了一個名為five,型別為Number的變數.
當用屬性訪問符作用在變數five上時,它的值會轉換為Object型別.這種操作稱之為“包裝”,並且依賴於Number建構函式,該建構函式會產生一個物件,而不是一個原始值.第二行程式碼實際上等同於下面的程式碼:
1 |
(new Number(five)).three=3; |
正如你看到的,我們並沒有將新生成的Number物件的引用儲存到一個變數中.在該表示式執行過後,被新增three屬性的這個物件會被丟棄.而five變數的值沒有任何變化.
第三行程式碼中的表示式five.three會再一次建立一個Number物件.這個新的物件並沒有three屬性,所以返回了特殊值undefined.結果等同於:
1 |
alert(5+undefined); |
加法運算子會把兩邊的運算元全部轉換為數字.在本例中,undefined會被轉換為NaN,也就成了:
1 |
alert(5+NaN); |
這也就解釋了為什麼最後的彈出框中僅僅顯示了一個NaN.
(譯者注:還差最後一步隱式轉換,在進行alert()的時候,NaN會被自動轉換為”NaN”)
評論:
jerone:
你這篇文章所講的內容只是JavaScript的一部分,確切的說只包含了ECMAScript.除了這些,JavaScript還包含了DOM,也就是說還有Node, Element, HTMLElement等更多的資料型別.
作者回復:
Node, Element 以及 HTMLElement 都不是資料型別,就像Array, Date 和 RegExp一樣,他們都屬於物件(Object)型別.
Marcus Pope:
我認為應該指出正確的編寫方式,下面的程式碼才能夠正常執行
1234 var five = new Number(5);alert(five); //5five.three = 3;alert(five + five.three); //8我還認為最好應該用“number”來指代數字原始值,而用“Number”來指代數字型別的包裝物件,這樣才能減少讀者的困惑.這也是在JavaScript中使用typeof操作符來檢視這些值的內部類屬性時顯示的字串.(譯者注:這裡指的是typeof 5 === “number”)
ACRESTAN:
是啊,這篇文章很讓人困惑,就是因為作者使用了大寫字母開頭的型別字串來描述原始值…
比如這句 “the first line creates a variable called five whose type is Number”,這是不對的!因為你的程式碼就證明了這一點.
metadings:
還有第七種資料型別,JavaScript中最重要的型別:函式(Function).在JavaScript中,使用函式可以建立新的物件,所以原始值型別和函式型別還是有區別的.
123456789 var Project = function () { };// Project instanceof Function === true// Project instanceof Object === true// (typeof Project === 'function') === truevar o = new Project();// o instanceof Project === true// o instanceof Object === true// (typeof Project === 'object') === true請仔細閱讀這個連結http://metadea.de/V/,看看我是怎麼寫出真正的JavaScript類函式的.(譯者注:還有一句沒翻譯,看不明白,覺的這個人在扯蛋.)
作者回復:
很顯然函式在JavaScript中非常重要,值得用單獨的文章來講解它,但函式不是另外一種資料型別.函式只是一種特殊的物件.雖然特殊,但仍是物件.
由許許多多不同的建構函式建立的物件們都繼承了同一個型別:Object.這很容易檢測到:如果Object(a) === a,則a是一個物件型別,而不是其他原始值型別.
ulu:
這篇文章正好進一步證明了JavaScript不只是一個最強大的語言,同時也是最令人困惑的語言.應該不惜一切代價避免使用它.總有一天,會有一個更人性化的語言整合在所有的主流瀏覽器中,我希望在我有生之年能夠趕上.
作者回復:
當你手中拿著一把錘子時,所有事物看上去都像釘子.當你認為JavaScript很讓人迷惑時,任何特性都能成為你的證據.
Dave Chapman :
這種型別的編碼錯誤正是我們為什麼需要一個更好的能夠在我們碼字的同時,在執行程式碼之前進行語法檢查的IDE的原因.比如.在closure compiler中執行例子中的程式碼會產生如下的警告資訊:
“JSC_INEXISTENT_PROPERTY: Property three never defined on Number at line 4 character 13:
alert(five + five.three);”
(譯者注:closure compiler是谷歌的程式碼壓縮器或者叫編譯器.https://developers.google.com/closure/compiler/)
Sponge Bob:
我推薦你看一下這個視訊: https://www.destroyallsoftware.com/talks/wat/
Enjoy
(譯者注:這個視訊主要吐槽了ruby和JavaScript的詭異用法,這裡有中文字幕版 http://v.ku6.com/show/nhlYYNrbd5d62ASt-aaDrA…html)
simonleung :
一個數字可以是“object”型別的,也可以是“number”型別的.
當用在條件語句中或者其他需要轉換為布林值的地方,這兩種型別的數字會產生不同的結果,例如.
Number(0)會得到true,因為Number(0)是一個物件,而0顯然會得到false.
(譯者注:這裡他說錯了.Number作為函式只是一個型別轉換函式,返回的仍然是原始值,只有用new Number(0),Number才算是一個建構函式,返回的才是物件)
另外一個,在null上進行typeof操作,返回”object”,但它不是真正的物件,null是假值,會被轉換為false.