【譯】JavaScript進階 從實現(xiàn)理解閉包

來源于 現(xiàn)代JavaScript教程
閉包章節(jié)
中文翻譯計劃
本文很清晰地解釋了閉包是什么水援,以及閉包如何產(chǎn)生衡蚂,相信你看完也會有所收獲

關(guān)鍵字
Closure 閉包
Lexical Environment 詞法環(huán)境
Environment Record 環(huán)境記錄

閉包(Closure)

JavaScript 是一個 function-oriented 的語言。這帶來了很大的操作自由条霜。函數(shù)只需創(chuàng)建一次忿峻,可以拷貝到另一個變量昆箕,或者作為一個參數(shù)傳入另一個函數(shù)然后在一個全新的環(huán)境調(diào)用。

我們知道函數(shù)可以訪問它外部的變量妥色,這個 feature 十分常用搪花。

但是當(dāng)外部變量改變時會發(fā)生什么?函數(shù)時獲取最新的值嘹害,還是函數(shù)創(chuàng)建當(dāng)時的值撮竿?

還有一個問題,當(dāng)函數(shù)被送到其他地方再調(diào)用……他能訪問那個地方的外部變量嗎笔呀?

不同語言的表現(xiàn)有所不同幢踏,下面我們研究一下 JavaScript 中的表現(xiàn)。

兩個問題

我們先思考下面兩種情況许师,看完這篇文章你就可以回答這兩個問題房蝉,更復(fù)雜的問題也不在話下。

  1. sayHi 函數(shù)使用了外部變量 name微渠。函數(shù)運行時搭幻,會使用兩個值中的哪個?

    let name = "John";
    
    function sayHi() {
      alert("Hi, " + name);
    }
    
    name = "Pete";
    
    sayHi(); // "John" 還是 "Pete"逞盆?
    

    這個情況不論是瀏覽器端還是服務(wù)器端都很常見檀蹋。函數(shù)很可能在它創(chuàng)建一段時間后才執(zhí)行,例如等待用戶操作或者網(wǎng)絡(luò)請求云芦。

    問題是:函數(shù)是否會選擇變量最新的值呢俯逾?

  1. makeWorker 函數(shù)創(chuàng)造并返回了另一個函數(shù)。這個新函數(shù)可以在任何地方調(diào)用焕数。他會訪問創(chuàng)建時的變量還是調(diào)用時的變量呢纱昧?

    function makeWorker() {
      let name = "Pete";
    
      return function() {
        alert(name);
      };
    }
    
    let name = "John";
    
    // 創(chuàng)建函數(shù)
    let work = makeWorker();
    
    // 調(diào)用函數(shù)
    work(); // "Pete" (創(chuàng)建時) 還是 "John" (調(diào)用時)?
    

Lexical Environment (詞法環(huán)境)

要理解里面發(fā)生了什么堡赔,必須先明白“變量”到底是什么识脆。

在 JavaScript 里,任何運行的函數(shù)善已、代碼塊灼捂、整個 script 都會關(guān)聯(lián)一個被叫做 Lexical Environment (詞法環(huán)境) 的對象。

Lexical Environment 對象包含兩個部分:(譯者:這里是重點)

  1. Environment Record (環(huán)境記錄)是一個擁有全部局部變量作為屬性的對象(以及其他如 this 值的信息)换团。
  2. outer lexical environment (外部詞法環(huán)境)的引用悉稠,通常詞法關(guān)聯(lián)外面一層代碼(花括號外一層)。

所以艘包,“變量”就是內(nèi)部對象 Environment Record 的一個屬性的猛。要改變一個對象耀盗,意味著改變 Lexical Environment 的屬性。

例如在這段簡單的代碼中卦尊,只有一個 Lexical Environment:

lexical environment

這就是所謂 global Lexical Environment (全局語法環(huán)境)叛拷,對應(yīng)整個 script。對于瀏覽端岂却,整個 <script> 標(biāo)簽共享一個全局環(huán)境忿薇。

