你不懂JS:類型與文法 第二章:值

官方中文版原文鏈接

感謝社區(qū)中各位的大力支持,譯者再次奉上一點點福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠谣殊,并抽取幸運大獎:點擊這里領(lǐng)取

arraystring牺弄,和number是任何程序的最基礎(chǔ)構(gòu)建塊姻几,但是JavaScript在這些類型上有一些要么使你驚喜要么使你驚訝的獨特性質(zhì)。

讓我們來看幾種JS內(nèi)建的值類型势告,并探討一下我們?nèi)绾尾拍芨尤娴乩斫獠⒄_地利用它們的行為蛇捌。

Array

和其他強(qiáng)制類型的語言相比,JavaScript的array只是值的容器咱台,而這些值可以是任何類型:string或者number或者object络拌,甚至是另一個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

警告: 在一個array值上使用delete將會從這個array上移除一個值槽春贸,但就算你移除了最后一個元素混萝,它也 不會 更新length屬性,所以多加小心祥诽!我們會在第五章討論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

雖然這可以工作,但你留下的“空值槽”可能會導(dǎo)致一些令人困惑的行為雄坪。雖然這樣的值槽看起來擁有undefined值厘熟,但是它不會像被明確設(shè)置(a[1] = undefined)的值槽那樣動作。更多信息可以參見第三章的“Array”维哈。

array是被數(shù)字索引的(正如你認(rèn)為的那樣)绳姨,但微妙的是它們也是對象,可以在它們上面添加string鍵/屬性(但是這些屬性不會計算在arraylength中):

var a = [ ];

a[0] = 1;
a["foobar"] = 2;

a.length;       // 1
a["foobar"];    // 2
a.foobar;       // 2

然而阔挠,一個需要小心的坑是飘庄,如果一個可以被強(qiáng)制轉(zhuǎn)換為10進(jìn)制numberstring值被用作鍵的話,它會認(rèn)為你想使用number索引而不是一個string鍵购撼!

var a = [ ];

a["13"] = 42;

a.length; // 14

一般來說跪削,向array添加string鍵/屬性不是一個好主意。最好使用object來持有鍵/屬性形式的值迂求,而將array專用于嚴(yán)格地數(shù)字索引的值碾盐。

類Array

偶爾你需要將一個類array值(一個數(shù)字索引的值的集合)轉(zhuǎn)換為一個真正的array,通常你可以對這些值的集合調(diào)用數(shù)組的工具函數(shù)(比如indexOf(..)揩局,concat(..)毫玖,forEach(..)等等)。

舉個例子凌盯,各種DOM查詢操作會返回一個DOM元素的列表付枫,對于我們轉(zhuǎn)換的目的來說,這些列表不是真正的array但是也足夠類似array驰怎。另一個常見的例子是阐滩,函數(shù)為了像列表一樣訪問它的參數(shù)值,而暴露了arugumens對象(類array县忌,在ES6中被廢棄了)叶眉。

一個進(jìn)行這種轉(zhuǎn)換的很常見的方法是對這個值借用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)值會使它具有復(fù)制這個array(或者,在這個例子中莲趣,是一個類array)的效果鸳慈。

在ES6中,還有一種稱為Array.from(..)的內(nèi)建工具可以執(zhí)行相同的任務(wù):

...
var arr = Array.from( arguments );
...

注意: Array.from(..)擁有幾種其他強(qiáng)大的能力喧伞,我們將在本系列的 ES6與未來 中涵蓋它的細(xì)節(jié)走芋。

String

一個很常見的想法是绩郎,string實質(zhì)上只是字符的array。雖然內(nèi)部的實現(xiàn)可能是也可能不是array翁逞,但重要的是要理解JavaScript的string與字符的array確實不一樣肋杖。它們的相似性幾乎只是表面上的。

舉個例子挖函,讓我們考慮這兩個值:

var a = "foo";
var b = ["f","o","o"];

String確實與array有很膚淺的相似性 -- 也就是上面說的状植,類array -- 舉例來說,它們都有一個length屬性怨喘,一個indexOf(..)方法(在ES5中僅有array版本)津畸,和一個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上沒有一個方法是可以原地修改它的內(nèi)容的芋浮,而是創(chuàng)建并返回一個新的string。與之相對的是壳快,許多改變array內(nèi)容的方法實際上 原地修改的纸巷。

c = a.toUpperCase();
a === c;    // false
a;          // "foo"
c;          // "FOO"

b.push( "!" );
b;          // ["f","O","o","!"]

另外,許多array方法在處理string時非常有用眶痰,雖然這些方法不屬于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."

讓我們來看另一個例子:翻轉(zhuǎn)一個string(順帶一提,這是一個JavaScript面試中常見的細(xì)小問題J)存哲。array擁有一個原地的reverse()修改器方法,但是string沒有:

a.reverse;      // undefined

b.reverse();    // ["!","o","O","f"]
b;              // ["!","o","O","f"]

不幸的是七婴,這種“借用”array修改器不起作用祟偷,因為string是不可變的,因此它不能被原地修改:

Array.prototype.reverse.call( a );
// 仍然返回一個“foo”的String對象包裝器(見第三章) :(

