JavaScript 模塊化歷程

原文鏈接:http://www.cnblogs.com/lvdabao/p/js-modules-develop.html

image.png

這是一篇關于 js 模塊化歷程的長長的流水賬,記錄js模塊化思想的誕生與變遷,展望ES6模塊化標準的未來。經(jīng)歷過這段歷史的人或許會感到滄桑,沒經(jīng)歷過的人也應該知道這段歷史。

無模塊時代

在 Ajax 還未提出之前,js還只是一種“玩具語言”,由 Brendan Eich 花了不到十天時間發(fā)明,用來在網(wǎng)頁上進行表單校驗、實現(xiàn)簡單的動畫效果等等,你可以回想一下那個網(wǎng)頁上到處有公告塊飄來飄去的時代。
  這個時候并沒有前端工程師,服務端工程師只需在頁面上隨便寫寫 js 就能搞定需求。那個時候的前端代碼大概像這樣:

if(xx){
     //.......
}
else{
     //xxxxxxxxxxx
}
for(var i=0; i<10; i++){
     //........
}
element.onclick = function(){
     //.......
}

代碼簡單的堆在一起,只要能從上往下依次執(zhí)行就可以了。

模塊萌芽時代

2006 年,Ajax 的概念被提出,前端擁有了主動向服務端發(fā)送請求并操作返回數(shù)據(jù)的能力,隨著 Google 將此概念的發(fā)揚光大,傳統(tǒng)的網(wǎng)頁慢慢的向“富客戶端”發(fā)展。前端的業(yè)務邏輯越來越多,代碼也越來越多,于是一些問題就暴漏了出來:

  1. 全局變量的災難
    張三 定義了:i = 1
    李四 在后續(xù)的代碼里:i=0
    張三 在接下來的代碼里:if(i==1){...} //悲劇

  2. 函數(shù)命名沖突
    項目中通常會把一些通用的函數(shù)封裝成一個文件,常見的名字有 utils.jscommon.js……
    張三 定義了一個函數(shù):function formatData(){ }
    李四 想實現(xiàn)類似功能,于是這么寫:function formatData2(){ }
    王五 又有一個類似功能,于是:function formatData3(){ }
    ……
    避免命名沖突就只能這樣靠丑陋的方式人肉進行。

  3. 依賴關系不好管理
    b.js 依賴 a.js,標簽的書寫順序必須是

<script type="text/javascript" src="a.js"></script>
<script type="text/javascript" src="b.js"></script>

順序不能錯,也不能漏寫某個。在多人開發(fā)的時候很難協(xié)調(diào)。
尤其在開發(fā)單頁應用的時候,所有引用都要寫在 index.html 里面,這種現(xiàn)象尤為明顯!

萌芽時代的解決方案:
  1. 用自執(zhí)行函數(shù)來包裝代碼
modA = function(){
     var a,b; //變量a、b外部不可見
     return {
          add : function(c){
               a + b + c;
          },
          format: function(){
               //......
          }
     }
}()

這樣 function 內(nèi)部的變量就對全局隱藏了,達到是封裝的目的。但是這樣還是有缺陷的,modA這個變量還是暴漏到全局了,隨著模塊的增多,全局變量還是會越來越多。

  1. Java風格的命名空間
      為了避免全局變量造成的沖突,人們想到或許可以用多級命名空間來進行管理,于是,代碼就變成了這個風格:
app.util.modA = xxx;
app.tools.modA = xxx;
app.tools.modA.format = xxx;

Yahoo的YUI早期就是這么做的,調(diào)用的時候不得不這么寫:

app.tools.modA.format();

這樣調(diào)用函數(shù),寫寫都會覺得惡心,所以這種方式并沒有被很多人采用,YUI后來也不用這種方式了。

  1. jQuery風格的匿名自執(zhí)行函數(shù)
(function(window){
    //代碼
    window.jQuery = window.$ = jQuery;//通過給window添加屬性而暴漏到全局
})(window);

jQuery的封裝風格曾經(jīng)被很多框架模仿,通過匿名函數(shù)包裝代碼,所依賴的外部變量傳給這個函數(shù),在函數(shù)內(nèi)部可以使用這些依賴,然后在函數(shù)的最后把模塊自身暴漏給 window。
  如果需要添加擴展,則可以作為jQuery的插件,把它掛載到$上。
  這種風格雖然靈活了些,但并未解決根本問題:所需依賴還是得外部提前提供、還是增加了全局變量。