(譯者:這里是重點)
上圖中,正方形代表 Environment Record (變量儲存)躏哩,箭頭代表 outer reference (外部引用)署浩。global Lexical Environment 沒有外部引用,所以指向 null扫尺。

下圖展示 let 變量的工作機制:

lexical environment

右邊的正方形描述 global Lexical Environment 在執(zhí)行中如何改變:

  1. 腳本開始運行筋栋,Lexical Environment 空。
  2. let phrase 定義出現(xiàn)了器联。因為沒有賦值所以儲存為 undefined 二汛。
  3. phrase 被賦值。
  4. phrase 被賦新值拨拓。

看起來很簡單對不對肴颊?

總結(jié):

  • 變量是一個特殊內(nèi)部對象的屬性,關(guān)聯(lián)于執(zhí)行時的塊渣磷、函數(shù)婿着、 script 。
  • 對變量的操作實際上是對這個對象屬性的操作醋界。

Function Declaration (函數(shù)聲明)

Function Declaration 并非處理于被執(zhí)行的時候竟宋,而是 Lexical Environment 創(chuàng)建的時候。對于 global Lexical Environment 形纺,這意味著 script 開始運行的時候丘侠。

這就是函數(shù)可以在定義前調(diào)用的原因。

以下代碼 Lexical Environment 開始時非空逐样。因為有 say 函數(shù)聲明蜗字,之后又有了 let 聲明的 phrase

lexical environment

Inner and outer Lexical Environment (內(nèi)部詞法環(huán)境和外部詞法環(huán)境)

調(diào)用 say() 的過程中,它使用了外部變量,一起看看這里面發(fā)生了什么。

(譯者:這里是重點)
函數(shù)運行時會自動創(chuàng)建一個新的函數(shù) Lexical Environment 割粮。這是所有函數(shù)的通用規(guī)則。這個新的 Lexical Environment 用于當(dāng)前運行函數(shù)的存放局部變量和形參级零。

箭頭標(biāo)記的是執(zhí)行 say("John") 時的 Lexical Environment :

lexical environment

函數(shù)調(diào)用過程中,可以看到兩個 Lexical Environment :里面的是函數(shù)調(diào)用產(chǎn)生的滞乙,外面的是全局的:

  • 內(nèi)層 Lexical Environment 對應(yīng)當(dāng)前執(zhí)行的 say 奏纪。它只有一個變量: 函數(shù)實參 name 鉴嗤。我們調(diào)用 say("John") ,所以 name 的值是 "John" 序调。
  • 外層 Lexical Environment 是 global Lexical Environment 躬窜。

內(nèi)層 Lexical Environment 有一個 outer 屬性,指向外層 Lexical Environment炕置。

代碼要訪問一個變量,首先搜索內(nèi)層 Lexical Environment 男韧,接著是外層朴摊,再外層,直到鏈的結(jié)束此虑。

如果走完整條鏈變量都找不到甚纲,在 strict mode 就會報錯了。不使用 use strict 的情況下朦前,對未定義變量的賦值介杆,會創(chuàng)造一個新的全局變量。

下面一起看看變量搜索如何處理:

  • say 里的 alert 想要訪問 name 韭寸,立即就能在當(dāng)前函數(shù)的 Lexical Environment 找到春哨。
  • 對于 phrase ,局部變量不存在 phrase 恩伺,所以要循著 outer 在全局變量里找到赴背。
lexical environment lookup

現(xiàn)在我們可以回答本章開頭的第一個問題了。

函數(shù)獲取外部變量當(dāng)前值

舊變量值不儲存在任何地方晶渠,函數(shù)需要他們的時候凰荚,它取得來源于自身或外部 Lexical Environment 的當(dāng)前值。

所以第一個問題的答案是 Pete

let name = "John";

function sayHi() {
 alert("Hi, " + name);
}