另一種迂回的做法(也是黑科技)是打厘,將string轉(zhuǎn)換為一個array修肠,實施我們想做的操作,然后將它轉(zhuǎn)回string户盯。

var c = a
    // 將`a`切分成一個字符的數(shù)組
    .split( "" )
    // 翻轉(zhuǎn)字符的數(shù)組
    .reverse()
    // 將字符的數(shù)組連接回一個字符串
    .join( "" );

c; // "oof"

如果你覺得這很難看嵌施,沒錯饲化。不管怎樣,對于簡單的string好用吗伤,所以如果你需要某些快速但是“臟”的東西吃靠,像這樣的方式經(jīng)常能滿足你。

警告: 小心足淆!這種方法對含有復(fù)雜(unicode)字符(星號巢块,多字節(jié)字符等)的string 不起作用。你需要支持unicode的更精巧的工具庫來準(zhǔn)確地處理這種操作缸浦。在這個問題上可以咨詢Mathias Bynens的作品:Esreverhttps://github.com/mathiasbynens/esrever)夕冲。

另外一種考慮這個問題的方式是:如果你更經(jīng)常地將你的“string”基本上作為 字符的數(shù)組 來執(zhí)行一些任務(wù)的話,也許就將它們作為array而不是作為string存儲更好裂逐。你可能會因此省去很多每次都將string轉(zhuǎn)換為array的麻煩歹鱼。無論何時你確實需要string的表現(xiàn)形式的話,你總是可以調(diào)用 字符的 arrayjoin("")方法卜高。

Number

JavaScript只有一種數(shù)字類型:number弥姻。這種類型包含“整數(shù)”值和小數(shù)值。我說“整數(shù)”時加了引號掺涛,因為JS的一個長久以來為人詬病的原因是庭敦,和其他語言不同,JS沒有真正的整數(shù)薪缆。這可能在未來某個時候會改變秧廉,但是目前,我們只有number可用拣帽。

所以疼电,在JS中,一個“整數(shù)”只是一個沒有小數(shù)部分的小數(shù)值减拭。也就是說蔽豺,42.042一樣是“整數(shù)”。

像大多數(shù)現(xiàn)代計算機(jī)語言拧粪,以及幾乎所有的腳本語言一樣修陡,JavaScript的number的實現(xiàn)基于“IEEE 754”標(biāo)準(zhǔn),通常被稱為“浮點”可霎。JavaScript明確地使用了這個標(biāo)準(zhǔn)的“雙精度”(也就是“64位二進(jìn)制”)格式魄鸦。

在網(wǎng)絡(luò)上有許多了不起的文章都在介紹二進(jìn)制浮點數(shù)如何在內(nèi)存中存儲的細(xì)節(jié),以及選擇這些做法的意義癣朗。因為對于理解如何在JS中正確使用number來說号杏,理解內(nèi)存中的位模式不是必須的,所以我們將這個話題作為練習(xí)留給那些想要進(jìn)一步挖掘IEEE 754的細(xì)節(jié)的讀者。

數(shù)字的語法

在JavaScript中字面數(shù)字一般用10進(jìn)制小數(shù)表達(dá)盾致。例如:

var a = 42;
var b = 42.3;

小數(shù)的整數(shù)部分如果是0,是可選的:

var a = 0.42;
var b = .42;

相似地荣暮,一個小數(shù)在.之后的小數(shù)部分如果是0庭惜,是可選的:

var a = 42.0;
var b = 42.;

警告: 42.是極不常見的,如果你正在努力避免別人閱讀你的代碼時感到困惑穗酥,它可能不是一個好主意护赊。但不管怎樣,它是合法的砾跃。

默認(rèn)情況下骏啰,大多數(shù)number將會以10進(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

因為number值可以用Number對象包裝器封裝(見第三章)翘骂,number值可以訪問內(nèi)建在Number.prototype上的方法(見第三章)壁熄。舉個例子,toFixed(..)方法允許你指定一個值在被表示時碳竟,帶有多少位小數(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"

要注意的是草丧,它的輸出實際上是一個numberstring表現(xiàn)形式,而且如果你指定的位數(shù)多于值持有的小數(shù)位數(shù)時莹桅,會在右側(cè)補0昌执。

toPrecision(..)很相似,但它指定的是有多少 有效數(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"

你不必非得使用持有這個值的變量來訪問這些方法诈泼;你可以直接在number的字面上訪問這些方法懂拾。但你不得不小心.操作符。因為.是一個合法數(shù)字字符厂汗,如果可能的話委粉,它會首先被翻譯為number字面的一部分,而不是被翻譯為屬性訪問操作符娶桦。

// 不合法的語法:
42.toFixed( 3 );    // SyntaxError

// 這些都是合法的:
(42).toFixed( 3 );  // "42.000"
0.42.toFixed( 3 );  // "0.420"
42..toFixed( 3 );   // "42.000"

42.toFixed(3)是不合法的語法贾节,因為.作為42.字面(這是合法的 -- 參見上面的討論!)的一部分被吞掉了衷畦,因此沒有.屬性操作符來表示.toFixed訪問栗涂。

42..toFixed(3)可以工作,因為第一個.number的一部分祈争,而第二個.是屬性操作符斤程。但它可能看起來很古怪,而且確實在實際的JavaScript代碼中很少會看到這樣的東西。實際上忿墅,在任何基本類型上直接訪問方法是十分不常見的扁藕。但是不常見并不意味著 或者

注意: 有一些庫擴(kuò)展了內(nèi)建的Number.prototype(見第三章)疚脐,使用number或在number上提供了額外的操作亿柑,所以在這些情況下,像使用10..makeItRain()來設(shè)定一個10秒鐘的下錢雨的動畫棍弄,或者其他諸如此類的傻事是完全合法的望薄。

在技術(shù)上講,這也是合法的(注意那個空格):

42 .toFixed(3); // "42.000"

但是呼畸,尤其是對number字面量來說痕支,這是特別使人糊涂的代碼風(fēng)格,而且除了使其他開發(fā)者(和未來的你)糊涂以外沒有任何用處蛮原。避免它卧须。

number還可以使用科學(xué)計數(shù)法的形式指定,這在表示很大的number時很常見瞬痘,比如:

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)停止使用它,來擁抱未來(而且因為你現(xiàn)在應(yīng)當(dāng)在使用strict模式了4亍)闸度。

至于ES6,下面的新形式也是合法的:

0o363;      // 八進(jìn)制的: 243
0O363;      // 同上

0b11110011; // 二進(jìn)制的: 243
0B11110011; // 同上

請為你的開發(fā)者同胞們做件好事:絕不要使用0O363形式蚜印。把0放在大寫的O旁邊就是在制造困惑莺禁。保持使用小寫的謂詞0x0b窄赋,和0o哟冬。

小數(shù)值

使用二進(jìn)制浮點數(shù)的最出名(臭名昭著)的副作用是(記住,這是對 所有 使用IEEE 754的語言都成立的——不是許多人認(rèn)為/假裝 在JavaScript中存在的問題):

0.1 + 0.2 === 0.3; // false

從數(shù)學(xué)的意義上忆绰,我們知道這個語句應(yīng)當(dāng)為true浩峡。為什么它是false

簡單地說错敢,0.10.2的二進(jìn)制表示形式是不精確的翰灾,所以它們相加時,結(jié)果不是精確地0.3。而是 非常 接近的值:0.30000000000000004纸淮,但是如果你的比較失敗了平斩,“接近”是無關(guān)緊要的。

注意: JavaScript應(yīng)當(dāng)切換到可以精確表達(dá)所有值的一個不同的number實現(xiàn)嗎萎馅?有些人認(rèn)為應(yīng)該双戳。多年以來有許多選項出現(xiàn)過。但是沒有一個被采納糜芳,而且也許永遠(yuǎn)也不會。它看起來就像揮揮手然后說“已經(jīng)改好那個bug了!”那么簡單魄衅,但根本不是那么回事兒峭竣。如果真有這么簡單,它絕對就在很久以前被改掉了晃虫。

現(xiàn)在的問題是皆撩,如果一些number不能被 信任 為精確的,這不是意味著我們根本不能使用number嗎哲银? 當(dāng)然不是扛吞。

在一些應(yīng)用程序中你需要多加小心,特別是在對付小數(shù)的時候荆责。還有許多(也許是大多數(shù)滥比?)應(yīng)用程序只處理整數(shù),而且做院,最大只處理到幾百萬到幾萬億盲泛。這些應(yīng)用程序使用JS中的數(shù)字操作是,而且將總是键耕,非常安全 的寺滚。

要是我們 確實 需要比較兩個number,就像0.1 + 0.20.3屈雄,而且知道這個簡單的相等測試將會失敗呢村视?

可以接受的最常見的做法是使用一個很小的“錯誤舍入”值作為比較的 容差。這個很小的值經(jīng)常被稱為“機(jī)械極小值(machine epsilon)”酒奶,對于JavaScript來說這種number通常為2^-522.220446049250313e-16)蚁孔。

在ES6中,使用這個容差值預(yù)定義了Number.EPSILON讥蟆,所以你將會想要使用它勒虾,你也可以在前ES6中安全地填補這個定義:

if (!Number.EPSILON) {
    Number.EPSILON = Math.pow(2,-52);
}

我們可以使用這個Number.EPSILON來比較兩個number的“等價性”(帶有錯誤舍入的容差):

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(它真的非常,非常瘸彤,非常大P奕弧),它為你預(yù)定義為Number.MAX_VALUE。在極小的一端愕宋,Number.MIN_VALUE大概是5e-324玻靡,它不是負(fù)數(shù)但是非常接近于0!

安全整數(shù)范圍

由于number的表示方式中贝,對完全是number的“整數(shù)”而言有一個“安全”的值的范圍囤捻,而且它要比Number.MAX_VALUE小得多。

可以“安全地”被表示的最大整數(shù)(也就是說邻寿,可以保證被表示的值是實際可以無誤地表示的)是2^53 - 1蝎土,也就是9007199254740991,如果你插入一些數(shù)字分隔符绣否,可以看到它剛好超過9萬億誊涯。所以對于number能表示的上限來說它確實是夠TM大的。

在ES6中這個值實際上是自動預(yù)定義的蒜撮,它是Number.MAX_SAFE_INTEGER暴构。意料之中的是,還有一個最小值段磨,-9007199254740991取逾,它在ES6中定義為Number.MIN_SAFE_INTEGER

JS程序面臨處理這樣大的數(shù)字的主要情況是苹支,處理數(shù)據(jù)庫中的64位ID等等砾隅。64位數(shù)字不能使用number類型準(zhǔn)確表達(dá),所以在JavaScript中必須使用string表現(xiàn)形式存儲(和傳遞)沐序。

謝天謝地琉用,在這樣的大IDnumber值上的數(shù)字操作(除了比較,它使用string也沒問題)并不很常見策幼。但是如果你 確實 需要在這些非常大的值上實施數(shù)學(xué)操作邑时,目前來講你需要使用一個 大數(shù)字 工具。在未來版本的JavaScript中特姐,大數(shù)字也許會得到官方支持晶丘。

測試整數(shù)

測試一個值是否是整數(shù),你可以使用ES6定義的Number.isInteger(..)

Number.isInteger( 42 );     // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 );   // false

可以為前ES6填補Number.isInteger(..)

if (!Number.isInteger) {
    Number.isInteger = function(num) {
        return typeof num == "number" && num % 1 == 0;
    };
}

要測試一個值是否是 安全整數(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瀏覽器填補Number.isSafeInteger(..)

if (!Number.isSafeInteger) {
    Number.isSafeInteger = function(num) {
        return Number.isInteger( num ) &&
            Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
    };
}

32位(有符號)整數(shù)

雖然整數(shù)可以安全地最大達(dá)到約9萬億(53比特)浅浮,但有一些數(shù)字操作(比如位操作符)是僅僅為32位number定義的,所以對于被這樣使用的number來說捷枯,“安全范圍”一定會小得多滚秩。

這個范圍是從Math.pow(-2,31)-2147483648,大約-21億)到Math.pow(2,31)-12147483647淮捆,大約+21億)郁油。

