特別說明,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS
array
、string
挟鸠、和 number
是任何程序的最基礎(chǔ)構(gòu)建塊,但是 JavaScript 在這些類型上有一些或使你驚喜或使你驚訝的獨(dú)特性質(zhì)亩冬。
讓我們來看幾種 JS 內(nèi)建的值類型艘希,并探討一下我們?nèi)绾尾拍芨尤娴乩斫獠⒄_地利用它們的行為。
Array
和其他強(qiáng)制類型的語言相比硅急,JavaScript 的 array
只是值的容器枢冤,而這些值可以是任何類型:string
或者 number
或者 object
,甚至是另一個(gè) array
(這也是你得到多維數(shù)組的方法)铜秆。
var a = [ 1, "2", [3] ];
a.length; // 3
a[0] === 1; // true
a[2][0] === 3; // true
你不需要預(yù)先指定 array
的大小,你可以僅聲明它們并加入你覺得合適的值:
var a = [ ];
a.length; // 0
a[0] = 1;
a[1] = "2";
a[2] = [ 3 ];
a.length; // 3
警告: 在一個(gè) array
值上使用 delete
將會(huì)從這個(gè) array
上移除一個(gè)值槽讶迁,但就算你移除了最后一個(gè)元素连茧,它也 不會(huì) 更新 length
屬性核蘸,所以多加小心!我們會(huì)在第五章討論 delete
操作符的更多細(xì)節(jié)啸驯。
要小心創(chuàng)建“稀散”的 array
(留下或創(chuàng)建空的/丟失的值槽):
var a = [ ];
a[0] = 1;
// 這里沒有設(shè)置值槽 `a[1]`
a[2] = [ 3 ];
a[1]; // undefined
a.length; // 3
雖然這可以工作客扎,但你留下的“空值槽”可能會(huì)導(dǎo)致一些令人困惑的行為。雖然這樣的值槽看起來擁有 undefined
值罚斗,但是它不會(huì)像被明確設(shè)置(a[1] = undefined
)的值槽那樣動(dòng)作徙鱼。更多信息可以參見第三章的“Array”。
array
是被數(shù)字索引的(正如你所想的那樣)针姿,但微妙的是它們也是對象袱吆,可以在它們上面添加 string
鍵/屬性(但是這些屬性不會(huì)計(jì)算在 array
的 length
中):
var a = [ ];
a[0] = 1;
a["foobar"] = 2;
a.length; // 1
a["foobar"]; // 2
a.foobar; // 2
然而,一個(gè)需要小心的坑是距淫,如果一個(gè)可以被強(qiáng)制轉(zhuǎn)換為10進(jìn)制 number
的 string
值被用作鍵的話绞绒,它會(huì)認(rèn)為你想使用 number
索引而不是一個(gè) string
鍵!
var a = [ ];
a["13"] = 42;
a.length; // 14
一般來說榕暇,向 array
添加 string
鍵/屬性不是一個(gè)好主意蓬衡。最好使用 object
來持有鍵/屬性形式的值咐低,而將 array
專用于嚴(yán)格地?cái)?shù)字索引的值箱歧。
類 Array
偶爾你需要將一個(gè)類 array
值(一個(gè)數(shù)字索引的值的集合)轉(zhuǎn)換為一個(gè)真正的 array
,通常你可以對這些值的集合調(diào)用數(shù)組的工具函數(shù)(比如 indexOf(..)
狼纬、concat(..)
缴啡、forEach(..)
等等)壁晒。
舉個(gè)例子,各種 DOM 查詢操作會(huì)返回一個(gè) DOM 元素的列表盟猖,對于我們轉(zhuǎn)換的目的來說讨衣,這些列表不是真正的 array
但是也足夠類似 array
。另一個(gè)常見的例子是式镐,函數(shù)為了像列表一樣訪問它的參數(shù)值反镇,而暴露了 arugumens
對象(類 array
,在 ES6 中被廢棄了)娘汞。
一個(gè)進(jìn)行這種轉(zhuǎn)換的很常見的方法是對這個(gè)值借用 slice(..)
工具:
function foo() {
var arr = Array.prototype.slice.call( arguments );
arr.push( "bam" );
console.log( arr );
}
foo( "bar", "baz" ); // ["bar","baz","bam"]
如果 slice()
沒有用其他額外的參數(shù)調(diào)用歹茶,就像上面的代碼段那樣,它的參數(shù)的默認(rèn)值會(huì)使它具有復(fù)制這個(gè) array
(或者你弦,在這個(gè)例子中惊豺,是一個(gè)類 array
)的效果。
在 ES6 中禽作,還有一種稱為 Array.from(..)
的內(nèi)建工具可以執(zhí)行相同的任務(wù):
...
var arr = Array.from( arguments );
...
注意: Array.from(..)
擁有其他幾種強(qiáng)大的能力尸昧,我們將在本系列的 ES6 與未來 中涵蓋它的細(xì)節(jié)。
String
一個(gè)很常見的想法是旷偿,string
實(shí)質(zhì)上只是字符的 array
烹俗。雖然內(nèi)部的實(shí)現(xiàn)可能是也可能不是 array
爆侣,但重要的是要理解 JavaScript 的 string
與字符的 array
確實(shí)不一樣。它們的相似性幾乎只是表面上的幢妄。
舉個(gè)例子兔仰,讓我們考慮這兩個(gè)值:
var a = "foo";
var b = ["f","o","o"];
String 確實(shí)與 array
有很膚淺的相似性 -- 也就是上面說的,類 array
-- 舉例來說蕉鸳,它們都有一個(gè) length
屬性乎赴,一個(gè) indexOf(..)
方法(在 ES5 中僅有 array
版本),和一個(gè) concat(..)
方法:
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 的 string
是不可變的衍锚,而 array
是相當(dāng)可變的友题。另外,在 JavaScript 中用位置訪問字符的 a[1]
形式不總是廣泛合法的戴质。老版本的 IE 就不允許這種語法(但是它們現(xiàn)在允許了)度宦。相反,正確的 方式是 a.charAt(1)
告匠。
string
不可變性的進(jìn)一步的后果是戈抄,string
上沒有一個(gè)方法是可以原地修改它的內(nèi)容的,而是創(chuàng)建并返回一個(gè)新的 string
后专。與之相對的是划鸽,許多改變 array
內(nèi)容的方法實(shí)際上 是 原地修改的。
c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"
b.push( "!" );
b; // ["f","O","o","!"]
另外戚哎,許多 array
方法在處理 string
時(shí)非常有用裸诽,雖然這些方法不屬于 string
,但我們可以對我們的 string
“借用”非變化的 array
方法:
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è)例子:翻轉(zhuǎn)一個(gè) string
(順帶一提型凳,這是一個(gè) JavaScript 面試中常見的細(xì)節(jié)問題U啥)。array
擁有一個(gè)原地的 reverse()
修改器方法甘畅,但是 string
沒有:
a.reverse; // undefined
b.reverse(); // ["!","o","O","f"]
b; // ["!","o","O","f"]
不幸的是埂蕊,這種“借用” array
修改器不起作用,因?yàn)?string
是不可變的疏唾,因此它不能被原地修改:
Array.prototype.reverse.call( a );
// 仍然返回一個(gè)“foo”的 String 對象包裝器(見第三章) :(
另一種迂回的做法(也是黑科技)是蓄氧,將 string
轉(zhuǎn)換為一個(gè) array
,實(shí)施我們想做的操作槐脏,然后將它轉(zhuǎn)回 string
喉童。
var c = a
// 將 `a` 切分成一個(gè)字符的數(shù)組
.split( "" )
// 翻轉(zhuǎn)字符的數(shù)組
.reverse()
// 將字符的數(shù)組連接回一個(gè)字符串
.join( "" );
c; // "oof"
如果你覺得這很難看,沒錯(cuò)顿天。不管怎樣泄朴,對于簡單的 string
它 好用重抖,所以如果你需要某些快速但是“臟”的東西,像這樣的方式經(jīng)常能滿足你祖灰。
警告: 小心!這種方法對含有復(fù)雜(unicode)字符(星型字符畔规、多字節(jié)字符等)的 string
不起作用局扶。你需要支持 unicode 的更精巧的工具庫來準(zhǔn)確地處理這種操作。在這個(gè)問題上可以咨詢 Mathias Bynens 的作品:Esrever(https://github.com/mathiasbynens/esrever)叁扫。
另外一種考慮這個(gè)問題的方式是:如果你更經(jīng)常地將你的“string”基本上作為 字符的數(shù)組 來執(zhí)行一些任務(wù)的話三妈,也許就將它們作為 array
而不是作為 string
存儲(chǔ)更好。你可能會(huì)因此省去很多每次都將 string
轉(zhuǎn)換為 array
的麻煩莫绣。無論何時(shí)你確實(shí)需要 string
的表現(xiàn)形式的話畴蒲,你總是可以調(diào)用 字符的 array
的 join("")
方法。
Number
JavaScript 只有一種數(shù)字類型:number
对室。這種類型包含“整數(shù)”值和小數(shù)值模燥。我說“整數(shù)”時(shí)加了引號,因?yàn)?JS 的一個(gè)長久以來為人詬病的原因是掩宜,和其他語言不同蔫骂,JS 沒有真正的整數(shù)。這可能在未來某個(gè)時(shí)候會(huì)改變牺汤,但是目前辽旋,我們只有 number
可用。
所以檐迟,在 JS 中补胚,一個(gè)“整數(shù)”只是一個(gè)沒有小數(shù)部分的小數(shù)值。也就是說追迟,42.0
和 42
一樣是“整數(shù)”溶其。
像大多數(shù)現(xiàn)代計(jì)算機(jī)語言,以及幾乎所有的腳本語言一樣怔匣,JavaScript 的 number
的實(shí)現(xiàn)基于“IEEE 754”標(biāo)準(zhǔn)握联,通常被稱為“浮點(diǎn)”。JavaScript 明確地使用了這個(gè)標(biāo)準(zhǔn)的“雙精度”(也就是“64位二進(jìn)制”)格式每瞒。
在網(wǎng)絡(luò)上有許多了不起的文章都在介紹二進(jìn)制浮點(diǎn)數(shù)如何在內(nèi)存中存儲(chǔ)的細(xì)節(jié)金闽,以及選擇這些做法的意義。因?yàn)閷τ诶斫馊绾卧?JS 中正確使用 number
來說剿骨,理解內(nèi)存中的位模式不是必須的代芜,所以我們將這個(gè)話題作為練習(xí)留給那些想要進(jìn)一步挖掘 IEEE 754 的細(xì)節(jié)的讀者。
數(shù)字的語法
在 JavaScript 中字面數(shù)字一般用十進(jìn)制小數(shù)表達(dá)浓利。例如:
var a = 42;
var b = 42.3;
小數(shù)的整數(shù)部分如果是 0
挤庇,是可選的:
var a = 0.42;
var b = .42;
相似地钞速,一個(gè)小數(shù)在 .
之后的小數(shù)部分如果是 0
,是可選的:
var a = 42.0;
var b = 42.;
警告: 42.
是極不常見的嫡秕,如果你正在努力避免別人閱讀你的代碼時(shí)感到困惑渴语,它可能不是一個(gè)好主意。但不管怎樣昆咽,它是合法的驾凶。
默認(rèn)情況下,大多數(shù) number
將會(huì)以十進(jìn)制小數(shù)的形式輸出掷酗,并去掉末尾小數(shù)部分的 0
调违。所以:
var a = 42.300;
var b = 42.0;
a; // 42.3
b; // 42
非常大或非常小的 number
將默認(rèn)以指數(shù)形式輸出,與 toExponential()
方法的輸出一樣泻轰,比如:
var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11
因?yàn)?number
值可以用 Number
對象包裝器封裝(見第三章)技肩,所以 number
值可以訪問內(nèi)建在 Number.prototype
上的方法(見第三章)。舉個(gè)例子浮声,toFixed(..)
方法允許你指定一個(gè)值在被表示時(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"
要注意的是,它的輸出實(shí)際上是一個(gè) number
的 string
表現(xiàn)形式阿蝶,而且如果你指定的位數(shù)多于值持有的小數(shù)位數(shù)時(shí)雳锋,會(huì)在右側(cè)補(bǔ) 0
。
toPrecision(..)
很相似羡洁,但它指定的是有多少 有效數(shù)字 用來表示這個(gè)值:
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"
你不必非得使用持有這個(gè)值的變量來訪問這些方法玷过;你可以直接在 number
的字面上訪問這些方法。但你不得不小心 .
操作符筑煮。因?yàn)?.
是一個(gè)合法數(shù)字字符辛蚊,如果可能的話,它會(huì)首先被翻譯為 number
字面的一部分真仲,而不是被翻譯為屬性訪問操作符袋马。
// 不合法的語法:
42.toFixed( 3 ); // SyntaxError
// 這些都是合法的:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
42.toFixed(3)
是不合法的語法,因?yàn)?.
作為 42.
字面(這是合法的 -- 參見上面的討論=沼Α)的一部分被吞掉了虑凛,因此沒有 .
屬性操作符來表示 .toFixed
訪問。
42..toFixed(3)
可以工作软啼,因?yàn)榈谝粋€(gè) .
是 number
的一部分桑谍,而第二個(gè) .
是屬性操作符。但它可能看起來很古怪祸挪,而且確實(shí)在實(shí)際的 JavaScript 代碼中很少會(huì)看到這樣的東西锣披。實(shí)際上,在任何基本類型上直接訪問方法是十分不常見的。但是不常見并不意味著 壞 或者 錯(cuò)雹仿。
注意: 有一些庫擴(kuò)展了內(nèi)建的 Number.prototype
(見第三章)增热,使用 number
或在 number
上提供了額外的操作,所以在這些情況下胧辽,像使用 10..makeItRain()
來設(shè)定一個(gè)十秒鐘的下錢雨的動(dòng)畫峻仇,或者其他諸如此類的傻事是完全合法的。
在技術(shù)上講票顾,這也是合法的(注意那個(gè)空格):
42 .toFixed(3); // "42.000"
但是础浮,尤其是對 number
字面量來說,這是特別使人糊涂的代碼風(fēng)格奠骄,而且除了使其他開發(fā)者(和未來的你)糊涂以外沒有任何用處。避免它番刊。
number
還可以使用科學(xué)計(jì)數(shù)法的形式指定含鳞,這在表示很大的 number
時(shí)很常見,比如:
var onethousand = 1E3; // 代表 1 * 10^3
var onemilliononehundredthousand = 1.1E6; // 代表 1.1 * 10^6
number
字面量還可以使用其他進(jìn)制表達(dá)芹务,比如二進(jìn)制蝉绷,八進(jìn)制,和十六進(jìn)制枣抱。
這些格式是可以在當(dāng)前版本的 JavaScript 中使用的:
0xf3; // 十六進(jìn)制的: 243
0Xf3; // 同上
0363; // 八進(jìn)制的: 243
注意: 從 ES6 + strict
模式開始熔吗,不再允許 0363
這樣的的八進(jìn)制形式(新的形式參見后面的討論)。0363
在非 strict
模式下依然是允許的佳晶,但是不管怎樣你應(yīng)當(dāng)停止使用它桅狠,來擁抱未來(而且因?yàn)槟悻F(xiàn)在應(yīng)當(dāng)在使用 strict
模式了!)轿秧。
至于 ES6中跌,下面的新形式也是合法的:
0o363; // 八進(jìn)制的: 243
0O363; // 同上
0b11110011; // 二進(jìn)制的: 243
0B11110011; // 同上
請為你的開發(fā)者同胞們做件好事:絕不要使用 0O363
形式。把 0
放在大寫的 O
旁邊就是在制造困惑菇篡。保持使用小寫的謂詞 0x
漩符、0b
、和0o
驱还。
小數(shù)值
使用二進(jìn)制浮點(diǎn)數(shù)的最出名(臭名昭著)的副作用是(記住嗜暴,這是對 所有 使用 IEEE 754 的語言都成立的 —— 不是許多人認(rèn)為/假裝 僅 在 JavaScript 中存在的問題):
0.1 + 0.2 === 0.3; // false
從數(shù)學(xué)的意義上,我們知道這個(gè)語句應(yīng)當(dāng)為 true
议蟆。為什么它是 false
闷沥?
簡單地說,0.1
和 0.2
的二進(jìn)制表示形式是不精確的咪鲜,所以它們相加時(shí)狐赡,結(jié)果不是精確地 0.3
。而是 非常 接近的值:0.30000000000000004
,但是如果你的比較失敗了颖侄,“接近”是無關(guān)緊要的鸟雏。
注意: JavaScript 應(yīng)當(dāng)切換到可以精確表達(dá)所有值的一個(gè)不同的 number
實(shí)現(xiàn)嗎?有些人認(rèn)為應(yīng)該览祖。多年以來有許多選項(xiàng)出現(xiàn)過孝鹊。但是沒有一個(gè)被采納,而且也許永遠(yuǎn)也不會(huì)展蒂。它看起來就像揮揮手然后說“已經(jīng)改好那個(gè) bug 了!”那么簡單又活,但根本不是那么回事兒。如果真有這么簡單锰悼,它絕對在很久以前就被改掉了柳骄。
現(xiàn)在的問題是,如果一些 number
不能被 信任 為精確的箕般,這不是意味著我們根本不能使用 number
嗎耐薯? 當(dāng)然不是。
在一些應(yīng)用程序中你需要多加小心丝里,特別是在對付小數(shù)的時(shí)候曲初。還有許多(也許是大多數(shù)?)應(yīng)用程序只處理整數(shù)杯聚,而且臼婆,最大只處理到幾百萬到幾萬億。這些應(yīng)用程序使用 JS 中的數(shù)字操作是幌绍,而且將總是颁褂,非常安全 的。
要是我們 確實(shí) 需要比較兩個(gè) number
纷捞,就像 0.1 + 0.2
與 0.3
痢虹,而且知道這個(gè)簡單的相等測試將會(huì)失敗呢?
可以接受的最常見的做法是使用一個(gè)很小的“錯(cuò)誤舍入”值作為比較的 容差主儡。這個(gè)很小的值經(jīng)常被稱為“機(jī)械極小值(machine epsilon)”奖唯,對于 JavaScript 來說這種 number
通常為 2^-52
(2.220446049250313e-16
)。
在 ES6 中糜值,使用這個(gè)容差值預(yù)定義了 Number.EPSILON
丰捷,所以你將會(huì)想要使用它,你也可以在前 ES6 中安全地填補(bǔ)這個(gè)定義:
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}
我們可以使用這個(gè) Number.EPSILON
來比較兩個(gè) number
的“等價(jià)性”(帶有錯(cuò)誤舍入的容差):
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
可以被表示的最大的浮點(diǎn)值大概是 1.798e+308
(它真的非常寂汇,非常病往,非常大!)骄瓣,它為你預(yù)定義為 Number.MAX_VALUE
停巷。在極小的一端,Number.MIN_VALUE
大概是 5e-324
,它不是負(fù)數(shù)但是非常接近于0畔勤!
安全整數(shù)范圍
由于 number
的表示方式蕾各,對完全是 number
的“整數(shù)”而言有一個(gè)“安全”的值的范圍,而且它要比 Number.MAX_VALUE
小得多庆揪。
可以“安全地”被表示的最大整數(shù)(也就是說式曲,可以保證被表示的值是實(shí)際可以無誤地表示的)是2^53 - 1
,也就是9007199254740991
缸榛,如果你插入一些數(shù)字分隔符吝羞,可以看到它剛好超過9萬億。所以對于number
能表示的上限來說它確實(shí)是夠TM大的内颗。
在ES6中這個(gè)值實(shí)際上是自動(dòng)預(yù)定義的钧排,它是Number.MAX_SAFE_INTEGER
。意料之中的是均澳,還有一個(gè)最小值卖氨,-9007199254740991
,它在ES6中定義為Number.MIN_SAFE_INTEGER
负懦。
JS 程序面臨處理這樣大的數(shù)字的主要情況是,處理數(shù)據(jù)庫中的64位 ID 等等柏腻。64位數(shù)字不能使用 number
類型準(zhǔn)確表達(dá)纸厉,所以在 JavaScript 中必須使用 string
表現(xiàn)形式存儲(chǔ)(和傳遞)。
謝天謝地五嫂,在這樣的大 ID number
值上的數(shù)字操作(除了比較颗品,它使用 string
也沒問題)并不很常見。但是如果你 確實(shí) 需要在這些非常大的值上實(shí)施數(shù)學(xué)操作沃缘,目前來講你需要使用一個(gè) 大數(shù)字 工具躯枢。在未來版本的 JavaScript 中,大數(shù)字也許會(huì)得到官方支持槐臀。
測試整數(shù)
測試一個(gè)值是否是整數(shù)锄蹂,你可以使用 ES6 定義的 Number.isInteger(..)
:
Number.isInteger( 42 ); // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 ); // false
可以為前 ES6 填補(bǔ) Number.isInteger(..)
:
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}
要測試一個(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 瀏覽器填補(bǔ) Number.isSafeInteger(..)
:
if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger( num ) &&
Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
};
}
32位(有符號)整數(shù)
雖然整數(shù)可以安全地最大達(dá)到約九萬億(53比特)水慨,但有一些數(shù)字操作(比如位操作符)是僅僅為32位 number
定義的得糜,所以對于被這樣使用的 number
來說,“安全范圍”一定會(huì)小得多晰洒。
這個(gè)范圍是從 Math.pow(-2,31)
(-2147483648
朝抖,大約-21億)到 Math.pow(2,31)-1
(2147483647
,大約+21億)谍珊。
要強(qiáng)制 a
中的 number
值是32位有符號整數(shù)治宣,使用 a | 0
。這可以工作是因?yàn)?|
位操作符僅僅對32位值起作用(意味著它可以只關(guān)注32位,而其他的位將被丟掉)侮邀。而且坏怪,和 0 進(jìn)行“或”的位操作實(shí)質(zhì)上是什么也不做。
注意: 特定的特殊值(我們將在下一節(jié)討論)豌拙,比如 NaN
和 Infinity
不是“32位安全”的陕悬,當(dāng)這些值被傳入位操作符時(shí)將會(huì)通過一個(gè)抽象操作 ToInt32
(見第四章)并為了位操作而簡單地變成 +0
值。
特殊值
在各種類型中散布著一些特殊值按傅,需要 警惕 的 JS 開發(fā)者小心捉超,并正確使用。
不是值的值
對于 undefined
類型來說唯绍,有且僅有一個(gè)值:undefined
拼岳。對于 null
類型來說,有且僅有一個(gè)值:null
况芒。所以對它們而言惜纸,這些文字既是它們的類型也是它們的值。
undefined
和 null
作為“空”值或者“沒有”值绝骚,經(jīng)常被認(rèn)為是可以互換的耐版。另一些開發(fā)者偏好于使用微妙的區(qū)別將它們區(qū)分開。舉例來講:
-
null
是一個(gè)空值 -
undefined
是一個(gè)丟失的值
或者:
-
undefined
還沒有值 -
null
曾經(jīng)有過值但現(xiàn)在沒有
不管你選擇如何“定義”和使用這兩個(gè)值压汪,null
是一個(gè)特殊的關(guān)鍵字粪牲,不是一個(gè)標(biāo)識符,因此你不能將它作為一個(gè)變量對待來給它賦值(為什么你要給它賦值呢止剖?O傺簟)。然而穿香,undefined
(不幸地)是 一個(gè)標(biāo)識符亭引。噢。
Undefined
在非 strict
模式下皮获,給在全局上提供的 undefined
標(biāo)識符賦一個(gè)值實(shí)際上是可能的(雖然這是一個(gè)非常不好的做法1候尽):
function foo() {
undefined = 2; // 非常差勁兒的主意!
}
foo();
function foo() {
"use strict";
undefined = 2; // TypeError!
}
foo();
但是魔市,在非 strict
模式和 strict
模式下主届,你可以創(chuàng)建一個(gè)名叫 undefined
局部變量。但這又是一個(gè)很差勁兒的主意待德!
function foo() {
"use strict";
var undefined = 2;
console.log( undefined ); // 2
}
foo();
朋友永遠(yuǎn)不讓朋友覆蓋 undefined
君丁。
void
操作符
雖然 undefined
是一個(gè)持有內(nèi)建的值 undefined
的內(nèi)建標(biāo)識符(除非被修改 —— 見上面的討論!)将宪,另一個(gè)得到這個(gè)值的方法是 void
操作符绘闷。
表達(dá)式 void __
會(huì)“躲開”任何值橡庞,所以這個(gè)表達(dá)式的結(jié)果總是值 undefined
。它不會(huì)修改任何已經(jīng)存在的值印蔗;只是確保不會(huì)有值從操作符表達(dá)式中返回來扒最。
var a = 42;
console.log( void a, a ); // undefined 42
從慣例上講(大約是從 C 語言編程中發(fā)展而來),要通過使用 void
來獨(dú)立表現(xiàn)值 undefined
华嘹,你可以使用 void 0
(雖然吧趣,很明顯,void true
或者任何其他的 void
表達(dá)式都做同樣的事情)耙厚。在 void 0
强挫、void 1
和 undefined
之間沒有實(shí)際上的區(qū)別。
但是在幾種其他的環(huán)境下 void
操作符可以十分有用:如果你需要確保一個(gè)表達(dá)式?jīng)]有結(jié)果值(即便它有副作用)薛躬。
舉個(gè)例子:
function doSomething() {
// 注意:`APP.ready` 是由我們的應(yīng)用程序提供的
if (!APP.ready) {
// 稍后再試一次
return void setTimeout( doSomething, 100 );
}
var result;
// 做其他一些事情
return result;
}
// 我們能立即執(zhí)行嗎俯渤?
if (doSomething()) {
// 馬上處理其他任務(wù)
}
這里,setTimeout(..)
函數(shù)返回一個(gè)數(shù)字值(時(shí)間間隔定時(shí)器的唯一標(biāo)識符型宝,用于取消它自己)八匠,但是我們想 void
它,這樣我們函數(shù)的返回值不會(huì)在 if
語句上給出一個(gè)成立的誤報(bào)趴酣。
許多開發(fā)者寧愿將這些動(dòng)作分開梨树,這樣的效用相同但不使用 void
操作符:
if (!APP.ready) {
// 稍后再試一次
setTimeout( doSomething, 100 );
return;
}
一般來說,如果有那么一個(gè)地方岖寞,有一個(gè)值存在(來自某個(gè)表達(dá)式)而你發(fā)現(xiàn)這個(gè)值如果是 undefined
才有用劝萤,就使用 void
操作符。這可能在你的程序中不是非常常見慎璧,但如果在一些稀有的情況下你需要它,它就十分有用跨释。
特殊的數(shù)字
number
類型包含幾種特殊值胸私。我們將會(huì)仔細(xì)考察每一種。
不是數(shù)字的數(shù)字
如果你不使用同為 number
(或者可以被翻譯為十進(jìn)制或十六進(jìn)制的普通 number
的值)的兩個(gè)操作數(shù)進(jìn)行任何算數(shù)操作鳖谈,那么操作的結(jié)果將失敗而產(chǎn)生一個(gè)不合法的 number
岁疼,在這種情況下你將得到 NaN
值。
NaN
在字面上代表“不是一個(gè) number
(Not a Number)”缆娃,但是正如我們即將看到的捷绒,這種文字描述十分失敗而且容易誤導(dǎo)人。將 NaN
考慮為“不合法數(shù)字”贯要,“失敗的數(shù)字”暖侨,甚至是“壞掉的數(shù)字”都要比“不是一個(gè)數(shù)字”準(zhǔn)確得多。
舉例來說:
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
換句話說:“‘不是一個(gè)數(shù)字’的類型是‘?dāng)?shù)字’”崇渗!為這使人糊涂的名字和語義歡呼吧字逗。
NaN
是一種“哨兵值”(一個(gè)被賦予了特殊意義的普通的值)京郑,它代表 number
集合內(nèi)的一種特殊的錯(cuò)誤情況。這種錯(cuò)誤情況實(shí)質(zhì)上是:“我試著進(jìn)行數(shù)學(xué)操作但是失敗了葫掉,而這就是失敗的 number
結(jié)果些举。”
那么俭厚,如果你有一個(gè)值存在某個(gè)變量中户魏,而且你想要測試它是否是這個(gè)特殊的失敗數(shù)字 NaN
,你也許認(rèn)為你可以直接將它與 NaN
本身比較挪挤,就像你能對其它的值做的那樣叼丑,比如 null
和 undefined
。不是這樣电禀。
var a = 2 / "foo";
a == NaN; // false
a === NaN; // false
NaN
是一個(gè)非常特殊的值幢码,它從來不會(huì)等于另一個(gè) NaN
值(也就是,它從來不等于它自己)尖飞。實(shí)際上症副,它是唯一一個(gè)不具有反射性的值(沒有恒等性 x === x
)。所以政基,NaN !== NaN
贞铣。有點(diǎn)奇怪,對吧沮明?
那么辕坝,如果不能與 NaN
進(jìn)行比較(因?yàn)檫@種比較將總是失敗)荐健,我們該如何測試它呢酱畅?
var a = 2 / "foo";
isNaN( a ); // true
夠簡單的吧?我們使用稱為 isNaN(..)
的內(nèi)建全局工具江场,它告訴我們這個(gè)值是否是 NaN
纺酸。問題解決了!
別高興得太早址否。
isNaN(..)
工具有一個(gè)重大缺陷餐蔬。它似乎過于按照字面的意思(“不是一個(gè)數(shù)字”)去理解 NaN
的含義了 —— 它的工作基本上是:“測試這個(gè)傳進(jìn)來的東西是否不是一個(gè) number
或者是一個(gè) number
”。但這不是十分準(zhǔn)確佑附。
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; // "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true -- 噢!
很明顯樊诺,"foo"
根本 不是一個(gè) number
,但它也絕不是一個(gè) NaN
值音同!這個(gè) bug 從最開始的時(shí)候就存在于 JS 中了(存在超過了十九年的坑)词爬。
在 ES6 中,終于提供了一個(gè)替代它的工具:Number.isNaN(..)
权均。有一個(gè)簡單的填補(bǔ)缸夹,可以讓你即使是在前 ES6 的瀏覽器中安全地檢查 NaN
值:
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 -- 咻!
實(shí)際上痪寻,通過利用 NaN
與它自己不相等這個(gè)特殊的事實(shí),我們可以更簡單地實(shí)現(xiàn) Number.isNaN(..)
的填補(bǔ)虽惭。在整個(gè)語言中 NaN
是唯一一個(gè)這樣的值橡类;其他的值都總是 等于它自己。
所以:
if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}
怪吧芽唇?但是好用顾画!
不管有意還是無意,在許多真實(shí)世界的 JS 程序中 NaN
可能是一個(gè)現(xiàn)實(shí)的問題匆笤。使用 Number.isNaN(..)
(或者它的填補(bǔ))這樣的可靠測試來正確地識別它們是一個(gè)非常好的主意研侣。
如果你正在程序中僅使用 isNaN(..)
,悲慘的現(xiàn)實(shí)是你的程序 有 bug炮捧,即便是你還沒有被它咬到庶诡!
無窮
來自于像 C 這樣的傳統(tǒng)編譯型語言的開發(fā)者,可能習(xí)慣于看到編譯器錯(cuò)誤或者是運(yùn)行時(shí)異常咆课,比如對這樣一個(gè)操作給出的“除數(shù)為 0”:
var a = 1 / 0;
然而在 JS 中末誓,這個(gè)操作是明確定義的,而且它的結(jié)果是值 Infinity
(也就是 Number.POSITIVE_INFINITY
)书蚪。意料之中的是:
var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity
如你所見喇澡,-Infinity
(也就是 Number.NEGATIVE_INFINITY
)是從任一個(gè)被除數(shù)為負(fù)(不是兩個(gè)都是負(fù)數(shù)!)的除 0 操作得來的殊校。
JS 使用有限的數(shù)字表現(xiàn)形式(IEEE 754 浮點(diǎn)晴玖,我們早先討論過),所以和單純的數(shù)學(xué)相比为流,它看起來甚至在做加法和減法這樣的操作時(shí)都有可能溢出呕屎,這樣的情況下你將會(huì)得到 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
根據(jù)語言規(guī)范敬察,如果一個(gè)像加法這樣的操作得到一個(gè)太大而不能表示的值尼酿,IEEE 754 “就近舍入”模式將會(huì)指明結(jié)果應(yīng)該是什么故慈。所以粗略的意義上接校,Number.MAX_VALUE + Math.pow( 2, 969 )
比起 Infinity
更接近于 Number.MAX_VALUE
银室,所以它“向下舍入”备韧,而 Number.MAX_VALUE + Math.pow( 2, 970 )
距離 Infinity
更近致板,所以它“向上舍入”昆稿。
如果你對此考慮的太多辫封,它會(huì)使你頭疼的侠碧。所以別想了抹估。我是認(rèn)真的,停弄兜!
一旦你溢出了任意一個(gè) 無限值药蜻,那么瓷式,就沒有回頭路了。換句最有詩意的話說语泽,你可以從有限邁向無限贸典,但不能從無限回歸有限。
“無限除以無限等于什么”踱卵,這簡直是一個(gè)哲學(xué)問題廊驼。我們幼稚的大腦可能會(huì)說“1”或“無限”。事實(shí)表明它們都不對惋砂。在數(shù)學(xué)上和在 JavaScript 中妒挎,Infinity / Infinity
不是一個(gè)有定義的操作。在 JS 中西饵,它的結(jié)果為 NaN
酝掩。
一個(gè)有限的正 number
除以 Infinity
呢?簡單眷柔!0
期虾。那一個(gè)有限的負(fù) number
處理 Infinity
呢?接著往下讀闯割!
零
雖然這可能使有數(shù)學(xué)頭腦的讀者困惑彻消,但 JavaScript 擁有普通的零 0
(也稱為正零 +0
) 和 一個(gè)負(fù)零 -0
。在我們講解為什么 -0
存在之前宙拉,我們應(yīng)該考察 JS 如何處理它宾尚,因?yàn)樗赡苁至钊死Щ蟆?/p>
除了使用字面量 -0
指定,負(fù)的零還可以從特定的數(shù)學(xué)操作中得出谢澈。比如:
var a = 0 / -3; // -0
var b = 0 * -3; // -0
加法和減法無法得出負(fù)零煌贴。
在開發(fā)者控制臺(tái)中考察一個(gè)負(fù)的零,經(jīng)常顯示為 -0
锥忿,然而直到最近這才是一個(gè)常見情況牛郑,所以一些你可能遇到的老版本瀏覽器也許依然將它報(bào)告為 0
。
但是根據(jù)語言規(guī)范敬鬓,如果你試著將一個(gè)負(fù)零轉(zhuǎn)換為字符串淹朋,它將總會(huì)被報(bào)告為 "0"
。
var a = 0 / -3;
// 至少(有些瀏覽器)控制臺(tái)是對的
a; // -0
// 但是語言規(guī)范堅(jiān)持要向你撒謊钉答!
a.toString(); // "0"
a + ""; // "0"
String( a ); // "0"
// 奇怪的是础芍,就連 JSON 也加入了騙局之中
JSON.stringify( a ); // "0"
有趣的是,反向操作(從 string
到 number
)不會(huì)撒謊:
+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0
警告: 當(dāng)你觀察的時(shí)候数尿,JSON.stringify( -0 )
產(chǎn)生 "0"
顯得特別奇怪仑性,因?yàn)樗c反向操作不符:JSON.parse( "-0" )
將像你期望地那樣報(bào)告-0
。
除了一個(gè)負(fù)零的字符串化會(huì)欺騙性地隱藏它實(shí)際的值外右蹦,比較操作符也被設(shè)定為(有意地) 要說謊诊杆。
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
,你就不能僅依靠開發(fā)者控制臺(tái)的輸出晨汹,你必須更聰明一些:
function isNegZero(n) {
n = Number( n );
return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false
那么豹储,除了學(xué)院派的細(xì)節(jié)以外,我們?yōu)槭裁葱枰粋€(gè)負(fù)零呢宰缤?
在一些應(yīng)用程序中颂翼,開發(fā)者使用值的大小來表示一部分信息(比如動(dòng)畫中每一幀的速度),而這個(gè) number
的符號來表示另一部分信息(比如移動(dòng)的方向)慨灭。
在這些應(yīng)用程序中朦乏,舉例來說,如果一個(gè)變量的值變成了 0氧骤,而它丟失了符號呻疹,那么你就丟失了它是從哪個(gè)方向移動(dòng)到 0 的信息。保留零的符號避免了潛在的意外信息丟失筹陵。
特殊等價(jià)
正如我們上面看到的刽锤,當(dāng)使用等價(jià)性比較時(shí),值 NaN
和值 -0
擁有特殊的行為朦佩。NaN
永遠(yuǎn)不會(huì)和自己相等并思,所以你不得不使用 ES6 的 Number.isNaN(..)
(或者它的填補(bǔ))。相似地语稠,-0
撒謊并假裝它和普通的正零相等(即使使用 ===
嚴(yán)格等價(jià) —— 見第四章)宋彼,所以你不得不使用我們上面建議的某些 isNegZero(..)
黑科技工具。
在 ES6 中仙畦,有一個(gè)新工具可以用于測試兩個(gè)值的絕對等價(jià)性输涕,而沒有任何這些例外。它稱為 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
對于前 ES6 環(huán)境慨畸,這是一個(gè)相當(dāng)簡單的 Object.is(..)
填補(bǔ):
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(..)
可能不應(yīng)當(dāng)用于那些 ==
或 ===
已知 安全 的情況(見第四章“強(qiáng)制轉(zhuǎn)換”)莱坎,因?yàn)檫@些操作符可能高效得多,并且更慣用/常見寸士。Object.is(..)
很大程度上是為這些特殊的等價(jià)情況準(zhǔn)備的檐什。
值與引用
在其他許多語言中,根據(jù)你使用的語法弱卡,值可以通過值拷貝乃正,也可以通過引用拷貝來賦予/傳遞。
比如谐宙,在 C++ 中如果你想要把一個(gè) number
變量傳遞進(jìn)一個(gè)函數(shù),并使這個(gè)變量的值被更新界弧,你可以用 int& myNum
這樣的東西來聲明函數(shù)參數(shù)凡蜻,當(dāng)你傳入一個(gè)變量 x
時(shí)搭综,myNum
將是一個(gè) 指向 x
的引用;引用就像一個(gè)特殊形式的指針划栓,你得到的是一個(gè)指向另一個(gè)變量的指針(像一個(gè) 別名(alias)) 兑巾。如果你沒有聲明一個(gè)引用參數(shù),被傳入的值將 總是 被拷貝的忠荞,就算它是一個(gè)復(fù)雜的對象蒋歌。
在 JavaScript 中,沒有指針委煤,并且引用的工作方式有一點(diǎn)兒不同堂油。你不能擁有一個(gè)從一個(gè) JS 變量到另一個(gè) JS 變量的引用。這是完全不可能的碧绞。
JS 中的引用指向一個(gè)(共享的) 值府框,所以如果你有十個(gè)不同的引用,它們都總是同一個(gè)共享值的不同引用讥邻;它們沒有一個(gè)是另一個(gè)的引用/指針迫靖。
另外,在 JavaScript 中兴使,沒有語法上的提示可以控制值和引用的賦值/傳遞系宜。取而代之的是,值的 類型 用來 唯一 控制值是通過值拷貝发魄,還是引用拷貝來賦予盹牧。
讓我們來展示一下:
var a = 2;
var b = a; // `b` 總是 `a` 中的值的拷貝
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // `d` 是共享值 `[1,2,3]` 的引用
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
簡單值(也叫基本標(biāo)量) 總是 通過值拷貝來賦予/傳遞:null
、undefined
欠母、string
欢策、number
、 boolean
赏淌、以及 ES6 的 symbol
踩寇。
復(fù)合值 —— object
(包括 array
,和所有的對象包裝器 —— 見第三章)和 function
—— 總是 在賦值或傳遞時(shí)創(chuàng)建一個(gè)引用的拷貝六水。
在上面的代碼段中俺孙,因?yàn)?2
是一個(gè)基本標(biāo)量,a
持有一個(gè)這個(gè)值的初始拷貝掷贾,而 b
被賦予了這個(gè)值的另一個(gè)拷貝睛榄。當(dāng)改變 b
時(shí),你根本沒有在改變 a
中的值想帅。
但 c
和 d
兩個(gè)都 是同一個(gè)共享的值 [1,2,3]
的分離的引用场靴。重要的是,c
和 d
對值 [1,2,3]
的“擁有”程度上是一樣的 —— 它們只是同一個(gè)值的對等引用。所以旨剥,不管使用哪一個(gè)引用去修改(.push(4)
)實(shí)際上共享的 array
值本身咧欣,影響的僅僅是這一個(gè)共享值,而且這兩個(gè)引用將會(huì)指向新修改的值 [1,2,3,4]
轨帜。
因?yàn)橐弥赶虻氖侵当旧矶皇亲兞科枪荆悴荒苁褂靡粋€(gè)引用來改變另一個(gè)引用所指向的值:
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]
當(dāng)我們做賦值操作 b = [4,5,6]
時(shí),我們做的事情絕對不會(huì)對 a
所指向的 位置([1,2,3]
)造成任何影響蚌父。如果那可能的話哮兰,b
就會(huì)是 a
的指針而不是這個(gè) array
的引用 —— 但是這樣的能力在 JS 中是不存在的!
這樣的困惑最常見于函數(shù)參數(shù):
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]
當(dāng)我們傳入?yún)?shù) a
時(shí)苟弛,它將一份 a
引用的拷貝賦值給 x
喝滞。x
和 a
是指向相同的 [1,2,3]
的不同引用。現(xiàn)在嗡午,在函數(shù)內(nèi)部囤躁,我們可以使用這個(gè)引用來改變值本身(push(4)
)。但是當(dāng)我們進(jìn)行賦值操作 x = [4,5,6]
時(shí)荔睹,不可能影響原來的引用 a
所指向的東西 —— 它仍然指向(已經(jīng)被修改了的)值 [1,2,3,4]
狸演。
沒有辦法可以使用 x
引用來改變 a
指向哪里。我們只能修改 a
和 x
共通指向的那個(gè)共享值的內(nèi)容僻他。
要想改變 a
來使它擁有內(nèi)容為 [4,5,6,7]
的值宵距,你不能創(chuàng)建一個(gè)新的 array
并賦值 —— 你必須修改現(xiàn)存的 array
值:
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]
正如你看到的,x.length = 0
和 x.push(4,5,6,7)
沒有創(chuàng)建一個(gè)新的 array
吨拗,但是修改了現(xiàn)存的共享 array
满哪。所以理所當(dāng)然地,a
引用了新的內(nèi)容 [4,5,6,7]
劝篷。
記咨谘肌:你不能直接控制/覆蓋值拷貝和引用拷貝的行為 —— 這些語義是完全由當(dāng)前值的類型來控制的。
為了實(shí)質(zhì)上地通過值拷貝傳遞一個(gè)復(fù)合值(比如一個(gè) array
)娇妓,你需要手動(dòng)制造一個(gè)它的拷貝像鸡,使被傳遞的引用不指向原來的值。比如:
foo( a.slice() );
不帶參數(shù)的 slice(..)
方法默認(rèn)地為這個(gè) array
制造一個(gè)全新的(淺)拷貝哈恰。所以只估,我們傳入的引用僅指向拷貝的 array
,這樣 foo(..)
不會(huì)影響 a
的內(nèi)容着绷。
反之 —— 傳遞一個(gè)基本標(biāo)量值蛔钙,使它的值的變化可見,就像引用那樣 —— 你不得不將這個(gè)值包裝在另一個(gè)可以通過引用拷貝來傳遞的復(fù)合值中(object
荠医、array
等等):
function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42
這里吁脱,obj
作為基本標(biāo)量屬性 a
的包裝桑涎。當(dāng)傳遞給 foo(..)
時(shí),一個(gè) obj
引用的拷貝被傳入并設(shè)置給 wrapper
參數(shù)兼贡。我們現(xiàn)在可以使用 wrapper
引用來訪問這個(gè)共享的對象石洗,并更新它的值。在函數(shù)完成時(shí)紧显,obj.a
將被更新為值 42
。
你可能會(huì)遇到這樣的情況缕棵,如果你想要傳入一個(gè)像 2
這樣的基本標(biāo)量值的引用孵班,你可以將這個(gè)值包裝在它的 Number
對象包裝器中(見第三章)。
這個(gè) Number
對象的引用的拷貝 將 會(huì)被傳遞給函數(shù)是事實(shí)招驴,但不幸的是篙程,和你可能期望的不同,擁有一個(gè)共享獨(dú)享的引用不會(huì)給你修改這個(gè)共享的基本值的能力:
function foo(x) {
x = x + 1;
x; // 3
}
var a = 2;
var b = new Number( a ); // 或等價(jià)的 `Object(a)`
foo( b );
console.log( b ); // 2, 不是 3
這里的問題是别厘,底層的基本標(biāo)量值是 不可變的(String
和 Boolean
也一樣)虱饿。如果一個(gè) Number
對象持有一個(gè)基本標(biāo)量值 2
,那么這個(gè) Number
對象就永遠(yuǎn)不能再持有另一個(gè)值触趴;你只能用一個(gè)不同的值創(chuàng)建一個(gè)全新的 Number
對象氮发。
當(dāng) x
用于表達(dá)式 x + 1
時(shí),底層的基本標(biāo)量值 2
被自動(dòng)地從 Number
對象中開箱(抽出)冗懦,所以 x = x + 1
這一行很微妙地將 x
從一個(gè)共享的 Number
對象的引用爽冕,改變?yōu)閮H持有加法操作 2 + 1
的結(jié)果 3
的基本標(biāo)量值。因此披蕉,外面的 b
仍然引用原來的未被改變/不可變的颈畸,持有 2
的 Number
對象。
你 可以 在 Number
對象上添加屬性(只是不要改變它內(nèi)部的基本值)没讲,所以你可間接地通過這些額外的屬性交換信息眯娱。
不過,這可不太常見爬凑;對大多數(shù)開發(fā)者來說這可能不是一個(gè)好的做法徙缴。
與其這樣使用 Number
包裝器對象,使用早先的代碼段中那樣的手動(dòng)對象包裝器(obj
)要好得多贰谣。這不是說像 Number
這樣包裝好的對象包裝器沒有用處 —— 而是說在大多數(shù)情況下娜搂,你可能應(yīng)該優(yōu)先使用基本標(biāo)量值的形式。
引用十分強(qiáng)大吱抚,但是有時(shí)候它們礙你的事兒百宇,而有時(shí)你會(huì)在它們不存在時(shí)需要它們。你唯一可以用來控制引用與值拷貝的東西是值本身的類型秘豹,所以你必須通過你選用的值的類型來間接地影響賦值/傳遞行為携御。
復(fù)習(xí)
在 JavaScript 中,array
僅僅是數(shù)字索引的集合,可以容納任何類型的值啄刹。string
是某種“類 array
”涮坐,但它們有著不同的行為,如果你想要將它們作為 array
對待的話誓军,必須要小心袱讹。JavaScript 中的數(shù)字既包括“整數(shù)”也包括浮點(diǎn)數(shù)。
幾種特殊值被定義在基本類型內(nèi)部昵时。
null
類型只有一個(gè)值 null
捷雕,undefined
類型同樣地只有 undefined
值。對于任何沒有值存在的變量或?qū)傩裕?code>undefined 基本上是默認(rèn)值壹甥。void
操作符允許你從任意另一個(gè)值中創(chuàng)建 undefined
值救巷。
number
包含幾種特殊值,比如 NaN
(意為“不是一個(gè)數(shù)字”句柠,但稱為“非法數(shù)字”更合適)浦译;+Infinity
和 -Infinity
;還有 -0
溯职。
簡單基本標(biāo)量(string
精盅、number
等)通過值拷貝進(jìn)行賦值/傳遞,而復(fù)合值(object
等)通過引用拷貝進(jìn)行賦值/傳遞谜酒。引用與其他語言中的引用/指針不同 —— 它們從不指向其他的變量/引用渤弛,而僅指向底層的值。