Javascript 異步編程(二)Event Loop

Event Loop

const test=()=>{
  console.log(1)
  setTimeout(()=>{
    console.log(2)
  },0)
  Promise.resolve().then(()=>{
    console.log(3)
  })
  console.log(4)
}
test();
//1
//4
//3
//2

可以看出:

  1. Promise和setTimeout都是是異步
  2. Promise優(yōu)先級(jí)高于setTimeout

為什么呢~~我們先來熟悉下基本概念

image_1.png

執(zhí)行上下文 (execution context)

當(dāng)一個(gè)函數(shù)被調(diào)用時(shí)个初,會(huì)創(chuàng)建一個(gè)活動(dòng)記錄(執(zhí)行上下文)猴蹂,這個(gè)記錄會(huì)包含函數(shù)在哪里被調(diào)用(調(diào)用棧)、函數(shù)的調(diào)用方式晕讲、傳入的參數(shù)等信息马澈。

以下三種情況會(huì)分別創(chuàng)建上下文

  • 全局執(zhí)行上下文:是為運(yùn)行代碼主體而創(chuàng)建的執(zhí)行上下文弄息,也就是說它是為那些存在于JavaScript 函數(shù)之外的任何代碼而創(chuàng)建的。
  • 函數(shù)執(zhí)行上下文:每個(gè)函數(shù)會(huì)在執(zhí)行的時(shí)候創(chuàng)建自己的執(zhí)行上下文涤伐。這個(gè)上下文就是通常說的 “本地上下文”缨称。
  • 使用 eval() 函數(shù)

執(zhí)行棧(call stack)

執(zhí)行棧(也稱為調(diào)用棧),是解釋器(比如瀏覽器中的 JavaScript 解釋器)追蹤函數(shù)執(zhí)行流的一種機(jī)制睦尽。當(dāng)執(zhí)行環(huán)境中調(diào)用了多個(gè)函數(shù)時(shí),通過這種機(jī)制山害,我們能夠追蹤到哪個(gè)函數(shù)正在執(zhí)行沿量,執(zhí)行的函數(shù)體中又調(diào)用了哪個(gè)函數(shù)。

  • 每調(diào)用一個(gè)函數(shù)朴则,解釋器就會(huì)把該函數(shù)添加進(jìn)調(diào)用棧并開始執(zhí)行。
  • 正在調(diào)用棧中執(zhí)行的函數(shù)還調(diào)用了其它函數(shù)汹想,那么新函數(shù)也將會(huì)被添加進(jìn)調(diào)用棧芥被,一旦這個(gè)函數(shù)被調(diào)用,便會(huì)立即執(zhí)行拴魄。
  • 當(dāng)前函數(shù)執(zhí)行完畢后,解釋器將其清出調(diào)用棧夏漱,繼續(xù)執(zhí)行當(dāng)前執(zhí)行環(huán)境下的剩余的代碼顶捷。
  • 當(dāng)分配的調(diào)用棧空間被占滿時(shí)服赎,會(huì)引發(fā)“堆棧溢出”錯(cuò)誤(如遞歸使用不當(dāng))RangeError:Maximum call stack size exceeded交播。

分析以下程序:

let a = 'Hello World!';
function first() {  
  console.log('Inside first function');  
  second();  
  console.log('Again inside first function');  
}
function second() {  
  console.log('Inside second function');  
}
first();  
console.log('Inside Global Execution Context');

當(dāng)上述代碼執(zhí)行時(shí)践付,Javascript引擎會(huì)創(chuàng)建 執(zhí)行上下文棧,每個(gè)代碼段開始執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)新的上下文來運(yùn)行它隧土,每個(gè)上下文創(chuàng)建時(shí)都會(huì)push到棧中命爬,在代碼退出的時(shí)候從上下文棧中pop,完成銷毀饲宛。

其大致流程:

  1. 程序開始運(yùn)行落萎,創(chuàng)建全局執(zhí)行上下文并壓入執(zhí)行棧中
  2. 當(dāng)執(zhí)行到first()時(shí)炭剪,會(huì)為該函數(shù)創(chuàng)建函數(shù)執(zhí)行上下文,并push到棧中
  3. 當(dāng)first()調(diào)用second(),創(chuàng)建一個(gè)新的函數(shù)執(zhí)行上下文,并push到棧中
  4. 當(dāng)second()執(zhí)行完畢奴拦,將其上下文從棧中彈出并銷毀,同時(shí)從棧中取棧頂?shù)纳舷挛牟⒒謴?fù)執(zhí)行绿鸣,也就是執(zhí)行程序剩余部分
  5. first()執(zhí)行完畢后暂氯,其上下文中棧中彈出并銷毀
  6. 程序結(jié)束,全局執(zhí)行上下文從執(zhí)行棧中彈出并銷毀

通過執(zhí)行棧機(jī)制痴施,每個(gè)程序和函數(shù)都有自己對(duì)應(yīng)的上下文,每一個(gè)上下文中都能夠跟蹤程序中的下一行需要執(zhí)行的代碼以及相應(yīng)的上下文信息动遭,方便我們調(diào)試程序神得,追蹤異常。

眾所周知哩簿,Javascript是單線程酝静,也就意味著一個(gè)call stack,同一時(shí)間只執(zhí)行一件事羡玛。
而我們?cè)谕綀?zhí)行一些耗時(shí)代碼片段時(shí),會(huì)阻塞(block)當(dāng)前的線程亿遂,在這個(gè)過程中渺杉,CPU是閑置的,為了充分利用資源是越,避免競(jìng)態(tài)條件出現(xiàn),這一類任務(wù)被設(shè)計(jì)成允許暫時(shí)掛起浦徊,等到有了結(jié)果再執(zhí)行的任務(wù)天梧,從而引入異步。

任務(wù)隊(duì)列(task queue)

在執(zhí)行棧中冕香,如果遇到異步函數(shù)后豫,如setTimeout() 會(huì)交給指定模塊處理,然后繼續(xù)執(zhí)行同步代碼挫酿。而當(dāng)異步函數(shù)達(dá)到觸發(fā)條件時(shí),會(huì)根據(jù)函數(shù)類型惫霸,壓入指定的任務(wù)隊(duì)列拄衰。
有兩類任務(wù)隊(duì)列:宏任務(wù)隊(duì)列(macro tasks)和微任務(wù)隊(duì)列(micro tasks)。宏任務(wù)隊(duì)列可以有多個(gè)翘悉,微任務(wù)隊(duì)列只有一個(gè)。

返回剛才的例子老赤,當(dāng)函數(shù)test()調(diào)用,執(zhí)行到setTimeout()Promise()時(shí)抬旺,會(huì)將其放入任務(wù)隊(duì)列,繼續(xù)執(zhí)行函數(shù)的剩余部分知道出棧銷毀其上下文汉柒。當(dāng)stack空時(shí)责鳍,從任務(wù)隊(duì)列中取任務(wù)來執(zhí)行,但是因?yàn)g覽器和Node的event loop機(jī)制不同正塌,所以分別對(duì)其分析恤溶。

image_6.png

瀏覽器環(huán)境中的Event Loop

基于HTML5標(biāo)準(zhǔn)中的Event Loop,其Event Loop 中的異步任務(wù)分到兩個(gè)隊(duì)列中咒程。

macrotask queue 即我們所說的任務(wù)隊(duì)列。一個(gè)事件循環(huán)中有一個(gè)或多個(gè)任務(wù)隊(duì)列孵坚,其實(shí)并不是 queue,而是set,因?yàn)槭录h(huán)處理模型的第一步從選定的隊(duì)列中獲取第一個(gè)可運(yùn)行任務(wù)忧饭,而不是使第一個(gè)任務(wù)出隊(duì)。

microtask queue 并不真正的任務(wù)隊(duì)列刺洒。一個(gè)事件循環(huán)中只有一個(gè)microtask queue

一個(gè)任務(wù)可以被放入到macrotask隊(duì)列吼砂,也可以放入microtask隊(duì)列

回到最開始的例子test(),其執(zhí)行過程:

  1. 程序開始渔肩,創(chuàng)建全局執(zhí)行上下文,壓入棧中干跛。
  2. 開始調(diào)用test(),創(chuàng)建其函數(shù)執(zhí)行上下文,壓入棧中
  3. 執(zhí)行到setTimeout(cb)時(shí)铐炫,創(chuàng)建其上下文胡嘿,并壓入棧中钳踊,因?yàn)椴皇峭胶瘮?shù),交由webapi進(jìn)行處理并出棧銷毀其上下文(在0s后逢享,壓入macrotask queue)吴藻,繼續(xù)執(zhí)行下一個(gè)代碼片段。
  4. 執(zhí)行到Promise(cb)時(shí)沟堡,創(chuàng)建其上下文,并壓入棧中禀横,因?yàn)椴皇峭胶瘮?shù)粥血,交由webapi進(jìn)行處理并出棧銷毀其上下文(并壓入microtask queue),繼續(xù)執(zhí)行下一個(gè)代碼片段复亏。
  5. 函數(shù)test()執(zhí)行完畢缔御,彈出其上下文,并銷毀
  6. 程序執(zhí)行完畢耕突,銷毀全局執(zhí)行上下文
  7. microtask queue中取出Promise任務(wù),執(zhí)行其回調(diào)函數(shù)cb
  8. 再從macrotask queue中取出setTimeout任務(wù)炕泳,執(zhí)行其回調(diào)函數(shù)cb

那么加入U(xiǎn)I Rendering呢上祈?

let test2=()=>{
  console.log('process start');
  setTimeout(()=>{
    console.log('processing setTimeout')
  },100)
  let dom=document.getElementById('app')
  dom.style.backgroundColor="red"
  Promise.resolve().then(()=>{
    console.log("processing promise")
  })
  console.log('process end')
}
test2();

作為腳本的一部分浙芙,程序必須執(zhí)行完成荤懂,瀏覽器才會(huì)執(zhí)行渲染。

Event Loop可以保證任務(wù)在下一次渲染前執(zhí)行完成晤锥。

我們可以使用requestAnimationFrame (RAF回調(diào)) 廊宪,會(huì)以16.6ms的頻率執(zhí)行

setTimeout實(shí)際會(huì)多出4ms左右的延時(shí)

那么完整的Event Loop 流程如下:

  1. 從宏任務(wù)隊(duì)列出列并執(zhí)行最前面的任務(wù)(比如“script”)。
  2. 調(diào)用棧為空箭启,檢查microtask queue
  3. 執(zhí)行microtask隊(duì)列,按照隊(duì)列 先進(jìn)先出 的原則放妈,執(zhí)行完所有microtask隊(duì)列任務(wù)荐操;
  4. 有需要執(zhí)行渲染(在一幀以內(nèi)的多次Dom變動(dòng)瀏覽器不會(huì)立即響應(yīng),而是會(huì)積攢變動(dòng)以最高60HZ的頻率更新視圖)宅倒。
  5. 執(zhí)行任務(wù)隊(duì)列屯耸,如果任務(wù)隊(duì)列不為空,取出任務(wù)隊(duì)列中第一個(gè)可運(yùn)行的宏任務(wù)疗绣,執(zhí)行完畢后,檢查microtask queue;而如果任務(wù)隊(duì)列為空灶搜,則直接檢查microtask queue

注意事項(xiàng):

  1. microtask 工窍,一直執(zhí)行前酿,直到隊(duì)列為空,但是如果過程中有新的任務(wù)加進(jìn)來淹仑,且添加的速度比執(zhí)行快,那么就會(huì)永遠(yuǎn)執(zhí)行微任務(wù)匀借,從而導(dǎo)致阻塞Event Loop。
  2. macrotask ,每次執(zhí)行一個(gè)任務(wù)凳怨,如果有新的任務(wù)是鬼,就添加到隊(duì)列尾部。
  3. animation cb均蜜,一直執(zhí)行囤耳,直到隊(duì)列中的所有任務(wù)完成,如果動(dòng)畫回調(diào)中又有動(dòng)畫回調(diào)充择,它們會(huì)在下一幀執(zhí)行
  4. new Promise 構(gòu)造函數(shù)內(nèi)部是同步執(zhí)行

總結(jié):

  1. 調(diào)用棧清空時(shí)會(huì)立刻先處理所有微任務(wù)隊(duì)列中的事件,然后再去宏任務(wù)隊(duì)列中取出一個(gè)事件化焕。同一次事件循環(huán)中铃剔,微任務(wù)永遠(yuǎn)在宏任務(wù)之前執(zhí)行。
  2. 本質(zhì)上來說 在一個(gè)事件循環(huán)中凤类,Microtask的執(zhí)行方式基本上就是用同步的
  3. 當(dāng)引擎處理任務(wù)時(shí)不會(huì)執(zhí)行渲染普气。如果執(zhí)行需要很長(zhǎng)一段時(shí)間也是如此。對(duì)于 DOM 的修改只有當(dāng)任務(wù)執(zhí)行完成才會(huì)被繪制

宏任務(wù)和微任務(wù)的區(qū)別

宏任務(wù):由事件回調(diào)现诀、程序啟動(dòng)或觸發(fā)間隔運(yùn)行的任何JavaScript代碼,包括解析HTML,生成DOM坐桩,執(zhí)行主線程JS代碼以及其他事件封锉,例如頁面加載膘螟,輸入碾局,網(wǎng)絡(luò)事件,計(jì)時(shí)器事件等内斯。從瀏覽器的角度來看蚯瞧,Macrotasks代表了一些離散且獨(dú)立的工作。
微任務(wù):只是一個(gè)簡(jiǎn)短的函數(shù)(因此得名)埋合,它在創(chuàng)建函數(shù)退出后執(zhí)行,是完成一些次要任務(wù)來更新應(yīng)用程序狀態(tài),例如處理Promise的回調(diào)和DOM修改蜜猾,以便可以在重新渲染瀏覽器之前執(zhí)行這些任務(wù)振诬。微任務(wù)應(yīng)盡快異步執(zhí)行,因此其成本低于Macrotask肩豁,它可使我們?cè)赨I呈現(xiàn)之前再次執(zhí)行辫呻,從而避免不必要的UI呈現(xiàn)。

問題

// 同步
[1,2,3,4].forEach((i)=>{
  console.log(i);
})
// 異步
function asyncForEach(arr,cb){
  arr.forEach((i)=>{
    setTimeout(()=>{
      cb(i)
    },0)
  })
}
asyncForEach(['a','b','c','d'],(i)=>{
  console.log(i)
})

microtask的執(zhí)行機(jī)制并不是穩(wěn)定的祟昭,實(shí)際上是因調(diào)用棧的情況而有所不同

btn.addEventListener('click',()=>{
  Promise.resolve().then(()=>console.log('microtask 1'))
  console.log('Listener 1')
})

btn.addEventListener('click',()=>{
  Promise.resolve().then(()=>console.log('microtask 2'))
  console.log('Listener 2');
})

// Listener 1 
// microtask 1
// Listener 2
// Listener 2 
  1. 點(diǎn)擊時(shí)怖侦,觸發(fā)第一個(gè)cb,將Promise訪入microtask
  2. 執(zhí)行console.log('Listener 1')
  3. 此時(shí)調(diào)用棧為空匾寝,執(zhí)行第一個(gè)microtask,即console.log('microtask 1')
  4. 同樣的踩萎,觸發(fā)第二個(gè)cb很钓。。企孩。袁稽。

但是如果是js 觸發(fā)的click()

btn.addEventListener('click',()=>{
  Promise.resolve().then(()=>console.log('microtask 1'))
  console.log('Listener 1')
})

btn.addEventListener('click',()=>{
  Promise.resolve().then(()=>console.log('microtask 2'))
  console.log('Listener 2');
})
btn.click(); 
//?
  1. 首先棧中執(zhí)行click()
  2. 事件調(diào)度,將執(zhí)行第一個(gè)cb,將Promise訪入microtask,執(zhí)行console.log('Listener 1')补疑,銷毀監(jiān)聽器
  3. 時(shí)間調(diào)度歹撒,執(zhí)行第二個(gè)cb,并將Promise訪入microtask,執(zhí)行console.log('Listener 2'),銷毀監(jiān)聽器
  4. 調(diào)用棧中彈出click上下文并銷毀
  5. 開始執(zhí)行microtask暖夭,首先是console.log('microtask 1'),接著是console.log('microtask 2')

現(xiàn)實(shí)場(chǎng)景中,如果我們?cè)趫?zhí)行自動(dòng)化測(cè)試時(shí)竭望,通過腳本控制執(zhí)行事件裕菠,就可能會(huì)導(dǎo)致結(jié)果的差異。

保證條件性使用 promises 時(shí)的順序

https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_DOM_API/Microtask_guide

Node.js的Event Loop