要強(qiáng)制a中的number值是32位有符號整數(shù)本股,使用a | 0。這可以工作是因為|位操作符僅僅對32位值起作用(意味著它可以只關(guān)注32位桐腌,而其他的位將被丟掉)拄显。而且,和0進(jìn)行“或”的位操作實質(zhì)上是什么也不做案站。

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

特殊值

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

不是值的值

對于undefined類型來說炒刁,有且僅有一個值:undefined。對于null類型來說誊稚,有且僅有一個值:null。所以對它們而言罗心,這些文字既是它們的類型也是它們的值里伯。

undefinednull作為“空”值或者“沒有”值,經(jīng)常被認(rèn)為是可以互換的渤闷。另一些開發(fā)者偏好于使用微妙的區(qū)別將它們區(qū)分開。舉例來講:

  • null是一個空值
  • undefined是一個丟失的值

或者:

  • undefined還沒有值
  • null曾經(jīng)有過值但現(xiàn)在沒有

不管你選擇如何“定義”和使用這兩個值,null是一個特殊的關(guān)鍵字易核,不是一個標(biāo)識符遏弱,因此你不能將它作為一個變量對待來給它賦值(為什么你要給它賦值呢?O阴濉)肩碟。然而,undefined(不幸地) 一個標(biāo)識符凸椿。噢削祈。

Undefined

在非strict模式下,給在全局上提供的undefined標(biāo)識符賦一個值實際上是可能的(雖然這是一個非常不好的做法D月):

function foo() {
    undefined = 2; // 非常差勁兒的主意髓抑!
}

foo();
function foo() {
    "use strict";
    undefined = 2; // TypeError!
}

foo();

但是,在非strict模式和strict模式下优幸,你可以創(chuàng)建一個名叫undefined局部變量吨拍。但這又是一個很差勁兒的主意!

function foo() {
    "use strict";
    var undefined = 2;
    console.log( undefined ); // 2
}

foo();

朋友永遠(yuǎn)不讓朋友覆蓋undefined网杆。

void操作符

雖然undefined是一個持有內(nèi)建的值undefined的內(nèi)建標(biāo)識符(除非被修改——見上面的討論8巍)伊滋,另一個得到這個值的方法是void操作符。

表達(dá)式void __會“躲開”任何值严里,所以這個表達(dá)式的結(jié)果總是值undefined新啼。它不會修改任何已經(jīng)存在的值;只是確保不會有值從操作符表達(dá)式中返回來刹碾。

var a = 42;

console.log( void a, a ); // undefined 42

從慣例上講(大約是從C語言編程中發(fā)展而來)燥撞,要通過使用void來獨立表現(xiàn)值undefined,你可以使用void 0(雖然迷帜,很明顯物舒,void true或者任何其他的void表達(dá)式都做同樣的事情)。在void 0戏锹,void 1undefined之間沒有實際上的區(qū)別冠胯。

但是在幾種其他的環(huán)境下void操作符可以十分有用:如果你需要確保一個表達(dá)式?jīng)]有結(jié)果值(即便它有副作用)。

舉個例子:

function doSomething() {
    // 注意:`APP.ready`是由我們的應(yīng)用程序提供的
    if (!APP.ready) {
        // 稍后再試一次
        return void setTimeout( doSomething, 100 );
    }

    var result;

    // 做其他一些事情
    return result;
}

// 我們能立即執(zhí)行嗎锦针?
if (doSomething()) {
    // 馬上處理其他任務(wù)
}

這里荠察,setTimeout(..)函數(shù)返回一個數(shù)字值(時間間隔定時器的唯一標(biāo)識符,用于取消它自己)奈搜,但是我們想void它悉盆,這樣我們函數(shù)的返回值不會在if語句上給出一個成立的誤報。

許多開發(fā)者寧愿將這些動作分開馋吗,這樣的效用相同但不使用void操作符:

if (!APP.ready) {
    // 稍后再試一次
    setTimeout( doSomething, 100 );
    return;
}

一般來說焕盟,如果有那么一個地方,有一個值存在(來自某個表達(dá)式)而你發(fā)現(xiàn)這個值如果是undefined才有用宏粤,就使用void操作符脚翘。這可能在你的程序中不是非常常見,但如果在一些稀有的情況下你需要它绍哎,它就十分有用来农。

特殊的數(shù)字

number類型包含幾種特殊值。我們將會仔細(xì)考察每一種蛇摸。

不是數(shù)字的數(shù)字

如果你不使用同為number(或者可以被翻譯為10進(jìn)制或16進(jìn)制的普通number的值)的兩個操作數(shù)進(jìn)行任何算數(shù)操作备图,那么操作的結(jié)果將失敗而產(chǎn)生一個不合法的number,在這種情況下你將得到NaN值赶袄。

NaN在字面上代表“不是一個number(Not a Number)”揽涮,但是正如我們即將看到的,這種文字描述十分失敗而且容易誤導(dǎo)人饿肺。將NaN考慮為“不合法數(shù)字”蒋困,“失敗的數(shù)字”,甚至是“壞掉的數(shù)字”都要比“不是一個數(shù)字”準(zhǔn)確得多敬辣。

舉例來說:

var a = 2 / "foo";      // NaN

typeof a === "number";  // true

換句話說:“‘不是一個數(shù)字’的類型是‘?dāng)?shù)字’”雪标!為這使人糊涂的名字和語義歡呼吧零院。

NaN是一種“哨兵值”(一個被賦予了特殊意義的普通的值),它代表number集合內(nèi)的一種特殊的錯誤情況村刨。這種錯誤情況實質(zhì)上是:“我試著進(jìn)行數(shù)學(xué)操作但是失敗了告抄,而這就是失敗的number結(jié)果∏段”

那么打洼,如果你有一個值存在某個變量中,而且你想要測試它是否是這個特殊的失敗數(shù)字NaN逆粹,你也許認(rèn)為你可以直接將它與NaN本身比較募疮,就像你能對其它的值做的那樣,比如nullundefined僻弹。不是這樣阿浓。

var a = 2 / "foo";

a == NaN;   // false
a === NaN;  // false

NaN是一個非常特殊的值,它從來不會等于另一個NaN值(也就是蹋绽,它從來不等于它自己)芭毙。實際上,它是唯一一個不具有反射性的值(沒有恒等性x === x)卸耘。所以稿蹲,NaN !== NaN。有點奇怪鹊奖,對吧?

那么涂炎,如果不能與NaN進(jìn)行比較(因為這種比較將總是失斨揖邸),我們該如何測試它呢唱捣?

var a = 2 / "foo";

isNaN( a ); // true

夠簡單的吧两蟀?我們使用稱為isNaN(..)的內(nèi)建全局工具,它告訴我們這個值是否是NaN震缭。問題解決了赂毯!

別高興得太早。

isNaN(..)工具有一個重大缺陷拣宰。它似乎過于按照字面的意思(“不是一個數(shù)字”)去理解NaN的含義了——它的工作基本上是:“測試這個傳進(jìn)來的東西是否不是一個number或者是一個number”党涕。但這不是十分準(zhǔn)確。

var a = 2 / "foo";
var b = "foo";

a; // NaN
b; // "foo"

window.isNaN( a ); // true
window.isNaN( b ); // true -- 噢!

很明顯巡社,"foo"根本 不是一個number膛堤,但它也絕不是一個NaN值!這個bug從最開始的時候就存在于JS中了(存在超過19年的坑)晌该。

在ES6中肥荔,終于提供了一個替代它的工具:Number.isNaN(..)绿渣。有一個簡單的填補,可以讓你即使是在前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 -- 咻!

實際上燕耿,通過利用NaN與它自己不相等這個特殊的事實中符,我們可以更簡單地實現(xiàn)Number.isNaN(..)的填補。在整個語言中NaN是唯一一個這樣的值誉帅;其他的值都總是 等于它自己淀散。

所以:

if (!Number.isNaN) {
    Number.isNaN = function(n) {
        return n !== n;
    };
}

怪吧?但是好用堵第!

不管有意還是無意吧凉,在許多真實世界的JS程序中NaN可能是一個現(xiàn)實的問題。使用Number.isNaN(..)(或者它的填補)這樣的可靠測試來正確地識別它們是一個非常好的主意踏志。

