JavaScript基礎——基本概念:資料型別及其轉換
任何語言的核心必然會描述這門語言最基本的工作原理。而描述的內容通常都要設計這門語言的語法、操作符、資料型別、內建功能等用於構建複雜解決方案的基本概念。
語法
ECMAScript的語法大量借鑑了C及其他類語言(如Java和Perl)的語法。因此,熟悉那些語言的開發人員在接受ECNAScript更加寬鬆的語法時,一定會有一種輕鬆自在的感覺。
區分大小寫
要理解的第一個概念就是ECMAScript中的一切(變數、函式和操作符)都區分大小寫。識別符號
所謂識別符號,就是指變數、函式、屬性的名字,或者函式的引數。識別符號可以使按照下列格式規則組合起來的一或多個字元:
□ 第一個字元必須是字母、下劃線(_)活一個美元符號($);
□ 其他字元可以是字母、下劃線、美元符或數字。
識別符號中的字母也可以包含擴充套件的ASCII或Unicode字母字元(如À和Æ),但不推薦這樣做。
按照慣例,ECMAScript標識符采用駝峰大小寫格式,也就是第一個字母小寫,剩下的每個有意義的單詞的首字母大寫,例如:myFirstJavaScriptName。雖然沒有強制要求使用這種格式,但為了與ECMAScript內建的函式和物件命名格式保持一致,可以將其當作一種最佳實踐。
註釋
ECMAScript使用C風格的註釋,包括單行註釋和塊級註釋。單行註釋以兩個斜槓開頭,如:
//單行註釋
塊級註釋以一個斜槓和一個星號(/*)開頭,以一個星號和一個斜槓(*/)結尾,如:
/*
*這是一個多行
*(塊級)註釋
*/
雖然上面註釋中的第二和第三行都以一個星號開頭,但這不是必需的。之所以以星號開頭,純粹是為了提高註釋的可讀性。
嚴格模式
ECMAScript 5引入了嚴格模式(strictmode)的概念。嚴格模式是為JavaScript定義了一種不同的解析與執行模型。在嚴格模式下,ECMAScript3中的一些不確定的行為將得到處理,而且對某些不安全的操作也會丟擲錯誤。要在指令碼中啟用嚴格模式,可以在頂部新增如下程式碼:
“use strict”;
這行程式碼看起來像是字串,而且也沒有賦值給任何變數,但其實它是一個編譯指示(pragma),用於告訴支援的JavaScript引擎切換到嚴格模式。這是為不破壞ECMAScript3語法而特意選定的語法。
在函式內部的上方包含這條編譯指示,也可以指定函式在嚴格模式下執行:
function doSomething(){
“usestrict”;
//函式體
}
嚴格模式下,JavaScript的執行結果會有很大不同。支援嚴格模式的瀏覽器包括IE10+、Firefox4+、Safari5.1+、Opera12和Chrome。
語句
ECMAScript中的語句以一個分號結尾;如果省略分號,則由解析器確定語句的結尾,如下例所示:
var sun = a + b //即使沒有分號也是有效的語句——不推薦
var diff = a –b; //有效的語句——推薦
雖然語句結尾的分號不是必須的,但我們建議任何時候都不要省略它。因為加上這個分號可以避免很多錯誤(例如不完整的輸入),開發人員也可以放心地通過刪除多餘的空格來壓縮ECMAScript程式碼(程式碼行結尾處沒有分號會導致壓縮錯誤)。另外,加上分號也會在某些情況下增進程式碼的效能,因為這樣解析器就不必再話時間推測應該在哪裡插入分號了。
可以使用C風格的語法把多條語句組合到一個程式碼塊中,即程式碼塊以左花括號({)開頭,以右花括號(})結尾。
雖然條件控制語句(如if語句)只在執行所調語句的情況下菜要求使用程式碼塊,但最佳實踐是始終在控制語句中使用程式碼塊——即使程式碼塊中只有一條語句。
關鍵字和保留字
ECMA-262描述了一組具有特定用途的關鍵字,這些關鍵字可用於表示控制語句的開始或結束,或者用於執行特定操作符等。按照規則,關鍵字也是語言保留的,不能用作識別符號。下表是ECMAScript的全部關鍵字(帶*號上標的是第5版新增的關鍵字):
break |
do |
instanceof |
typeof |
case |
else |
new |
var |
catch |
finally |
return |
void |
continue |
for |
switch |
while |
debugger* |
function |
this |
with |
default |
id |
throw |
|
delete |
in |
try |
ECMA-262還描述了另外一組不能用作識別符號的保留字。儘管保留字在這門語言中還沒有任何特定的用途,但它們有可能在將來被用作關鍵字。以下是ECMA-262第三版定義的全部保留字:
abstract |
enum |
int |
short |
boolean |
export |
interface |
static |
byte |
extends |
long |
super |
char |
final |
native |
synchoronized |
class |
float |
package |
throws |
const |
goto |
private |
transient |
debugger |
implements |
protected |
volatile |
double |
import |
public |
第5版把在非嚴格模式下執行時的保留字縮減為下列這些:
class |
enum |
extends |
super |
const |
export |
import |
在嚴格模式下,第5版還對以下保留字施加了限制:
implements |
package |
public |
interface |
private |
static |
let |
protected |
yield |
注意,let和yield是第5版新增的保留字,其他保留字都是第3版定義的。
變數
ECMAScript的變數是鬆散型別的,所謂鬆散型別就是可以用來儲存任何型別的資料。換句話說,每個變數僅僅是一個用於儲存值的佔位符而已。定義變數時要使用car操作符(注意var是一個關鍵字),後跟變數名,如下所示:
var message;
這行程式碼定義了一個名為message的變數,該變數可以用來儲存任何值(像這樣未經過初始化的變數,會儲存一個特殊的值——undefined)。ECMAScript也支援直接初始化變數,因此在定義變數的同時就可以設定變數的值:
var message = “hi”;
再次,變數message中儲存了一個字串值”hi”。像這樣初始化變數並不會把它標記為字串型別;初始化的過程就是給變數賦一個值那麼簡單。因此,可以在修改變數值的同時修改值的型別,如:
var message = “hi”;
message = 100;
在這個例子中,變數message一開始儲存了一個字串”hi”,然後該值又被一個數字值100取代。雖然不建議修改變數所儲存值的型別,但這種操作在ECMAScript中完全有效。
有一點必須注意,即使用var操作符定義的變數將成為定義該變數的作用域中的區域性變數。也就是說,如果在函式中使用var定義一個變數,那麼這個變數在函式退出後就會被銷燬,例如:
function test(){
var message = “hi”;//區域性變數
}
test();
alert(message);//錯誤!
這裡,變數message是在函式中使用var定義的。當函式被呼叫時,就會建立該變數併為其賦值。而在此之後,這個變數又會被立即銷燬,因此例子中的下一行程式碼就會導致錯誤。不過,可以像下面這樣省略var操作符,從而建立一個全域性變數:
function test(){
message = “hi”;//全域性變數
}
test();
alert(message);//hi
這個例子省略了var操作符,因而message就成了全域性變數。這樣,只要呼叫過一次test()函式,這個變數就有了定義,就可以在函式外部的任何地方被訪問到。
雖然省略var操作符可以定義全域性變數,但這也不是我們推薦的做法。因為在區域性作用域中定義的全域性變數很難維護,而且如果有意地忽略了var操作符,也會由於相應變數不會馬上就有定義而導致不必要的混亂。給未經宣告的變數賦值在嚴格模式下會導致丟擲ReferenceError。
可以使用一條語句定義多個變數,只要像下面這樣把每個變數(初始化或不初始化均可)用逗號分隔開即可:
var message = “hi”, found = false , age = 29;
這個例子定義並初始化了3個變數。同樣由於ECMAScript是鬆散型別的,因而使用不同型別初始化變數的操作可以放在一條語句中來完成。雖然程式碼裡的換行和變數縮排不是必需的,但這樣做可以提高可讀性。
在嚴格模式下,不能定義名為eval或arguments的變數,否則會導致語法錯誤。
資料型別
ECMAScript中有5種簡單資料型別(也稱為基本資料型別):Undefined、Null、Boolean、Number和String。還有一種負責資料型別——Object,Object本質上是由一組無序的名值對組成的。ECMAScript不支援任何自定義型別的機制,而所有值最終都將是上述6種資料型別之一。乍一看,好像只有6種資料型別不足以表示多有資料;但是,由於ECMAScript資料型別具有動態性,因此的確沒有再定義其他資料型別的必要了。
typeof操作符
鑑於ECMAScript是鬆散型別的,因此需要有一種手段來檢測給定變數的資料型別——typeof就是負責提供這方面資訊的操作符。對一個值使用typeof操作符可能返回下列某個字串:
□ “undefined”——如果這個值未定義;
□ “boolean”——如果這個值是布林值;
□ “string”——如果這個值是字串;
□ “number”——如果這個值是數值;
□ “object”——如果這個值是物件或null;
□ “function”——如果這個值是函式。
下面是幾個使用typeof的例子:
var message = "hi";
alert(typeof typeofTest);//function
alert(typeof message);//string
alert(typeof message1);//undefined
alert(typeof null);//object
alert(typeof 123);//number
這幾個例子說明,typeof操作符的運算元可以使變數(message),也可以是數值字面變數。注意,typeof是一個操作符而不是函式,因此例子中的圓括號儘管可以使用,但不是必須的。
有些時候,typeof操作符會返回一些疑惑但技術上卻正確的值。比如,呼叫typeof null會返回“object”,因為特殊值null被認為是一個空的物件引用。Safari 5及之前版本、Chrome 7及之前版本在對正則表示式呼叫typeof操作符會返回”function”,而其他瀏覽器在這種情況下會返回”object”。
從技術角度將,函式在ECMAScript中是物件,不是一種資料型別。然而,函式也確實有一些特殊的屬性,因此通過typeof操作符來區分函式和其他物件是有必要的。
Undefined型別
Undefined型別只有一個值,即特殊的undefined。在使用var宣告變數但未對其加以初始化時,這個變數的值就是undefined,例如:
var message;
alert(message == undefined);//true
這個例子只聲明瞭變數message,但未對其進行初始化。比較這個變數與undefined字面量,結果表名它們是相等的。這個例子與下面的例子是等價的:
var message = undefined;
alert(message == undefined);//true
這個例子使用undefined值初始化了變數message。但沒必要這麼做,因為未經初始化的值預設就會取得undefined值。不過,包含undefined值的變數與尚未定義的變數還是不一樣的。例如:
var message;
//下面這個變數並沒有宣告
//var age;
alert(message);//”undefined”
alert(age);//產生錯誤
執行以上程式碼,第一個警告框會顯示變數message的值,即”undefined”。而第二個警告框——由於傳遞給alert()函式的是尚未宣告的變數age——則會導致一個錯誤。對於尚未宣告過的變數,只能執行一項操作,即使用typeof操作符檢測其資料型別(對未經宣告的變數呼叫delete不會導致錯誤,但這樣做沒什麼實際意義,而且在嚴格模式下確實會導致錯誤)。
然而,如在typeof小節中的測試程式碼一樣,對未經初始化的變數執行typeof操作符也會返回undefined值,而對未宣告的變數執行typeof操作符同樣也會返回undefined值。這樣的結果有其合理性。因為雖然這兩種變數從技術角度看有本質區別,但實際上無論對哪種變數也不可能執行真正的操作。
Null型別
Null型別是第二個只有一個值的資料型別,這個特殊的值是null。從邏輯角度來看,null值表示一個空物件指標,而這也正是使用type操作符檢測null值會返回”object”的原因。
如果定義的變數準備在將來用於儲存物件,那麼最好將該變數初始化為null而不是其他值。這樣一來,只要直接檢查null值就可以知道相應的變數是否已經儲存了一個物件的引用。
實際上,undefined值是派生自null值的,因此ECMA-262規定對它們的相等性測試要返回true:
alert(null ==undefined);//true
這裡,位於null和undefined之間的相等操作符(==)總是返回true,不過要注意的,這個操作符處於比較的目的會轉換其運算元。
儘管null和undefined有這樣的關係,但它們的用途完全不同。如前所述,無論在什麼情況下都沒有必要把一個變數的值顯式地設定為undefined,可是同樣的規則對null卻不適用。換句話說,只要意在儲存物件的變數還沒有真正儲存物件,就應該明確地讓該變數儲存null值。這樣不僅可以體現null作為空物件指標的慣例,而且也有助於進一步區分null和undefined。
Boolean型別
Boolean型別是ECMAScript中使用得最多的一種型別,該型別只有兩個字面值:true額false。這兩個值與數字值不是一回事,因此true不一定等於1,而false也不一定等於0。需要注意的是,Boolean型別的字面值true和false是區分大小寫的。雖然Boolean型別的字面值只有兩個,但ECMAScript中所有型別的值都有與這兩個Boolean值等價的值。要將一個值轉換為其對應的Boolean值,可以呼叫轉型函式Boolean(),如:
var message = “hi”;
var BooleanFromMessage = Boolean(message);
這個例子中,字串message被轉換成了一個Boolean值,該值被儲存在BooleanFromMessage變數中。可以對任何資料型別的值呼叫Boolean()函式,而且總會返回一個惡Boolean值。至於返回的這個值是true還是false,取決於要轉換的資料型別及其實際值。
資料型別 |
轉化為true的值 |
轉化為false的值 |
Boolean |
true |
false |
String |
任何非空字串 |
“”(空字串) |
Number |
任何非零數字值(包括無窮大) |
0和NaN |
Object |
任何物件 |
null |
Undefined |
n/a(not applicable , 不適用) |
undefined |
這些轉換規則對理解控制語句(如if語句)自動執行相應的Boolean轉換非常重要,例如:
var message = “hi”;
if(message){
alert(message);
}
執行這個示例,就會顯示一個警告框,因為字串message被自動轉換成了對應的Boolean值(true)。由於存在這種自動執行的Boolean轉換,因此確切地知道在流程控制語句中使用的是什麼變數至關重要。錯誤地使用一個物件而不是一個Boolean值,就有可能徹底改變應用程式的流程。
Number型別
Number型別應該是ECMAScript中最令人關注的資料型別了,這種型別使用IEEE754格式來表示整數和浮點數值。為支援各種數值型別,ECMA-262定義了不同的數值字面量格式。
最基本的數值字面量格式是十進位制整數,十進位制整數可以像下面這樣直接在程式碼中輸入:
var intNum =55;//整數
除了以十進位制表示外,整數還可以通過八進位制或十六進位制的字面值來表示。其中,八進位制字面值的第一位必須是零(0),然後是八進位制數字序列(0~7)。如果字面值中的數值超出了範圍,那麼前導零將被忽略,後面的數值將被當作十進位制數值進行解析。
八進位制字面量在嚴格模式下是無效的,會導致支援的JavaScript引擎丟擲錯誤。
十六進位制字面值的前兩位必須是0x,後跟任何十六進位制數字(0~9及A~F)。其中字母A~F可以大寫也可以小寫。
在進行算術運算時,所有以八進位制和十六進位制表示的數值最終都將被轉換成十進位制數值。
alert("0x1A+023+12 = 十六進位制1A + 八進位制23 + 十進位制12 = 十進位制26 + 十進位制19 + 十進位制12 = " + (0x1A+023+12) );
上面的程式碼會在頁面中彈出:
0x1A+023+12 = 十六進位制1A + 八進位制23 + 十進位制12 = 十進位制26 + 十進位制19 + 十進位制12 = 57
更直接的:
alert("0x1A+023+12 = 十六進位制1A = 十進位制26 = " + 0x1A );
將會在頁面中彈出:
0x1A = 十六進位制1A = 十進位制26 = 26
浮點數值
所謂浮點數值,就是該數值中必須包含一個小數點,並且小數點後面必須至少有一位數字。雖然小數點前面可以沒有整數,但不推薦這種寫法。以下是浮點數值的幾個例子:
var floatNum1 =1.1;
var floatNum2 =0.1;
var floatNum3 =.1;//有效,但不推薦
由於儲存浮點數值需要的記憶體控制元件是儲存整數值的兩倍,因此ECMAScript會不失時機地將浮點數值轉換為整數值。顯然,如果小數點後面沒有跟任何數字,那麼這個數值就可以作為整數值來儲存。同樣地,如果浮點數值本身表示的就是一個整數(如1.0),那麼該值也會被轉換為整數,如下面的例子所示:
var floatNum1 =1.;//小數點後面沒有數字——解析為1
var floatNum2 =10.0;//整數——解析為10
對於那些極大或極小的數值,可以用e表示法(即科學計數法)表示的浮點數值表示。用e表示法表示的數值等於e前面的數值乘以10的指數次冪。ECMAScript中e表示法的格式也是如此,即前面是一個數值(可以是整數也可以是浮點數),中間是一個大寫或小寫的字母E,後面是10的冪中的指數,該冪值將用來與前面的數相乘。如:
var floatNum =3.125e7;//等於31250000
在這個例子中,使用e表示法表示的變數floatNum的形式雖然簡潔,但它的十幾只則是31250000。在此,e表示法的實際含義就是”3.125乘以 ”。
也可以使用e表示法表示極小的數值,如:0.00000000000000003,這個數值可以使用更簡潔的3e-17表示。在預設情況下,ECMAScript會將那些小數點後面帶有6個零以上的浮點數值轉換為以e表示法表示的數值。浮點數值的最高精度是17位小數,但在進行算術計算時其精度遠遠不如整數。例如0.1+0.2的結果不是0.3,而是0.30000000000000004。這個小小的舍入誤差會導致無法測試特定的浮點數值。例如:
if(0.1+0.2 ==0.3){
alert(“You got 0.3.”);
}
程式將永遠無法執行alert(“You got 0.3.”);語句。
數值範圍
由於記憶體的限制,ECMAScript並不能儲存世界上所有的數值。ECMAScript能夠表示的最小數值儲存在Number.MIN_VALUE中——在大多數瀏覽器中,這個值是5e-324;能夠表示的最大數值儲存在Number.MAX_VALUE中——在大多數瀏覽器中,這個值是1.7976931348623157e+308。如果某次計算的結果得到了一個超出JavaScript數值範圍的值,那麼這個數值將自動轉換成特殊的Infinity值。具體來說,如果這個數值是負數,則會被轉換成-Infinity(負無窮),如果這個數值是正數,則會被轉換成Infinity(正無窮)。
如上所述,如果某次計算返回了正或負的Infinity值,那麼該值將無法參與下一次的計算,因為Infinity不是能夠參與計算的數值。要想確定一個數值是不是有窮的(歡聚換說,是不是位於最小和最大的數值之間),可以使用isFinite()函式。這個函式在引數位於最小與最大數值之間時會返回true。
NaN
NaN,即非數值(Not aNumber)是一個特殊的數值,這個數值用於表示一個本來要返回是指的運算元未返回數值的情況(這樣就不會丟擲錯誤了)。例如,在其他程式語言中。任何數值除以0都會導致錯誤,從而停止程式碼執行。但在ECMAScript中,任何數值除以0會返回NaN,因此不會影響其他程式碼的執行。
NaN本身有兩個非同尋常的特點,首先,任何涉及NaN的操作都會返回NaN,這個特點在多步計算中有可能導致問題。其次,NaN與任何值都不相等,包括NaN本身。針對NaN的這兩個特點,ECMAScript定義了isNan()函式。這個函式接受一個引數,該引數可以是任何型別,而函式會判斷這個引數是否“不是數值”。isNaN()在接收到一個值之後,會嘗試將這個值轉換為數值。某些不是數值的值會直接轉換為數值,例如字串”10”或Boolean值。而任何不能被轉換為數值的值都會導致這個函式返回true:
alert(isNaN(NaN));//true
alert(isNaN(10));//false(10是一個整數)
alert(isNaN("10"));//false(可以被轉換成數值10)
alert(isNaN("blue"));//true(不能轉換成數值)
alert(isNaN(true));//false(可以被轉換成數值1)
儘管有點不可思議,但isNan()確實也適用於物件。在基於物件呼叫isNaN()函式時,會首先呼叫物件的valueof()方法,然後確定該方法返回的值是否可以轉換為數值。如果不能,則基於這個返回值再呼叫tostring()方法,再測試返回值。
數值轉換
有3個函式可以把非數值轉換為數值:Number()、parseInt()和parseFloat()。第一個函式,即轉型函式Number()可以用於任何資料型別,而另外兩個函式則專門用於把字串轉換成數值。這3個函式對於同樣的輸入會有返回不同的結果。
Number()函式的轉換規則如下。
(1) 如果是Boolean值,true和false將分別轉換為1和0。
(2) 如果是數字值,只是簡單的傳入和返回。
(3) 如果是null值,返回0。
(4) 如果是undefined,返回NaN。
(5) 如果是字串,遵循下列規則:
(5.1)如果字串中只包含數字(包含前面帶正號或負號的情況),則將其轉換為十進位制數值,如果有前導零將會被忽略。
(5.2)如果字串中包含有效的浮點格式,則將其轉換為對應的浮點數值,同樣,也會忽略前導零;
(5.3)如果字串中包含有效的十六進位制,則將前期轉換為相同大小的十進位制整數值;
(5.4)如果字串是空的(不包含任何字元),則將其轉換為0;
(5.5)如果字串中包含上述格式之外的字元,則將其轉換為NaN。
(6)如果是物件,則呼叫物件的valueof()方法,然後依照前面的規則轉換返回的值。如果轉換的結果是NaN,則呼叫物件的tostring()方法,然後再一次按照前面的規則轉換返回的字串值。
由於Number()函式在轉換字串時比較複雜而且不夠合理,因此在處理整數的時候更常用的是parseInt()函式。parseInt()函式在轉換字串時,更多的是看其是否符合數值模式。它會忽略字串前面的空格,直至找到第一個非空格字元。如果第一個字元不是數字字元或者負號,parseInt()就會返回NaN;也就是說,用parseInt()轉換空字串會返回NaN。如果第一個字元是數字字元,parseInt()會繼續解析第二個字元,知道解析完所有後續字元或者遇到了一個非數字字元。例如“123www”會被轉換為123,“www”則會被忽略。類似地,“22.5”會被轉換為22,因為小數點並不是有效的整數字符。
如果字串中的第一個字元是數字字元,parseInt()也能夠識別出各種整數格式(八進位制、十進位制、十六進位制)。也就是說,如果字串以”0x”開頭且後跟數字字元,就會將其當作一個十六進位制整數;如果以”0”開頭且後跟數字字元,則會將其當作一個八進位制數來解析。如:
alert(parseInt("123456www"));//123456
alert(parseInt(""));//NaN
alert(parseInt("0xB"));//11
alert(parseInt("22.5"));//22
alert(parseInt("010"));//10
alert(parseInt("0xz"));//NaN
在使用parseInt()解析像八進位制字面量的字串時,ECMAScript3和5存在分歧。例如:
//ECMAScript3認為是56(八進位制),ECMAScript5認為是0(十進位制)
var num = parseInt(“070”);
在ECMAScript 3JavaScript引擎中,”070”被當成八進位制字面量,因此轉換後的值是十進位制的56。而在ECMAScript 5 引擎中,parseInt()已經不具有解析八進位制值的能力,因此前導的零會被認為無效,從而將這個值當成”0”,結果就得到十進位制的0。在ECMAScript 5中,即使是在嚴格模式下也會如此。
為了消除在使用parseInt()函式時可能導致的上述困惑,可以為這個函式提供第二個引數:轉換時使用的基數(即多少進位制)。如果知道要解析的值是十六進位制格式的字串,那麼指定基數16作為第二個引數,可以保證得到正確的結果,例如:
var num =parseInt(“0xAF” , 16);//175
實際上,如果指定了16作為第二個引數,字串可以不帶前面的”0x”,如:
var num1 =parseInt(“AF” , 16);//175
var num2 =parseInt(“AF”);//NaN
這個例子中的第一個轉換成功了,而第二個則失敗了,差別在於第一個轉換傳入了基數,明確告訴parseInt()要解析一個十六進位制格式的字串;而第二個轉換髮現第一個字元不是數字字元,因此就自動終止了。
指定基數會影響到轉換的輸出結果。例如:
alert(parseInt("11" , 2));//3
alert(parseInt("11" , 3));//4
alert(parseInt("11" , 8));//9
alert(parseInt("11" , 10));//11
alert(parseInt("11" , 16));//17
不指定基數意味著讓parseInt()決定如何解析輸入的字串,因此為了避免錯誤的解析,建議無論在什麼情況下都明確指定基數。
多數情況下,要解析的都是十進位制數值,因此始終將10作為第二個引數是非常必要的。
與parseInt()函式類似,parseFloat()也是從第一個字元(位置0)開始解析每個字元。而且也是一直解析到字串末尾,或者解析到預見一個無效的浮點數字字元為止。也就是說,字串中的第一個小數點是有效的,而第二個小數點就是無效的了,因此它後面的字串將被忽略。
除了第一個小數點有效之外,parseFloat()與parseInt()的第二個區別在於它始終都會忽略前導的零。parseFloat()可以識別前面討論過的所有浮點數值格式,也包括十進位制整數格式。但十六進位制格式的字串則始終會被轉換成0。由於parseFloat()只解析十進位制值,因此它沒有用第二個引數指定基數的用法。最後還要注意一點:如果字串包含的是一個可解析為整數的數(沒有小數點,或者小數點後都是零),parseFloat()會返回整數。
String型別
String型別用於表示由零或多個16位Unicode字元組成的字元序列,即字串。字串可以由雙引號(”)或單引號(‘)表示。
與PHP中的雙引號和單引號會影響對字串的解釋方式不同,ECMAScript中的這兩種語法形式沒有什麼區別。用雙引號表示的字串和用單引號表示的字串完全相同。不過,以雙引號開頭的字串也必須以雙引號結尾,而以單引號開頭的字串必須以單引號結尾。
字元字面量
String資料型別包含一些特殊的字元字面量,也叫轉義序列,用於表示非列印字元,或者具有其他用途的字元,如下表所示:
字面量 |
含義 |
\n |
換行 |
\t |
製表 |
\b |
空格 |
\r |
回車 |
\f |
進紙 |
\\ |
斜槓 |
\’ |
單引號,在用單引號表示的字串中使用。例如:’He said,\’hey.\’’ |
\” |
雙引號,在使用雙引號的字串中使用。例如:”He said \“hey.\”” |
\xnn |
以十六進位制程式碼nn表示的一個字元(其中n為0~F)。例如,\x41表示”A” |
\unnnn |
以十六進位制程式碼nnnn表示的一個Unicode字元(其中n為0~F)。例如,\u03a3表示希臘字母∑ |
這些字元字面量可以出現在字串中的任意位置,而且也將被作為一個字元來解析,如下面的例子所示:
var text = “Thisis the letter sigama:\u03a3.”;
這個例子中的變數text有28個字元,其中6個字元長度的轉義序列表示1個字元。
任何字串的長度都可以通過訪問其length屬性取得,例如:
alert(text.length);//輸出28
這個屬性返回的字元數包括16位字元的數目。如果字串中包含雙子姐字元,那麼length屬性可能不會精確地返回字串中的字元數目。
字串的特點
ECMAScript中的字串是不可變的,也就是說,字串一旦建立,它們的值就不能改變。要改變某個變數儲存的字串,首先要銷燬原來的字串,然後再用另一個包含新值的字串填充該變數。
轉換為字串
要把一個值轉換為一個字串有兩種方式。第一種是幾乎每個值都會有的tostring()方法。這個方法唯一要做的就是返回相應值的字串表現。
數值、布林值、物件和字串都有tostring()方法。但null和undefined值沒有這個方法。
多數情況下,呼叫tostring()方法不必傳遞引數。但是,在呼叫數值的tostring()方法時,可以傳遞一個引數:輸出數值的基數。預設情況下,tostring()方法以十進位制格式返回數值的字串表示。而通過傳遞基數,tostring()可以輸出以二進位制、八進位制、十六進位制,乃至其他任意有效進位制格式表示的字串值。例如:
var num = 10;
alert(num.toString());//10
alert(num.toString(2));//1010
alert(num.toString(4));//22
alert(num.toString(8));//12
alert(num.toString(10));//10
alert(num.toString(16));//a
通過上面的例子可以看出,通過指定基數,toString()方法會改變輸出的值。而數值10根據基數的不同,可以在輸出時被轉換為不同的數值格式。注意,預設的(沒有引數)輸出值與指定基數10時的輸出值相同。
在不知道要轉換的值是不是null或undefined的情況下,還可以使用轉型函式String(),這個函式能夠將任何型別的值轉換為字串。String()函式遵循下列轉換規則:
□ 如果值有toString()方法,則呼叫該方法(沒有引數)並返回相應的結果;
□ 如果值是null,則返回”null”;
□ 如果值是undefined,則返回”undefined”。
要把某個值轉換為字串,可以使用加號操作符把它與一個字串(“”)加在一起。
Object型別
ECMAScript中的物件其實就是一組資料和功能的集合。物件可以通過執行new操作符後跟要建立的物件型別的名稱來建立。而建立Object型別的例項併為其新增屬性和(或)方法,就可以建立自定義物件,如:
var obj = new Object();
這個語法與Java中建立物件的語法相似;但在ECMAScript中,如果不給建構函式傳遞引數,則可以省略後面的那一對圓括號。也就是說,在像前面這個示例一樣不傳遞引數的情況下,完全可以省略圓括號(但不推薦這樣的做法):
var obj = new Object;//有效,但不推薦省略圓括號
僅僅建立Object的例項並沒有什麼用處,但關鍵是要理解一個重要的思想:即在ECMAScript中,(就像Java中的java.lang.Object物件一樣)Object型別是多有它的例項基礎。換句話說,Object型別所具有的任何屬性和方法也同樣存在於更具體的物件中。
Object的每個例項都具有下列屬性和方法。
□ Constructor:儲存著用於建立當前物件的函式。對於前面的例子而言,建構函式(constructor)就是Object()。
□ hasOwnProperty(propertyName):用於檢查給定的屬性在當前物件例項中(而不是在例項的原型中)是否存在。其中,作為引數的屬性名(propertyName)必須以字串形式指定。
□ isPropertyOf(object):用於檢查傳入的物件是否是另一個物件的原型。
□ propertyIsEnumerable(propertyName):用於檢查給定的屬性是否能夠使用for-in語句來列舉。與hasOwnProperty()方法一樣,作為引數的屬性名必須以字串形式指定。
□ toLocaleString():返回物件的字串表示,該字串與執行環境的地區對應。
□ toString():返回物件的字串表示。
□ valueof():返回物件的字串、數值或布林值表示、通常與toString()方法的返回值相同。
由於在ECMAScript中Object是所有物件的基礎,因此多有物件都具有這些基本的屬性和方法。
操作符
ECMAScript-262描述了一組用於操作資料的操作符,包括算術操作符(如加號和減號)、位操作符、關係操作符和相等操作符。ECMAScript操作符的與眾不同之處在於,它們能夠適用於很多值,例如字串、數字值、布林值,設定物件。不過,在應用於物件時,相應的操作符通常都會呼叫物件的valueof()和(或)toString()方法,以便取得可操作的值。
一元操作符
只能操作一個值的操作符叫做一元操作符。一元操作符是ECMAScript中最簡單的操作符。
遞增和遞減操作符
遞增和遞減操作符直接借鑑自C,而且各有兩個版本:前置型和後置型。顧名思義,前置型應該位於要操作的變數之前,而後置型則應該位於要操作的變數之後。因此,在使用前置遞增操作符給一個數值加1時,要把兩個加號(++)放在這個數值變數前面,如:
var age = 21;
age++;
在這個例子中,前置遞增操作符把age的值變成了22(為21加上1)。實際上,執行這個前置遞增操作與執行以下操作的效果相同:
var age = 21;
age = age + 1;
執行前置遞減操作的方法也類似,結果會從一個數值中減去1。使用前置遞減操作符時,要把兩個減號(--)放在相應變數的前面,如:
var age = 21;
age--;
這樣age變數的值就減少為20。
執行前置遞增和遞減操作時,變數的值都是在語句被求職以前改變的:
var age = 21;
var anotherAge =--age + 2;
alert(age);//20
alert(anotherAge);//22
這個例子中變數anotherAge的初始值等於變數age的值前置遞減之後加2。由於限制性了剪髮操作,age變成了20,所以加上2的結果就是22。
由於前置遞增和遞減操作與執行語句的優先順序相等,因此整個語句會從左至右被求值。
後置型遞增和遞減操作符的語法不變(仍然分別是++和--),只不過要放在變數的後面而不是前面。後置型遞增和遞減與前置型有一個非重要的區別,即遞增和遞減操作是在包含它們的語句被求值之後才執行。前置與後置的效果比較:
//前置型
var num1 = 10 ;
var num11 = --num1 + 2;
alert("前置型結果:num1=" + num1 + "; num11=" + num11);//前置型結果:num1=9; num11=11
//後置型
var num2 = 10;
var num21 = num2-- + 2;
alert("前置型結果:num2=" + num2 + "; num21=" + num21);//後置型結果:num2=9; num21=12
上面的例子中結果有些差異,是因為前置型遞減操作會按照語句的順序執行,在語句
var num11 = --num1 + 2;
中num1先執行了遞減操作,然後執行加操作和賦值操作,此時num1的值為9,加2後num11的值為11;而在語句
var num21 = num2--+ 2;
中先執行num2+2操作和賦值操作,此時num2的值仍為10,所以num2+2=12,在執行完上述操作後num2遞減操作。
前置型和後置型遞增、遞減操作符對任何值都適用,也就是它們不僅適用於整數,還可以用於字串、布林值、浮點數值和物件。在應用於不同的值時,遞增和遞減操作符遵循以下規則:
□ 在應用域一個包含數字字串時,先將其轉換為數字值,再執行加減1的操作。字串變數變成數值變數。
□ 在應用於一個不含有效數字字元的字串時,將變數的值設定為NaN。字串變數變成數值變數。
□ 在應用於布林值false時,先將其轉換為0再執行加減1的操作。布林值變數變成數值變數。
□ 在應用於布林值true時,先將其轉換為1再執行加減1的操作。布林值變數變成數值變數。
□ 在應用於浮點數值時,執行加減1的操作。
□ 在應用於物件時,先呼叫物件的valueof()方法以取得一個可供操作的值。然後對該值應用前述規則。如果結果是NaN,則再呼叫toString()方法後再應用前述規則。物件變數變成數值變數。
一元加和減操作符
絕大多數開發人員對一元加和減操作符都不會陌生,而且這兩個ECMAScript操作符的作用與數學書上將的完全一樣。一元加操作符以一個加號(+)表示,放在數值前面,對數值不會產生任何影響。不過,在對非數值應用一元加操作符時,該操作符會像Number()轉型函式一樣對這個值執行轉換。換句話說,布林值false和true將被轉換為0和1,字串值會按照一組特殊的規則進行解析,而物件是先呼叫它們的valueof和(或)toString()方法,再轉換得到的值。
一元減操作符應用於數值時,該值會變成負數。而當應用於非數值時,一元減操作符遵循與一元加操作符相同的規則,最後再將得到的數值轉換為負數。
位操作符
位操作符用於在最基本的層次上,即按記憶體中表示數值的位來運算元值。ECMAScript中的所有數值都以IEEE-754 64位格式儲存,但位操作符並不直接操作64位的值。而是先將64位的值轉換成32位的整數,然後執行操作,最後再將結果轉換回64位。對於開發人員來說,由於64位儲存格式是透明的,因此整個過程就像是隻存在32位的整數一樣。
對於有符號的整數,32位中的前31位用於表示整數的值。第32位用於表示數值的符號:0表示正數,1表示負數。這個表示符號的位叫做符號位,符號位的值決定了其他位數值的格式。其中,正數以純二進位制格式儲存,31位中的每一位都表示2的冪。第一位(叫做位0)表示 ,以此類推。沒有用到的位以0填充,即忽略不計。例如,數值18的二進位制表示是00000000000000000000000000010010,或者更簡潔的10010。這是5個有效位,這5位本身就決定了實際的值。
負數同樣以二進位制碼儲存,但使用的格式是二進位制補碼。計算一個數值的二進位制補碼,需要經過下列3個步驟:
(1) 求這個數值絕對值的二進位制碼(例如,要求-18的二進位制補碼,先求18的二進位制碼);
(2) 求二進位制反碼,即將0替換為1,將1替換為0;
(3) 得到的二進位制反碼加1。
按照上面三個步驟求-18的補碼,首先就要求的18的二進位制碼,即:
0000 0000 0000 0000 0000 0000 0001 0010
然後,求其二進位制反碼,即0和1互換:
1111 1111 1111 1111 1111 1111 1110 1101
最後二進位制反碼加1:
1111 1111 1111 1111 1111 1111 1110 1110
這樣就得到了-18的二進位制表示,即11111111 1111 1111 1111 1111 1110 1110。要注意的是,在處理有符號整數時,是不能訪問位31的。
ECMAScript會盡力向我們隱藏所有這些資訊。換句話說,在以二進位制字串形式輸出一個負數時,我們看到的只是這個負數絕對值的二進位制碼前面加上了一個負號。如:
var num = -18;
alert(num.toString(2));//-10010
要把數值-18轉換成二進位制字串時,得到的結果是”-10010”。這說明轉換過程理解了二進位制補碼並將其以更合乎邏輯的形式展現了出來。
在ECMAScript中,當對數值應用位操作符時,後臺會發生如下轉換過程:64位的數值被轉換成32位數值,然後執行位操作,最後再將32位的結果轉換成64位數值,這樣,表面上看起來好像是在操作32位數值,就跟在其他語言中以類似方式執行二進位制操作一樣。但這個轉換過程也導致了一個嚴重的副效應,即在對特殊的NaN和Infinity值應用為操作符時,這兩個值都會被當成0來處理。
如果對非數值應用位操作符,會先使用Number()函式將該值轉換為一個數值(自動完成),然後再應用位操作。得到的結果將是一個數值。
按位非(NOT)
按位非操作符由一個波浪線(~)表示,執行按位非的結果就是返回數值的反碼。按位非是ECMAScript操作符中少數幾個與二進位制計算有關的操作符之一。如:
var num1 = 25;//二進位制00000000000000000000000000011001
var num2 = ~num1;//二進位制11111111111111111111111111100110
alert(num2);//”-26”
這裡得到-26,驗證了按位非操作的本質:運算元的負值減1。
按位與(AND)
按位與操作符由一個和號字元(&)表示,它有兩個操作符數。從本質上講,按位與操作就是將兩個數值的每一位對齊,然後對每一位進行AND操作,也就是隻有兩個數值的該位同時為1,結果中的該位才為1,否則為0。如:
alert(25&3);//1
該語句的底層計算:
25 二進位制:0000 0000 00000000 0000 0000 0001 1001
3 二進位制:0000 00000000 0000 0000 0000 0000 0011
-----------------------------------------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001
按位或(OR)
按位或操作符由一個豎線符號(|)表示,同樣也有兩個運算元。在有一個味是1的情況下就返回1,而只有在兩個位都是0的情況下才返回0。
按位異或(XOR)
按位異或操作符由一個插入符號(^)表示,也有兩個運算元。按位異或與按位或的不同之處在於,這個操作在兩個數值對應位上只有一個1時才返回1,如果對應的兩位都是1或都是0,則返回0。
左移
左移操作符由兩個小於號(<<)表示,這個操作符會將數值的所有位向左移動指定的位數。比如,如果將數值2(二進位制碼為10)向左移動5位,結果就是64(二進位制碼為1000000)。
注意,左移不會影響運算元的符號位。換句話說,如果將-2向左移動5位,結果將是-64,而非64。
有符號的右移
有符號的右移操作符由兩個大於號(>>)表示,這個操作符會將數值向由移動,但保留符號位。有符號的右移操作與左移操作七號相反,即如果將64向右移動5位,結果變回2。
無符號右移
無符號右移操作符由3個大於號(>>>)表示,這個操作符會將數值的所有32位都向右移動。對正數來說,無符號右移的結果與有符號右移相同。如果將64無符號右移5位,結果仍為2。但是對於負數來說,情況就不一樣了。首先,無符號右移是以0來填充空位,而不是像有符號右移那樣以符號位的值來填充空位。隨意,對正數的無符號右移與有符號右移結果相同,但對負數的結果就不一樣了。其次,無符號右移操作會把負數的二進位制碼當成正數的二進位制碼。而且,由於負數以其絕對值的二進位制補碼形式表示,因此就會導致無符號右移後的結果非常之大。
布林操作符
在一門程式語言中,布林操作符的重要性堪比相等操作符。如果沒有測試兩個值關係的能力,那麼諸如if…else個迴圈之類的語句就不會有用武之地了。布林操作符一共有3個:非(NOT)、與(AND)、或(OR)。
邏輯非
邏輯非由一個歎號(!)表示,可以應用於ECMAScript中的任何值。無論這個值是什麼資料型別,這個操作符都會返回一個布林值。邏輯非操作符首先會將它的運算元轉換為一個布林值,然後再對其求反。下表中列出了邏輯非操作符返回的結果:
運算元 |
返回值 |
物件 |
false |
空字串 |
true |
非空字串 |
false |
0 |
true |
任意非0數值(包括Infinity) |
false |
null |
true |
NaN |
true |
undefined |
true |
邏輯非操作符也可以用於將一個值轉換為與其對應的布林值。而同時使用兩個邏輯非操作符,實際上就會模擬Boolean()轉型函式的行為,其中,第一個邏輯非操作會基於無論什麼運算元返回一個布林值,而第二個邏輯非操作則對該布林值求反,於是就得到了這個值真正對應的布林值。當然,最終結果與對這個值使用Boolean()函式相同。
邏輯與
邏輯與操作符由兩個和號(&&)表示,有兩個運算元,邏輯與的真值表如下所示: