你不知道的JavaScript(中卷)|異步

分塊的程序
可以把JavaScript程序?qū)懺趩蝹€.js文件中倔韭,但是這個程序幾乎一定是由多個塊構成的疲迂。這些快中只有一個是現(xiàn)在執(zhí)行,其余的則會在將來執(zhí)行。最常見的塊單位是函數(shù)早处。
考慮:

// ajax(..)是某個庫中提供的某個Ajax函數(shù)
var data = ajax( "http://some.url.1" );
console.log( data );
// 啊哦!data通常不會包含Ajax結(jié)果

你可能已經(jīng)了解转绷,標準Ajax請求不是同步完成的修然,這意味著ajax(..)函數(shù)還沒有返回任何值可以賦給變量data。如果ajax(..)能夠阻塞到響應返回袜刷,那么data=..賦值就會正確工作聪富。
但是我們并不是這么使用Ajax的。現(xiàn)在我們發(fā)出一個異步ajax請求著蟹,然后在將來才能得到返回的結(jié)果墩蔓。
從現(xiàn)在到將來的“等待”,最簡單的方法(但絕對不是唯一的萧豆,甚至也不是最好的<榕)是使用一個通常稱為回調(diào)函數(shù)的函數(shù):

// ajax(..)是某個庫中提供的某個Ajax函數(shù)
ajax( "http://some.url.1", function myCallbackFunction(data){
    console.log( data ); // 耶!這里得到了一些數(shù)據(jù)涮雷!
} );

可能你已經(jīng)聽說過阵面,可以發(fā)送同步Ajax請求。盡管技術上說是這樣,但是样刷,在任何情況下都不應該使用這種方式仑扑,因為它會鎖定瀏覽器UI(按鈕、菜單颂斜、滾動條等)夫壁,并阻塞所有的用戶交互。這是一個可怕的想法沃疮,一定要避免盒让。

// 現(xiàn)在:
function now() {
    return 21;
}
function later() { .. }
var answer = now();
setTimeout(later, 1000);
// 將來:
answer = answer * 2;
console.log("Meaning of life:", answer);

現(xiàn)在這一塊在程序運行之后就會立即執(zhí)行。但是司蔬,setTimeout(..)還設置了一個事件(定時)在將來執(zhí)行邑茄,所以函數(shù)later()的內(nèi)容會在之后的某個時間(從現(xiàn)在起1000毫秒之后)執(zhí)行。
任何時候俊啼,只要把一段代碼包裝成一個函數(shù)肺缕,并制定它在響應某個事件(定時器、鼠標點擊授帕、Ajax響應等)時執(zhí)行同木,你就是在代碼中創(chuàng)建了一個將來執(zhí)行的塊,也由此在這個程序中引入了異步機制跛十。

異步控制臺
并沒有什么規(guī)范或一組需求指定console.*方法族如何工作——它們并不是JavaScript正式的一部分彤路,而是由宿主環(huán)境添加到JavaScript中的。因此芥映,不同的瀏覽器和JavaScript環(huán)境可以按照自己的意愿來實現(xiàn)洲尊,有時候這會引起混淆。尤其要提出的是奈偏,在某些條件下坞嘀,某些瀏覽器的console.log(..)并不會把傳入的內(nèi)容立即輸出。出現(xiàn)這種情況的主要原因是惊来,在許多程序(不只是JavaScript)中丽涩,I/O是非常低速的阻塞部分。所以裁蚁,(從頁面/UI的角度來說)瀏覽器在后臺異步處理控制臺I/O能夠提高性能内狸,這時用戶甚至可能根本意識不到其發(fā)生。

var a = {
    index: 1
};
// 然后
console.log(a); // ??
// 再然后
a.index++;

我們通常認為恰好在執(zhí)行到console.log(..)語句的時候會看到a對象的快照厘擂,打印出類似于{index:1}這樣的內(nèi)容昆淡,然后在下一條語句a.index++執(zhí)行時將其修改,這句的執(zhí)行會嚴格在a的輸出之后刽严。
多數(shù)情況下昂灵,前述代碼在開發(fā)者工具的控制臺中輸出的對象表示與期望是一致的避凝。但是,這段代碼運行的時候眨补,瀏覽器可能會認為需要把控制臺I/O延遲到后臺管削,在這種情況下,等到瀏覽器控制臺輸出對象內(nèi)容時撑螺,a.index++可能已經(jīng)執(zhí)行含思,因此會顯示{index:2}。
到底什么時候控制臺I/O會延遲甘晤,甚至是否能夠被觀察到含潘,這都是游移不定的。如果在調(diào)試的過程中遇到對象在console.log(..)語句之后被修改线婚,可你卻看到了意料之外的結(jié)果遏弱,要意識到這可能是這種I/O的異步化造成的。

