第二章:值

特別說明,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS

arraystring挟鸠、和 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ì)算在 arraylength 中):

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)制 numberstring 值被用作鍵的話绞绒,它會(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 的作品:Esreverhttps://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)用 字符的 arrayjoin("") 方法。

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.042 一樣是“整數(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è) numberstring 表現(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.10.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.20.3痢虹,而且知道這個(gè)簡單的相等測試將會(huì)失敗呢?

可以接受的最常見的做法是使用一個(gè)很小的“錯(cuò)誤舍入”值作為比較的 容差主儡。這個(gè)很小的值經(jīng)常被稱為“機(jī)械極小值(machine epsilon)”奖唯,對于 JavaScript 來說這種 number 通常為 2^-522.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)-12147483647,大約+21億)谍珊。

要強(qiáng)制 a 中的 number 值是32位有符號整數(shù)治宣,使用 a | 0。這可以工作是因?yàn)?| 位操作符僅僅對32位值起作用(意味著它可以只關(guān)注32位,而其他的位將被丟掉)侮邀。而且坏怪,和 0 進(jìn)行“或”的位操作實(shí)質(zhì)上是什么也不做。

注意: 特定的特殊值(我們將在下一節(jié)討論)豌拙,比如 NaNInfinity 不是“32位安全”的陕悬,當(dāng)這些值被傳入位操作符時(shí)將會(huì)通過一個(gè)抽象操作 ToInt32(見第四章)并為了位操作而簡單地變成 +0 值。

特殊值

在各種類型中散布著一些特殊值按傅,需要 警惕 的 JS 開發(fā)者小心捉超,并正確使用。

不是值的值

對于 undefined 類型來說唯绍,有且僅有一個(gè)值:undefined拼岳。對于 null 類型來說,有且僅有一個(gè)值:null况芒。所以對它們而言惜纸,這些文字既是它們的類型也是它們的值。

undefinednull 作為“空”值或者“沒有”值绝骚,經(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 1undefined 之間沒有實(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 本身比較挪挤,就像你能對其它的值做的那樣叼丑,比如 nullundefined。不是這樣电禀。

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"

有趣的是,反向操作(從 stringnumber)不會(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ū)分 -00,你就不能僅依靠開發(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)量) 總是 通過值拷貝來賦予/傳遞:nullundefined欠母、string欢策、numberboolean赏淌、以及 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 中的值想帅。

cd 兩個(gè)都 是同一個(gè)共享的值 [1,2,3] 的分離的引用场靴。重要的是,cd 對值 [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喝滞。xa 是指向相同的 [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 指向哪里。我們只能修改 ax 共通指向的那個(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 = 0x.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)量值是 不可變的StringBoolean 也一樣)虱饿。如果一個(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 仍然引用原來的未被改變/不可變的颈畸,持有 2Number 對象。

可以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)行賦值/傳遞谜酒。引用與其他語言中的引用/指針不同 —— 它們從不指向其他的變量/引用渤弛,而僅指向底層的值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甚带,一起剝皮案震驚了整個(gè)濱河市她肯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鹰贵,老刑警劉巖晴氨,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碉输,居然都是意外死亡籽前,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門敷钾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枝哄,“玉大人,你說我怎么就攤上這事阻荒∧幼叮” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵侨赡,是天一觀的道長蓖租。 經(jīng)常有香客問我粱侣,道長,這世上最難降的妖魔是什么蓖宦? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任齐婴,我火速辦了婚禮,結(jié)果婚禮上稠茂,老公的妹妹穿的比我還像新娘柠偶。我一直安慰自己,他們只是感情好睬关,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布嚣州。 她就那樣靜靜地躺著,像睡著了一般共螺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上情竹,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天藐不,我揣著相機(jī)與錄音,去河邊找鬼秦效。 笑死雏蛮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阱州。 我是一名探鬼主播挑秉,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼苔货!你這毒婦竟也來了犀概?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤夜惭,失蹤者是張志新(化名)和其女友劉穎姻灶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诈茧,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡产喉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敢会。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曾沈。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸥昏,靈堂內(nèi)的尸體忽然破棺而出塞俱,到底是詐尸還是另有隱情,我是刑警寧澤吏垮,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布敛腌,位于F島的核電站卧土,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏像樊。R本人自食惡果不足惜尤莺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望生棍。 院中可真熱鬧颤霎,春花似錦、人聲如沸涂滴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柔纵。三九已至缔杉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搁料,已是汗流浹背或详。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留郭计,地道東北人霸琴。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像昭伸,于是被迫代替她去往敵國和親梧乘。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持庐杨,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券选调,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 1,322評論 0 15
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持灵份,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券学歧,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 2,501評論 1 17
  • 特別說明各吨,為便于查閱枝笨,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 464評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)揭蜒,斷路器横浑,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 話說我們在暑期見過一次面。說起來還是他為了我回成都屉更。但是他感覺每天都好忙徙融,我也有自己的事情。導(dǎo)致瑰谜,我們長達(dá)兩個(gè)月的...
    智賢醬閱讀 161評論 0 0