如果你正在程序中僅使用isNaN(..)阀捅,悲慘的現(xiàn)實是你的程序 有bug,即便是你還沒有被它咬到针余!

無窮

來自于像C這樣的傳統(tǒng)編譯型語言的開發(fā)者饲鄙,可能習(xí)慣于看到編譯器錯誤或者是運行時異常,比如對這樣一個操作給出的“除數(shù)為0”:

var a = 1 / 0;

然而在JS中圆雁,這個操作是明確定義的忍级,而且它的結(jié)果是值Infinity(也就是Number.POSITIVE_INFINITY)。意料之中的是:

var a = 1 / 0;  // Infinity
var b = -1 / 0; // -Infinity

如你所見伪朽,-Infinity(也就是Number.NEGATIVE_INFINITY)是從任一個被除數(shù)為負(fù)(不是兩個都是負(fù)數(shù)V嵩邸)的除0操作得來的。

JS使用有限的數(shù)字表現(xiàn)形式(IEEE 754 浮點烈涮,我們早先討論過)朴肺,所以和單純的數(shù)學(xué)相比,它看起來甚至在做加法和減法這樣的操作時都有可能溢出坚洽,這樣的情況下你將會得到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ī)范,如果一個像加法這樣的操作得到一個太大而不能表示的值讶舰,IEEE 754“就近舍入”模式將會指明結(jié)果應(yīng)該是什么鞍盗。所以粗略的意義上,Number.MAX_VALUE + Math.pow( 2, 969 )比起Infinity更接近于Number.MAX_VALUE跳昼,所以它“向下舍入”般甲,而Number.MAX_VALUE + Math.pow( 2, 970 )距離Infinity更近,所以它“向上舍入”鹅颊。

如果你對此考慮的太多欣除,它會使你頭疼的。所以別想了挪略。我是認(rèn)真的历帚,停滔岳!

一旦你溢出了任意一個 無限值,那么挽牢,就沒有回頭路了谱煤。換句最有詩意的話說,你可以從有限邁向無限禽拔,但不能從無限回歸有限刘离。

“無限除以無限等于什么”,這簡直是一個哲學(xué)問題睹栖。我們幼稚的大腦可能會說“1”或“無限”硫惕。事實表明它們都不對。在數(shù)學(xué)上和在JavaScript中野来,Infinity / Infinity不是一個有定義的操作恼除。在JS中,它的結(jié)果為NaN曼氛。

一個有限的正number除以Infinity呢豁辉?簡單!0舀患。那一個有限的負(fù)number處理Infinity呢徽级?接著往下讀!

雖然這可能使有數(shù)學(xué)頭腦的讀者困惑聊浅,JavaScript擁有普通的零0(也稱為正零+0 一個負(fù)零-0餐抢。在我們講解為什么-0存在之前,我們應(yīng)該考察JS如何處理它低匙,因為它可能十分令人困惑弹澎。

除了使用字面量-0指定,負(fù)的零還可以從特定的數(shù)學(xué)操作中得出努咐。比如:

var a = 0 / -3; // -0
var b = 0 * -3; // -0

加法和減法無法得出負(fù)零。

在開發(fā)者控制臺中考察一個負(fù)的零殴胧,經(jīng)常顯示為-0渗稍,然而直到最近這才是一個常見情況,所以一些你可能遇到的老版本瀏覽器也許依然將它報告為0团滥。

但是根據(jù)語言規(guī)范竿屹,如果你試著將一個負(fù)零轉(zhuǎn)換為字符串,它將總會被報告為"0"灸姊。

var a = 0 / -3;

// 至少(有些瀏覽器)控制臺是對的
a;                          // -0

// 但是語言規(guī)范堅持要向你撒謊拱燃!
a.toString();               // "0"
a + "";                     // "0"
String( a );                // "0"

// 奇怪的是,就連JSON也加入了騙局之中
JSON.stringify( a );        // "0"

有趣的是力惯,反向操作(從stringnumber)不會撒謊:

+"-0";              // -0
Number( "-0" );     // -0
JSON.parse( "-0" ); // -0

警告: 當(dāng)你觀察的時候碗誉,JSON.stringify( -0 )產(chǎn)生"0"顯得特別奇怪召嘶,因為它與反向操作不符:JSON.parse( "-0" )將像你期望地那樣報告-0

除了一個負(fù)零的字符串化會欺騙性地隱藏它實際的值外哮缺,比較操作符也被設(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ā)者控制臺的輸出铛只,你必須更聰明一些:

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)槭裁葱枰粋€負(fù)零呢淳玩?

在一些應(yīng)用程序中,開發(fā)者使用值的大小來表示一部分信息(比如動畫中每一幀的速度)非竿,而這個number的符號來表示另一部分信息(比如移動的方向)蜕着。

在這些應(yīng)用程序中,舉例來說汽馋,如果一個變量的值變成了0侮东,而它丟失了符號,那么你就丟失了它是從哪個方向移動到0的信息豹芯。保留零的符號避免了潛在的意外信息丟失悄雅。

特殊等價