如果遇到這種少見的情況塞弊,最好的選擇是在JavaScript調(diào)試器中使用斷點漱逸,而不要依賴控制臺輸出。次優(yōu)的方案是把對象序列化到一個字符串中游沿,以強制執(zhí)行一次“快照”饰抒,比如通過JSON.stringify(..)。

并行線程
術語“異步”和“并行”常常被混為一談诀黍,但實際上它們的意義完全不同循集。記住,異步是關于現(xiàn)在和將來的時間間隙蔗草,而并行是關于能夠同時發(fā)生的事情。
并行計算最常見的工具就是進程和線程疆柔。進程和線程獨立運行咒精,并可能同時運行:在不同的處理器,甚至不同的計算機上旷档,但多個線程能夠共享單個進程的內(nèi)存模叙。
與之相對的是,事件循環(huán)把自身的工作分成一個個任務并順序執(zhí)行鞋屈,不允許對共享內(nèi)存的并行訪問和修改范咨。通過分立線程中彼此合作的事件循環(huán),并行和順序執(zhí)行可以共存厂庇。
多線程編程是非常復雜的渠啊。因為如果不通過特殊的步驟來防止這種中斷和交錯運行的話,可能會得到出乎意料的权旷、不確定的行為替蛉,通常這很讓人頭疼。
JavaScript從不跨線程共享數(shù)據(jù),這意味著不需要考慮這一層次的不確定性躲查。但是這并不意味著JavaScript總是確定性的它浅。

完整運行
由于JavaScript的單線程特性,foo()(以及bar())中的代碼具有原子性镣煮。也就是說姐霍,一旦foo()開始運行,它的所有代碼都會在bar()中的任意代碼運行之前完成典唇,或者相反镊折。這稱為完整運行特性。

var a = 1;
var b = 2;
function foo() {
    a++;
    b = b * a;
    a = b + 3;
}
function bar() {
    b--;
    a = 8 + b;
    b = a * 2;
}
// ajax(..)是某個庫中提供的某個Ajax函數(shù)
ajax("http://some.url.1", foo);
ajax("http://some.url.2", bar);

由于foo()不會被bar()中斷蚓聘,bar()也不會被foo()中斷腌乡,所以這個程序只有兩個可能的輸出,取決于這兩個函數(shù)哪個先運行——如果存在多線程夜牡,且foo()和bar()中的語句可以交替運行的話与纽,可能輸出的數(shù)目將會增加不少!
塊1是同步的(現(xiàn)在運行)塘装,而塊2和塊3是異步的(將來運行)急迂,也就是說,它們的運行在時間上是分隔的蹦肴。

//塊1:
var a = 1;
var b = 2;
//塊2(foo()):
a++;
b = b * a;
a = b + 3;
//塊3(bar()):
b--;
a = 8 + b;
b = a * 2;

塊2和塊3哪個先運行都有可能僚碎。
同一段代碼有兩個可能輸出意味著還是存在不確定性!但是阴幌,這種不確定性是在函數(shù)(事件)順序級別上勺阐,而不是多線程情況下的語句順序級別(或者說,表達式運算順序級別)矛双。換句話說渊抽,這一確定性要高于多線程情況。
在JavaScript的特性中议忽,這種函數(shù)順序的不確定性就是通常所說的競態(tài)條件懒闷,foo()和bar()相互競爭,看誰先運行栈幸。具體來說愤估,因為無法可靠預測a和b的最終結(jié)果,所以才是競態(tài)條件速址。

并發(fā)
現(xiàn)在讓我們來設想一個展示狀態(tài)更新列表(比如社交網(wǎng)絡新聞種子)的網(wǎng)站玩焰,其隨著用戶向下滾動列表而逐漸加載更多內(nèi)容。要正確地實現(xiàn)這一特性芍锚,需要(至少)兩個獨立的“進程”同時運行(也就是說震捣,是在同一段時間內(nèi)荔棉,并不需要在同一時刻)。
舉例來說蒿赢,假設這些事件的時間線是這樣的:

onscroll, 請求1
onscroll, 請求2 響應1
onscroll, 請求3 響應2
響應3
onscroll, 請求4
onscroll, 請求5
onscroll, 請求6 響應4
onscroll, 請求7
響應6
響應5
響應7

