1、數組
在 JavaScript 中,數組可以容納任何類型的值,可以是字符串、數字、對象(object),甚至是其他數組(多維數組就是通過這種方式來實現的) 。----《你所不知道的JavaScript(中)》P11
看看下面的代碼:
var a = [ 1, "2", [3] ]; a.length; // 3 a[0] === 1; // true a[2][0] === 3; // true var b = [ ]; b.length; // 0 b[0] = 1; b[1] = "2"; b[2] = [ 3 ]; b.length; // 3
對數組聲明后即可向其中加入值,不需要預先設定大小 。有一點需要注意的是使用delete標識符刪除數組元素的時候,數組的長度不變。
var a = [ 1, "2", [3] ]; delete a[0]; // true a.length; // 3 a; // [empty, "2", Array(1)]
在創建“稀疏”數組(sparse array,即含有空白或空缺單元的數組)時也要特別注意:
var a = [ ]; a[0] = 1; // 此處沒有設置a[1]單元 a[2] = [ 3 ]; a[1]; // undefined a.length; // 3
上面的代碼可以正常運行,但其中的“空白單元”(empty slot)可能會導致出人意料的結果。 a[1] 的值為 undefined,但這與將其顯式賦值為 undefined(a[1] = undefined)還是有所區別。 另外,還有這種情況,
var b = new Array(13); b; // [empty × 13] b.length; // 13
數組通過數字進行索引,但有趣的是它們也是對象,所以也可以包含字符串鍵值和屬性(但這些並不計算在數組長度內):
var a = [ ]; a[0] = 1; a["foobar"] = 2; a.length; // 1 a["foobar"]; // 2 a.foobar; // 2
數組具有 length 屬性,如果修改其 length 屬性,會修改到數組的值,所以需要特別謹慎,避免修改到數組的 length 屬性。
var c = [1,2,3,4,5]; c.length; // 5 c; // [1,2,3,4,5]; c.length = 3; c; // [1,2,3] c.length = 6; c; // [1, 2, 3, empty × 3]
這里有個問題需要特別注意,如果字符串鍵值能夠被強制類型轉換為十進制數字的話,它就會被當作數字索引來處理。
var a = [ ]; a["13"] = 13; a.length; // 14
2、字符串
字符串和數組的確很相似,都有 length 屬性以及 indexOf(..)(從 ES5開始數組支持此方法)和 concat(..) 方法:
var a = "foo"; var b = ["f","o","o"]; a[1]; // "o"; b[1]; // "o"; a.length; // 3 b.length; // 3 a.indexOf( "o" ); // 1 b.indexOf( "o" ); // 1 var c = a.concat( "bar" ); // "foobar" var d = b.concat( ["b","a","r"] ); // ["f","o","o","b","a","r"] a === c; // false b === d; // false
從上面看雖然字符串和數組的確有很多相似的地方,但這並不意味着它們都是“字符數組”。
JavaScript 中字符串是不可變的,而數組是可變的。並且 a[1] 在 JavaScript 中並非總是合法語法,在老版本的 IE 中就不被允許(現在可以了)。 正確的方法應該是 a.charAt(1)。
var a = "foo"; a.length; // 3 a.length = 2; a; // "foo" a.length; // 3 a.charAt(0); // "f"
3、數字
JavaScript 只有一種數值類型: number(數字),包括“整數”和帶小數的十進制數。此處“整數”之所以加引號是因為和其他語言不同, JavaScript 沒有真正意義上的整數,這也是它一直以來為人詬病的地方。這種情況在將來或許會有所改觀,但目前只有數字類型。 ----《你所不知道的JavaScript(中)》P15
JavaScript 中的“整數”就是沒有小數的十進制數。所以 42.0 即等同於“整數” 42。
3.1 數字的語法
JavaScript 中的數字常量一般用十進制表示。例如:
var a = 42; var b = 42.3;
數字前面的 0 可以省略,
var a = 0.42; var b = .42;
小數點后小數部分最后面的 0 也可以省略,
var a = 42.0; var b = 42.; //42. 這種寫法沒問題,只是不常見,但從代碼的可讀性考慮,不建議這樣寫。
默認情況下大部分數字都以十進制顯示,小數部分最后面的 0 被省略,如:
var a = 42.300; var b = 42.0; a; // 42.3 b; // 42
由於數字值可以使用 Number 對象進行封裝,因此數字值可以調用 Number.prototype 中的方法。例如, tofixed(..) 方法可指定小數部分的顯示位數 :
var a = 42.59; a.toFixed( 0 ); // "43" a.toFixed( 1 ); // "42.6" a.toFixed( 2 ); // "42.59" a.toFixed( 3 ); // "42.590" a.toFixed( 4 ); // "42.5900"
上面的方法不僅適用於數字變量,也適用於數字常量。不過對於 . 運算符需要給予特別注意,因為它是一個有效的數字字符,會被優先識別為數字常量的一部分,然后才是對象屬性訪問運算符。
// 無效語法: 42.toFixed( 3 ); // SyntaxError // 下面的語法都有效: (42).toFixed( 3 ); // "42.000" 0.42.toFixed( 3 ); // "0.420" 42..toFixed( 3 ); // "42.000 42 .toFixed(3); // "42.000" 注意.運算符前有空格
42.tofixed(3) 是無效語法,因為 . 被視為常量 42. 的一部分(如前所述),所以沒有 . 屬性訪問運算符來調用 tofixed 方法。
42..tofixed(3) 則沒有問題,因為第一個 . 被視為 number 的一部分,第二個 . 是屬性訪問運算符。只是這樣看着奇怪,實際情況中也很少見。在基本類型值上直接調用的方法並不多見,不過這並不代表不好或不對。
3.2 較小的數值
0.1 + 0.2 === 0.3; // false
從數學角度來說,上面的條件判斷應該為 true,可結果卻是 false 。這個問題相信很多人在剛接觸JavaScript的時候或多或少聽過或者見過。原因是,二進制浮點數中的 0.1 和 0.2 並不是十分精確,它們相加的結果並非剛好等於0.3,而是一個比較接近的數字 0.30000000000000004,所以條件判斷結果為 false。
那么應該怎樣來判斷 0.1 + 0.2 和 0.3 是否相等呢?
最常見的方法是設置一個誤差范圍值,通常稱為“機器精度”(machine epsilon), 對JavaScript 的數字來說,這個值通常是 2^-52 (2.220446049250313e-16)。從 ES6 開始,該值定義在 Number.EPSILON 中,我們可以直接拿來用,也可以為 ES6 之前的版本寫 polyfill:
if (!Number.EPSILON) { Number.EPSILON = Math.pow(2,-52); }
可以使用 Number.EPSILON 來比較兩個數字是否相等(在指定的誤差范圍內):
function numbersCloseEnoughToEqual(n1,n2) { return Math.abs( n1 - n2 ) < Number.EPSILON; } var a = 0.1 + 0.2; var b = 0.3; numbersCloseEnoughToEqual( a, b ); // true numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false
能夠呈現的最大浮點數大約是 1.798e+308(這是一個相當大的數字),它定義在 Number.MAX_VALUE 中。最小浮點數定義在 Number.MIN_VALUE 中,大約是 5e-324,它不是負數,但無限接近於 0 !
3.3 整數的安全范圍
數字的呈現方式決定了“整數”的安全值范圍遠遠小於 Number.MAX_VALUE。能夠被“安全”呈現的最大整數是 2^53 - 1,即 9007199254740991,在 ES6 中被定義為Number.MAX_SAFE_INTEGER。最小整數是 -9007199254740991,在 ES6 中被定義為 Number.MIN_SAFE_INTEGER。
Number.isSafeInteger( Number.MAX_SAFE_INTEGER ); // true Number.isSafeInteger( Math.pow( 2, 53 ) ); // false Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true
有時 JavaScript 程序需要處理一些比較大的數字,如數據庫中的 64 位 ID 等。由於JavaScript 的數字類型無法精確呈現 64 位數值,所以必須將它們保存(轉換)為字符串。
3.4 特殊數值
JavaScript 數據類型中有幾個特殊的值需要開發人員特別注意和小心使用。
3.4.1 不是值的值
undefined 類型只有一個值,即 undefined。 null 類型也只有一個值,即 null。它們的名稱既是類型也是值。
undefined 和 null 常被用來表示“空的”值或“不是值”的值。二者之間有一些細微的差別。例如:
• null 指空值(empty value)
• undefined 指沒有值(missing value)
或者:
• undefined 指從未賦值
• null 指曾賦過值,但是目前沒有值
null 是一個特殊關鍵字,不是標識符,我們不能將其當作變量來使用和賦值。然而undefined 卻是一個標識符,可以被當作變量來使用和賦值。
3.4.2 undefined
在非嚴格模式下,我們可以為全局標識符 undefined 賦值:
function foo() { undefined = 2; // 非常糟糕的做法! } foo(); function foo() { "use strict"; undefined = 2; // TypeError! } foo();
在非嚴格和嚴格兩種模式下,我們可以聲明一個名為 undefined 的局部變量(強烈禁止此做法)。
function foo() { "use strict"; var undefined = 2; console.log( undefined ); // 2 } foo();
注意:永遠不要重新定義 undefined。
void 運算符
undefined 是一個內置標識符(除非被重新定義,見前面的介紹),它的值為 undefined,通過 void 運算符即可得到該值。表達式 void ___ 沒有返回值,因此返回結果是 undefined。 void 並不改變表達式的結果,只是讓表達式不返回值:
var a = 42; console.log( void a, a ); // undefined 42
我們可以用 void 0 來獲得 undefined,當然只用void true 或其他void 表達式也是可以的,void 0、 void 1 、void true 和 undefined 之間並沒有實質上的區別。都是得到 undefined。
3.5 特殊的數字
數字類型中有幾個特殊的值,下面將詳細介紹。
3.5.1 不是數字的數字
如果數學運算的操作數不是數字類型(或者無法解析為常規的十進制或十六進制數字),就無法返回一個有效的數字,這種情況下返回值為 NaN。NaN 意指“不是一個數字”(not a number),這個名字容易引起誤會,后面將會提到。將它理解為“無效數值”“失敗數值”或者“壞數值”可能更准確些。 或者也可以把NaN理解為“不是數字的數字” 。
var a = 2 / "foo"; // NaN typeof a === "number"; // true
NaN 是一個“警戒值”(sentinel value,有特殊用途的常規值),用於指出數字類型中的錯誤情況,即“執行數學運算沒有成功,這是失敗后返回的結果”。
NaN 是一個特殊值,它和自身不相等,是唯一一個非自反的值。即NaN==NaN為false,而 NaN != NaN 為 true,很奇怪吧? 那這樣的話我們該如何比較和確定某個返回結果是否為NaN呢?
var a = 2 / "foo"; Number.isNaN(a); // true
實際上還有一個更簡單的方法,即利用 NaN 不等於自身這個特點。 NaN 是 JavaScript 中唯一一個不等於自身的值。 那么就可以這樣判斷
isNaN = function(n) { return n !== n; };
3.5.2 無窮數
var a = 1 / 0;
上例的結果為 Infinity(即 Number.POSITIVE_INfiNITY)。同樣:
var a = 1 / 0; // Infinity var b = -1 / 0; // -Infinity
如 果 除 法 運 算 中 的 一 個 操 作 數 為 負 數, 則 結 果 為 -Infinity( 即 Number.NEGATIVE_INfiNITY)。
和純粹的數學運算不同, JavaScript 的運算結果有可能溢出,此時結果為Infinity 或者 -Infinity。 計算結果一旦溢出為無窮數(infinity)就無法再得到有窮數。換句話說,就是你可以從有窮走向無窮,但無法從無窮回到有窮。
有人也許會問:“那么無窮除以無窮會得到什么結果呢?”我們的第一反應可能會是“1”或者“無窮”,可惜都不是。因為從數學運算和 JavaScript 語言的角度來說, Infinity/Infinity 是一個未定義操作,結果為 NaN。
那么有窮正數除以 Infinity 呢?很簡單,結果是 0。有窮負數除以 Infinity 呢?結果是 -0。
3.6 零值
JavaScript 有一個常規的 0(也叫作 +0)和一個 -0。
-0 除了可以用作常量以外,也可以是某些數學運算的返回值。例如:
var a = 0 / -3; // -0 var b = 0 * -3; // -0
負零在開發調試控制台中通常顯示為 -0,但在一些老版本的瀏覽器中仍然會顯示為 0。 注意:加法和減法運算不會得到負零(negative zero)。
根據規范,對負零進行字符串化會返回 "0":
var a = 0 / -3; a; // -0 a.toString(); // "0" a + ""; // "0" String( a ); // "0" // JSON也如此 JSON.stringify( a ); // "0"
有意思的是,如果反過來將其從字符串轉換為數字,得到的結果是准確的:
+"-0"; // -0 Number( "-0" ); // -0 JSON.parse( "-0" ); // -0
負零轉換為字符串的結果令人費解,它的比較操作也是如此:
var a = 0; var b = 0 / -3; a == b; // true -0 == 0; // true a === b; // true -0 === 0; // true 0 > -0; // false a > b; // false
-0===0?那這樣我們該如何判斷是 0 還是 -0?可以試試以下的方法:
function isNegZero(n) { n = Number( n ); return (n === 0) && (1 / n === -Infinity); } isNegZero( -0 ); // true isNegZero( 0 / -3 ); // true isNegZero( 0 ); // false
3.7 特殊等式
如前所述, NaN 和 -0 在相等比較時的表現有些特別。由於 NaN 和自身不相等,所以必須使用 ES6 中的 Number.isNaN(..)。而 -0 等於 0(對於 === 也是如此),因此我們必須使用 isNegZero(..) 這樣的工具函數。好在ES6 中新加入了一個工具方法 Object.is(..) 來判斷兩個值是否絕對相等,可以用來處理上述所有的特殊情況:
var a = 2 / "foo"; var b = -3 * 0; Object.is( a, NaN ); // true Object.is( b, -0 ); // true Object.is( b, 0 ); // false
上面的 Object.is( ) 我們大致可以這樣理解
Object.is = function(v1, v2) { // 判斷是否是-0 if (v1 === 0 && v2 === 0) { return 1 / v1 === 1 / v2; } // 判斷是否是NaN if (v1 !== v1) { return v2 !== v2; } // 其他情況 return v1 === v2; };
注意:能使用 == 和 === 時就盡量不要使用 Object.is(..),因為前者效率更高、更為通用。 Object.is(..) 主要用來處理那些特殊的相等比較。