模塊化面臨什么問題

從以上的嘗試中,可以歸納出 js 模塊化需要解決那些問題:

  1. 如何安全的包裝一個模塊的代碼?(不污染模塊外的任何代碼)
  2. 如何唯一標識一個模塊?
  3. 如何優(yōu)雅的把模塊的API暴漏出去?(不能增加全局變量)
  4. 如何方便的使用所依賴的模塊?
    圍繞著這些問題,js 模塊化開始了一段艱苦而曲折的征途。

源自 Nodejs 的規(guī)范 CommonJS

2009 年,Nodejs 橫空出世,開創(chuàng)了一個新紀元,人們可以用 js 來編寫服務端的代碼了。如果說瀏覽器端的 js 即便沒有模塊化也可以忍的話,那服務端是萬萬不能的。
  大牛云集的 CommonJS 社區(qū)發(fā)力,制定了 Modules/1.0 規(guī)范,首次定義了一個模塊應該長啥樣。具體來說,Modules/1.0 規(guī)范包含以下內(nèi)容:

  1. 模塊的標識應遵循的規(guī)則(書寫規(guī)范)
  2. 定義全局函數(shù) require,通過傳入模塊標識來引入其他模塊,執(zhí)行的結(jié)果即為別的模塊暴漏出來的 API
  3. 如果被 require 函數(shù)引入的模塊中也包含依賴,那么依次加載這些依賴
  4. 如果引入模塊失敗,那么 require 函數(shù)應該報一個異常
  5. 模塊通過變量 exports 來向往暴漏 API,exports 只能是一個對象,暴漏的 API 須作為此對象的屬性。

此規(guī)范一出,立刻產(chǎn)生了良好的效果,由于其簡單而直接,在 Nodejs 中,這種模塊化方案立刻被推廣開了。
  遵循 CommonJS 規(guī)范的代碼看起來是這樣的:(來自官方的例子)

//math.js
exports.add = function() {
    var sum = 0, i = 0, args = arguments, l = args.length;
    while (i < l) {
        sum += args[i++];
    }
    return sum;
};
//increment.js
var add = require('math').add;
exports.increment = function(val) {
    return add(val, 1);
};
//program.js
var inc = require('increment').increment;
var a = 1;
inc(a); // 2

服務端向前端進軍

Modules/1.0規(guī)范源于服務端,無法直接用于瀏覽器端,原因表現(xiàn)為:

  1. 外層沒有 function 包裹,變量全暴漏在全局。如上面例子中 increment.js 中的 add。
  2. 資源的加載方式與服務端完全不同。服務端 require 一個模塊,直接就從硬盤或者內(nèi)存中讀取了,消耗的時間可以忽略。而瀏覽器則不同,需要從服務端來下載這個文件,然后運行里面的代碼才能得到 API,需要花費一個 http 請求,也就是說,require 后面的一行代碼,需要資源請求完成才能執(zhí)行。由于瀏覽器端是以插入 <script> 標簽的形式來加載資源的(Ajax 方式不行,有跨域問題),沒辦法讓代碼同步執(zhí)行,所以像 CommonJS 那樣的寫法會直接報錯。

所以,社區(qū)意識到,要想在瀏覽器環(huán)境中也能模塊化,需要對規(guī)范進行升級。順便說一句,CommonJS 原來是叫 ServerJS,從名字可以看出是專攻服務端的,為了統(tǒng)一前后端而改名 CommonJS。(論起名的重要性~)
  而就在社區(qū)討論制定下一版規(guī)范的時候,內(nèi)部發(fā)生了比較大的分歧,分裂出了三個主張,漸漸的形成三個不同的派別:

  1. Modules/1.x 派
      這一波人認為,在現(xiàn)有基礎上進行改進即可滿足瀏覽器端的需要,既然瀏覽器端需要 function 包裝,需要異步加載,那么新增一個方案,能把現(xiàn)有模塊轉(zhuǎn)化為適合瀏覽器端的就行了,有點像“保皇派”。基于這個主張,制定了 Modules/Transport 規(guī)范,提出了先通過工具把現(xiàn)有模塊轉(zhuǎn)化為復合瀏覽器上使用的模塊,然后再使用的方案。
      Browserify 就是這樣一個工具,可以把 Nodejs 的模塊編譯成瀏覽器可用的模塊。(Modules/Transport規(guī)范晦澀難懂,我也不確定Browserify跟它是何關聯(lián),有知道的朋友可以講一下)
      目前的最新版是 Modules/1.1.1,增加了一些require的屬性,以及模塊內(nèi)增加module變量來描述模塊信息,變動不大。

  2. Modules/Async派
      這一波人有點像“革新派”,他們認為瀏覽器與服務器環(huán)境差別太大,不能沿用舊的模塊標準。既然瀏覽器必須異步加載代碼,那么模塊在定義的時候就必須指明所依賴的模塊,然后把本模塊的代碼寫在回調(diào)函數(shù)里。模塊的加載也是通過下載-回調(diào)這樣的過程來進行,這個思想就是AMD的基礎,由于“革新派”與“保皇派”的思想無法達成一致,最終從 CommonJS 中分裂了出去,獨立制定了瀏覽器端的 js 模塊化規(guī)范 AMD(Asynchronous Module Definition) 本文后續(xù)會繼續(xù)討論AMD規(guī)范的內(nèi)容。

  3. Modules/2.0派
      這一波人有點像“中間派”,既不想丟掉舊的規(guī)范,也不想像 AMD 那樣推到重來。他們認為,Modules/1.0 固然不適合瀏覽器,但它里面的一些理念還是很好的,(如通過 require 來聲明依賴),新的規(guī)范應該兼容這些,AMD規(guī)范也有它好的地方(例如模塊的預先加載以及通過 return 可以暴漏任意類型的數(shù)據(jù),而不是像 CommonJS 那樣 exports 只能為 object ),也應采納。最終他們制定了一個 Modules/Wrappings 規(guī)范,此規(guī)范指出了一個模塊應該如何“包裝”,包含以下內(nèi)容:

  1. 全局有一個module變量,用來定義模塊
  2. 通過module.declare方法來定義一個模塊
  3. module.declare方法只接收一個參數(shù),那就是模塊的factory,次factory可以是函數(shù)也可以是對象,如果是對象,那么模塊輸出就是此對象。
  4. 模塊的factory函數(shù)傳入三個參數(shù):require,exports,module,用來引入其他依賴和導出本模塊API
  5. 如果factory函數(shù)最后明確寫有return數(shù)據(jù)(js函數(shù)中不寫return默認返回undefined),那么return的內(nèi)容即為模塊的輸出。

使用該規(guī)范的例子看起來像這樣:

//可以使用exprots來對外暴漏API
module.declare(function(require, exports, module)
{
    exports.foo = "bar";
});
//也可以直接return來對外暴漏數(shù)據(jù)
module.declare(function(require)
{
return { foo: "bar" };
});

AMD/RequireJs的崛起與妥協(xié)

AMD的思想正如其名,異步加載所需的模塊,然后在回調(diào)函數(shù)中執(zhí)行主邏輯。這正是我們在瀏覽器端開發(fā)所習慣了的方式,其作者親自實現(xiàn)了符合AMD規(guī)范的 requirejs,AMD/RequireJs迅速被廣大開發(fā)者所接受。
AMD規(guī)范包含以下內(nèi)容:

  1. 用全局函數(shù)define來定義模塊,用法為:define(id?, dependencies?, factory);
  2. id為模塊標識,遵從 CommonJS Module Identifiers 規(guī)范
  3. dependencies為依賴的模塊數(shù)組,在factory中需傳入形參與之一一對應
  4. 如果dependencies的值中有"require"、"exports"或"module",則與 CommonJS 中的實現(xiàn)保持一致
  5. 如果dependencies省略不寫,則默認為["require", "exports", "module"],factory中也會默認傳入require,exports,module
  6. 如果factory為函數(shù),模塊對外暴漏API的方法有三種:return任意類型的數(shù)據(jù)、exports.xxx=xxx、module.exports=xxx
  7. 如果factory為對象,則該對象即為模塊的返回值

基于以上幾點基本規(guī)范,我們便可以用這樣的方式來進行模塊化組織代碼了:

//a.js
define(function(){
     console.log('a.js執(zhí)行');
     return {
          hello: function(){
               console.log('hello, a.js');
          }
     }
});
//b.js
define(function(){
     console.log('b.js執(zhí)行');
     return {
          hello: function(){
               console.log('hello, b.js');
          }
     }
});
//main.js
require(['a', 'b'], function(a, b){
     console.log('main.js執(zhí)行');
     a.hello();
     $('#b').click(function(){
          b.hello();
     });
})

上面的main.js被執(zhí)行的時候,會有如下的輸出:

a.js執(zhí)行
b.js執(zhí)行
main.js執(zhí)行
hello, a.js

在點擊按鈕后,會輸出:

hello, b.js

這結(jié)局,如你所愿嗎?大體來看,是沒什么問題的,因為你要的兩個hello方法都正確的執(zhí)行了。
  但是如果細細來看,b.js 被預先加載并且預先執(zhí)行了,(第二行輸出),b.hello 這個方法是在點擊了按鈕之后才會執(zhí)行,如果用戶壓根就沒點,那么b.js中的代碼應不應該執(zhí)行呢?
  這其實也是AMD/RequireJs被吐槽的一點,預先下載沒什么爭議,由于瀏覽器的環(huán)境特點,被依賴的模塊肯定要預先下載的。問題在于,是否需要預先執(zhí)行?如果一個模塊依賴了十個其他模塊,那么在本模塊的代碼執(zhí)行之前,要先把其他十個模塊的代碼都執(zhí)行一遍,不管這些模塊是不是馬上會被用到。這個性能消耗是不容忽視的。
  另一點被吐槽的是,在定義模塊的時候,要把所有依賴模塊都羅列一遍,而且還要在 factory 中作為形參傳進去,要寫兩遍很大一串模塊名稱,像這樣:

define(['a', 'b', 'c', 'd', 'e', 'f', 'g'], function(a, b, c, d, e, f, g){  ..... })

編碼過程略有不爽。
  好的一點是,AMD保留了 CommonJS 中的require、exprots、module這三個功能(上面提到的第4條)。你也可以不把依賴羅列在 dependencies 數(shù)組中。而是在代碼中用 require 來引入,如下:

define(function(){
     console.log('main2.js執(zhí)行');

     require(['a'], function(a){
          a.hello();    
     });

     $('#b').click(function(){
          require(['b'], function(b){
               b.hello();
          });
     });
});

我們在define的參數(shù)中未寫明依賴,那么main2.js在執(zhí)行的時候,就不會預先加載 a.js 和 b.js,只是執(zhí)行到 require 語句的時候才會去加載,上述代碼的輸出如下:

main2.js執(zhí)行
a.js執(zhí)行
hello, a.js

可以看到b.js并未執(zhí)行,從網(wǎng)絡請求中看,b.js也并未被下載。只有在按鈕被點擊的時候b.js才會被下載執(zhí)行,并且在回調(diào)函數(shù)中執(zhí)行模塊中的方法。這就是名副其實的“懶加載”了。
  這樣的懶加載無疑會大大減輕初始化時的損耗(下載和執(zhí)行都被省去了),但是弊端也是顯而易見的,在后續(xù)執(zhí)行 a.hello 和 b.hello 時,必須得實時下載代碼然后在回調(diào)中才能執(zhí)行,這樣的用戶體驗是不好的,用戶的操作會有明顯的延遲卡頓。
但這樣的現(xiàn)實并非是無法接受的,畢竟是瀏覽器環(huán)境,我們已經(jīng)習慣了操作網(wǎng)頁時伴隨的各種 loading...

但是話說過來,有沒有更好的方法來處理問題呢?資源的下載階段還是預先進行,資源執(zhí)行階段后置,等到需要的時候再執(zhí)行。這樣一種折衷的方式,能夠融合前面兩種方式的優(yōu)點,而又回避了缺點。
  這就是 Modules/Wrappings 規(guī)范,還記得前面提到的“中間派”嗎?
  在AMD的陣營中,也有一部分人提出這樣的觀點,代碼里寫一堆回調(diào)實在是太惡心了,他們更喜歡這樣來使用模塊:

var a = require('a');
a.hello();

$('#b').click(function(){
        var b = require('b');
        b.hello();
});