但是润樱,前面已經(jīng)介紹過,JavaScript一次只能處理一個事件羡棵,所以要么是onscroll壹若,請求2先發(fā)生,要么是響應1先發(fā)生皂冰,但是不會嚴格地同時發(fā)生店展。
下面列出了事件循環(huán)隊列中所有這些交替的事件:

onscroll, 請求1 <--- 進程1啟動
onscroll, 請求2
響應1 <--- 進程2啟動
onscroll, 請求3
響應2
響應3
onscroll, 請求4
onscroll, 請求5
onscroll, 請求6
響應4
onscroll, 請求7 <--- 進程1結(jié)束
響應6
響應5
響應7 <--- 進程2結(jié)束

“進程”1和“進程”2并發(fā)運行(任務級并行),但是它們的各個事件是在事件循環(huán)隊列中依次運行的秃流。
單線程事件循環(huán)是并發(fā)的一種形式赂蕴。

非交互
兩個或多個“進程”在同一個程序內(nèi)并發(fā)地交替運行它們的步驟/事件時,如果這些任務彼此不相關舶胀,就不一定需要交互概说。如果進程間沒有相互影響的話,不確定性是完全可以接受的嚣伐。

var res = {};
function foo(results) {
    res.foo = results;
}
function bar(results) {
    res.bar = results;
}
// ajax(..)是某個庫提供的某個Ajax函數(shù)
ajax("http://some.url.1", foo);
ajax("http://some.url.2", bar);

foo()和bar()是兩個并發(fā)執(zhí)行的“進程”糖赔,按照什么順序執(zhí)行是不確定的。但是轩端,我們構建程序的方式使得無論按哪種順序執(zhí)行都無所謂放典,因為它們是獨立運行的,不會相互影響基茵。
這并不是競態(tài)條件bug奋构,因為不管順序如何,代碼總會正常工作拱层。

交互
更常見的情況是弥臼,并發(fā)的“進程”需要相互交流,通過作用域或DOM間接交互舱呻。正如前面介紹的,如果出現(xiàn)這樣的交互悠汽,就需要對它們的交互進行協(xié)調(diào)以避免競態(tài)的出現(xiàn)箱吕。
下面是一個簡單的例子,兩個并發(fā)的“進程”通過隱含的順序相互影響柿冲,這個順序有時會被破壞:

var res = [];
function response(data) {
    res.push(data);
}
// ajax(..)是某個庫中提供的某個Ajax函數(shù)
ajax("http://some.url.1", response);
ajax("http://some.url.2", response);

這種不確定性很可能就是競態(tài)條件bug茬高。在這些情況下,你對可能做出的假定要持十分謹慎的態(tài)度假抄。比如怎栽,開發(fā)者可能會觀察到對"http://some.url.2"的響應速度總是顯著慢于對"http://some.url.1"的響應丽猬,這可能是由它們所執(zhí)行任務的性質(zhì)決定的(比如,一個執(zhí)行數(shù)據(jù)庫任務熏瞄,而另一個只是獲取靜態(tài)文件)脚祟,所以觀察到的順序總是符合預期。即使兩個請求都發(fā)送到同一個服務器强饮,也總會按照固定的順序響應由桌,但對于響應返回瀏覽器的順序,也沒有人可以真正保證邮丰。
所以行您,可以協(xié)調(diào)交互順序來處理這樣的競態(tài)條件:

var res = [];
function response(data) {
    if (data.url == "http://some.url.1") {
        res[0] = data;
    }
    else if (data.url == "http://some.url.2") {
        res[1] = data;
    }
}
// ajax(..)是某個庫中提供的某個Ajax函數(shù)
ajax("http://some.url.1", response);
ajax("http://some.url.2", response);

有些并發(fā)場景如果不做協(xié)調(diào),就總是(并非偶爾)會出錯:

var a, b;
function foo(x) {
    a = x * 2;
    baz();
}
function bar(y) {
    b = y * 2;
    baz();
}
function baz() {
    console.log(a + b);
}
// ajax(..)是某個庫中的某個Ajax函數(shù)
ajax("http://some.url.1", foo);
ajax("http://some.url.2", bar);

在這個例子中剪廉,無論foo()和bar()哪一個先被觸發(fā)娃循,總會使baz()過早運行(a或者b扔處于未定義狀態(tài));但對baz()的第二次調(diào)用就沒有問題斗蒋,因為這時候a和b都已經(jīng)可用了捌斧。
要解決這個問題有很多種方法。這里給出了一種簡單方法:

