JavaScript的工作原理:解析、抽象語法樹(AST)+ 提升編譯速度5個(gè)技巧

摘要: JS的"編譯原理"。

Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。

這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 14 篇。

如果你錯(cuò)過了前面的章節(jié),可以在這里找到它們:

概述

我們都知道運(yùn)行一大段 JavaScript 代碼性能會(huì)變得很糟糕。這段代碼不僅需要通過網(wǎng)絡(luò)傳輸,而且還需要解析、編譯成字節(jié)碼,最后執(zhí)行。在之前的文章中,我們討論了 JS 引擎、運(yùn)行時(shí)和調(diào)用堆棧等,以及主要由谷歌 Chrome 和 NodeJS 使用的V8引擎。它們?cè)谡麄€(gè) JavaScript 執(zhí)行過程中都發(fā)揮著至關(guān)重要的作用。這篇說的抽象語法樹同樣重要:在這我們將了解大多數(shù) JavaScript 引擎如何將文本解析為對(duì)機(jī)器有意義的內(nèi)容,轉(zhuǎn)換之后發(fā)生的事情以及做為 Web 開發(fā)者如何利用這一知識(shí)。

編程語言原理

那么,首先讓我們回顧一下編程語言原理。不管你使用什么編程語言,你需要一些軟件來處理源代碼以便讓計(jì)算機(jī)能夠理解。該軟件可以是解釋器,也可以是編譯器。無論你使用的是解釋型語言(JavaScript、Python、Ruby)還是編譯型語言(c#、Java、Rust),都有一個(gè)共同的部分:將源代碼作為純文本解析為 抽象語法樹(abstract syntax tree, AST) 的數(shù)據(jù)結(jié)構(gòu)。

AST 不僅以結(jié)構(gòu)化的方式顯示源代碼,而且在語義分析中扮演著重要角色。在語義分析中,編譯器驗(yàn)證程序和語言元素的語法使用是否正確。之后,使用 AST 來生成實(shí)際的字節(jié)碼或者機(jī)器碼。

抽象語法樹(abstract syntax tree 或者縮寫為 AST),或者語法樹(syntax tree),是源代碼的抽象語法結(jié)構(gòu)的樹狀表現(xiàn)形式,這里特指編程語言的源代碼。和抽象語法樹相對(duì)的是具體語法樹(concrete syntaxtree),通常稱作分析樹(parse tree)。一般的,在源代碼的翻譯和編譯過程中,語法分析器創(chuàng)建出分析樹。一旦 AST 被創(chuàng)建出來,在后續(xù)的處理過程中,比如語義分析階段,會(huì)添加一些信息。

AST 程序

AST 不僅僅是用于語言解釋器和編譯器,在計(jì)算機(jī)世界中,它們還有多種應(yīng)用。使用它們最常見的方法之一是進(jìn)行靜態(tài)代碼分析。靜態(tài)分析器不執(zhí)行輸入的代碼,但是,他們?nèi)匀恍枰斫獯a的結(jié)構(gòu)。

例如,你可能想要實(shí)現(xiàn)一個(gè)工具,該工具可以找到公共代碼結(jié)構(gòu),以便你可以重構(gòu)它們以減少重復(fù)。你可能會(huì)通過使用字符串比較來實(shí)現(xiàn)這一點(diǎn),但這個(gè)會(huì)相當(dāng)簡(jiǎn)單且有局限性。

當(dāng)然,如果你對(duì)實(shí)現(xiàn)這樣的工具感興趣,你不需要編寫自己的解析器。有許多與 Ecmascript規(guī)范完全兼容的開源項(xiàng)目。EsprimaAcorn 即是黃金搭檔,還有許多工具可以幫助解析器生成輸出,即 ASTs ,ASTs 被廣泛應(yīng)用于代碼轉(zhuǎn)換。

例如,你可能希望實(shí)現(xiàn)一個(gè)將 Python 代碼轉(zhuǎn)換為J avaScript 的轉(zhuǎn)換器。基本思想是使用Python 轉(zhuǎn)換器生成 AST,然后使用 AST 生成JavaScript代碼。

你可能會(huì)覺得難以置信,事實(shí)是 ASTs 只是部分語言的不同表示法。在解析之前,它被表示為遵循一些規(guī)則的文本,這些規(guī)則構(gòu)成了一種語言。在解析之后,它被表示為一個(gè)樹結(jié)構(gòu),其中包含與輸入文本完全相同的信息。因此,也可以進(jìn)行反向解析然后回到文本。

代碼部署后可能存在的BUG沒法實(shí)時(shí)知道,事后為了解決這些BUG,花了大量的時(shí)間進(jìn)行l(wèi)og 調(diào)試,這邊順便給大家推薦一個(gè)好用的BUG監(jiān)控工具Fundebug

JavaScript 解析

讓我們看看 AST 是如何構(gòu)建的。我們用一個(gè)簡(jiǎn)單的 JavaScript 函數(shù)作為例子:

function foo(x) {
    if (x > 10) {
        var a = 2;
        return a * x;
    }

    return x + 10;
}

解析器會(huì)產(chǎn)生如下的 AST:

image

注意,為了觀看方便,這里是解析器將生成的結(jié)果的簡(jiǎn)化版本。實(shí)際的 AST 要復(fù)雜得多。然而,這里的目的是為了運(yùn)行源碼之前的第一個(gè)步驟前。如果人想查看實(shí)際的 AST 是什么樣子,可以訪問 AST Explorer。它是一個(gè)在線工具,你以在其中輸入一些 JavaScript 并輸出對(duì)應(yīng)的 AST。

你可能會(huì)問,為什么需要知道 JavaScript解析器工作原理,畢竟這是瀏覽器工作,你想法是部分正確。下圖展示了 JavaScript 執(zhí)行過程中不同階段的耗時(shí)。仔細(xì)瞅瞅,你或許會(huì)發(fā)現(xiàn)一些有趣的東西。

image

發(fā)現(xiàn)沒? 通常情況下,瀏覽器解析 JavaScript 大約需占總執(zhí)行時(shí)間的 15%20%。我沒有具體統(tǒng)計(jì)過這些數(shù)值。這些是來自真實(shí)應(yīng)用程序和以某種方式使用 JavaScript 的網(wǎng)站的統(tǒng)計(jì)數(shù)據(jù)。也許 15% 看起來不是很多,但相信我,這是很多。

一個(gè)典型的單頁程序加載 0.4 mb 左右的 JavaScript,瀏覽器需要大約 370ms 來解析它。也許你會(huì)又說,這也不是很多嘛,本身花費(fèi)的時(shí)間并不多。但請(qǐng)記住,這只是將 JavaScript 代碼解析為 AST 所需要的時(shí)間。這并不包括運(yùn)行本身的時(shí)間,也不包括在頁面加載 ,如 CSS 和 HTML 渲染過程的耗時(shí)。這些還只涉及桌面,移動(dòng)瀏覽器的情況會(huì)更加復(fù)雜,在手機(jī)上花在解析上的時(shí)間通常是桌面瀏覽器的 2 到 5 倍。

image

上圖顯示了 1MB JavaScript 包在不同類的移動(dòng)和桌面瀏覽器解析時(shí)間。

更重要的是,為了獲得更多類原生的用戶體驗(yàn)而把越來越多的業(yè)務(wù)邏輯堆積在前端,Web 應(yīng)用程序正變得越來越復(fù)雜。你可以輕易地想到網(wǎng)絡(luò)應(yīng)用受到的性能影響。只需打開瀏覽器開發(fā)工具,然后使用該工具來解析、編譯和瀏覽器中發(fā)生的所有其他事情上所消耗的時(shí)間。

image

不幸的是,移動(dòng)瀏覽器上沒有開發(fā)者工具。不過不用擔(dān)心,這并不意味著你對(duì)此無能為力。因?yàn)橛?DeviceTiming 工具,它可以用來幫助檢測(cè)受控環(huán)境中腳本的解析和運(yùn)行時(shí)間。它通過插入代碼來封裝本地代碼,這樣每次從不同的設(shè)備訪問頁面時(shí),就可以在本地測(cè)量解析和運(yùn)行時(shí)間。

好事就是 JavaScript 引擎做了很多工作來避免冗余的工作,并得到了更好的優(yōu)化,以下為主流瀏覽器使用的技術(shù)。

例如,V8 實(shí)現(xiàn)腳本流(script streaming)和代碼緩存技術(shù)。腳本流即腳本一旦開始下載,asyncdeferred的 腳本就會(huì)在單獨(dú)的線程上解析。這意味著在下載腳本完成后幾乎立即完成解析,這會(huì)提升 10% 的頁面加載速度。

每次訪問頁面時(shí),JavaScript 代碼通常編譯為字節(jié)碼。 然而,一旦用戶訪問另一頁面,該字節(jié)碼就被丟棄。 發(fā)生這種情況是因?yàn)榫幾g后的代碼很大程度上依賴于編譯時(shí)機(jī)器的狀態(tài)和上下文。 這是 Chrome 42 引入字節(jié)碼緩存的原因。 該技術(shù)會(huì)本地緩存編譯過的代碼,這樣當(dāng)用戶返回同一頁面時(shí),諸如下載,解析和編譯等所有步驟都會(huì)被跳過。 這使得 Chrome 可以節(jié)省大約 40% 的解析和編譯時(shí)間。 此外,這還可以節(jié)省移動(dòng)設(shè)備的電量。

在 Opera 中,Carakan 引擎可以重用另一個(gè)程序最近編譯過的輸出。沒有要求代碼必須來自相同的頁面甚至同個(gè)域下。這種緩存技術(shù)實(shí)際上非常高效,還可以完全跳過編譯步驟。它依賴于典型的用戶行為和瀏覽場(chǎng)景:每當(dāng)用戶在應(yīng)用程序/網(wǎng)站中遵循某個(gè)用戶的特定瀏覽習(xí)慣,都會(huì)加載相同的 JavaScript 代碼。不過,Carakan 引擎早已被谷歌的 V8 所取代。

Opera 新的 JavaScript 引擎 “Carakan”,目前速度是其他已存在 JavaScript 引擎(基于 SunSpider)的2.5倍。其在轉(zhuǎn)化為本地機(jī)器代碼時(shí)專門針對(duì)正則表達(dá)式做了優(yōu)化。

Firefox 使用的 SpiderMonkey 引擎不會(huì)緩存所有內(nèi)容。它可以過渡到監(jiān)視階段,在這個(gè)階段中,它計(jì)算執(zhí)行給定腳本的次數(shù)。基于此計(jì)算,它推導(dǎo)出頻繁使用而可以被優(yōu)化的代碼部分。

SpiderMonkey 是 Mozilla 項(xiàng)目的一部分,是一個(gè)用 C 語言實(shí)現(xiàn)的 JavaScript 腳本引擎,另外還有一個(gè)叫做Rhino 的 Java 版本。

顯然,有些人決定什么都不做。Safari 的首席開發(fā)人員 Maciej Stachowiak 表示,Safari 不會(huì)對(duì)編譯后的字節(jié)碼進(jìn)行任何緩存。緩存技術(shù)他們是有考慮過的問題,但是他們還沒有實(shí)現(xiàn),因?yàn)樯纱a的耗時(shí)小于總運(yùn)行時(shí)間的 2%。

這些優(yōu)化不會(huì)直接影響 JavaScript 源代碼的解析,但是會(huì)盡可能完全避免。畢竟做總比沒做好點(diǎn)?

我們可以做很多事情來改善應(yīng)用程序的初始加載時(shí)間。最小化加載的 JavaScript 數(shù)量:代碼越小、解析所需要時(shí)間就越少,運(yùn)行時(shí)間也就越小。要做到這一點(diǎn),我們只能在當(dāng)前的路由上加載所需的代碼,而不是加載一大陀的代碼。例如,PRPL模式即表示該種代碼傳輸類型。或者,可以檢查代碼的依賴關(guān)系,看看是否有什么冗余的依賴導(dǎo)致代碼庫膨脹,然而,這些東西需要很大的篇幅來進(jìn)行討論。

本文的主要的目的討論作為 Web 開發(fā)人員可以做些什么來幫助 JavaScript 解析器更快地完成它的工作。還有,現(xiàn)代JavaScript 解析器使用 啟發(fā)法(heuristics) 來決定是否立即運(yùn)行指定的代碼片段或者推遲在未來的某個(gè)時(shí)候運(yùn)行。基于這些啟發(fā)法,解析器將進(jìn)行即時(shí)或懶解析。

啟發(fā)法是針對(duì)模型求解方法而言的,是一種逐次逼近最優(yōu)解的方法。這種方法對(duì)所求得的解進(jìn)行反復(fù)判斷實(shí)踐修正直至滿意為止。啟發(fā)法的特點(diǎn)是模型簡(jiǎn)單,需要進(jìn)行方案組合的個(gè)數(shù)少,因此便于找出最終答案。此方法雖不能保證得到最優(yōu)解,但只要處理得當(dāng),可獲得決策者滿意的近似最優(yōu)解。一般步驟包括:定義一個(gè)計(jì)算總費(fèi)用的方法;報(bào)定判別準(zhǔn)則;規(guī)定方案改選的途徑;建立相應(yīng)的模型;送代求解。

立即解析會(huì)運(yùn)行需要立即編譯的函數(shù)。它主要做三件事:構(gòu)建 AST,構(gòu)建作用域?qū)蛹?jí)和查找所有語法錯(cuò)誤。另一方面, 懶解析只運(yùn)行未編譯的函數(shù)。它不構(gòu)建AST,也不查找所有語法錯(cuò)誤,它只構(gòu)建作用域?qū)蛹?jí),與立即解析相比節(jié)省了大約一半的時(shí)間。

顯然,這不是一個(gè)新概念。即使像 IE 9 這樣的瀏覽器也支持這種類型的優(yōu)化,盡管與現(xiàn)在的解析器的工作方式相比,這種優(yōu)化方式還很初級(jí)。

來看一個(gè)例子,假設(shè)有以下代碼片段:

function foo() {
    function bar(x) {
        return x + 10;
    }

    function baz(x, y) {
        return x + y;
    }

    console.log(baz(100, 200));
}

foo()

就像前面的例子一樣,代碼被輸入到語法分析器中,語法分析器進(jìn)行語法分析并輸出AST,如下:

  • 聲明函數(shù) foo
  • 調(diào)用函數(shù) foo
  • foo 里聲明函數(shù) bar 接收參數(shù) x, 并返回 x 和 10 相加的結(jié)果
  • foo 里聲明函數(shù) baz 接收參數(shù) xy, 并返回 xy 相加的結(jié)果
  • 調(diào)用 baz 函數(shù)傳入 100 和 2。
  • 調(diào)用 console.log 參數(shù)為之前函數(shù)調(diào)用的返回值。
image

那么期間發(fā)生了什么? 解析器看到 bar 函數(shù)的聲明、baz 函數(shù)的聲明、bar函數(shù)的調(diào)用和 console.log 的調(diào)用。但是,解析器做了一些完全無關(guān)的額外工作即解析 bar 函數(shù)。為什么這無關(guān)緊要? 因?yàn)楹瘮?shù) bar 從來沒有被調(diào)用過(或者至少在那個(gè)時(shí)候沒有)。這是一個(gè)簡(jiǎn)單的示例,看起來可能有些不同尋常,但在許多實(shí)際應(yīng)用程序中,許多聲明的函數(shù)從未被調(diào)用。

這里不解析bar函數(shù),該函數(shù)聲明了卻沒有調(diào)用它。只在需要的時(shí)候在函數(shù)運(yùn)行前進(jìn)行真正的解析。懶解析仍然需要找到函數(shù)的整個(gè)主體并為其聲明,但僅此而已。它不需要語法樹,因?yàn)樗€沒有被處理。另外,它不會(huì)從堆中分配內(nèi)存,而堆通常會(huì)占用相當(dāng)多的系統(tǒng)資源,簡(jiǎn)而言之,跳過這些步驟會(huì)帶來很大的性能改進(jìn)。

所以之前的例子,解析器實(shí)際上會(huì)像如下這樣解析:

image

注意,這里只確認(rèn) bar 函數(shù)聲明,沒有進(jìn)入 bar 函數(shù)體。在這種情況下,函數(shù)體只是一個(gè)返回語句。但是,與大多數(shù)實(shí)際應(yīng)用程序一樣,它可以更大,包含多個(gè)返回語句、條件語句、循環(huán)、變量聲明,甚至嵌套函數(shù)聲明。這完全是在浪費(fèi)時(shí)間和系統(tǒng)資源,因?yàn)檫@個(gè)函數(shù)永遠(yuǎn)不會(huì)被調(diào)用。

這是一個(gè)相當(dāng)簡(jiǎn)單的概念,但實(shí)際上,它的實(shí)現(xiàn)是非常難的,不局限于以上示例。整個(gè)方法還可以適用于函數(shù)、循環(huán)、條件、對(duì)象等。基本上,所有需要解析的東西。

例如,下面是一個(gè)非常常見的 JavaScript 模式。

var myModule = (function() {
     // 整個(gè)模塊的邏輯
     // 返回模塊對(duì)象
})();

大多數(shù)現(xiàn)代 JavaScript 解析器都能識(shí)別這種模式,此模式表示代碼需要立即解析。

那么為什么解析器不都使用懶解析呢? 如果懶解析某些代碼,這些代碼需要立即執(zhí)行,這實(shí)際上會(huì)使代碼運(yùn)行速度變慢。需要運(yùn)行一次懶解析之后進(jìn)行另一個(gè)立即解析,這和立即解析相比,運(yùn)行速度會(huì)慢 50%。

現(xiàn)在對(duì)解析器底層原理有了大致的了解,是時(shí)候考慮如何提高解析器的解析速度。可以用這種方式編寫代碼,以便在正確的時(shí)間解析函數(shù)。大多數(shù)解析器都能識(shí)別一種模式:使用括號(hào)封裝函數(shù)。對(duì)于解析器來說,這幾乎總是一個(gè)積極的信號(hào),即函數(shù)需要立即執(zhí)行。如果解析器看到一個(gè)左括號(hào),緊接著是一個(gè)函數(shù)聲明,它將立即解析這個(gè)函數(shù)。可以通過顯式地聲明立即執(zhí)行的函數(shù)來幫助解析器加快解析速度。

假設(shè)有一個(gè)名為 foo 的函數(shù)。

function foo(x) {
    return x * 10;
}

因?yàn)闆]有明顯地標(biāo)識(shí)表明需要立即運(yùn)行該函數(shù)所以瀏覽器會(huì)進(jìn)行懶解析。然而,我們確定這是不對(duì)的,那么可以運(yùn)行兩個(gè)步驟。

首先,將函數(shù)存儲(chǔ)在一個(gè)變量中:

var foo = function foo(x) {
    return x * 10;
};

注意,這里有使用函數(shù)的名稱 foo,這不是必需的,但是建議這樣做,因?yàn)樵趻伋霎惓5那闆r下,stacktrace 會(huì)保留實(shí)際函數(shù)名稱,而不僅僅是 <anonymous>

以上事例解析器執(zhí)行懶解析,可以用括號(hào)封裝起來,讓解析器進(jìn)行立即解析:

var foo = (function foo(x) {
    return x * 10;
});

現(xiàn)在,解析器看見 function 關(guān)鍵字前的左括號(hào)便會(huì)立即進(jìn)行解析。

因?yàn)樾枰澜馕銎髟谀男┣闆r下執(zhí)行懶解析或者立即解析,所以很難手動(dòng)管理。此外,還需要花時(shí)間考慮是否立即調(diào)用某個(gè)函數(shù),肯定沒人想這么做的。

最后,這種地讓代碼更難閱讀和理解。可以使用 Optimize.js 可以幫我們做這類事情,該工具只是用來優(yōu)化 JavaScript 源代碼的初始加載時(shí)間,它們對(duì)代碼進(jìn)行靜態(tài)分析,然后通過使用括號(hào)封裝需要立即運(yùn)行的函數(shù)以便瀏覽器立即解析并準(zhǔn)備運(yùn)行它們。

像往常一樣編碼,然后有一段代碼看起來像這樣的:

(function() {
    console.log('Hello, World!');
})();

一切看起來都很好,如預(yù)期的那樣工作,而且速度很快,因?yàn)樵诤瘮?shù)聲明之前添加左括號(hào)。當(dāng)然,在進(jìn)入生產(chǎn)環(huán)境之前需要進(jìn)行代碼壓縮,以下為壓縮工具的輸出:

!function(){console.log('Hello, World!')}();