于是, AMD 也終于決定作妥協(xié),兼容 Modules/Wrappings 的寫法,但只是部分兼容,例如并沒有使用 module.declare 來定義模塊,而還是用 define,模塊的執(zhí)行時機也沒有改變,依舊是預先執(zhí)行。因此, AMD 將此兼容稱為 Simplified CommonJS wrapping,即并不是完整的實現(xiàn) Modules/Wrappings。
  作了此兼容后,使用requirejs就可以這么寫代碼了:

//d.js
define(function(require, exports, module){
     console.log('d.js執(zhí)行');
     return {
          helloA: function(){
               var a = require('a');
               a.hello();
          },
          run: function(){
               $('#b').click(function(){
                    var b = require('b');
                    b.hello();
               });
          }
     }
});

注意定義模塊時候的輕微差異,dependencies 數(shù)組為空,但是 factory 函數(shù)的形參必須手工寫上 require, exports, module,(這不同于之前的 dependencies 和 factory 形參全不寫),這樣寫即可使用 Simplified CommonJS wrapping 風格,與 CommonJS 的格式一致了。
  雖然使用上看起來簡單,然而在理解上卻給后人埋下了一個大坑。因為 AMD 只是支持了這樣的語法,而并沒有真正實現(xiàn)模塊的延后執(zhí)行。什么意思呢?上面的代碼,正常來講應該是預先下載 a.js 和 b.js,然后在執(zhí)行模塊的 helloA 方法的時候開始執(zhí)行 a.js 里面的代碼,在點擊按鈕的時候開始執(zhí)行 b.js 中的方法。實際卻不是這樣,只要此模塊被別的模塊引入,a.js 和 b.js 中的代碼還是被預先執(zhí)行了。
  我們把上面的代碼命名為d.js,在別的地方使用它:

require(['d'], function(d){
   
});

上面的代碼會輸出

a.js執(zhí)行
b.js執(zhí)行
d.js執(zhí)行

可以看出,盡管還未調(diào)用d模塊的API,里面所依賴的 a.js 和 b.js 中的代碼已經(jīng)執(zhí)行了。AMD 的這種只實現(xiàn)語法卻未真正實現(xiàn)功能的做法容易給人造成理解上的困難,被強烈吐槽。
(在 requirejs2.0 中,作者聲明已經(jīng)處理了此問題,但是我用2.1.20版測試的時候還是會預先執(zhí)行,我有點不太明白原因,如果有懂的高手請指教)

兼容并包的CMD/SeaJS

既然 requirejs 有上述種種不甚優(yōu)雅的地方,所以必然會有新東西來完善它,這就是后起之秀 SeaJS,SeaJS 的作者是國內(nèi)大牛淘寶前端布道者玉伯。SeaJS 全面擁抱 Modules/Wrappings 規(guī)范,不用 requirejs 那樣回調(diào)的方式來編寫模塊。而它也不是完全按照 Modules/Wrappings 規(guī)范,SeaJS 并沒有使用 declare 來定義模塊,而是使用和 requirejs 一樣的 define,或許作者本人更喜歡這個名字吧。(然而這或多或少又會給人們造成理解上的混淆),用 SeaJS 定義模塊的寫法如下:

//a.js
define(function(require, exports, module){
     console.log('a.js執(zhí)行');
     return {
          hello: function(){
               console.log('hello, a.js');
          }
     }
});
//b.js
define(function(require, exports, module){
     console.log('b.js執(zhí)行');
     return {
          hello: function(){
               console.log('hello, b.js');
          }
     }
});
//main.js
define(function(require, exports, module){
     console.log('main.js執(zhí)行');

     var a = require('a');
     a.hello();    

     $('#b').click(function(){
          var b = require('b');
          b.hello();
     });
    
});

定義模塊時無需羅列依賴數(shù)組,在factory函數(shù)中需傳入形參 require, exports, module,然后它會調(diào)用 factory 函數(shù)的 toString 方法,對函數(shù)的內(nèi)容進行正則匹配,通過匹配到的 require 語句來分析依賴,這樣就真正實現(xiàn)了 CommonJS 風格的代碼。
  上面的 main.js 執(zhí)行會輸出如下:

main.js執(zhí)行
a.js執(zhí)行
hello, a.js