正如我們上面看到的,當(dāng)使用等價性比較時铁蹈,值NaN和值-0擁有特殊的行為宽闲。NaN永遠(yuǎn)不會和自己相等,所以你不得不使用ES6的Number.isNaN(..)(或者它的填補)握牧。相似地容诬,-0撒謊并假裝它和普通的正零相等(即使使用===嚴(yán)格等價——見第四章),所以你不得不使用我們上面建議的某些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

對于前ES6環(huán)境习蓬,這是一個相當(dāng)簡單的Object.is(..)填補:

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)換”),因為這些操作符可能高效得多措嵌,并且更慣用/常見躲叼。Object.is(..)很大程度上是為這些特殊的等價情況準(zhǔn)備的。

值與引用

在其他許多語言中企巢,根據(jù)你使用的語法枫慷,值可以通過值拷貝,也可以通過引用拷貝來賦予/傳遞。

比如或听,在C++中如果你想要把一個number變量傳遞進(jìn)一個函數(shù)探孝,并使這個變量的值被更新,你可以用int& myNum這樣的東西來聲明函數(shù)參數(shù)神帅,當(dāng)你傳入一個變量x時再姑,myNum將是一個 指向x的引用;引用就像一個特殊形式的指針找御,你得到的是一個指向另一個變量的指針(像一個 別名(alias)) 元镀。如果你沒有聲明一個引用參數(shù),被傳入的值將 總是 被拷貝的霎桅,就算它是一個復(fù)雜的對象栖疑。

在JavaScript中,沒有指針滔驶,并且引用的工作方式有一點兒不同遇革。你不能擁有一個從一個JS變量到另一個JS變量的引用。這是完全不可能的揭糕。

JS中的引用指向一個(共享的) 萝快,所以如果你有10個不同的引用,它們都總是同一個共享值的不同引用著角;它們沒有一個是另一個的引用/指針揪漩。

另外,在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——總是 在賦值或傳遞時創(chuàng)建一個引用的拷貝痴荐。

在上面的代碼段中,因為2是一個基本標(biāo)量官册,a持有一個這個值的初始拷貝生兆,而b被賦予了這個值的另一個拷貝。當(dāng)改變b時,你根本沒有在改變a中的值鸦难。

cd兩個都 是同一個共享的值[1,2,3]的分離的引用根吁。重要的是,cd對值[1,2,3]的“擁有”程度上是一樣的——它們只是同一個值的對等引用合蔽。所以击敌,不管使用哪一個引用去修改(.push(4))實際上共享的array值本身,影響的僅僅是這一個共享值拴事,而且這兩個引用將會指向新修改的值[1,2,3,4]沃斤。

因為引用指向的是值本身而不是變量,你不能使用一個引用來改變另一個引用所指向的值:

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]時刃宵,我們做的事情絕對不會對a所指向的 位置[1,2,3])造成任何影響衡瓶。如果那可能的話,b就會是a的指針而不是這個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時哮针,它將一份a引用的拷貝賦值給xxa是指向相同的[1,2,3]的不同引用√古郏現(xiàn)在十厢,在函數(shù)內(nèi)部,我們可以使用這個引用來改變值本身(push(4))捂齐。但是當(dāng)我們進(jìn)行賦值操作x = [4,5,6]時蛮放,不可能影響原來的引用a所指向的東西——它仍然指向(已經(jīng)被修改了的)值[1,2,3,4]

沒有辦法可以使用x引用來改變a指向哪里辛燥。我們只能修改ax共通指向的那個共享值的內(nèi)容筛武。

要想改變a來使它擁有內(nèi)容為[4,5,6,7]的值,你不能創(chuàng)建一個新的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)建一個新的array徘六,但是修改了現(xiàn)存的共享array。所以理所當(dāng)然地榴都,a引用了新的內(nèi)容[4,5,6,7]待锈。

記住:你不能直接控制/覆蓋值拷貝和引用拷貝的行為 —— 這些語義是完全由當(dāng)前值的類型來控制的嘴高。

為了實質(zhì)上地通過值拷貝傳遞一個復(fù)合值(比如一個array)竿音,你需要手動制造一個它的拷貝,使被傳遞的引用不指向原來的值拴驮。比如:

foo( a.slice() );

不帶參數(shù)的slice(..)方法默認(rèn)地為這個array制造一個全新的(淺)拷貝春瞬。所以,我們傳入的引用僅指向拷貝的array套啤,這樣foo(..)不會影響a的內(nèi)容宽气。

反之 —— 傳遞一個基本標(biāo)量值,使它的值的變化可見,就像引用那樣 —— 你不得不將這個值包裝在另一個可以通過引用拷貝來傳遞的復(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(..)時枣察,一個obj引用的拷貝被傳入并設(shè)置給wrapper參數(shù)。我們現(xiàn)在可以使用wrapper引用來訪問這個共享的對象燃逻,并更新它的值序目。在函數(shù)完成時,obj.a將被更新為值42唆樊。

你可能會遇到這樣的情況宛琅,如果你想要傳入一個像2這樣的基本標(biāo)量值的引用,你可以將這個值包裝在它的Number對象包裝器中(見第三章)逗旁。

這個Number對象的引用的拷貝 會被傳遞給函數(shù)是事實嘿辟,但不幸的是,和你可能期望的不同片效,擁有一個共享獨享的引用不會給你修改這個共享的基本值的能力:

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)量值是 不可變的StringBoolean也一樣)。如果一個Number對象持有一個基本標(biāo)量值2淀衣,那么這個Number對象就永遠(yuǎn)不能再持有另一個值昙读;你只能用一個不同的值創(chuàng)建一個全新的Number對象。