var a, b;
function foo(x) {
    a = x * 2;
    if (a && b) {
        baz();
    }
}
function bar(y) {
    b = y * 2;
    if (a && b) {
        baz();
    }
}
function baz() {
    console.log(a + b);
}
// ajax(..)是某個庫中的某個Ajax函數(shù)
ajax("http://some.url.1", foo);
ajax("http://some.url.2", bar);

另一種可能遇到的并發(fā)交互條件有時稱為競態(tài)吹泡,但是更精確的叫法是門閂骤星。它的特性可以描述為“只有第一名取勝”。在這里爆哑,不確定性是可以接受的洞难,因為它明確指出了這一點是可以接受的:需要“競爭”到終點,且只有唯一的勝利者揭朝。

var a;
function foo(x) {
    a = x * 2;
    baz();
}
function bar(x) {
    a = x / 2;
    baz();
}
function baz() {
    console.log(a);
}
// ajax(..)是某個庫中的某個Ajax函數(shù)
ajax("http://some.url.1", foo);
ajax("http://some.url.2", bar);

所以队贱,可以通過一個簡單的門閂協(xié)調(diào)這個交互過程,只讓第一個通過:

var a;
function foo(x) {
    if (!a) {
        a = x * 2;
        baz();
    }
}
function bar(x) {
    if (!a) {
        a = x / 2;
        baz();
    }
}
function baz() {
    console.log(a);
}
// ajax(..)是某個庫中的某個Ajax函數(shù)
ajax("http://some.url.1", foo);
ajax("http://some.url.2", bar);

條件判斷if(!a)使得只有foo()和bar()中的第一個可以通過潭袱,第二個(實際上是任何后續(xù)的)調(diào)用會被忽略柱嫌。也就是說,第二名沒有任何意義屯换!

協(xié)作
還有一種并發(fā)合作方式编丘,稱為并發(fā)協(xié)作。這里的重點不再是通過共享作用域中的值進行交互(盡管顯然這也是允許的M凇)嘉抓。這里的目標是取到一個長期運行的“進程”,并將其分割成多個步驟或多批任務晕窑,使得其他并發(fā)“進程”有機會將自己的運算插入到事件循環(huán)隊列中交替運行抑片。

var res = [];
// response(..)從Ajax調(diào)用中取得結(jié)果數(shù)組
function response(data) {
    // 添加到已有的res數(shù)組
    res = res.concat(
        // 創(chuàng)建一個新的變換數(shù)組把所有data值加倍
        data.map(function (val) {
            return val * 2;
        })
    );
}
// ajax(..)是某個庫中提供的某個Ajax函數(shù)
ajax("http://some.url.1", response);
ajax("http://some.url.2", response);

如果"http://some.url.1"首先取得結(jié)果,那么整個列表會立刻映射到res中杨赤。如果記錄有幾千條或更少敞斋,這不算什么截汪。但是如果有像1000萬條記錄的話,就可能需要運行相當一段時間了(在高性能筆記本上需要幾秒鐘植捎,在移動設備上需要更長時間衙解,等等)。
這樣的“進程”運行時鸥跟,頁面上的其他代碼都不能運行丢郊,包括不能有其他的response(..)調(diào)用或UI刷新,甚至是想滾動医咨、輸入枫匾、按鈕點擊這樣的用戶事件。這是非常痛苦的拟淮。
這里給出一個非常簡單的方法:

var res = [];
// response(..)從Ajax調(diào)用中取得結(jié)果數(shù)組
function response(data) {
    // 一次處理1000個
    var chunk = data.splice(0, 1000);
    // 添加到已有的res組
    res = res.concat(
        // 創(chuàng)建一個新的數(shù)組把chunk中所有值加倍
        chunk.map(function (val) {
            return val * 2;
        })
    );
    // 還有剩下的需要處理嗎干茉?
    if (data.length > 0) {
        // 異步調(diào)度下一次批處理
        setTimeout(function () {
            response(data);
        }, 0);
    }
}
// ajax(..)是某個庫中提供的某個Ajax函數(shù)
ajax("http://some.url.1", response);
ajax("http://some.url.2", response);

我們把數(shù)據(jù)集合放在最多包含1000條項目的塊中。這樣很泊,我們就確保了“進程”運行事件會很短角虫,即使這意味著需要更多的后續(xù)“進程”,因為事件循環(huán)隊列的交替運行會提高站點/App的響應(性能)委造。
這里使用setTimeout(..0)(hack)進行異步調(diào)度戳鹅,基本上它的意思就是“把這個函數(shù)插入到當前事件循環(huán)隊列的結(jié)尾處”。

嚴格說來昏兆,setTimeout(..0)并不直接把項目插入到事件循環(huán)隊列枫虏。定時器會在有機會的時候插入事件。舉例來說爬虱,兩個連續(xù)的setTimeout(..0)調(diào)用不能保證會嚴格按照調(diào)用順序處理隶债,所以各種情況都有可能出現(xiàn),比如定時器漂移跑筝,在這種情況下死讹,這些事件的順序就不可預測。在Node.js中曲梗,類似的方法是process.nextTick(..)赞警。盡管它們使用方便(通常性能也高),但并沒有(至少到目前為止)直接的方法可以適應所有環(huán)境來確保異步事件的順序虏两。

任務
在ES6中愧旦,有一個新的概念建立在事件循環(huán)隊列上,叫做任務隊列碘举。這個概念給大家?guī)淼淖畲笥绊懣赡苁荘romise的異步特性忘瓦。
我認為對于任務隊列最好的理解方式是搁廓,它是掛在事件循環(huán)隊列的每個tick之后的一個隊列引颈。在事件循環(huán)的每個tick中耕皮,可能出現(xiàn)的異步動作不會導致一個完整的新事件添加到事件循環(huán)隊列中,而會在當前tick的任務隊列末尾添加一個項目(一個任務)蝙场。
這就像是在說:“哦凌停,這里還有一件事將來要做,但要確保在其他任何事情發(fā)生之前就完成它售滤》D猓”
一個任務可能引起更多任務被添加到同一個隊列末尾。所以完箩,理論上說赐俗,任務循環(huán)可能無限循環(huán),進而導致程序的餓死弊知,無法轉(zhuǎn)移到下一個事件循環(huán)tick阻逮。從概念上看,這和代碼中的無限循環(huán)(就像while(true)..)的體驗幾乎是一樣的秩彤。
任務和setTimeout(..0) hack的思路類似叔扼,但是其實現(xiàn)方式的定義更加良好,對順序的保證性更強:盡可能早的將來漫雷。
設想一個調(diào)度任務(直接地瓜富,不要hack)的API,稱其為schedule(..):

console.log("A");
setTimeout(function () {
    console.log("B");
}, 0);
// 理論上的"任務API"
schedule(function () {
    console.log("C");
    schedule(function () {
        console.log("D");
    });
});

可能你認為這里會打印出A B C D降盹,但實際打印的結(jié)果是A C D B与柑。因為任務處理是在當前事件循環(huán)tick結(jié)尾處,且定時器觸發(fā)是為了調(diào)度一下個事件循環(huán)tick(如果可用的話E煜帧)仅胞。

語句順序
代碼中語句的順序和JavaScript引擎執(zhí)行語句的順序并不一定要一致。

var a, b;
a = 10;
b = 30;
a = a + 1;
b = b + 1;
console.log( a + b ); // 42

這段代碼中沒有顯式的異步(除了前面介紹過的很少見的異步I/O=1琛)干旧,所以很可能它的執(zhí)行過程是從上到下一行進行的。
但是妹蔽,JavaScript引擎在編譯這段代碼之后可能會發(fā)現(xiàn)通過(安全地)重新安排這些語句的順序有可能提高執(zhí)行速度椎眯。重點是,只要這個重新排序是不可見的胳岂,一切都沒問題编整。
比如,引擎可能會發(fā)現(xiàn)乳丰,其實這樣執(zhí)行會更快:

var a, b;
a = 10;
a++;
b = 30;
b++;
console.log( a + b ); // 42

或者這樣:

var a, b;
a = 11;
b = 31;
console.log( a + b ); // 42

或者甚至這樣:

// 因為a和b不會被再次使用
// 我們可以inline掌测,從而完全不需要它們!
console.log( 42 ); // 42

前面的所有情況中产园,JavaScript引擎在編譯期間執(zhí)行的都是安全的優(yōu)化汞斧,最后可見的結(jié)果都是一樣的夜郁。
但是這里有一種場景,其中特定的優(yōu)化是不安全的粘勒,因此也是不允許的(當然竞端,不用說這其實也根本不能稱為優(yōu)化):

var a, b;
a = 10;
b = 30;
// 我們需要a和b處于遞增之前的狀態(tài)!
console.log( a * b ); // 300
a = a + 1;
b = b + 1;
console.log( a + b ); // 42