a.js 和 b.js 都會預先下載,但是 b.js 中的代碼卻沒有執(zhí)行,因為還沒有點擊按鈕。當點擊按鈕的時候,會輸出如下:

b.js執(zhí)行
hello, b.js

可以看到 b.js 中的代碼此時才執(zhí)行。這樣就真正實現(xiàn)了“就近書寫,延遲執(zhí)行“,不可謂不優(yōu)雅。

如果你一定要挑出一點不爽的話,那就是 b.js 的預先下載了。你可能不太想一開始就下載好所有的資源,希望像 requirejs 那樣,等點擊按鈕的時候再開始下載b.js。本著兼容并包的思想,SeaJS 也實現(xiàn)了這一功能,提供 require.async API,在點擊按鈕的時候,只需這樣寫:

var b = require.async('b');
b.hello();

b.js 就不會在一開始的時候就加載了。這個API可以說是簡單漂亮。

關于模塊對外暴漏API的方式,SeaJS 也是融合了各家之長,支持 CommonJS 的exports.xxx = xxx 和 module.exports = xxx 的寫法,也支持 AMD 的 return 寫法,暴露的 API 可以是任意類型。

你可能會覺得 SeaJS 無非就是一個抄,把別人家的優(yōu)點都抄過來組合了一下。其實不然,SeaJS 是 CommonJS 規(guī)范在瀏覽器端的踐行者,對于 requirejs 的優(yōu)點也加以吸收。看人家的名字,就是海納百川之意。(再論起名的重要性~),既然它的思想是海納百川,討論是不是抄就沒意義了。
  鑒于 SeaJS 融合了太多的東西,已經(jīng)無法說它遵循哪個規(guī)范了,所以玉伯干脆就自立門戶,起名曰 CMD(Common Module Definition)規(guī)范,有了綱領,就不會再存在非議了。

面向未來的ES6模塊標準

既然模塊化開發(fā)的呼聲這么高,作為官方的 ECMA 必然要有所行動,js 模塊很早就列入草案,終于在2015年6月份發(fā)布了ES6正式版。然而,可能由于所涉及的技術還未成熟,ES6移除了關于模塊如何加載/執(zhí)行的內(nèi)容,只保留了定義、引入模塊的語法。所以說現(xiàn)在的 ES6 Module 還只是個雛形,半成品都算不上。但是這并不妨礙我們先窺探一下 ES6 模塊標準。
  定義一個模塊不需要專門的工作,因為一個模塊的作用就是對外提供API,所以只需用 exoprt 導出就可以了:

//方式一, a.js
export var a = 1;
export var obj = {name: 'abc', age: 20};
export function run(){....}
//方式二, b.js
var a = 1;
var obj = {name: 'abc', age: 20};
function run(){....}
export {a, obj, run}

使用模塊的時候用import關鍵字,如:

import {run as go} from  'a'
run()

如果想要使用模塊中的全部API,也可以不必把每個都列一遍,使用module關鍵字可以全部引入,用法:

module foo from 'a'
console.log(foo.obj);
a.run();

在花括號中指明需使用的API,并且可以用as指定別名。

ES6 Module 的基本用法就是這樣,可以看到確實是有些薄弱,而且目前還沒有瀏覽器能支持,只能說它是面向未來了。
  目前我們可以使用一些第三方模塊來對 ES6 進行編譯,轉(zhuǎn)化為可以使用的ES5代碼,或者是符合 AMD 規(guī)范的模塊,例如 ES6 module transpiler。另外有一個項目也提供了加載ES6模塊的方法,es6-module-loader,不過這都是一些臨時的方案,或許明年ES7一發(fā)布,模塊的加載有了標準,瀏覽器給與了實現(xiàn),這些工具也就沒有用武之地了。

未來還是很值得期待的,從語言的標準上支持模塊化,js 就可以更加自信的走進大規(guī)模企業(yè)級開發(fā)。


參考資料:

前端模塊化開發(fā)那點歷史
Modules/AsynchronousDefinition
AMD:瀏覽器中的模塊規(guī)范
JavaScript模塊化開發(fā)庫之SeaJS
AMD 的 CommonJS wrapping
Upgrading to RequireJS 2.0 - Delayed module evaluation

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

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