name = "Pete"; // (*)

sayHi(); // Pete

上述代碼的執(zhí)行流:

  1. global Lexical Environment 存在 name: "John" 褒脯。
  2. (*) 行中便瑟,全局變量修改了,現(xiàn)在成了這樣 name: "Pete" 番川。
  3. say() 執(zhí)行的時候到涂, 取外部 name 。此時在 global Lexical Environment 中已經(jīng)是 "Pete"爽彤。

一次調(diào)用养盗,一個 Lexical Environment
請注意,每當(dāng)一個函數(shù)運行适篙,就會創(chuàng)建一個新的 function Lexical Environment往核。
如果一個函數(shù)被多次調(diào)用,那么每次調(diào)用都會生成一個屬于當(dāng)前調(diào)用的全新 Lexical Environment 嚷节,里面裝載著當(dāng)前調(diào)用的變量和實參聂儒。

Lexical Environment 是一個標(biāo)準(zhǔn)對象 (specification object)
"Lexical Environment" 是一個標(biāo)準(zhǔn)對象 (specification object)虎锚。我們不能直接獲取或設(shè)置它,JavaScript 引擎也可能優(yōu)化它衩婚,拋棄未使用的變量來節(jié)省內(nèi)存或者作其他優(yōu)化窜护,但是可見行為應(yīng)該如上面所述。

嵌套函數(shù)

在一個函數(shù)中創(chuàng)建另一個函數(shù)非春,稱為“嵌套”柱徙。這在 JavaScript 很容易做到:

function sayHiBye(firstName, lastName) {

 // helper nested function to use below
 function getFullName() {
   return firstName + " " + lastName;
 }

 alert( "Hello, " + getFullName() );
 alert( "Bye, " + getFullName() );

}

嵌套函數(shù) getFullName() 可以訪問外部變量,幫助我們很方便地返回 FullName 奇昙。

更有趣的是护侮,嵌套函數(shù)可以被 return ,作為一個新對象的屬性或者作為自己的結(jié)果储耐。這樣它們就能在其他地方使用羊初,無論在哪里,它都能訪問同樣的外部變量什湘。

一個構(gòu)造函數(shù)(詳見 info:constructor-new)的例子:

// 構(gòu)造函數(shù)返回一個新對象
function User(name) {

 // 嵌套函數(shù)創(chuàng)造對象方法
 this.sayHi = function() {
   alert(name);
 };
}

let user = new User("John");
user.sayHi(); // 方法返回外部 "name"

一個 return 函數(shù)的例子:

function makeCounter() {
 let count = 0;

 return function() {
   return count++; // has access to the outer counter
 };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2

我們接著研究 makeCounter 长赞。counter 函數(shù)每調(diào)用一次就會返回下一個數(shù)。盡管這很簡單闽撤,但只要輕微修改得哆,它便具有一定的實用性,例如偽隨機數(shù)生成器腹尖。

counter 內(nèi)部如何工作柳恐?

內(nèi)部函數(shù)運行, count++ 中的變量由內(nèi)到外搜索:

image
  1. 嵌套函數(shù)局部變量……
  2. 外層函數(shù)……
  3. 直到全局變量热幔。

第二步我們找到了 count 乐设。當(dāng)外部變量被修改,它所在的地方就被修改绎巨。所以 count++ 檢索外部變量并對其加一是操作于該變量自己的 Lexical Environment 近尚。就像操作了 let count = 1 一樣。

這里需要思考兩個問題:

  1. 我們能通過 makeCounter 以外的方法重置 counter 嗎场勤?
  2. 如果我們可以多次調(diào)用 makeCounter() 戈锻,返回了很多 counter 函數(shù),他們的 count 是獨立的還是共享的和媳?

繼續(xù)閱讀前可以先嘗試思考一下格遭。

...

ok ?

那我們開始揭曉謎底:

  1. 沒門留瞳。 counter 是局部變量拒迅,不可能在外部直接訪問。
  2. 每次調(diào)用 makeCounter() 都會新建 Lexical Environment,每一個環(huán)境都有自己的 counter 璧微。所以不同 counter 里的 count 是獨立的作箍。

一個 demo :

function makeCounter() {
 let count = 0;
 return function() {
   return count++;
 };
}

let counter1 = makeCounter();
let counter2 = makeCounter();

alert( counter1() ); // 0
alert( counter1() ); // 1

alert( counter2() ); // 0 (獨立)

現(xiàn)在你能清楚外部變量的使用,但是你仍然需要更深入地理解以面對更復(fù)雜的情況前硫,現(xiàn)在我們進入下一步胞得。

Environment 細(xì)節(jié)

對 closure (閉包)有了初步了解之后,可以開始深入細(xì)節(jié)了屹电。

下面是 makeCounter 例子的動作分解阶剑,跟著看你就能理解一切了。注意危号, [[Environment]] 屬性我們之前還未介紹个扰。

  1. 腳本開始運行,此時只存在 global Lexical Environment :

    image

    這時候只有 makeCounter 一個函數(shù)葱色,這是函數(shù)聲明,還未被調(diào)用娘香。

    所有函數(shù)都帶著一個隱藏屬性 [[Environment]] “誕生”苍狰。 [[Environment]] 指向它們創(chuàng)建的 Lexical Environment 。是[[Environment]] 讓函數(shù)知道它“誕生”于什么環(huán)境烘绽。

    makeCounter 創(chuàng)建于 global Lexical Environment 淋昭,所以 [[Environment]] 指向它。

    換句話說安接,Lexical Environment 在函數(shù)誕生時就“銘刻”在這個函數(shù)中翔忽。[[Environment]] 是指向 Lexical Environment 的隱藏函數(shù)屬性。

  2. 代碼繼續(xù)走盏檐, makeCounter() 登上舞臺歇式。這是代碼運行到 makeCounter() 瞬間的快照:

    image

    makeCounter() 調(diào)用時,保存當(dāng)前變量和實參的 Lexical Environment 已經(jīng)被創(chuàng)建胡野。

    Lexical Environment 儲存 2 個東西:

    1. 帶有局部變量的 Environment Record 材失。例子中 count 是唯一的局部變量( let count 被執(zhí)行的時候記錄)。
    2. 被綁定到函數(shù) [[Environment]] 的外部詞法引用硫豆。例子里 makeCounter[[Environment]] 引用了 global Lexical Environment 龙巨。

    所以這里有兩個 Lexical Environments :全局,和 makeCounter (outer 引用全局)熊响。

  3. makeCounter() 執(zhí)行的過程中旨别,創(chuàng)建了一個嵌套函數(shù)。

    這無關(guān)于函數(shù)創(chuàng)建使用的是 Function Declaration (函數(shù)聲明)還是 Function Expression (函數(shù)表達式)汗茄。所有函數(shù)都會得到引用他們被創(chuàng)建時 Lexical Environment 的 [[Environment]] 屬性秸弛。

    這個嵌套函數(shù)的 [[Environment]]makeCounter() (它的誕生地)的 Lexical Environment:

    image

    同樣注意,這一步是函數(shù)聲明而非調(diào)用。

  4. 代碼繼續(xù)執(zhí)行胆屿,makeCounter() 調(diào)用結(jié)束奥喻,內(nèi)嵌函數(shù)被賦值到全局變量 counter

    image

    這個函數(shù)只有一行: return count++

  5. counter() 被調(diào)用非迹,自動創(chuàng)建一個 “空” Lexical Environment 环鲤。 此函數(shù)無局部變量,但是 [[Environment]] 引用了外面一層憎兽,所以它可以訪問 makeCounter() 的變量冷离。

    image

    要訪問變量,先檢索自己的 Lexical Environment (empty)纯命,然后是 makeCounter() 的西剥,最后是全局的。例子中在最近的外層 Lexical Environment makeCounter 中發(fā)現(xiàn)了 count 亿汞。

    重點來了瞭空,內(nèi)存在這里是怎么管理的?盡管 makeCounter() 調(diào)用結(jié)束了疗我,它的 Lexical Environment 依然保存在內(nèi)存中咆畏,這是因為嵌套函數(shù)的 [[Environment]] 引用了它。

    通常吴裤, Lexical Environment 對象隨著使用它的函數(shù)的存在而存在旧找。沒有函數(shù)引用它的時候,它才會被清除麦牺。

  6. counter() 函數(shù)不只是返回 count 钮蛛,還會對其 +1 操作。這個修改已經(jīng)在“適當(dāng)?shù)奈恢谩蓖瓿闪恕?code>count 的值在它被找到的環(huán)境中被修改剖膳。

    image

    這一步出了返回了新的 count 魏颓,其他完全相同。

    (譯者:總結(jié)一下吱晒,聲明時記錄環(huán)境 [[Environment]](函數(shù)所在環(huán)境)琼开,執(zhí)行時創(chuàng)建詞法環(huán)境(局部+outer 就是引用 [[Environment]] ),而閉包就是函數(shù) + 它的詞法環(huán)境枕荞,所以定義上來說所有函數(shù)都是閉包柜候,但是之后被返回出來可以使用的閉包才是“實用意義”上的閉包)

  7. 下一個 counter() 調(diào)用操作同上。