還有其他一些例子庙睡,其中編譯器重新排雷會產(chǎn)生可見的副作用(因此必須禁止)事富,比如會產(chǎn)生副作用的函數(shù)調(diào)用(特別是getter函數(shù)),或ES6代理對象乘陪。

function foo() {
    console.log(b);
    return 1;
}
var a, b, c;
// ES5.1 getter字面量語法
c = {
    get bar() {
        console.log(a);
        return 1;
    }
};
a = 10;
b = 30;
a += foo(); // 30
b += c.bar; // 11
console.log(a + b); // 42

如果不是因為代碼片段中的語句console.log(..)(只是作為一種方便的形式說明可見的副作用)统台,JavaScript引擎如果愿意的話,本來可以自由地把代碼重新排序如下:

// ...
a = 10 + foo();
b = 30 + c.bar;
// ...

盡管JavaScript語義讓我們不會見到編譯器語句重排序可能導致的噩夢啡邑,這是一種幸運饺谬,但是代碼編寫的方式(從上到下的模式)和編譯后執(zhí)行的方式之間的聯(lián)系非常脆弱,理解這一點也非常重要谣拣。
編譯器語句重排序幾乎就是并發(fā)和交互的微型隱喻募寨。作為一個一般性的概念,清楚這一點能夠使你更好地理解異步JavaScript代碼流問題森缠。

小結(jié)
實際上拔鹰,JavaScript程序總是至少分為兩個快:第一塊現(xiàn)在運行;下一塊將來運行贵涵,以響應某個事件列肢。盡管程序是一塊一塊執(zhí)行的,但是所有這些塊共享對程序作用域和狀態(tài)的訪問宾茂,所以對狀態(tài)的修改都是在之前累計的修改之上進行的瓷马。
一旦有事件需要運行,事件循環(huán)就會運行跨晴,直到隊列清空欧聘。事件循環(huán)的每一輪稱為一個tick。用戶交互端盆、IO和定時器會向事件隊列中加入事件怀骤。
任意時刻,一次只能從隊列中處理一個事件焕妙。執(zhí)行事件的時候蒋伦,可能直接或間接地引發(fā)一個或多個后續(xù)事件。
并發(fā)是指兩個或多個事件鏈隨時間發(fā)展交替執(zhí)行焚鹊,以至于從更高的層次來看痕届,就像是同時在運行(盡管在任意時刻只處理一個事件)。
通常需要對這些并發(fā)執(zhí)行的“進程”(有別于操作系統(tǒng)中的進程概念)進行某種形式的交互協(xié)調(diào),比如需要確保執(zhí)行順序或者需要防止競態(tài)出現(xiàn)研叫。這些“進程”也可以通過把自身分割為更小的塊势决,以便其他“進程”插入進來。

11.jpg
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蓝撇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陈莽,更是在濱河造成了極大的恐慌渤昌,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件走搁,死亡現(xiàn)場離奇詭異独柑,居然都是意外死亡,警方通過查閱死者的電腦和手機私植,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門忌栅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曲稼,你說我怎么就攤上這事索绪。” “怎么了贫悄?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵瑞驱,是天一觀的道長。 經(jīng)常有香客問我窄坦,道長唤反,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任鸭津,我火速辦了婚禮彤侍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逆趋。我一直安慰自己盏阶,他們只是感情好,可當我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布闻书。 她就那樣靜靜地躺著般哼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惠窄。 梳的紋絲不亂的頭發(fā)上蒸眠,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機與錄音杆融,去河邊找鬼楞卡。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蒋腮。 我是一名探鬼主播淘捡,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼池摧!你這毒婦竟也來了焦除?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤作彤,失蹤者是張志新(化名)和其女友劉穎膘魄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竭讳,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡创葡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绢慢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灿渴。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胰舆,靈堂內(nèi)的尸體忽然破棺而出骚露,到底是詐尸還是另有隱情,我是刑警寧澤缚窿,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布荸百,位于F島的核電站,受9級特大地震影響滨攻,放射性物質(zhì)發(fā)生泄漏够话。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一光绕、第九天 我趴在偏房一處隱蔽的房頂上張望女嘲。 院中可真熱鬧,春花似錦诞帐、人聲如沸欣尼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愕鼓。三九已至,卻和暖如春慧起,著一層夾襖步出監(jiān)牢的瞬間菇晃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工蚓挤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留磺送,地道東北人驻子。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像估灿,于是被迫代替她去往敵國和親崇呵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,500評論 2 359

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