當(dāng)x用于表達(dá)式x + 1時膨桥,底層的基本標(biāo)量值2被自動地從Number對象中開箱(抽出)蛮浑,所以x = x + 1這一行很微妙地將x從一個共享的Number對象的引用,改變?yōu)閮H持有加法操作2 + 1的結(jié)果3的基本標(biāo)量值只嚣。因此沮稚,外面的b仍然引用原來的未被改變/不可變的,持有2Number對象册舞。

可以Number對象上添加屬性(只是不要改變它內(nèi)部的基本值)蕴掏,所以你可間接地通過這些額外的屬性交換信息。

不過调鲸,這可不太常見盛杰;對大多數(shù)開發(fā)者來說這可能不是一個好的做法。

與其這樣使用Number包裝器對象藐石,使用早先的代碼段中那樣的手動對象包裝器(obj)要好得多即供。這不是說像Number這樣包裝好的對象包裝器沒有用處——而是說在大多數(shù)情況下,你可能應(yīng)該優(yōu)先使用基本標(biāo)量值的形式于微。

引用十分強(qiáng)大逗嫡,但是有時候它們礙你的事兒办素,而有時你會在它們不存在時需要它們。你唯一可以用來控制引用與值拷貝的東西是值本身的類型祸穷,所以你必須通過你選用的值的類型來間接地影響賦值/傳遞行為。

復(fù)習(xí)

在JavaScript中勺三,array僅僅是數(shù)字索引的集合雷滚,可以容納任何類型的值。string是某種“類array”吗坚,但它們有著不同的行為祈远,如果你想要將它們作為array對待的話,必須要小心商源。JavaScript中的數(shù)字既包括“整數(shù)”也包括浮點數(shù)车份。

幾種特殊值被定義在基本類型內(nèi)部牡彻。

null類型只有一個值nullundefined類型同樣地只有undefined值缎除。對于任何沒有值存在的變量或?qū)傩裕?code>undefined基本上是默認(rèn)值。void操作符允許你從任意另一個值中創(chuàng)建undefined值总寻。

number包含幾種特殊值渐行,比如NaN(意為“不是一個數(shù)字”轰坊,但稱為“非法數(shù)字”更合適);+Infinity-Infinity祟印; 還有-0旁理。

簡單基本標(biāo)量(stringnumber等)通過值拷貝進(jìn)行賦值/傳遞驻襟,而復(fù)合值(object等)通過引用拷貝進(jìn)行賦值/傳遞芋哭。引用與其他語言中的引用/指針不同 —— 它們從不指向其他的變量/引用减牺,而僅指向底層的值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末既荚,一起剝皮案震驚了整個濱河市恰聘,隨后出現(xiàn)的幾起案子吸占,更是在濱河造成了極大的恐慌矾屯,老刑警劉巖件蚕,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件排作,死亡現(xiàn)場離奇詭異,居然都是意外死亡纽绍,警方通過查閱死者的電腦和手機(jī)拌夏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門障簿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來站故,“玉大人西篓,你說我怎么就攤上這事岂津。” “怎么了橱乱?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵泳叠,是天一觀的道長危纫。 經(jīng)常有香客問我叶摄,道長蛤吓,這世上最難降的妖魔是什么会傲? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任淌山,我火速辦了婚禮泼疑,結(jié)果婚禮上退渗,老公的妹妹穿的比我還像新娘蕴纳。我一直安慰自己古毛,他們只是感情好稻薇,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布塞椎。 她就那樣靜靜地躺著忱屑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪急波。 梳的紋絲不亂的頭發(fā)上澄暮,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天泣懊,我揣著相機(jī)與錄音馍刮,去河邊找鬼卡啰。 笑死警没,一個胖子當(dāng)著我的面吹牛杀迹,可吹牛的內(nèi)容都是我干的树酪。 我是一名探鬼主播嗅回,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼绵载,長吁一口氣:“原來是場噩夢啊……” “哼娃豹!你這毒婦竟也來了懂版?” 一聲冷哼從身側(cè)響起躯畴,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夯到,沒想到半個月后耍贾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荐开,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡晃听,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年移层,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片予借。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖瀑粥,靈堂內(nèi)的尸體忽然破棺而出狞换,到底是詐尸還是另有隱情舟肉,我是刑警寧澤路媚,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布脏款,位于F島的核電站弛矛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏周循。R本人自食惡果不足惜湾笛,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望临扮。 院中可真熱鬧杆勇,春花似錦饱亿、人聲如沸彪笼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凡伊。三九已至系忙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洁墙,已是汗流浹背热监。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工孝扛, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留苦始,地道東北人陌选。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像役电,于是被迫代替她去往敵國和親宴霸。 傳聞我的和親對象是個殘疾皇子膏蚓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

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