NodeJs是以非阻塞的I/O單線程旧烧,實(shí)現(xiàn)主要依賴于[libuv](http://docs.libuv.org/en/v1.x/design.html)

libuv,由C語言編寫的事件驅(qū)動(dòng)庫萤彩,是NodeJs異步編程的基礎(chǔ),屬于底層I/O引擎雀扶。主要負(fù)責(zé)Node API的執(zhí)行,將不同的任務(wù)分配給不同的線程予权,從而形成了Node Event Loop浪册,以異步的方式將執(zhí)行結(jié)果返回給V8引擎

image_3.png

NodeJs Event Loop 的運(yùn)行是這樣的:

  1. timers:執(zhí)行timer(setTimeout/setInterval)回調(diào)
  2. pending callbacks:執(zhí)行系統(tǒng)操作的回調(diào)--內(nèi)部
  3. idle, prepare:執(zhí)行空閑/準(zhǔn)備句柄回調(diào)-內(nèi)部使用
  4. poll:等待新I/O事件
  5. check:執(zhí)行setImmediate回調(diào)
  6. close callbacks :關(guān)閉回調(diào)--內(nèi)部執(zhí)行
  • 每個(gè)階段都會(huì)有一個(gè)callbacks的先進(jìn)先出的隊(duì)列執(zhí)行村象。
  • 當(dāng)event loop 運(yùn)行到一個(gè)指定階段時(shí)攒至,該階段的fifo隊(duì)列將被執(zhí)行躁劣,當(dāng)隊(duì)列執(zhí)行完或執(zhí)行的callbacks數(shù)量超過該階段的上線時(shí),轉(zhuǎn)入下一階段
   ┌───────────────────────────┐
┌─>│           timers         │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks     │
   └───────────────────────────┘

Node.js的Event Loop過程

  1. 執(zhí)行全局Script的同步任務(wù)
  2. 執(zhí)行microtask微任務(wù)志膀,先執(zhí)行所有Next Tick Queue(process.nextTick)中的所有任務(wù)鳖擒,再執(zhí)行 Other Microtask queue中的所有任務(wù)
  3. 開始執(zhí)行macrotask宏任務(wù),共6個(gè)階段戳稽,從第一個(gè)階段開始執(zhí)行對(duì)應(yīng)階段的macrotask queue中的所有任務(wù)
  4. Timer Queue->步驟2->I/O Queue ->步驟2->Check Queue->Close Callback Queue...

Poll階段細(xì)節(jié)

Poll階段的兩個(gè)主要的功能:

  1. 計(jì)算應(yīng)該被block多久
  2. 處理poll隊(duì)列的事件

主要流程:

  1. 檢測(cè)poll隊(duì)列如果為空/達(dá)到閾值圆裕,繼續(xù)第2步;否則執(zhí)行第3步
  2. 如果設(shè)置了setImmediate(),進(jìn)入check階段吓妆,否則等待回調(diào)添加到隊(duì)列行拢,立即執(zhí)行
  3. 同步執(zhí)行poll隊(duì)列中的回調(diào)
  4. 如果poll隊(duì)列為空,檢查是否有到達(dá)時(shí)間的timer舟奠,有則執(zhí)行timers回調(diào);否則繼續(xù)等在callback加入poll隊(duì)列

練習(xí)

Promise.resolve(123).then((res)=>console.log(res))
process.nextTick(()=>console.log(456))

解釋:

這里process.nextTick()是一個(gè)異步的node Api抬纸,但不屬于event loop的階段耿戚,調(diào)用時(shí)會(huì)中斷event loop優(yōu)先執(zhí)行process.nextTick的回調(diào)。

setTimeout(()=>console.log('setTimeout'),0)
setImmediate(()=>console.log('setImmediate'))

解釋:

setImmediate()用于中斷長(zhǎng)時(shí)間運(yùn)行的操作坛猪,并在完成其他操作后立即執(zhí)行其回調(diào)皂股。
setImmediate()setTimeout()執(zhí)行順序不固定,取決于node的準(zhǔn)備時(shí)間。
setTimeout()setInterval()的第二個(gè)參數(shù)的取值范圍是[1,2^32-1]悍募,如果超過這個(gè)范圍战转,初始化為1,即setTimeout(fn,0)===setTimeout(fn,1)槐秧。
我們知道setTimeout的回調(diào)函數(shù)在timer階段執(zhí)行忧设,setImmediate的回調(diào)函數(shù)在check階段執(zhí)行址晕,event loop 的開始會(huì)檢查timer階段,但是在開始之前timer階段會(huì)消耗一定的時(shí)間谨垃;

  1. timer前的準(zhǔn)備時(shí)間超過1ms,滿足loop->time>=1,則執(zhí)行timer階段(setTimeout)的回調(diào)函數(shù)
  2. timer前的準(zhǔn)備時(shí)間小于1ms胳赌,則先自行check階段(setImmediate)的回調(diào)函數(shù)匙隔,下一次event loop再次開始執(zhí)行timer階段(setTimeout)的回調(diào)函數(shù)

如果我們想確保先執(zhí)行setTimeout的回調(diào)

setTimeout(()=>console.log('setTimeout'),0)
setImmediate(()=>console.log('setImmediate'))
const start=new Date()
//睡眠10ms
while (Date.now()-start<10);

那如果我們想先執(zhí)行setImmediate的回調(diào)呢?從正常的event loop開始一定是先執(zhí)行timer的回調(diào)捍掺,如果我們可以在pending callbacks->idea/prepare->pool這個(gè)階段內(nèi)開始觸發(fā)setTimeout再膳,那么就可以先執(zhí)行setImmediate的回調(diào)了。

const fs=require('fs')
fs.readFile(__dirname,()=>{
  setTimeout(()=>console.log('setTimeout'),0);
  setImmediate(()=>console.log('setImmediate'));
})

上段代碼中喂柒,我們將event loop 的起始階段放在了 poll階段,等待i/o湃番,然后就會(huì)先執(zhí)行setImmediate的回調(diào)吭露。

瀏覽器與Node區(qū)別

  1. 瀏覽器端,一次執(zhí)行一個(gè)宏任務(wù)泥兰,兩個(gè)宏任務(wù)間隔內(nèi)執(zhí)行微任務(wù)隊(duì)列中的所有微任務(wù)
  2. Node端,一個(gè)階段執(zhí)行當(dāng)前宏任務(wù)隊(duì)列中的所有宏任務(wù)鞋诗,每個(gè)階段間隔輪詢微任務(wù)隊(duì)列中的微任務(wù)

NodeV11發(fā)生了變化

setTimeout(() => console.log('timeout1'));
setTimeout(() => {
    console.log('timeout2')
    Promise.resolve().then(() => console.log('promise resolve'))
});
setTimeout(() => console.log('timeout3'));
setTimeout(() => console.log('timeout4'));

請(qǐng)分別在瀏覽器、Node V10全庸,Node V11 運(yùn)行以上代碼

MacroTask and MicroTask execution order

待學(xué)習(xí)

從event loop規(guī)范探究javaScript異步及瀏覽器更新渲染時(shí)機(jī)
【轉(zhuǎn)向Javascript系列】深入理解Web Worker
Web Worker淺識(shí)
In depth: Microtasks and the JavaScript runtime environment

問題

image_4.png

Node.js v10.15.3版本
https://github.com/nodejs/node/issues/27747

References

tasks, microtasks, queues and schedules

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壶笼,一起剝皮案震驚了整個(gè)濱河市雁刷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沛励,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坤候,死亡現(xiàn)場(chǎng)離奇詭異址貌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)遍蟋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門螟凭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棒厘,你說我怎么就攤上這事下隧。” “怎么了淆院?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我支救,道長(zhǎng),這世上最難降的妖魔是什么指孤? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任贬堵,我火速辦了婚禮,結(jié)果婚禮上详恼,老公的妹妹穿的比我還像新娘引几。我一直安慰自己挽铁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布楣铁。 她就那樣靜靜地躺著更扁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浓镜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天听隐,我揣著相機(jī)與錄音哄啄,去河邊找鬼。 笑死咨跌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的禽车。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼猪腕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼钦勘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起彻采,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤肛响,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后特笋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虎囚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年蔫磨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒲列。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搀罢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出魄揉,到底是詐尸還是另有隱情,我是刑警寧澤瓣俯,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布兵怯,位于F島的核電站,受9級(jí)特大地震影響驼仪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绪爸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一奠货、第九天 我趴在偏房一處隱蔽的房頂上張望介褥。 院中可真熱鬧递惋,春花似錦、人聲如沸睛廊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邓馒。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間偿衰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工缤言, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留视事,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓跌穗,卻偏偏與公主長(zhǎng)得像虏辫,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砌庄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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