好像沒問題,代碼像以前一樣工作。但是好像少了什么,壓縮工具刪除包裹函數(shù)的括號(hào),而是在函數(shù)前放置了一個(gè)感嘆號(hào),這意味著解析器將跳過此并將執(zhí)行惰解析。

最重要的是,為了能夠執(zhí)行該函數(shù),它將在懶解析之后立即進(jìn)行立即解析。 這會(huì)使代碼運(yùn)行得更慢,幸運(yùn)的是,可以利用 Optimize.js 來解決此類問題,傳給 Optimize.js 壓縮過的代碼會(huì)輸出如下代碼:

!(function(){console.log('Hello, World!')})();

這還差不多,現(xiàn)在擁有兩全其美方案:壓縮代碼且解析器正確地識(shí)別懶解析和立即解析的函數(shù)。

預(yù)編譯

但為什么不能在服務(wù)器端完成所有這些工作呢? 畢竟,最好這樣做一次并將結(jié)果提供給客戶端,而不強(qiáng)制各個(gè)客戶端重復(fù)做該項(xiàng)事情。那么,目前正在討論引擎是否應(yīng)該提供一種執(zhí)行預(yù)編譯腳本的方法,這樣就可以節(jié)省瀏覽器運(yùn)行時(shí)間。

從本質(zhì)上講,該思路是擁有可以生成字節(jié)碼的務(wù)器端工具,這樣只需要傳輸字節(jié)碼并在客戶端運(yùn)行,之后會(huì)看到啟動(dòng)時(shí)間的一些主要差異。 這可能聽起來很誘人,但事情并非那么簡(jiǎn)單,還可能會(huì)產(chǎn)生相反的效果,因?yàn)樗鼤?huì)更大,并且很可能需要簽署代碼并出于安全原因?qū)ζ溥M(jìn)行處理。 例如,V8 團(tuán)隊(duì)正在努力解決重復(fù)解析問題,這樣預(yù)編譯有可能實(shí)際并沒有多大的用處。

提升編譯速度一些建議

  • 檢查依賴,減少不必要的依賴
  • 分割代碼為更小的塊而不是一整陀的
  • 盡可能推遲加載 JavaScript,按需要加載或者動(dòng)態(tài)加載。
  • 使用開發(fā)者工具和 DeviceTiming 來檢測(cè)性能瓶頸
  • 用像 Optimize.js 的工具來幫助解析器選擇立即解析或者懶解析以加快解析速度

原文:How JavaScript works: Parsing, Abstract Syntax Trees (ASTs) + 5 tips on how to minimize parse time

關(guān)于Fundebug

Fundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java線上應(yīng)用實(shí)時(shí)BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計(jì)處理了9億+錯(cuò)誤事件,付費(fèi)客戶有Google、360、金山軟件、百姓網(wǎng)等眾多品牌企業(yè)。歡迎大家免費(fèi)試用

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

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

  • 首先,JavaScript確實(shí)是一門編譯型語言,與C等典型編譯型語言相比,區(qū)別在于JavaScript的編譯過程(...
    環(huán)零弦閱讀 5,625評(píng)論 0 18
  • JavaScript絕對(duì)是最火的編程語言之一,一直具有很大的用戶群,隨著在服務(wù)端的使用(NodeJs),更是爆發(fā)了...
    不去解釋閱讀 2,422評(píng)論 1 16
  • V8的前世今生 V8是JavaScript渲染引擎,第一個(gè)版本隨著Chrome的發(fā)布而發(fā)布(具體時(shí)間為2008年9...
    燕京博士閱讀 2,692評(píng)論 1 3
  • 戀愛詩歌大抵都是騙人的。因?yàn)閷懺娖鋵?shí)是最好在理性下狀態(tài)完成,無情自然不應(yīng)強(qiáng)作,而情濫亦必有妨寄興。其實(shí)當(dāng)你真正喜歡...
    云中賈生閱讀 219評(píng)論 3 1
  • 【書籍名稱】 《實(shí)用影視剪輯技巧》傅正義 著 【總結(jié)內(nèi)容】 P67 §2-5 畫面造型的連接 畫面造型的組成 構(gòu)圖...
    雁十二閱讀 1,065評(píng)論 0 0