第 2 章值
數(shù)組( array)偿曙、字符串( string)和數(shù)字( number)是一個(gè)程序最基本的組成部分
2.1 數(shù)組
var a = [ 1, "2", [3] ];
a[2][0] === 3; // true
對(duì)數(shù)組聲明后即可向其中加入值丢胚,不需要預(yù)先設(shè)定大小
var a = [ ];
a.length; // 0
a[0] = 1;
a[1] = "2";
a[2] = [ 3 ];
a.length; // 3
使用 delete 運(yùn)算符可以將單元從數(shù)組中刪除舍扰,但是請(qǐng)注意具垫,單元?jiǎng)h除后趟卸,數(shù)
組的 length 屬性并不會(huì)發(fā)生變化贮庞。
在創(chuàng)建“稀疏”數(shù)組( sparse array峦筒,即含有空白或空缺單元的數(shù)組)時(shí)要特別注意:
var a = [ ];
a[0] = 1;
// 此處沒(méi)有設(shè)置a[1]單元
a[2] = [ 3 ];
a[1]; // undefined
a.length; // 3
a[1] 的值為 undefined,但這與將其顯式賦值為 undefined( a[1] = undefined)還是
有所區(qū)別
數(shù)組通過(guò)數(shù)字進(jìn)行索引窗慎,但有趣的是它們也是對(duì)象物喷,所以也可以包含字符串鍵值和屬性
(但這些并不計(jì)算在數(shù)組長(zhǎng)度內(nèi)):
var a = [ ];
a[0] = 1;
a["foobar"] = 2;
a.length; // 1
a["foobar"]; // 2
a.foobar; // 2
這里有個(gè)問(wèn)題需要特別注意,如果字符串鍵值能夠被強(qiáng)制類型轉(zhuǎn)換為十進(jìn)制數(shù)字的話遮斥,它
就會(huì)被當(dāng)作數(shù)字索引來(lái)處理峦失。
var a = [ ];
a["13"] = 42;
a.length; // 14
使用對(duì)象來(lái)存放鍵值 / 屬性值,用數(shù)組來(lái)存放數(shù)字索引值术吗。
類數(shù)組
有時(shí)需要將類數(shù)組(一組通過(guò)數(shù)字索引的值)轉(zhuǎn)換為真正的數(shù)組尉辑,這一般通過(guò)數(shù)組工具函數(shù)(如 indexOf(..)、 concat(..)较屿、 forEach(..) 等)來(lái)實(shí)現(xiàn)隧魄。
一些 DOM 查詢操作會(huì)返回 DOM 元素列表卓练,它們并非真正意義上的數(shù)組,但十分
類似购啄。另一個(gè)例子是通過(guò) arguments 對(duì)象(類數(shù)組)將函數(shù)的參數(shù)當(dāng)作列表來(lái)訪問(wèn)(從
ES6 開(kāi)始已廢止)襟企。
function foo() {
var arr = Array.prototype.slice.call( arguments );
arr.push( "bam" );
console.log( arr );
}
foo( "bar", "baz" ); // ["bar","baz","bam"]
用 ES6 中的內(nèi)置工具函數(shù) Array.from(..) 也能實(shí)現(xiàn)同樣的功能:
...
var arr = Array.from( arguments );
...
2.2 字符串
var a = "foo";
var b = ["f","o","o"];
字符串和數(shù)組的確很相似,它們都是類數(shù)組
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
a; // "foo"
b; // ["f","o","o"]
但這并不意味著它們都是“字符數(shù)組”闸溃,比如:
a[1] = "O";
b[1] = "O";
a; // "foo"
b; // ["f","O","o"]
JavaScript 中字符串是不可變的整吆,而數(shù)組是可變的。并且 a[1] 在 JavaScript 中并非總是合法語(yǔ)法辉川,在老版本的 IE 中就不被允許(現(xiàn)在可以了)。 正確的方法應(yīng)該是 a.charAt(1)拴测。
字符串不可變是指字符串的成員函數(shù)不會(huì)改變其原始值乓旗,而是創(chuàng)建并返回一個(gè)新的字符
串。而數(shù)組的成員函數(shù)都是在其原始值上進(jìn)行操作集索。
c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"
b.push( "!" );
b; // ["f","O","o","!"]
許多數(shù)組函數(shù)用來(lái)處理字符串很方便屿愚。雖然字符串沒(méi)有這些函數(shù),但可以通過(guò)“借用”數(shù)
組的非變更方法來(lái)處理字符串:
a.join; // undefined
a.map; // undefined
var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
return v.toUpperCase() + ".";
} ).join( "" );
c; // "f-o-o"
d; // "F.O.O.
另一個(gè)不同點(diǎn)在于字符串反轉(zhuǎn)( JavaScript 面試常見(jiàn)問(wèn)題)务荆。數(shù)組有一個(gè)字符串沒(méi)有的可變更成員函數(shù) reverse():
a.reverse; // undefined
b.reverse(); // ["!","o","O","f"]
b; // ["f","O","o","!"]
可惜我們無(wú)法“借用”數(shù)組的可變更成員函數(shù)妆距,因?yàn)樽址遣豢勺兊模?/p>
Array.prototype.reverse.call( a );
// 返回值仍然是字符串"foo"的一個(gè)封裝對(duì)象
一個(gè)變通(破解)的辦法是先將字符串轉(zhuǎn)換為數(shù)組,待處理完后再將結(jié)果轉(zhuǎn)換回字符串:
var c = a
// 將a的值轉(zhuǎn)換為字符數(shù)組
.split( "" )
// 將數(shù)組中的字符進(jìn)行倒轉(zhuǎn)
.reverse()
// 將數(shù)組中的字符拼接回字符串
.join( "" );
c; // "oof"
請(qǐng)注意函匕!上述方法對(duì)于包含復(fù)雜字符( Unicode娱据,如星號(hào)、多字節(jié)字符等)的
字符串并不適用盅惜。這時(shí)則需要功能更加完備中剩、能夠處理 Unicode 的工具庫(kù)。
可以參考 Mathias Bynen 的 Esrever( https://github.com/mathiasbynents/esrever)抒寂。
如果需要經(jīng)常以字符數(shù)組的方式來(lái)處理字符串的話结啼,倒不如直接使用數(shù)組。這樣就不用在
字符串和數(shù)組之間來(lái)回折騰屈芜〗祭ⅲ可以在需要時(shí)使用 join("") 將字符數(shù)組轉(zhuǎn)換為字符串。
2.3 數(shù)字
JavaScript 沒(méi)有真正意義上的整數(shù)
與大部分現(xiàn)代編程語(yǔ)言(包括幾乎所有的腳本語(yǔ)言)一樣井佑, JavaScript 中的數(shù)字類型是基
于 IEEE 754 標(biāo)準(zhǔn)來(lái)實(shí)現(xiàn)的属铁,該標(biāo)準(zhǔn)通常也被稱為“浮點(diǎn)數(shù)”。 JavaScript 使用的是“雙精
度”格式(即 64 位二進(jìn)制)毅糟。
2.3.1 數(shù)字的語(yǔ)法
數(shù)字前面的 0 可以省略:
var a = 0.42;
var b = .42;
小數(shù)點(diǎn)后小數(shù)部分最后面的 0 也可以省略:
var a = 42.0;
var b = 42.;
** 這種寫(xiě)法沒(méi)問(wèn)題红选,只是不常見(jiàn),但從代碼的可讀性考慮姆另,不建議這樣寫(xiě)喇肋。**
默認(rèn)情況下大部分?jǐn)?shù)字都以十進(jìn)制顯示坟乾,小數(shù)部分最后面的 0 被省略,如:
var a = 42.300;
var b = 42.0;
a; // 42.3
b; // 42
特別大和特別小的數(shù)字默認(rèn)用指數(shù)格式顯示蝶防,與 toExponential() 函數(shù)的輸出結(jié)果相同甚侣。
例如:
var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11
Number.prototype 中的方法(參見(jiàn)第 3 章)。例如间学, tofixed(..) 方法可指定小數(shù)部分的顯示位數(shù):
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"
上例中的輸出結(jié)果實(shí)際上是給定數(shù)字的字符串形式殷费,如果指定的小數(shù)部分的顯示
位數(shù)多于實(shí)際位數(shù)就用 0 補(bǔ)齊。
toPrecision(..) 方法用來(lái)指定有效數(shù)位的顯示位數(shù):
var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"
上面的方法不僅適用于數(shù)字變量低葫,也適用于數(shù)字常量详羡。
// 無(wú)效語(yǔ)法:
42.toFixed( 3 ); // SyntaxError
// 下面的語(yǔ)法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
42.tofixed(3) 是無(wú)效語(yǔ)法,因?yàn)?. 被視為常量 42. 的一部分
一些工具庫(kù)擴(kuò)展了 Number.prototype 的內(nèi)置方法(參見(jiàn)第 3 章)以提供更
多的數(shù)值操作嘿悬,比如用 10..makeItRain() 方法來(lái)實(shí)現(xiàn)十秒鐘金錢(qián)雨動(dòng)畫(huà)等
效果实柠。
下面的語(yǔ)法也是有效的(請(qǐng)注意其中的空格):
42 .toFixed(3); // "42.000"
然而對(duì)數(shù)字常量而言,這樣的語(yǔ)法很容易引起誤會(huì)善涨,不建議使用窒盐。
當(dāng)前的 JavaScript 版本都支持這些格式:
0xf3; // 243的十六進(jìn)制
0Xf3; // 同上
0363; // 243的八進(jìn)制
從 ES6 開(kāi)始,嚴(yán)格模式( strict mode)不再支持 0363 八進(jìn)制格式(新格式如
下)钢拧。 0363 格式在非嚴(yán)格模式( non-strict mode)中仍然受支持蟹漓,但是考慮到
將來(lái)的兼容性,最好不要再使用(我們現(xiàn)在使用的應(yīng)該是嚴(yán)格模式)源内。
0o363; // 243的八進(jìn)制
0O363; // 同上
0b11110011; // 243的二進(jìn)制
0B11110011; // 同上
建議盡量使用小寫(xiě)的 0x葡粒、 0b 和 0o。
2.3.2 較小的數(shù)值
0.1 + 0.2 === 0.3; // false
二進(jìn)制浮點(diǎn)數(shù)中的 0.1 和 0.2 并不是十分精確姿锭,它們相加的結(jié)果并非剛好等于
0.3塔鳍,而是一個(gè)比較接近的數(shù)字 0.30000000000000004,所以條件判斷結(jié)果為 false呻此。
判斷 0.1 + 0.2 和 0.3 是否相等轮纫。最常見(jiàn)的方法是設(shè)置一個(gè)誤差范圍值,通常稱為“機(jī)器精度”( machine epsilon)焚鲜, 對(duì)JavaScript 的數(shù)字來(lái)說(shuō)掌唾,這個(gè)值通常是 2^-52 (2.220446049250313e-16)。
從 ES6 開(kāi)始忿磅,該值定義在 Number.EPSILON 中糯彬,我們可以直接拿來(lái)用,也可以為 ES6 之前
的版本寫(xiě) polyfill:
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}
可以使用 Number.EPSILON 來(lái)比較兩個(gè)數(shù)字是否相等(在指定的誤差范圍內(nèi)):
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 ); //
能夠呈現(xiàn)的最大浮點(diǎn)數(shù)大約是 1.798e+308(這是一個(gè)相當(dāng)大的數(shù)字)葱她,它定義在 Number.
MAX_VALUE 中撩扒。最小浮點(diǎn)數(shù)定義在 Number.MIN_VALUE 中,大約是 5e-324吨些,它不是負(fù)數(shù)搓谆,但
無(wú)限接近于 0 炒辉!
2.3.3 整數(shù)的安全范圍
能夠被“安全”呈現(xiàn)的最大整數(shù)是 2^53 - 1,即 9007199254740991泉手,在 ES6 中被定義為
Number.MAX_SAFE_INTEGER黔寇。最小整數(shù)是 -9007199254740991,在 ES6 中被定義為 Number.MIN_SAFE_INTEGER斩萌。
有時(shí) JavaScript 程序需要處理一些比較大的數(shù)字缝裤,如數(shù)據(jù)庫(kù)中的 64 位 ID 等。由于
JavaScript 的數(shù)字類型無(wú)法精確呈現(xiàn) 64 位數(shù)值颊郎,所以必須將它們保存(轉(zhuǎn)換)為字符串憋飞。
好在大數(shù)值操作并不常見(jiàn)(它們的比較操作可以通過(guò)字符串來(lái)實(shí)現(xiàn))。如果確實(shí)需要對(duì)大
數(shù)值進(jìn)行數(shù)學(xué)運(yùn)算袭艺,目前還是需要借助相關(guān)的工具庫(kù)搀崭。
2.3.4 整數(shù)檢測(cè)
要檢測(cè)一個(gè)值是否是整數(shù),可以使用 ES6 中的 Number.isInteger(..) 方法:
Number.isInteger( 42 ); // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 ); // false
也可以為 ES6 之前的版本 polyfill Number.isInteger(..) 方法:
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}
要檢測(cè)一個(gè)值是否是安全的整數(shù)猾编,可以使用 ES6 中的 Number.isSafeInteger(..) 方法:
Number.isSafeInteger( Number.MAX_SAFE_INTEGER ); // true
Number.isSafeInteger( Math.pow( 2, 53 ) ); // false
Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true
可以為 ES6 之前的版本 polyfill Number.isSafeInteger(..) 方法:
if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger( num ) &&
Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
};
}
2.3.5 32 位有符號(hào)整數(shù)
雖然整數(shù)最大能夠達(dá)到 53 位,但是有些數(shù)字操作(如數(shù)位操作)只適用于 32 位數(shù)字升敲,
所以這些操作中數(shù)字的安全范圍就要小很多答倡,變成從 Math.pow(-2,31)( -2147483648,
約-21 億)到 Math.pow(2,31) - 1( 2147483647驴党,約 21 億)瘪撇。
a | 0 可以將變量 a 中的數(shù)值轉(zhuǎn)換為 32 位有符號(hào)整數(shù),因?yàn)閿?shù)位運(yùn)算符 | 只適用于 32 位
整數(shù)(它只關(guān)心 32 位以內(nèi)的值港庄,其他的數(shù)位將被忽略)倔既。因此與 0 進(jìn)行操作即可截取 a 中
的 32 位數(shù)位
某些特殊的值并不是 32 位安全范圍的,如 NaN 和 Infinity(下節(jié)將作相關(guān)
介紹)鹏氧,此時(shí)會(huì)對(duì)它們執(zhí)行虛擬操作( abstract operation) ToInt32(參見(jiàn)第 4
章)渤涌,以便轉(zhuǎn)換為符合數(shù)位運(yùn)算符要求的 +0 值。
2.4 特殊數(shù)值
2.4.1 不是值的值
undefined 類型只有一個(gè)值把还,即 undefined实蓬。 null 類型也只有一個(gè)值,即 null吊履。它們的名
稱既是類型也是值安皱。
- null 指空值( empty value)
- undefined 指沒(méi)有值( missing value)
或者:
undefined 指從未賦值
null 指曾賦過(guò)值,但是目前沒(méi)有值
null 是一個(gè)特殊關(guān)鍵字艇炎,不是標(biāo)識(shí)符酌伊,我們不能將其當(dāng)作變量來(lái)使用和賦值。然而
undefined 卻是一個(gè)標(biāo)識(shí)符缀踪,可以被當(dāng)作變量來(lái)使用和賦值居砖。
2.4.2 undefined
在非嚴(yán)格模式下虹脯,我們可以為全局標(biāo)識(shí)符 undefined 賦值(這樣的設(shè)計(jì)實(shí)在是欠考慮!):
function foo() {
undefined = 2; // 非常糟糕的做法悯蝉!
}
foo();
function foo() {
"use strict";
undefined = 2; // TypeError!
}
foo();
在非嚴(yán)格和嚴(yán)格兩種模式下归形,我們可以聲明一個(gè)名為 undefined 的局部變量。再次強(qiáng)調(diào)最
好不要這樣做鼻由!
function foo() {
"use strict";
var undefined = 2;
console.log( undefined ); // 2
}
foo();
//永遠(yuǎn)不要重新定義 undefined暇榴。
void 運(yùn)算符
undefined 是一個(gè)內(nèi)置標(biāo)識(shí)符通過(guò) void 運(yùn)算符即可得到該值
void 并不改變表達(dá)式的結(jié)果,
只是讓表達(dá)式不返回值:
var a = 42;
console.log( void a, a ); // undefined 42
void 運(yùn)算符在其他地方也能派上用場(chǎng)蕉世,比如不讓表達(dá)式返回任何結(jié)果
function doSomething() {
// 注: APP.ready 由程序自己定義
if (!APP.ready) {
// 稍后再試
return void setTimeout( doSomething,100 );
}
var result;
// 其他
return result;
}
// 現(xiàn)在可以了嗎蔼紧?
if (doSomething()) {
// 立即執(zhí)行下一個(gè)任務(wù)
}
是為了確保 if 語(yǔ)句不產(chǎn)生誤報(bào)( false positive),我們要 void 掉它
if (!APP.ready) {
// 稍后再試
setTimeout( doSomething,100 );
return;
}
2.4.3 特殊的數(shù)字
1. 不是數(shù)字的數(shù)字
數(shù)學(xué)運(yùn)算的操作數(shù)不是數(shù)字類型(或者無(wú)法解析為常規(guī)的十進(jìn)制或十六進(jìn)制數(shù)字),將它
理解為“無(wú)效數(shù)值”“失敗數(shù)值”或者“壞數(shù)值”可能更準(zhǔn)確些.
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
“不是數(shù)字的數(shù)字”仍然是數(shù)字類型
var a = 2 / "foo";
a == NaN; // false
a === NaN; // false
而 NaN != NaN 為 true
var a = 2 / "foo";
isNaN( a ); // true
isNaN(..) 有一個(gè)嚴(yán)重的缺陷狠轻,它的檢查方式過(guò)于死板奸例,就
是“檢查參數(shù)是否不是 NaN,也不是數(shù)字”向楼。
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true——暈查吊!
從 ES6 開(kāi)始我們可以使用工具函數(shù) Number.isNaN(..)。 ES6 之前的瀏覽器的 polyfill 如下:
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
);
};
}
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false——好!
NaN 是 JavaScript 中唯一一個(gè)不等于自身的值。
if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}
盡量使用 Number.isNaN(..)贬养,無(wú)論是系統(tǒng)內(nèi)置還是 polyfill歪赢。如果你仍在代碼中使用 isNaN(..),那么你的程序遲早會(huì)出現(xiàn) bug。
2. 無(wú)窮數(shù)
var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity
上例的結(jié)果為 Infinity(即 Number.POSITIVE_INfiNITY),如 果 除 法 運(yùn) 算 中 的 一 個(gè) 操 作 數(shù) 為 負(fù) 數(shù), 則 結(jié) 果 為 -Infinity( 即 Number.NEGATIVE_INfiNITY)。
JavaScript 使用有限數(shù)字表示法( finite numeric representation盗迟,即之前介紹過(guò)的 IEEE 754
浮點(diǎn)數(shù)),所以和純粹的數(shù)學(xué)運(yùn)算不同熙含, JavaScript 的運(yùn)算結(jié)果有可能溢出罚缕,此時(shí)結(jié)果為
Infinity 或者 -Infinity。
var a = Number.MAX_VALUE; // 1.7976931348623157e+308
a + a; // Infinity
a + Math.pow( 2, 970 ); // Infinity
a + Math.pow( 2, 969 ); // 1.7976931348623157e+308
規(guī)范規(guī)定婆芦,如果數(shù)學(xué)運(yùn)算(如加法)的結(jié)果超出處理范圍怕磨,則由 IEEE 754 規(guī)范中的“就
近取整”( round-to-nearest)模式來(lái)決定最后的結(jié)果。例如消约,相對(duì)于 Infinity肠鲫, Number.MAX_
VALUE + Math.pow(2, 969) 與 Number.MAX_VALUE 更為接近,因此它被“向下取整”( round
down)或粮;而 Number.MAX_VALUE + Math.pow(2, 970) 與 Infinity 更為接近导饲,所以它被“向上
取整”( round up)。
Infinity/Infinity 是一個(gè)未定義操作,結(jié)果為 NaN渣锦。那么有窮正數(shù)除以 Infinity 呢硝岗?很簡(jiǎn)單,結(jié)果是 0
** 3. 零值**
var a = 0 / -3; // -0
var b = 0 * -3; // -0
加法和減法運(yùn)算不會(huì)得到負(fù)零( negative zero)袋毙。
負(fù)零在開(kāi)發(fā)調(diào)試控制臺(tái)中通常顯示為 -0型檀,但在一些老版本的瀏覽器中仍然會(huì)顯示為 0。
根據(jù)規(guī)范听盖,對(duì)負(fù)零進(jìn)行字符串化會(huì)返回 "0":
var a = 0 / -3;
// 至少在某些瀏覽器的控制臺(tái)中顯示是正確的
a; // -0
// 但是規(guī)范定義的返回結(jié)果是這樣胀溺!
a.toString(); // "0"
a + ""; // "0"
String( a ); // "0"
// JSON也如此,很奇怪
JSON.stringify( a ); // "0"
如果反過(guò)來(lái)將其從字符串轉(zhuǎn)換為數(shù)字皆看,得到的結(jié)果是準(zhǔn)確的:
+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0
JSON.stringify(-0) 返回 "0"
負(fù)零轉(zhuǎn)換為字符串的結(jié)果令人費(fèi)解
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
要區(qū)分 -0 和 0仓坞,不能僅僅依賴開(kāi)發(fā)調(diào)試窗口的顯示結(jié)果,還需要做一些特殊處理:
function isNegZero(n) {
n = Number( n );
return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false
有些應(yīng)用程序中的數(shù)據(jù)需要以級(jí)數(shù)形式來(lái)表示(比如動(dòng)畫(huà)幀的移動(dòng)速度)腰吟,數(shù)字的符號(hào)位
( sign)用來(lái)代表其他信息(比如移動(dòng)的方向)无埃。此時(shí)如果一個(gè)值為 0 的變量失去了它的符
號(hào)位,它的方向信息就會(huì)丟失毛雇。所以保留 0 值的符號(hào)位可以防止這類情況發(fā)生嫉称。
ES6 中新加入了一個(gè)工具方法 Object.is(..) 來(lái)判斷兩個(gè)值是否絕對(duì)相等,可以用來(lái)處理
上述所有的特殊情況:
var a = 2 / "foo";
var b = -3 * 0;
Object.is( a, NaN ); // true
Object.is( b, -0 ); // true
Object.is( b, 0 ); // false
對(duì)于 ES6 之前的版本灵疮, Object.is(..) 有一個(gè)簡(jiǎn)單的 polyfill:
if (!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(..) 主要用來(lái)處理那些特殊的相等比較
2.5 值和引用
var a = 2;
var b = a; // b是a的值的一個(gè)副本
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // d是[1,2,3]的一個(gè)引用
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
簡(jiǎn)單值(即標(biāo)量基本類型值澎埠, scalar primitive) 總是通過(guò)值復(fù)制的方式來(lái)賦值 / 傳遞,包括
null始藕、 undefined、字符串氮趋、數(shù)字伍派、布爾和 ES6 中的 symbol。
復(fù)合值( compound value)——對(duì)象(包括數(shù)組和封裝對(duì)象剩胁,參見(jiàn)第 3 章)和函數(shù)诉植,則總
是通過(guò)引用復(fù)制的方式來(lái)賦值 / 傳遞。
c 和 d 則分別指向同一個(gè)復(fù)合值 [1,2,3] 的兩個(gè)不同引用昵观。請(qǐng)注意晾腔, c 和 d 僅僅是指向值
[1,2,3],并非持有啊犬。
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// 然后
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// 然后
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[1,2,3,4]灼擂,不是[4,5,6,7]
向函數(shù)傳遞 a 的時(shí)候,實(shí)際是將引用 a 的一個(gè)復(fù)本賦值給 x觉至,而 a 仍然指向 [1,2,3]剔应。
在函數(shù)中我們可以通過(guò)引用 x 來(lái)更改數(shù)組的值( push(4) 之后變?yōu)?[1,2,3,4])。但 x =
[4,5,6] 并不影響 a 的指向,所以 a 仍然指向 [1,2,3,4]峻贮。
不能通過(guò)引用 x 來(lái)更改引用 a 的指向席怪,只能更改 a 和 x 共同指向的值。
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// 然后
x.length = 0; // 清空數(shù)組
x.push( 4, 5, 6, 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[4,5,6,7]纤控,不是[1,2,3,4]
foo( a.slice() );
slice(..) 不帶參數(shù)會(huì)返回當(dāng)前數(shù)組的一個(gè)淺復(fù)本( shallow copy)挂捻。由于傳遞給函數(shù)的是指
向該復(fù)本的引用,所以 foo(..) 中的操作不會(huì)影響 a 指向的數(shù)組船万。
function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42
與預(yù)期不同的是刻撒,雖然傳遞的是指向數(shù)字對(duì)象的引用復(fù)本,但我們并不能通過(guò)它來(lái)更改其
中的基本類型值:
function foo(x) {
x = x + 1;
x; // 3
}
var a = 2;
var b = new Number( a ); // Object(a)也一樣
foo( b );
console.log( b ); // 是2唬涧,不是3
原因是標(biāo)量基本類型值是不可更改的(字符串和布爾也是如此)疫赎。如果一個(gè)數(shù)字對(duì)象的標(biāo)
量基本類型值是 2,那么該值就不能更改碎节,除非創(chuàng)建一個(gè)包含新值的數(shù)字對(duì)象捧搞。
x = x + 1 中, x 中的標(biāo)量基本類型值 2 從數(shù)字對(duì)象中拆封(或者提仁ɡ蟆)出來(lái)后, x 就神不
知鬼不覺(jué)地從引用變成了數(shù)字對(duì)象殖氏,它的值為 2 + 1 等于 3。然而函數(shù)外的 b 仍然指向原
來(lái)那個(gè)值為 2 的數(shù)字對(duì)象爵憎。
我們還可以為數(shù)字對(duì)象添加屬性(只要不更改其內(nèi)部的基本類型值即可),通過(guò)它們間接
地進(jìn)行數(shù)據(jù)交換婚瓜。
不過(guò)這種做法不太常見(jiàn)宝鼓,大多數(shù)開(kāi)發(fā)人員可能都覺(jué)得這不是一個(gè)好辦法巴刻。
相對(duì)而言,前面用 obj 作為封裝對(duì)象的辦法可能更好一些胡陪。這并不是說(shuō)數(shù)字等封裝對(duì)象沒(méi)
有什么用沥寥,只是多數(shù)情況下我們應(yīng)該優(yōu)先考慮使用標(biāo)量基本類型。