前端性能優(yōu)化之 JavaScript

前言

本文為 《高性能 JavaScript》 讀書(shū)筆記垮抗,是利用中午休息時(shí)間、下班時(shí)間以及周末整理出來(lái)的,此書(shū)雖有點(diǎn)老舊经瓷,但談?wù)摰男阅軆?yōu)化話題是每位同學(xué)必須理解和掌握的,業(yè)務(wù)響應(yīng)速度直接影響用戶體驗(yàn)洞难。

一舆吮、加載和運(yùn)行

大多數(shù)瀏覽器使用單進(jìn)程處理 UI 更新和 JavaScript 運(yùn)行等多個(gè)任務(wù),而同一時(shí)間只能有一個(gè)任務(wù)被執(zhí)行

腳本位置

將所有script標(biāo)簽放在頁(yè)面底部队贱,緊靠</body>上方色冀,以保證頁(yè)面腳本運(yùn)行之前完成解析

<html>
  <head> </head>
  <body>
    <p>Hello World</p>
    <!--  -->
    <script type="text/javascript" src="file.js"></script>
  </body>
</html>

defer & async

常規(guī)script腳本瀏覽器會(huì)立即加載并執(zhí)行,異步加載使用asyncdefer
二者區(qū)別在于aysnc為無(wú)序柱嫌,defer會(huì)異步根據(jù)腳本位置先后依次加載執(zhí)行

<!-- file1呐伞、file2依次加載 -->
<script type="text/javascript" src="file1.js" defer></script>
<script type="text/javascript" src="file2.js" defer></script>
<!-- file1、file2無(wú)序加載 -->
<script type="text/javascript" src="file1.js" async></script>
<script type="text/javascript" src="file2.js" async></script>

動(dòng)態(tài)腳本

無(wú)論在何處啟動(dòng)下載慎式,文件的下載和運(yùn)行都不會(huì)阻塞其他頁(yè)面處理過(guò)程伶氢。你甚至可以將這些代碼放在<head>部分而不會(huì)對(duì)其余部分的頁(yè)面代碼造成影響(除了用于下載文件的 HTTP 連接)

var script = document.createElement("script");
script.type = "text/javascript";
script.src = "file1.js";
document.getElementsByTagName("head")[0].appendChild(script);

監(jiān)聽(tīng)加載函數(shù)

function loadScript(url, callback) {
  var script = document.createElement("script");
  script.type = "text/javascript";
  if (script.readyState) {
    //IE
    script.onreadystatechange = function() {
      if (script.readyState == "loaded" || script.readyState == "complete") {
        script.onreadystatechange = null;
        callback();
      }
    };
  } else {
    //Others
    script.onload = function() {
      callback();
    };
  }
  script.src = url;
  document.getElementsByTagName("head")[0].appendChild(script);
}

XHR 注入

前提條件為同域,此處與異步加載一樣瘪吏,只不過(guò)使用的是 XMLHttpRequest


總結(jié)

  • 將所有script標(biāo)簽放在頁(yè)面底部癣防,緊靠 body 關(guān)閉標(biāo)簽上方,以保證頁(yè)面腳本運(yùn)行之前完成解析
  • 將腳本成組打包掌眠,頁(yè)面 script 標(biāo)簽越少加載越快蕾盯,響應(yīng)也就更迅速。不論外部腳本文件或者內(nèi)聯(lián)代碼都是如此

二蓝丙、數(shù)據(jù)訪問(wèn)

數(shù)據(jù)存儲(chǔ)在哪里级遭,關(guān)系到代碼運(yùn)行期間數(shù)據(jù)被檢索到的速度.每一種數(shù)據(jù)存儲(chǔ)位置都具有特定的讀寫操作負(fù)擔(dān)望拖。大多數(shù)情況下,對(duì)一個(gè)直接量和一個(gè)局部變量數(shù)據(jù)訪問(wèn)的性能差異是微不足道的挫鸽。

在 JavaScript 中有四種基本的數(shù)據(jù)訪問(wèn)位置:

  • 直接量
    直接量?jī)H僅代表自己说敏,而不存儲(chǔ)于特定位置。 JavaScript 的直接量包括:字符串丢郊,數(shù)字盔沫,布爾值,對(duì)象枫匾,數(shù)組架诞,函數(shù),正則表達(dá)式干茉,具有特殊意義的空值谴忧,以及未定義
  • 變量
    使用 var / let 關(guān)鍵字創(chuàng)建用于存儲(chǔ)數(shù)據(jù)值
  • 數(shù)組項(xiàng)
    具有數(shù)字索引,存儲(chǔ)一個(gè) JavaScript 數(shù)組對(duì)象
  • 對(duì)象成員
    具有字符串索引角虫,存儲(chǔ)一個(gè) JavaScript 對(duì)象

總結(jié)

  • 直接量與局部變量訪問(wèn)速度非城温快,數(shù)組項(xiàng)和對(duì)象成員需要更長(zhǎng)時(shí)間

  • 局部變量比域外變量訪問(wèn)速度快上遥,因?yàn)樗挥谧饔糜蜴湹牡谝粋€(gè)對(duì)象中搏屑。變量在作用域鏈的位置越深,訪問(wèn)所需要的時(shí)間越長(zhǎng)粉楚。全局變量總是最慢的辣恋,因?yàn)樗鼈兛偽挥谧饔糜蜴湹淖詈笠画h(huán)。

  • 避免使用 with 表達(dá)式模软,因?yàn)樗淖兞诉\(yùn)行期上下文的作用域鏈伟骨,謹(jǐn)慎對(duì)待 try-catch 表達(dá)式中 catch 子句,因?yàn)樗哂型瑯拥男Ч?/p>

  • 嵌套對(duì)象成員會(huì)造成重大性能影響燃异,盡量少用

  • 屬性在原型鏈中的位置越深携狭,訪問(wèn)速度越慢

  • 將對(duì)象成員、數(shù)組項(xiàng)回俐、域外變量存入局部變量能提高 js 代碼的性能

三逛腿、dom 編程

對(duì) DOM 操作代價(jià)昂貴,在富網(wǎng)頁(yè)應(yīng)用中通常是一個(gè)性能瓶頸仅颇。通常處理以下三點(diǎn)

  • 訪問(wèn)和修改 DOM 元素
  • 修改 DOM 元素的樣式单默,造成重繪和重新排版
  • 通過(guò) DOM 事件處理用戶響應(yīng)

    一個(gè)很形象的比喻是把 DOM 看成一個(gè)島嶼,把 JavaScript(ECMAScript)看成另一個(gè)島嶼忘瓦,兩者之間以一座收費(fèi)橋連接(參見(jiàn) John Hrvatin搁廓,微軟,MIX09,http://videos.visitmix.com/MIX09/T53F)境蜕。每次 ECMAScript 需要訪問(wèn) DOM 時(shí)蝙场,你需要過(guò)橋,交一次“過(guò)橋費(fèi)”粱年。你操作 DOM 次數(shù)越多售滤,費(fèi)用就越高。一般的建議是盡量減少過(guò)橋次數(shù)逼泣,努力停留在 ECMAScript 島上。

DOM 訪問(wèn)和修改

訪問(wèn)或修改元素最壞的情況是使用循環(huán)執(zhí)行此操作舟舒,特別是在 HTML 集合中使用循環(huán)

function innerHTMLLoop() {
  for (var count = 0; count < 15000; count++) {
    document.getElementById("here").innerHTML += "a";
  }
}

此函數(shù)在循環(huán)中更新頁(yè)面內(nèi)容拉庶。這段代碼的問(wèn)題是,在每次循環(huán)單元中都對(duì) DOM 元素訪問(wèn)兩次:一次
讀取 innerHTML 屬性能容秃励,另一次寫入它


優(yōu)化如下

function innerHTMLLoop2() {
  var content = "";
  for (var count = 0; count < 15000; count++) {
    content += "a";
  }
  document.getElementById("here").innerHTML += content;
}

你訪問(wèn) DOM 越多氏仗,代碼的執(zhí)行速度就越慢。因此夺鲜,一般經(jīng)驗(yàn)法則是:輕輕地觸摸 DOM皆尔,并盡量保持在 ECMAScript 范圍內(nèi)

節(jié)點(diǎn)克隆

使用 DOM 方法更新頁(yè)面內(nèi)容的另一個(gè)途徑是克隆已有 DOM 元素,而不是創(chuàng)建新的——即使用 element.cloneNode()(element 是一個(gè)已存在的節(jié)點(diǎn))代替 document.createElement();

當(dāng)布局和幾何改變時(shí)發(fā)生重排版币励,下述情況會(huì)發(fā)生:

  • 添加或刪除可見(jiàn)的 DOM 元素
  • 元素位置改變
  • 元素尺寸改變(邊距慷蠕、填充、邊框?qū)挾仁成搿捔骺弧⒏叩葘傩裕?/li>
  • 內(nèi)容改變(文本或者圖片被另一個(gè)不同尺寸的所替代)
  • 最初的頁(yè)面渲染
  • 瀏覽器窗口尺寸改變

減少重排次數(shù)

  • 改變 display 屬性,臨時(shí)從文檔上移除然后再恢復(fù)
  • 在文檔之外創(chuàng)建并更新一個(gè)文檔片段仅胞,然后將它進(jìn)行附加
  • 先創(chuàng)建更新節(jié)點(diǎn)的副本每辟,再操作副本,最后用副本更新老節(jié)點(diǎn)

總結(jié)

  • 最小化 DOM 訪問(wèn)干旧,在 JavaScript 端做盡可能多的事情
  • 在反復(fù)訪問(wèn)的地方使用局部變量存放 dom 引用
  • 謹(jǐn)慎處理 HTML 集合渠欺,因?yàn)樗鼈儽憩F(xiàn)‘存在性’,總對(duì)底層文檔重新查詢椎眯。將 length 屬性緩存到一個(gè)變量中挠将,在迭代中使用這個(gè)變量。如果經(jīng)常操作這個(gè)集合编整,可以將集合拷貝到數(shù)組中
  • 如果可以捐名,使用速度更快的 API,比如 document.querySelectorAll()和 firstElementChild()
  • 注意重繪和重排闹击,批量修改風(fēng)格镶蹋,離線操作 DOM,緩存或減少對(duì)布局信息的訪問(wèn)
  • 動(dòng)畫(huà)中使用絕對(duì)坐標(biāo),使用拖放代理
  • 使用事件托管技術(shù)中的最小化事件句柄數(shù)量

四贺归、算法與流程控制

代碼整體結(jié)構(gòu)是執(zhí)行速度的決定因素之一尖啡。代碼量少不一定執(zhí)行快尤辱,代碼量多,也不一定執(zhí)行慢,性能損失與代碼組織方式和具體問(wèn)題解決辦法直接相關(guān)护昧。

Loops

在大多數(shù)編程語(yǔ)言中,代碼執(zhí)行時(shí)間多數(shù)在循環(huán)中度過(guò)两疚。在一系列編程模式中量窘,循環(huán)是最常見(jiàn)的模式之一,提高性能必須控制好循環(huán)赵颅,死循環(huán)和長(zhǎng)時(shí)間循環(huán)會(huì)嚴(yán)重影響用戶體驗(yàn)虽另。

Types of Loops

  • for
  • while
  • do while
  • for in

前三種循環(huán)幾乎所有編程語(yǔ)言都能通用,for in 循環(huán)遍歷對(duì)象命名屬性(包括自有屬性和原型屬性)

Loop Performance

循環(huán)性能爭(zhēng)論的源頭是應(yīng)當(dāng)選用哪種循環(huán)饺谬,在 JS 中 for-in 比其他循環(huán)明顯要慢(每次迭代都要搜索實(shí)例或原型屬性)捂刺,除非對(duì)數(shù)目不詳?shù)膶?duì)象屬性進(jìn)行操作,否則避免使用 for-in募寨。除開(kāi) for-in族展,選擇循環(huán)應(yīng)當(dāng)基于需求而不是性能

減少每次迭代的操作總數(shù)可以大幅提高循環(huán)的整體性能

優(yōu)化循環(huán):

  • 減少對(duì)象成員和數(shù)組項(xiàng)的查找,比如緩存數(shù)組長(zhǎng)度拔鹰,避免每次查找數(shù)組 length 屬性
  • 倒序循環(huán)是編程語(yǔ)言中常用的性能優(yōu)化方法

編程中經(jīng)常會(huì)聽(tīng)到此說(shuō)法仪缸,現(xiàn)在來(lái)驗(yàn)證一下,測(cè)試樣例

var arr = [];
for (var i = 0; i < 100000000; i++) {
  arr[i] = i;
}
var start = +new Date();
for (var j = arr.length; j > -1; j--) {
  arr[j] = j;
}
console.log("倒序循環(huán)耗時(shí):%s ms", Date.now() - start); //約180 ms
var start = +new Date();
for (var j = 0; j < arr.length; j++) {
  arr[j] = j;
}
console.log("正序序循環(huán)耗時(shí):%s ms", Date.now() - start); //約788 ms
循環(huán)正反序測(cè)試

基于函數(shù)的迭代

盡管基于函數(shù)的迭代顯得更加便利列肢,它還是比基于循環(huán)的迭代要慢一些腹殿。每個(gè)數(shù)組項(xiàng)要關(guān)聯(lián)額外的函數(shù)調(diào)用是造成速度慢的原因。在所有情況下例书,基于函數(shù)的迭代占用時(shí)間是基于循環(huán)的迭代的八倍锣尉,因此在關(guān)注執(zhí)行時(shí)間的情況下它并不是一個(gè)合適的辦法。

條件表達(dá)式

if-else VS switch

使用 if-else 或者 switch 的流行理論是基于測(cè)試條件的數(shù)量:條件數(shù)量較大决采,傾向使用 switch自沧,更易于閱讀
當(dāng)條件體增加時(shí),if-else 性能負(fù)擔(dān)增加的程度比 switch 更多树瞭。
一般來(lái)說(shuō)拇厢,if-else 適用于判斷兩個(gè)離散的值或者幾個(gè)不同的值域,如果判斷條件較多 switch 表達(dá)式將是更理想的選擇

優(yōu)化 if-else

  • 最小化找到正確分支:將最常見(jiàn)的條件放在首位

  • 查表法 當(dāng)使用查表法時(shí)晒喷,必須完全消除所有條件判斷孝偎,操作轉(zhuǎn)換成一個(gè)數(shù)組項(xiàng)查詢或者一個(gè)對(duì)象成員查詢。

遞歸

會(huì)受瀏覽器調(diào)用棧大小的限制

迭代

任何可以用遞歸實(shí)現(xiàn)的算法可以用迭代實(shí)現(xiàn)凉敲。使用優(yōu)化的循環(huán)替代長(zhǎng)時(shí)間運(yùn)行的遞歸函數(shù)可以提高性能衣盾,因?yàn)檫\(yùn)行一個(gè)循環(huán)比反復(fù)調(diào)用一個(gè)函數(shù)的開(kāi)銷要低

斐波那契

function fibonacci(n) {
  if (n === 1) return 1;
  if (n === 2) return 2;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

制表

//制表
function memorize(fundamental, cache) {
  cache = cache || {};
  var shell = function(args) {
    if (!cache.hasOwnProperty(args)) {
      cache[args] = fundamental(args);
    }
    return cache[args];
  };
  return shell;
}
//動(dòng)態(tài)規(guī)劃
function fibonacciOptimize(n) {
  if (n === 1) return 1;
  if (n === 2) return 2;
  var current = 2;
  var previous = 1;
  for (var i = 3; i <= n; i++) {
    var temp = current;
    current = previous + current;
    previous = temp;
  }
  return current;
}
//計(jì)算階乘
var res1 = fibonacci(40);
var res2 = memorize(fibonacci)(40);
var res3 = fibonacciOptimize(40);
//計(jì)算出來(lái)的res3優(yōu)于res2寺旺,res2優(yōu)于res1

總結(jié)

運(yùn)行代碼的總量越大,優(yōu)化帶來(lái)的性能提升越明顯
正如其他編程語(yǔ)言势决,代碼的寫法與算法選用影響 JS 的運(yùn)行時(shí)間阻塑,與其他編程語(yǔ)言不同,JS 可用資源有限果复,所以優(yōu)化固然重要

  • for, while, do while 循環(huán)的性能特性相似陈莽,誰(shuí)也不比誰(shuí)更快或更慢
  • 除非要迭代遍歷一個(gè)屬性未知的對(duì)象,否則不要使用 for-in 循環(huán)
  • 改善循環(huán)的最佳方式減少每次迭代中的運(yùn)算量虽抄,并減少循環(huán)迭代次數(shù)
  • 一般來(lái)說(shuō) switch 總比 if-else 更快走搁,但總不是最好的解決方法
  • 當(dāng)判斷條件較多,查表法優(yōu)于 if-else 和 switch
  • 瀏覽器的調(diào)用棧大小限制了遞歸算法在 js 中的應(yīng)用迈窟,棧溢出導(dǎo)致其他代碼不能正常執(zhí)行
  • 如果遇到棧溢出私植,將方法修改為制表法,可以避免重復(fù)工作

五菠隆、字符串和正則表達(dá)式 String And Regular Expression

在 JS 中兵琳,正則是必不可少的東西狂秘,它的重要性遠(yuǎn)遠(yuǎn)超過(guò)煩瑣的字符串處理

字符串鏈接 Stirng Concatenation

字符串連接表現(xiàn)出驚人的性能緊張骇径。通常一個(gè)任務(wù)通過(guò)一個(gè)循環(huán),向字符串末尾不斷地添加內(nèi)容者春,來(lái)創(chuàng)建一個(gè)字符串(例如破衔,創(chuàng)建一個(gè) HTML 表或者一個(gè) XML 文檔),但此類處理在一些瀏覽器上表現(xiàn)糟糕而遭人痛恨

Method Example
+ str = 'a' + 'b' + 'c';
+= str = 'a';
str += 'b';
str += 'c';
array.join() str = ['a','b','c'].join('');
string.concat() str = 'a';
str = str.concat('b', 'c');

當(dāng)連接少量的字符串钱烟,上述的方式都很快晰筛,可根據(jù)自己的習(xí)慣使用;
當(dāng)合并字符串的長(zhǎng)度和數(shù)量增加之后拴袭,有些函數(shù)就開(kāi)始發(fā)揮其作用了

+ & +=

str += "a" + "b";

此代碼執(zhí)行時(shí)读第,發(fā)生四個(gè)步驟

  1. 內(nèi)存中創(chuàng)建了一個(gè)臨時(shí)字符串
  2. 臨時(shí)字符串的值被賦予'ab'
  3. 臨時(shí)串與 str 進(jìn)行連接
  4. 將結(jié)果賦予 str

下面的代碼通過(guò)兩個(gè)離散的表達(dá)式直接將內(nèi)容附加在 str 上避免了臨時(shí)字符串

str += "a";
str += "b";

事實(shí)上用一行代碼就可以解決

str = str + "a" + "b";

賦值表達(dá)式以 str 開(kāi)頭,一次追加一個(gè)字符串拥刻,從左至右依次連接怜瞒。如果改變了連接順序(例如:str = 'a' + str + 'b'),你會(huì)失去這種優(yōu)化,這與瀏覽器合并字符串時(shí)分配內(nèi)存的方法有關(guān)般哼。除 IE 外吴汪,瀏覽器嘗試擴(kuò)展表達(dá)式左端字符串的內(nèi)存,然后簡(jiǎn)單地將第二個(gè)字符串拷貝到它的尾部蒸眠。如果在一個(gè)循環(huán)中漾橙,基本字符串在左端,可以避免多次復(fù)制一個(gè)越來(lái)越大的基本字符串楞卡。

Array.prototype.join

Array.prototype.join 將數(shù)組的所有元素合并成一個(gè)字符串霜运,并在每個(gè)元素之間插入一個(gè)分隔符字符串脾歇。若傳遞一個(gè)空字符串,可將數(shù)組的所有元素簡(jiǎn)單的拼接起來(lái)

var start = Date.now();
var str = "I'm a thirty-five character string.",
  newStr = "",
  appends = 5000000;
while (appends--) {
  newStr += str;
}
var time = Date.now() - start;
console.log("耗時(shí):" + time + "ms"); //耗時(shí):1360ms
var start = Date.now();
var str = "I'm a thirty-five character string.",
  strs = [],
  newStr = "",
  appends = 5000000;
while (appends--) {
  strs[strs.length] = str;
}
newStr = strs.join("");
var time = Date.now() - start;
console.log("耗時(shí):" + time + "ms"); //耗時(shí):414ms

這一難以置信的改進(jìn)結(jié)果是因?yàn)楸苊饬酥貜?fù)的內(nèi)存分配和拷貝越來(lái)越大的字符串觉渴。

String.prototype.concat

原生字符串連接函數(shù)接受任意數(shù)目的參數(shù)介劫,并將每一個(gè)參數(shù)都追加在調(diào)用函數(shù)的字符串上

var str = str.concat(s1);
var str = str.concat(s1, s2, s3);
var str = String.prototype.concat.apply(str, array);

大多數(shù)情況下 concat 比簡(jiǎn)單的+或+=慢一些

Regular Expression Optimization 正則表達(dá)式優(yōu)化

許多因素影響正則表達(dá)式的效率,首先案淋,正則適配的文本千差萬(wàn)別座韵,部分匹配時(shí)比完全不匹配所用的時(shí)間要長(zhǎng),每種瀏覽器的正則引擎也有不同的內(nèi)部?jī)?yōu)化

正則表達(dá)式工作原理

  1. 編譯
    當(dāng)你創(chuàng)建了一個(gè)正則表達(dá)式對(duì)象之后(使用一個(gè)正則表達(dá)式直接量或者 RegExp 構(gòu)造器)踢京,瀏覽器檢查你的模板有沒(méi)有錯(cuò)誤誉碴,然后將它轉(zhuǎn)換成一個(gè)本機(jī)代碼例程,用執(zhí)行匹配工作瓣距。如果你將正則表達(dá)式賦給一個(gè)變量黔帕,你可以避免重復(fù)執(zhí)行此步驟。

  2. 設(shè)置起始位置
    當(dāng)一個(gè)正則表達(dá)式投入使用時(shí)蹈丸,首先要確定目標(biāo)字符串中開(kāi)始搜索的位置成黄。它是字符串的起始位置,或者由正則表達(dá)式的 lastIndex 屬性指定逻杖,但是當(dāng)它從第四步返回到這里的時(shí)候(因?yàn)閲L試匹配失敺芩辍),此位置將位于最后一次嘗試起始位置推后一個(gè)字符的位置上

  3. 匹配每個(gè)正則表達(dá)式的字元
    正則表達(dá)式一旦找好起始位置荸百,它將一個(gè)一個(gè)地掃描目標(biāo)文本和正則表達(dá)式模板闻伶。當(dāng)一個(gè)特定字元匹配失敗時(shí),正則表達(dá)式將試圖回溯到掃描之前的位置上够话,然后進(jìn)入正則表達(dá)式其他可能的路徑上

  4. 匹配成功或失敗
    如果在字符串的當(dāng)前位置上發(fā)現(xiàn)一個(gè)完全匹配蓝翰,那么正則表達(dá)式宣布成功。如果正則表達(dá)式的所有可能路徑都嘗試過(guò)了女嘲,但是沒(méi)有成功地匹配畜份,那么正則表達(dá)式引擎回到第二步,從字符串的下一個(gè)字符重新嘗試欣尼。只有字符串中的每個(gè)字符(以及最后一個(gè)字符后面的位置)都經(jīng)歷了這樣的過(guò)程之后爆雹,還沒(méi)有成功匹配,那么正則表達(dá)式就宣布徹底失敗媒至。

理解回溯

在大多數(shù)現(xiàn)代正則表達(dá)式實(shí)現(xiàn)中(包括 JavaScript 所需的)顶别,回溯是匹配過(guò)程的基本組成部分。它很大程度上也是正則表達(dá)式如此美好和強(qiáng)大的根源拒啰。然而驯绎,回溯計(jì)算代價(jià)昂貴,如果你不夠小心的話容易失控谋旦。雖然回溯是整體性能的唯一因素剩失,理解它的工作原理屈尼,以及如何減少使用頻率,可能是編寫高效正則表達(dá)式最重要的關(guān)鍵點(diǎn)拴孤。

正則表達(dá)式匹配過(guò)程

  • 當(dāng)一個(gè)正則表達(dá)式掃描目標(biāo)字符串時(shí)脾歧,它從左到右逐個(gè)掃描正則表達(dá)式的組成部分,在每個(gè)位置上測(cè)試能不能找到一個(gè)匹配演熟。對(duì)于每一個(gè)量詞和分支鞭执,都必須決定如何繼續(xù)進(jìn)行。如果是一個(gè)量詞(諸如*芒粹,+?兄纺,或者{2,}),正則表達(dá)式必須決定何時(shí)嘗試匹配更多的字符化漆;如果遇到分支(通過(guò)|操作符)估脆,它必須從這些選項(xiàng)中選擇一個(gè)進(jìn)行嘗試。
  • 每當(dāng)正則表達(dá)式做出這樣的決定座云,如果有必要的話疙赠,它會(huì)記住另一個(gè)選項(xiàng),以備將來(lái)返回后使用朦拖。如果所選方案匹配成功圃阳,正則表達(dá)式將繼續(xù)掃描正則表達(dá)式模板,如果其余部分匹配也成功了贞谓,那么匹配就結(jié)束了限佩。但是如果所選擇的方案未能發(fā)現(xiàn)相應(yīng)匹配葵诈,或者后來(lái)的匹配也失敗了裸弦,正則表達(dá)式將回溯到最后一個(gè)決策點(diǎn),然后在剩余的選項(xiàng)中選擇一個(gè)作喘。它繼續(xù)這樣下去理疙,直到找到一個(gè)匹配,或者量詞和分支選項(xiàng)的所有可能的排列組合都嘗試失敗了泞坦,那么它將放棄這一過(guò)程窖贤,然后移動(dòng)到此過(guò)程開(kāi)始位置的下一個(gè)字符上,重復(fù)此過(guò)程贰锁。

示例分析

/h(ello|appy) hippo/.test("hello there, happy hippo");

此正則表達(dá)式匹配“hello hippo”或“happy hippo”赃梧。測(cè)試一開(kāi)始,它要查找一個(gè) h豌熄,目標(biāo)字符串的第一個(gè)字母恰好就是 h授嘀,它立刻就被找到了。接下來(lái)锣险,子表達(dá)式(ello|appy)提供了兩個(gè)處理選項(xiàng)蹄皱。正則表達(dá)式選擇最左邊的選項(xiàng)(分支選擇總是從左到右進(jìn)行)览闰,檢查 ello 是否匹配字符串的下一個(gè)字符。確實(shí)匹配巷折,然后正則表達(dá)式又匹配了后面的空格压鉴。然而在這一點(diǎn)上它走進(jìn)了死胡同,因?yàn)?hippo 中的 h 不能匹配字符串中的下一個(gè)字母 t锻拘。此時(shí)正則表達(dá)式還不能放棄油吭,因?yàn)樗€沒(méi)有嘗試過(guò)所有的選擇,隨后它回溯到最后一個(gè)檢查點(diǎn)(在它匹配了首字母 h 之后的那個(gè)位置上)并嘗試匹配第二個(gè)分支選項(xiàng)署拟。但是沒(méi)有成功上鞠,而且也沒(méi)有更多的選項(xiàng)了,所以正則表達(dá)式認(rèn)為從字符串的第一個(gè)字符開(kāi)始匹配是不能成功的芯丧,因此它從第二個(gè)字符開(kāi)始芍阎,重新進(jìn)行查找。它沒(méi)有找到 h缨恒,所以就繼續(xù)向后找谴咸,直到第 14 個(gè)字母才找到,它匹配 happy 的那個(gè) h骗露。然后它再次進(jìn)入分支過(guò)程岭佳。這次 ello 未能匹配,但是回溯之后第二次分支過(guò)程中萧锉,它匹配了整個(gè)字符串“happy hippo”(如圖 5-4)珊随。匹配成功了。

回溯失控

當(dāng)一個(gè)正則表達(dá)式占用瀏覽器上秒柿隙,上分鐘或者更長(zhǎng)時(shí)間時(shí)叶洞,問(wèn)題原因很可能是回溯失控。正則表達(dá)式處理慢往往是因?yàn)槠ヅ涫∵^(guò)程慢禀崖,而不是匹配成功過(guò)程慢衩辟。

var reg = /<html>[\s\S]*?<head>[\s\S]*?<title>[\s\S]*?<\/title>[\s\S]*?<\/head>[\s\S]*?<body>[\s\S]*?<\/body>[\s\S]*?<\/html>/;
//優(yōu)化如下
var regOptimize = /<html>(?=([\s\S]*?<head>))\1(?=([\s\S]*?<title>))\2(?=([\s\S]*?<\/title>))\3(?=([\s\S]*?<\/head>))\4(?=([\s\S]*?<body>))\5(?=([\s\S]*?<\/body>))\6[\s\S]*?<\/html>/;

現(xiàn)在如果沒(méi)有尾隨的</html>那么最后一個(gè)[\s\S]*?將擴(kuò)展至字符串結(jié)束,正則表達(dá)式將立刻失敗因?yàn)闆](méi)有回溯點(diǎn)可以返回

提高正則表達(dá)式效率的更多方法

  • 關(guān)注如何讓匹配更快失敗
  • 正則表達(dá)式以簡(jiǎn)單的波附,必需的字元開(kāi)始
  • 編寫量詞模板艺晴,使它們后面的字元互相排斥
  • 減少分支的數(shù)量,縮小它們的范圍
  • 使用非捕獲組
  • 捕獲感興趣的文字掸屡,減少后處理
  • 暴露所需的字元
  • 使用適當(dāng)?shù)牧吭~
  • 將正則表達(dá)式賦給變量封寞,以重用它們
  • 將復(fù)雜的正則表達(dá)式拆分為簡(jiǎn)單的片斷

什么時(shí)候不應(yīng)該使用正則表達(dá)式

var endsWithSemicolon = /;$/.test(str);

你可能覺(jué)得很奇怪,雖說(shuō)當(dāng)前沒(méi)有哪個(gè)瀏覽器聰明到這個(gè)程度仅财,能夠意識(shí)到這個(gè)正則表達(dá)式只能匹配字符串的末尾狈究。最終它們所做的將是一個(gè)一個(gè)地測(cè)試了整個(gè)字符串。字符串的長(zhǎng)度越長(zhǎng)(包含的分號(hào)越多)满着,它占用的時(shí)間也越長(zhǎng)

var endsWithSemicolon = str.charAt(str.length - 1) == ";";

這種情況下谦炒,更好的辦法是跳過(guò)正則表達(dá)式所需的所有中間步驟贯莺,簡(jiǎn)單地檢查最后一個(gè)字符是不是分號(hào):

這個(gè)例子使用 charAt 函數(shù)在特定位置上讀取字符。字符串函數(shù) slice宁改,substr缕探,和 substring 可用于在特定位置上提取并檢查字符串的值

所有這些字符串操作函數(shù)速度都很快,當(dāng)您搜索那些不依賴正則表達(dá)式復(fù)雜特性的文本字符串時(shí)还蹲,它們有助于您避免正則表達(dá)式帶來(lái)的性能開(kāi)銷

字符串修剪

正則表達(dá)式允許你用很少的代碼實(shí)現(xiàn)一個(gè)修剪函數(shù)爹耗,這對(duì) JavaScript 關(guān)心文件大小的庫(kù)來(lái)說(shuō)十分重要∶蘸埃可能最好的全面解決方案是使用兩個(gè)子表達(dá)式:一個(gè)用于去除頭部空格潭兽,另一個(gè)用于去除尾部空格。這樣處理簡(jiǎn)單而迅速斗遏,特別是處理長(zhǎng)字符串時(shí)山卦。

//方法 用正則表達(dá)式修剪
// trim1
String.prototype.trim = function() {
  return this.replace(/^\s+/, "").replace(/\s+$/, "");
};
//trim2
String.prototype.trim = function() {
  return this.replace(/^\s+|\s+$/g, "");
};
// trim 3
String.prototype.trim = function() {
  return this.replace(/^\s*([\s\S]*?)\s*$/, "$1");
};
// trim 4
String.prototype.trim = function() {
  return this.replace(/^\s*([\s\S]*\S)?\s*$/, "$1");
};
// trim 5
String.prototype.trim = function() {
  return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
};
//方法二 不使用正則表達(dá)式修剪
String.prototype.trim = function() {
  var start = 0;
  var end = this.length - 1;
  //ws 變量包括 ECMAScript 5 中定義的所有空白字符
  var ws =
    "\n\r\t\f\x0b\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000\ufeff";
  while (ws.indexOf(this.charAt(start)) > -1) {
    start++;
  }
  while (end > start && ws.indexOf(this.charAt(end)) > -1) {
    end--;
  }
  return this.slice(start, end + 1);
};
//方法三 混合解決方案
String.prototype.trim = function() {
  var str = this.replace(/^\s+/, ""),
    end = str.length - 1,
    ws = /\s/;
  while (ws.test(str.charAt(end))) {
    end--;
  }
  return str.slice(0, end + 1);
};

簡(jiǎn)單地使用兩個(gè)子正則表達(dá)式在所有瀏覽器上處理不同內(nèi)容和長(zhǎng)度的字符串時(shí),均表現(xiàn)出穩(wěn)定的性能诵次。因此它可以說(shuō)是最全面的解決方案账蓉。混合解決方案在處理長(zhǎng)字符串時(shí)特別快逾一,其代價(jià)是代碼稍長(zhǎng)铸本,在某些瀏覽器上處理尾部長(zhǎng)空格時(shí)存在弱點(diǎn)

總結(jié)

  • 使用簡(jiǎn)單的+和+=取代數(shù)組聯(lián)合,可避免(產(chǎn)生)不必要的中間字符串
  • 當(dāng)連接數(shù)量巨大或尺寸巨大的字符串時(shí)遵堵,使用數(shù)組聯(lián)合
  • 使相鄰字元互斥箱玷,避免嵌套量詞對(duì)一個(gè)字符串的相同部分多次匹配,通過(guò)重復(fù)利用前瞻操作的原子特性去除不必要的回溯

六陌宿、響應(yīng)接口

用戶傾向于重復(fù)嘗試這些不發(fā)生明顯變化的動(dòng)作锡足,所以確保網(wǎng)頁(yè)應(yīng)用程序的響應(yīng)速度也是一個(gè)重要的性能關(guān)注點(diǎn)

瀏覽器 UI 線程

JavaScript 和 UI 更新共享的進(jìn)程通常被稱作瀏覽器 UI 線程, UI 線程圍繞著一個(gè)簡(jiǎn)單的隊(duì)列系統(tǒng)工作,任務(wù)被保存到隊(duì)列中直至進(jìn)程空閑限番。一旦空閑舱污,隊(duì)列中的下一個(gè)任務(wù)將被檢索和運(yùn)行呀舔。這些任務(wù)不是運(yùn)行 JavaScript 代碼弥虐,就是執(zhí)行 UI 更新,包括重繪和重排版.
大多數(shù)瀏覽器在 JavaScript 運(yùn)行時(shí)停止 UI 線程隊(duì)列中的任務(wù)媚赖,也就是說(shuō) JavaScript 任務(wù)必須盡快結(jié)束霜瘪,以免對(duì)用戶體驗(yàn)造成不良影響

Brendan Eich,JavaScript 的創(chuàng)造者惧磺,引用他的話說(shuō)颖对,“[JavaScript]運(yùn)行了整整幾秒鐘很可能是做錯(cuò)了什么……”

定時(shí)器基礎(chǔ)

定時(shí)器與 UI 線程交互的方式有助于分解長(zhǎng)運(yùn)行腳本成為較短的片斷

定時(shí)器精度

所有瀏覽器試圖盡可能準(zhǔn)確,但通常會(huì)發(fā)生幾毫秒滑移磨隘,或快或慢缤底。正因?yàn)檫@個(gè)原因顾患,定時(shí)器不可用于測(cè)量實(shí)際時(shí)間

總結(jié)

  • JavaScript 運(yùn)行時(shí)間不應(yīng)該超過(guò) 100 毫秒。過(guò)長(zhǎng)的運(yùn)行時(shí)間導(dǎo)致 UI 更新出現(xiàn)可察覺(jué)的延遲个唧,從而對(duì)整體用戶體驗(yàn)產(chǎn)生負(fù)面影響
  • JavaScript 運(yùn)行期間江解,瀏覽器響應(yīng)用戶交互的行為存在差異。無(wú)論如何徙歼,JavaScript 長(zhǎng)時(shí)間運(yùn)行將導(dǎo)致用戶體驗(yàn)混亂和脫節(jié)犁河。
  • 同一時(shí)間只有一個(gè)定時(shí)器存在,只有當(dāng)這個(gè)定時(shí)器結(jié)束時(shí)才創(chuàng)建一個(gè)新的定時(shí)器魄梯。以這種方式使用定時(shí)器不會(huì)帶來(lái)性能問(wèn)題
  • 定時(shí)器可用于安排代碼推遲執(zhí)行桨螺,它使得你可以將長(zhǎng)運(yùn)行腳本分解成一系列較小的任務(wù)

七、Ajax

目前最常用的方法中酿秸,XMLHttpRequest(XHR)用來(lái)異步收發(fā)數(shù)據(jù)灭翔。所有現(xiàn)代瀏覽器都能夠很好地支持它,而且能夠精細(xì)地控制發(fā)送請(qǐng)求和數(shù)據(jù)接收辣苏。你可以向請(qǐng)求報(bào)文中添加任意的頭信息和參數(shù)(包括 GET 和 POST)缠局,并讀取從服務(wù)器返回的頭信息,以及響應(yīng)文本自身

請(qǐng)求數(shù)據(jù)

五種常用技術(shù)用于向服務(wù)器請(qǐng)求數(shù)據(jù)

  • XMLHttpRequest (XHR)
  • Dynamic script tag insertion 動(dòng)態(tài)腳本標(biāo)簽插入
  • iframes
  • Comet
  • Multipart XHR 多部分的 XHR

XMLHttpRequest

//封裝ajax
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status >= 200) {
    //
  }
};
xhr.open(type, url, true);
xhr.setRequestHeader("Content-Type", contentType);
xhr.send(null);

動(dòng)態(tài)腳本標(biāo)簽插入

發(fā)送數(shù)據(jù)

  • XMLHttpRequest
  • 圖像燈標(biāo)

數(shù)據(jù)格式

通過(guò) Douglas Crockford 的發(fā)明與推廣考润,JSON 是一個(gè)輕量級(jí)并易于解析的數(shù)據(jù)格式狭园,它按照 JavaScript 對(duì)象和數(shù)組字面語(yǔ)法所編寫

Ajax 性能向?qū)?/h3>

數(shù)據(jù)傳輸技術(shù)和數(shù)據(jù)格式

  • 緩存數(shù)據(jù)
  • 設(shè)置 HTTP 頭
  • 本地存儲(chǔ)數(shù)據(jù)

總結(jié)

高性能 Ajax 包括:知道你項(xiàng)目的具體需求,選擇正確的數(shù)據(jù)格式和與之相配的傳輸技術(shù)

  • 減少請(qǐng)求數(shù)量糊治,可合并 js 和 css 文件
  • 縮短頁(yè)面的加載時(shí)間唱矛,在頁(yè)面其它內(nèi)容加載之后,使用 Ajax 獲取少量重要文件
  • JSON 是高性能 AJAX 的基礎(chǔ)井辜,尤其在使用動(dòng)態(tài)腳本注入時(shí)
  • 學(xué)會(huì)何時(shí)使用一個(gè)健壯的 Ajax 庫(kù)绎谦,何時(shí)編寫自己的底層 Ajax 代碼

封裝自己的 ajax 庫(kù)

(function(root) {
  root.MyAjax = (config = {}) => {
    let url = config.url;
    let type = config.type || "GET";
    let async = config.async || true;
    let headers = config.headers || [];
    let contentType = config.contentType || "application/json;charset=utf-8";
    let data = config.data;
    let dataType = config.dataType || "json";
    let successFn = config.success;
    let errorFn = config.error;
    let completeFn = config.complete;
    let xhr;
    if (window.XMLHttpRequest) {
      xhr = new XMLHttpRequest();
    } else {
      xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          let rsp = xhr.responseText || xhr.responseXML;
          if (dataType === "json") {
            rsp = eval("(" + rsp + ")");
          }
          successFn(rsp, xhr.statusText, xhr);
        } else {
          errorFn(xhr.statusText, xhr);
        }
        if (completeFn) {
          completeFn(xhr.statusText, xhr);
        }
      }
    };
    xhr.open(type, url, async);
    //設(shè)置超時(shí)
    if (async) {
      xhr.timeout = config.timeout || 0;
    }
    //設(shè)置請(qǐng)求頭
    for (let i = 0; i < headers.length; ++i) {
      xhr.setRequestHeader(headers[i].name, headers[i].value);
    }
    xhr.setRequestHeader("Content-Type", contentType);
    //send
    if (
      typeof data == "object" &&
      contentType === "application/x-www-form-urlencoded"
    ) {
      let s = "";
      for (attr in data) {
        s += attr + "=" + data[attr] + "&";
      }
      if (s) {
        s = s.slice(0, s.length - 1);
      }
      xhr.send(s);
    } else {
      xhr.send(data);
    }
  };
})(window);

八、編程實(shí)踐

  • 避免二次評(píng)估粥脚,比如 eval窃肠,F(xiàn)unction
  • 使用對(duì)象/數(shù)組直接量
  • 不要重復(fù)工作
  • 延遲加載
  • 條件預(yù)加載
  • 使用速度快的部分
  • 位操作運(yùn)算符
    四種位邏輯操作符
    • 位與
      比如判斷數(shù)奇偶
    num % 2 === 0; //取模與0進(jìn)行判斷
    num & 1; //位與1結(jié)果位1則為奇數(shù),為0則為偶數(shù)
    
    • 位或
    • 位異或
    • 位非
  • 位掩碼
    位掩碼在計(jì)算機(jī)科學(xué)中是一種常用的技術(shù)刷允,可同時(shí)判斷多個(gè)布爾 選項(xiàng)冤留,快速地將數(shù)字轉(zhuǎn)換為布爾標(biāo)志數(shù)組。掩碼中每個(gè)選項(xiàng)的值都等于 2 的冪
var OPTION_A = 1;
var OPTION_B = 2;
var OPTION_C = 4;
var OPTION_D = 8;
var OPTION_E = 16;

通過(guò)定義這些選項(xiàng)树灶,你可以用位或操作創(chuàng)建一個(gè)數(shù)字來(lái)包含多個(gè)選項(xiàng):

var options = OPTION_A | OPTION_C | OPTION_D;

可以使用位與操作檢查一個(gè)給定的選項(xiàng)是否可用

//is option A in the list?
if (options & OPTION_A) {
  //do something
}
//is option B in the list?
if (options & OPTION_B) {
  //do something
}

像這樣的位掩碼操作非诚伺快,正因?yàn)榍懊嫣岬降脑蛱焱ǎ僮靼l(fā)生在系統(tǒng)底層泊窘。如果許多選項(xiàng)保存在一起并經(jīng)常檢查,位掩碼有助于加快整體性能

原生方法

無(wú)論你怎樣優(yōu)化 JavaScript 代碼,它永遠(yuǎn)不會(huì)比 JavaScript 引擎提供的原生方法更快烘豹。經(jīng)驗(yàn)不足的 JavaScript 開(kāi)發(fā)者經(jīng)常犯的一個(gè)錯(cuò)誤是在代碼中進(jìn)行復(fù)雜的數(shù)學(xué)運(yùn)算瓜贾,而沒(méi)有使用內(nèi)置 Math 對(duì)象中那些性能更好的版本。Math 對(duì)象包含專門設(shè)計(jì)的屬性和方法携悯,使數(shù)學(xué)運(yùn)算更容易阐虚。

//查看Math對(duì)象所有方法
Object.getOwnPropertyNames(Math);

總結(jié)

  • 通過(guò)避免使用 eval()和 Function()構(gòu)造器避免二次評(píng)估。此外蚌卤,給 setTimeout()和 setInterval()傳遞函數(shù)參數(shù)而不是字符串參數(shù)劫哼。
  • 創(chuàng)建新對(duì)象和數(shù)組時(shí)使用對(duì)象直接量和數(shù)組直接量赎婚。它們比非直接量形式創(chuàng)建和初始化更快谈竿。
  • 避免重復(fù)進(jìn)行相同工作匪燕。當(dāng)需要檢測(cè)瀏覽器時(shí),使用延遲加載或條件預(yù)加載
  • 當(dāng)執(zhí)行數(shù)學(xué)遠(yuǎn)算時(shí)侮叮,考慮使用位操作避矢,它直接在數(shù)字底層進(jìn)行操作。
  • 原生方法總是比 JavaScript 寫的東西要快囊榜。盡量使用原生方法

九审胸、創(chuàng)建并部署高性能 JavaScript 應(yīng)用程序

  • 合并 js 文件,減少 HTTP 請(qǐng)求的數(shù)量
  • 以壓縮形式提供 js 文件(gzip 編碼)
  • 通過(guò)設(shè)置 HTTP 響應(yīng)報(bào)文頭使 js 文件可緩存卸勺,通過(guò)向文件名附加時(shí)間戳解決緩存問(wèn)題
  • 使用CDN提供 js 文件砂沛,CDN 不僅可以提高性能,它還可以為你管理壓縮和緩存

十曙求、工具

當(dāng)網(wǎng)頁(yè)或應(yīng)用程序變慢時(shí)碍庵,分析網(wǎng)上傳來(lái)的資源,分析腳本的運(yùn)行性能悟狱,使你能夠集中精力在那些需要努力優(yōu)化的地方静浴。

  • 使用網(wǎng)絡(luò)分析器找出加載腳本和其它頁(yè)面資源的瓶頸所在,這有助于決定哪些腳本需要延遲加載挤渐,或者進(jìn)行進(jìn)一步分析
  • 盡量延遲加載腳本以使頁(yè)面渲染速度更快苹享,向用戶提供更好的整體體驗(yàn)。
  • 使用性能分析器找出腳本運(yùn)行時(shí)速度慢的部分浴麻,檢查每個(gè)函數(shù)所花費(fèi)的時(shí)間得问,以及函數(shù)被調(diào)用的次數(shù),通過(guò)調(diào)用棧自身提供的一些線索來(lái)找出哪些地方應(yīng)當(dāng)努力優(yōu)化

后記

能讀到最后的同學(xué)也不容易白胀,畢竟篇幅稍長(zhǎng)椭赋。本書(shū)大概花了三周的零碎時(shí)間讀完,建議大家讀一讀或杠。如果大家在看書(shū)過(guò)程中存在疑問(wèn),不妨打開(kāi)電腦驗(yàn)證書(shū)中作者的言論宣蔚,或許會(huì)更加深刻向抢。

若文中有錯(cuò)誤歡迎大家評(píng)論指出认境,或者加我微信好友一起交流gm4118679254

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市挟鸠,隨后出現(xiàn)的幾起案子叉信,更是在濱河造成了極大的恐慌,老刑警劉巖艘希,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件硼身,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡覆享,警方通過(guò)查閱死者的電腦和手機(jī)佳遂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撒顿,“玉大人丑罪,你說(shuō)我怎么就攤上這事》锉冢” “怎么了吩屹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拧抖。 經(jīng)常有香客問(wèn)我煤搜,道長(zhǎng),這世上最難降的妖魔是什么唧席? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任宅楞,我火速辦了婚禮,結(jié)果婚禮上袱吆,老公的妹妹穿的比我還像新娘厌衙。我一直安慰自己,他們只是感情好绞绒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布婶希。 她就那樣靜靜地躺著,像睡著了一般蓬衡。 火紅的嫁衣襯著肌膚如雪喻杈。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天狰晚,我揣著相機(jī)與錄音筒饰,去河邊找鬼。 笑死壁晒,一個(gè)胖子當(dāng)著我的面吹牛瓷们,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼谬晕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼碘裕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起攒钳,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤帮孔,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后不撑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體文兢,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年焕檬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了姆坚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揩页,死狀恐怖旷偿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情爆侣,我是刑警寧澤萍程,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站兔仰,受9級(jí)特大地震影響茫负,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乎赴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一忍法、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧榕吼,春花似錦饿序、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至顽素,卻和暖如春咽弦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胁出。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工型型, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人全蝶。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓闹蒜,卻偏偏與公主長(zhǎng)得像寺枉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫂用,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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