本章開頭第二個問題的答案現(xiàn)在顯而易見了躏精。

以下代碼的 work() 函數(shù)通過外層 lexical environment 引用了它原地點的 name

image

所以這里的答案是 "Pete" 渣刷。

但是如果 makeWorker() 沒了 let name ,如我們所見矗烛,作用域搜索會到達外層辅柴,獲取全局變量箩溃。這個情況下答案會是 "John"

閉包 (Closure)
開發(fā)者們都應(yīng)該知道編程領(lǐng)域的通用名詞閉包 (closure)碌嘀。
Closure 是一個記錄并可訪問外層變量的函數(shù)涣旨。在一些編程語言中,這是不可能的股冗,或者要以一種特殊的方式書寫以實現(xiàn)這個功能霹陡。但是如上面解釋的, JavaScript 的所有函數(shù)都(很自然地)是個閉包止状。(有一個例外烹棉,詳見info:new-function
這就是閉包:它們使用 [[Environment]] 屬性自動記錄各自的創(chuàng)建地點,然后由此訪問外部變量怯疤。
在前端面試中浆洗,如果面試官問你什么是閉包,正確答案應(yīng)該包括閉包的定義集峦,以及解釋為何 JavaScript 的所有函數(shù)都是閉包伏社,最好可以再簡單說說里面的技術(shù)細(xì)節(jié): [[Environment]] 屬性和 Lexical Environments 的原理。

代碼塊塔淤、循環(huán)洛口、 IIFE

上面的例子都著重于函數(shù),但是 Lexical Environment 也存在于代碼塊 {...} 凯沪。

它們在代碼塊運行時創(chuàng)建,包含塊局部變量买优。這里有一些例子妨马。

If

下例中,當(dāng)執(zhí)行到 if 塊杀赢,會為這個塊創(chuàng)建新的 "if-only" Lexical Environment :

image

與函數(shù)同樣原理烘跺,塊內(nèi)可以找到 phrase ,但是塊外不能使用塊內(nèi)的變量和函數(shù)脂崔。如果執(zhí)意在 if 外面用 user 滤淳,那只能得到一個報錯了。

For, while

對于循環(huán)砌左,每個 iteration 都會有自己的 Lexical Environment 脖咐,在 for 里定義的變量,也是塊的局部變量汇歹,也屬于塊的 Lexical Environment :

for (let i = 0; i < 10; i++) {
 // Each loop has its own Lexical Environment
 // {i: value}
}

alert(i); // Error, no such variable

let i 只在塊內(nèi)可用屁擅,每次循環(huán)都有它自己的 Lexical Environment ,每次循環(huán)都會帶著當(dāng)前的 i 产弹,最后循環(huán)結(jié)束派歌, i 不可用。

代碼塊

我們也可以直接用 {…} 把變量隔離到一個“局部作用域”(local scope)。

在瀏覽器中所有 script 共享全局變量胶果,這就很容易造成變量的重名匾嘱、覆蓋。

為了避免這種情況我們可以使用代碼塊隔離自己的代碼:

{
 // do some job with local variables that should not be seen outside

 let message = "Hello";

 alert(message); // Hello
}

alert(message); // Error: message is not defined

代碼塊有自己的 Lexical Environment 早抠,塊外無法訪問塊內(nèi)變量霎烙。

IIFE

以前沒有代碼塊,要實現(xiàn)上述效果要依靠所謂的“立即執(zhí)行函數(shù)表達式”(immediately-invoked function expressions 贝或,縮寫 IIFE):

(function() {

 let message = "Hello";

 alert(message); // Hello

})();

這個函數(shù)表達式創(chuàng)建后立即執(zhí)行吼过,這段代碼立即執(zhí)行并有自己的私有變量。

函數(shù)表達式需要被括號包裹咪奖。 JavaScript 執(zhí)行時遇到 "function" 會理解為一個函數(shù)聲明盗忱,函數(shù)聲明必須有名稱,沒有就會報錯:

// Error: Unexpected token (
function() { // <-- JavaScript cannot find function name, meets ( and gives error

 let message = "Hello";

 alert(message); // Hello

}();

你可能會說:“那我給他加個名字咯”羊赵,但這依然行不通趟佃,JavaScript 不允許函數(shù)聲明立刻被執(zhí)行:

// syntax error because of brackets below
function go() {

}(); // <-- can't call Function Declaration immediately

圓括號告訴 JavaScript 這個函數(shù)創(chuàng)建于其他表達式的上下文,因此這是個函數(shù)表達式昧捷。不需要名稱闲昭,也可以立即執(zhí)行。

也有其他方法告訴 JavaScript 我們需要的是函數(shù)表達式:

// 創(chuàng)建 IIFE 的方法

(function() {
 alert("Brackets around the function");
})();

(function() {
 alert("Brackets around the whole thing");
}());

!function() {
 alert("Bitwise NOT operator starts the expression");
}();

+function() {
 alert("Unary plus starts the expression");
}();

垃圾回收

Lexical Environment 對象與普通的值的內(nèi)存管理規(guī)則是一樣的靡挥。

  • 通常 Lexical Environment 在函數(shù)運行完畢就會被清理:

    function f() {
      let value1 = 123;
      let value2 = 456;
    }
    
    f();
    

    這兩個值是 Lexical Environment 的屬性序矩,但是 f() 執(zhí)行完后,這個 Lexical Environment 無任何變量引用(unreachable)跋破,所以它會從內(nèi)存刪除簸淀。

  • ...但是如果有內(nèi)嵌函數(shù),它的 [[Environment]] 會引用 f 的 Lexical Environment(reachable):

    function f() {
      let value = 123;
    
      function g() { alert(value); }
    
      return g;
    }
    
    let g = f(); // g is reachable, and keeps the outer lexical environment in memory
    
  • 注意毒返, f() 如果被多次調(diào)用租幕,返回的函數(shù)都被保存,相應(yīng)的 Lexical Environment 會分別保存在內(nèi)存:

    function f() {
      let value = Math.random();
    
      return function() { alert(value); };
    }
    
    // 3 functions in array, every one of them links to Lexical Environment
    // from the corresponding f() run
    //         LE   LE   LE
    let arr = [f(), f(), f()];
    
  • Lexical Environment 對象在不被引用 (unreachable) 后被清除: 無嵌套函數(shù)引用它拧簸。下例中劲绪, g 自身不被引用后, value 也會被清除:

    function f() {
      let value = 123;
    
      function g() { alert(value); }
    
      return g;
    }
    
    let g = f(); // while g is alive
    // there corresponding Lexical Environment lives
    
    g = null; // ...and now the memory is cleaned up
    

現(xiàn)實中的優(yōu)化

理論上盆赤,函數(shù)還在贾富,它的所有外部變量都會被保留。

但在實踐中牺六,JavaScript 引擎可能會對此作出優(yōu)化祷安,引擎在分析變量的使用情況后,把沒有使用的外部變量刪除兔乞。

在 V8 (Chrome, Opera) 有個問題汇鞭,這些被刪除的變量不能在 debugger 觀察了凉唐。

嘗試在 Chrome Developer Tools 運行以下代碼:

function f() {
 let value = Math.random();

 function g() {
   debugger; // 在 console 輸入 alert( value ); 發(fā)現(xiàn)無此變量!
 }

 return g;
}

let g = f();
g();

你可以看到霍骄,這里沒有保存 value 變量台囱!理論上它應(yīng)該是可訪問的,但是引擎優(yōu)化移除了這個變量读整。

還有一個有趣的 debug 問題簿训。下面的代碼 alert 出外面的同名變量而不是里面的:

let value = "Surprise!";

function f() {
 let value = "the closest value";

 function g() {
   debugger; // in console: type alert( value ); Surprise!
 }

 return g;
}

let g = f();
g();

再會!
如果你用 Chrome/Opera 來debug 米间,很快就能發(fā)現(xiàn)這個 V8 feature强品。
這不是 bug 而是 V8 feature,或許將來會被修改屈糊。至于改沒改的榛,運行一下上面的例子就能判斷啦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逻锐,一起剝皮案震驚了整個濱河市夫晌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昧诱,老刑警劉巖晓淀,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盏档,居然都是意外死亡凶掰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門蜈亩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懦窘,“玉大人,你說我怎么就攤上這事勺拣。” “怎么了鱼填?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵药有,是天一觀的道長。 經(jīng)常有香客問我苹丸,道長愤惰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任赘理,我火速辦了婚禮宦言,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘商模。我一直安慰自己奠旺,他們只是感情好蜘澜,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著响疚,像睡著了一般鄙信。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忿晕,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天装诡,我揣著相機與錄音,去河邊找鬼践盼。 笑死鸦采,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咕幻。 我是一名探鬼主播渔伯,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谅河!你這毒婦竟也來了咱旱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绷耍,失蹤者是張志新(化名)和其女友劉穎吐限,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褂始,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡诸典,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了崎苗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狐粱。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖胆数,靈堂內(nèi)的尸體忽然破棺而出肌蜻,到底是詐尸還是另有隱情,我是刑警寧澤必尼,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布蒋搜,位于F島的核電站,受9級特大地震影響判莉,放射性物質(zhì)發(fā)生泄漏豆挽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一券盅、第九天 我趴在偏房一處隱蔽的房頂上張望帮哈。 院中可真熱鬧,春花似錦锰镀、人聲如沸娘侍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽私蕾。三九已至僵缺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踩叭,已是汗流浹背磕潮。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留容贝,地道東北人自脯。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像斤富,于是被迫代替她去往敵國和親膏潮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

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

  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔满力,今天18年5月份再次想寫文章焕参,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,768評論 2 9
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)油额,斷路器叠纷,智...
    卡卡羅2017閱讀 134,702評論 18 139
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,835評論 0 38
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,151評論 0 13
  • HTTPS SSL 第一步潦嘶,愛麗絲給出協(xié)議版本號涩嚣、一個客戶端生成的隨機數(shù)(Client random),以及客戶端...
    longtolong閱讀 332評論 0 0