瀏覽器渲染過程與性能優(yōu)化

大家都知道萬維網的應用層使用了HTTP協議,并且用瀏覽器作為入口訪問網絡上的資源。用戶在使用瀏覽器訪問一個網站時需要先通過HTTP協議向服務器發(fā)送請求,之后服務器返回HTML文件與響應信息。這時,瀏覽器會根據HTML文件來進行解析與渲染(該階段還包括向服務器請求非內聯的CSS文件與JavaScript文件或者其他資源),最終再將頁面呈現在用戶面前。

現在知道了網頁的渲染都是由瀏覽器完成的,那么如果一個網站的頁面加載速度太慢會導致用戶體驗不夠友好,本文通過詳解瀏覽器渲染頁面的過程來引入一些基本的瀏覽器性能優(yōu)化方案。讓瀏覽器更快地渲染你的網頁并快速響應從而提高用戶體驗。

本文作者為: SylvanasSun(sylvanas.sun@gmail.com).轉載請務必將下面這段話置于文章開頭處(保留超鏈接).
本文首發(fā)自SylvanasSun Blog,原文鏈接: https://sylvanassun.github.io/2017/10/03/2017-10-03-BrowserCriticalRenderingPath

關鍵渲染路徑


瀏覽器接收到服務器返回的HTMLCSSJavaScript字節(jié)數據并對其進行解析和轉變成像素的渲染過程被稱為關鍵渲染路徑。通過優(yōu)化關鍵渲染路徑即可以縮短瀏覽器渲染頁面的時間。

瀏覽器在渲染頁面前需要先構建出DOM樹與CSSOM(如果沒有DOM樹和CSSOM樹就無法確定頁面的結構與樣式,所以這兩項是必須先構建出來的)。

DOM樹全稱為Document Object Model文檔對象模型,它是HTMLXML文檔的編程接口,提供了對文檔的結構化表示,并定義了一種可以使程序對該結構進行訪問的方式(比如JavaScript就是通過DOM來操作結構、樣式和內容)。DOM將文檔解析為一個由節(jié)點和對象組成的集合,可以說一個WEB頁面其實就是一個DOM

CSSOM樹全稱為Cascading Style Sheets Object Model層疊樣式表對象模型,它與DOM樹的含義相差不大,只不過它是CSS的對象集合。

構建DOM樹與CSSOM樹


瀏覽器從網絡或硬盤中獲得HTML字節(jié)數據后會經過一個流程將字節(jié)解析為DOM樹:

  • 編碼: 先將HTML的原始字節(jié)數據轉換為文件指定編碼的字符。

  • 令牌化: 然后瀏覽器會根據HTML規(guī)范來將字符串轉換成各種令牌(如<html><body>這樣的標簽以及標簽中的字符串和屬性等都會被轉化為令牌,每個令牌具有特殊含義和一組規(guī)則)。令牌記錄了標簽的開始與結束,通過這個特性可以輕松判斷一個標簽是否為子標簽(假設有<html><body>兩個標簽,當<html>標簽的令牌還未遇到它的結束令牌</html>就遇見了<body>標簽令牌,那么<body>就是<html>的子標簽)。

  • 生成對象: 接下來每個令牌都會被轉換成定義其屬性和規(guī)則的對象(這個對象就是節(jié)點對象)。

  • 構建完畢: DOM樹構建完成,整個對象集合就像是一棵樹形結構。可能有人會疑惑為什么DOM是一個樹形結構,這是因為標簽之間含有復雜的父子關系,樹形結構正好可以詮釋這個關系(CSSOS同理,層疊樣式也含有父子關系。例如: div p {font-size: 18px},會先尋找所有p標簽并判斷它的父標簽是否為div之后才會決定要不要采用這個樣式進行渲染)。

整個DOM樹的構建過程其實就是: 字節(jié) -> 字符 -> 令牌 -> 節(jié)點對象 -> 對象模型,下面將通過一個示例HTML代碼與配圖更形象地解釋這個過程。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
  </body>
</html>
DOM樹構建過程

當上述HTML代碼遇見<link>標簽時,瀏覽器會發(fā)送請求獲得該標簽中標記的CSS文件(使用內聯CSS可以省略請求的步驟提高速度,但沒有必要為了這點速度而丟失了模塊化與可維護性),style.css中的內容如下:

body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }

瀏覽器獲得外部CSS文件的數據后,就會像構建DOM樹一樣開始構建CSSOM樹,這個過程沒有什么特別的差別。

CSSOM樹

如果想要更詳細地去體驗一下關鍵渲染路徑的構建,可以使用Chrome開發(fā)者工具中的Timeline功能,它記錄了瀏覽器從請求頁面資源一直到渲染的各種操作過程,甚至還可以錄制某一時間段的過程(建議不要去看太大的網站,信息會比較雜亂)。

Timeline
Timeline

構建渲染樹


在構建了DOM樹和CSSOM樹之后,瀏覽器只是擁有了兩個互相獨立的對象集合,DOM樹描述了文檔的結構與內容,CSSOM樹則描述了對文檔應用的樣式規(guī)則,想要渲染出頁面,就需要將DOM樹與CSSOM樹結合在一起,這就是渲染樹。

渲染樹
渲染樹
  • 瀏覽器會先從DOM樹的根節(jié)點開始遍歷每個可見節(jié)點(不可見的節(jié)點自然就沒必要渲染到頁面了,不可見的節(jié)點還包括被CSS設置了display: none屬性的節(jié)點,值得注意的是visibility: hidden屬性并不算是不可見屬性,它的語義是隱藏元素,但元素仍然占據著布局空間,所以它會被渲染成一個空框)。

  • 對每個可見節(jié)點,找到其適配的CSS樣式規(guī)則并應用。

  • 渲染樹構建完成,每個節(jié)點都是可見節(jié)點并且都含有其內容和對應規(guī)則的樣式。

渲染樹構建完畢后,瀏覽器得到了每個可見節(jié)點的內容與其樣式,下一步工作則需要計算每個節(jié)點在窗口內的確切位置與大小,也就是布局階段。

CSS采用了一種叫做盒子模型的思維模型來表示每個節(jié)點與其他元素之間的距離,盒子模型包括外邊距(Margin),內邊距(Padding),邊框(Border),內容(Content)。頁面中的每個標簽其實都是一個個盒子。

盒子模型
盒子模型

布局階段會從渲染樹的根節(jié)點開始遍歷,然后確定每個節(jié)點對象在頁面上的確切大小與位置,布局階段的輸出是一個盒子模型,它會精確地捕獲每個元素在屏幕內的確切位置與大小,所有相對的測量值也都會被轉換為屏幕內的絕對像素值。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>
上述代碼的布局結果
上述代碼的布局結果

Layout布局事件完成后,瀏覽器會立即發(fā)出Paint SetupPaint事件,開始將渲染樹繪制成像素,繪制所需的時間跟CSS樣式的復雜度成正比,繪制完成后,用戶就可以看到頁面的最終呈現效果了。

我們對一個網頁發(fā)送請求并獲得渲染后的頁面可能也就經過了1~2秒,但瀏覽器其實已經做了上述所講的非常多的工作,總結一下瀏覽器關鍵渲染路徑的整個過程:

  • 處理HTML標記數據并生成DOM樹。

  • 處理CSS標記數據并生成CSSOM樹。

  • DOM樹與CSSOM樹合并在一起生成渲染樹。

  • 遍歷渲染樹開始布局,計算每個節(jié)點的位置信息。

  • 將每個節(jié)點繪制到屏幕。

渲染阻塞的優(yōu)化方案


瀏覽器想要渲染一個頁面就必須先構建出DOM樹與CSSOM樹,如果HTMLCSS文件結構非常龐大與復雜,這顯然會給頁面加載速度帶來嚴重影響。

所謂渲染阻塞資源,即是對該資源發(fā)送請求后還需要先構建對應的DOM樹或CSSOM樹,這種行為顯然會延遲渲染操作的開始時間。HTMLCSSJavaScript都是會對渲染產生阻塞的資源,HTML是必需的(沒有DOM還談何渲染),但還可以從CSSJavaScript著手優(yōu)化,盡可能地減少阻塞的產生。

優(yōu)化CSS


如果可以讓CSS資源只在特定條件下使用,這樣這些資源就可以在首次加載時先不進行構建CSSOM樹,只有在符合特定條件時,才會讓瀏覽器進行阻塞渲染然后構建CSSOM樹。

CSS的媒體查詢正是用來實現這個功能的,它由媒體類型以及零個或多個檢查特定媒體特征狀況的表達式組成。

<!-- 沒有使用媒體查詢,這個css資源會阻塞渲染  -->
<link href="style.css"    rel="stylesheet">
<!-- all是默認類型,它和不設置媒體查詢的效果是一樣的 -->
<link href="style.css"    rel="stylesheet" media="all">
<!-- 動態(tài)媒體查詢, 將在網頁加載時計算。
根據網頁加載時設備的方向,portrait.css 可能阻塞渲染,也可能不阻塞渲染。-->
<link href="portrait.css" rel="stylesheet" media="orientation:portrait">
<!-- 只在打印網頁時應用,因此網頁首次在瀏覽器中加載時,它不會阻塞渲染。 -->
<link href="print.css"    rel="stylesheet" media="print">

使用媒體查詢可以讓CSS資源不在首次加載中阻塞渲染,但不管是哪種CSS資源它們的下載請求都不會被忽略,瀏覽器仍然會先下載CSS文件

優(yōu)化JavaScript


當瀏覽器的HTML解析器遇到一個script標記時會暫停構建DOM,然后將控制權移交至JavaScript引擎,這時引擎會開始執(zhí)行JavaScript腳本,直到執(zhí)行結束后,瀏覽器才會從之前中斷的地方恢復,然后繼續(xù)構建DOM。每次去執(zhí)行JavaScript腳本都會嚴重地阻塞DOM樹的構建,如果JavaScript腳本還操作了CSSOM,而正好這個CSSOM還沒有下載和構建,瀏覽器甚至會延遲腳本執(zhí)行和構建DOM,直至完成其CSSOM的下載和構建。顯而易見,如果對JavaScript的執(zhí)行位置運用不當,這將會嚴重影響渲染的速度。

下面代碼中的JavaScript腳本并不會生效,這是因為DOM樹還沒有構建到<p>標簽時,JavaScript腳本就已經開始執(zhí)行了。這也是為什么經常有人在HTML文件的最下方寫內聯JavaScript代碼,又或者使用window.onload()JQuery中的$(function(){})(這兩個函數有一些區(qū)別,window.onload()是等待頁面完全加載完畢后觸發(fā)的事件,而$(function(){})DOM樹構建完畢后就會執(zhí)行)。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Hello,World</title>
    <script type="text/javascript">
        var p = document.getElementsByTagName('p')[0];
        p.textContent = 'SylvanasSun';  
    </script>
  </head>
  <body>
    <p>Hello,World!</p>
  </body>
</html>

使用async可以通知瀏覽器該腳本不需要在引用位置執(zhí)行,這樣瀏覽器就可以繼續(xù)構建DOMJavaScript腳本會在就緒后開始執(zhí)行,這樣將顯著提升頁面首次加載的性能(async只可以在src標簽中使用也就是外部引用的JavaScript文件)。

<!-- 下面2個用法效果是等價的 -->
<script type="text/javascript" src="demo_async.js" async="async"></script>
<script type="text/javascript" src="demo_async.js" async></script>

優(yōu)化關鍵渲染路徑總結


上文已經完整講述了瀏覽器是如何渲染頁面的以及渲染之前的準備工作,接下來我們以下面的案例來總結一下優(yōu)化關鍵渲染路徑的方法。

假設有一個HTML頁面,它只引入了一個CSS外部文件:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
  </body>
</html>

它的關鍵渲染路徑如下:

首先瀏覽器要先對服務器發(fā)送請求獲得HTML文件,得到HTML文件后開始構建DOM樹,在遇見<link>標簽時瀏覽器需要向服務器再次發(fā)出請求來獲得CSS文件,然后則是繼續(xù)構建DOM樹和CSSOM樹,瀏覽器合并出渲染樹,根據渲染樹進行布局計算,執(zhí)行繪制操作,頁面渲染完成。

有以下幾個用于描述關鍵渲染路徑性能的詞匯:

  • 關鍵資源:可能阻塞網頁首次渲染的資源(上圖中為2個,HTML文件與外部CSS文件style.css)。

  • 關鍵路徑長度: 獲取關鍵資源所需的往返次數或總時間(上圖為2次或以上,一次獲取HTML文件,一次獲取CSS文件,這個次數基于TCP協議的最大擁塞窗口,一個文件不一定能在一次連接內傳輸完畢)。

  • 關鍵字節(jié):所有關鍵資源文件大小的總和(上圖為9KB)。

接下來,案例代碼的需求發(fā)生了變化,它新增了一個JavaScript文件。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
    <script src="app.js"></script>
  </body>
</html>

JavaScript文件阻塞了DOM樹的構建,并且在執(zhí)行JavaScript腳本時還需要先等待構建CSSOM樹,上圖的關鍵渲染路徑特性如下:

  • 關鍵資源: 3(HTMLstyle.cssapp.js

  • 關鍵路徑長度: 2或以上(瀏覽器會在一次連接中一起下載style.cssapp.js

  • 關鍵字節(jié):11KB

現在,我們要優(yōu)化關鍵渲染路徑,首先將<script>標簽添加異步屬性async,這樣瀏覽器的HTML解析器就不會阻塞這個JavaScript文件了。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
    <script src="app.js" async></script>
  </body>
</html>
  • 關鍵資源:2(app.js為異步加載,不會成為阻塞渲染的資源)

  • 關鍵路徑長度: 2或以上

  • 關鍵字節(jié): 9KB(app.js不再是關鍵資源,所以沒有算上它的大小)

接下來對CSS進行優(yōu)化,比如添加上媒體查詢。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet" media="print">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div>![](awesome-photo.jpg)</div>
    <script src="app.js" async></script>
  </body>
</html>
  • 關鍵資源:1(app.js為異步加載,style.css只有在打印時才會使用,所以只剩下HTML一個關鍵資源,也就是說當DOM樹構建完畢,瀏覽器就會開始進行渲染)

  • 關鍵路徑長度:1或以上

  • 關鍵字節(jié):5KB

優(yōu)化關鍵渲染路徑就是在對關鍵資源、關鍵路徑長度和關鍵字節(jié)進行優(yōu)化。關鍵資源越少,瀏覽器在渲染前的準備工作就越少;同樣,關鍵路徑長度和關鍵字節(jié)關系到瀏覽器下載資源的效率,它們越少,瀏覽器下載資源的速度就越快。

其他優(yōu)化方案


除了異步加載JavaScript和使用媒體查詢外還有很多其他的優(yōu)化方案可以使頁面的首次加載變得更快,這些方案可以綜合起來使用,但核心的思想還是針對關鍵渲染路徑進行了優(yōu)化。

加載部分HTML


服務端在接收到請求時先只響應回HTML的初始部分,后續(xù)的HTML內容在需要時再通過AJAX獲得。由于服務端只發(fā)送了部分HTML文件,這讓構建DOM樹的工作量減少很多,從而讓用戶感覺頁面的加載速度很快。

注意,這個方法不能用在CSS上,瀏覽器不允許CSSOM只構建初始部分,否則會無法確定具體的樣式。

壓縮


通過對外部資源進行壓縮可以大幅度地減少瀏覽器需要下載的資源量,它會減少關鍵路徑長度與關鍵字節(jié),使頁面的加載速度變得更快。

對數據進行壓縮其實就是使用更少的位數來對數據進行重編碼。如今有非常多的壓縮算法,且每一個的作用領域也各不相同,它們的復雜度也不相同,不過在這里我不會講壓縮算法的細節(jié),感興趣的朋友可以自己Google。

在對HTMLCSSJavaScript這些文件進行壓縮之前,還需要先進行一次冗余壓縮。所謂冗余壓縮,就是去除多余的字符,例如注釋、空格符和換行符。這些字符對于程序員是有用的,畢竟沒有格式化的代碼可讀性是非常恐怖的,但它們對于瀏覽器是沒有任何意義的,去除這些冗余可以減少文件的數據量。在進行完冗余壓縮之后,再使用壓縮算法進一步對數據本身進行壓縮,例如GZIPGZIP是一個可以作用于任何字節(jié)流的通用壓縮算法,它會記憶之前已經看到的內容,然后再嘗試查找并替換重復的內容。)。

HTTP緩存


通過網絡來獲取資源通常是緩慢的,如果資源文件過于膨大,瀏覽器還需要與服務器之間進行多次往返通信才能獲得完整的資源文件。緩存可以復用之前獲取的資源,既然后端可以使用緩存來減少訪問數據庫的開銷,那前端自然也可以使用緩存來復用資源文件。

瀏覽器自帶了HTTP緩存的功能,只需要確保每個服務器響應的頭部都包含了以下的屬性:

  • ETag: ETag是一個傳遞驗證令牌,它對資源的更新進行檢查,如果資源未發(fā)生變化時不會傳送任何數據。當瀏覽器發(fā)送一個請求時,會把ETag一起發(fā)送到服務器,服務器會根據當前資源核對令牌(ETag通常是對內容進行Hash后得出的一個指紋),如果資源未發(fā)生變化,服務器將返回304 Not Modified響應,這時瀏覽器不必再次下載資源,而是繼續(xù)復用緩存。

  • Cache-Control: Cache-Control定義了緩存的策略,它規(guī)定在什么條件下可以緩存響應以及可以緩存多久

    • no-cache: no-cache表示必須先與服務器確認返回的響應是否發(fā)生了變化,然后才能使用該響應來滿足后續(xù)對同一網址的請求(每次都會根據ETag對服務器發(fā)送請求來確認變化,如果未發(fā)生變化,瀏覽器不會下載資源)。

    • no-store: no-store直接禁止瀏覽器以及所有中間緩存存儲任何版本的返回響應。簡單的說,該策略會禁止任何緩存,每次發(fā)送請求時,都會完整地下載服務器的響應。

    • public&private: 如果響應被標記為public,則即使它有關聯的HTTP身份驗證,甚至響應狀態(tài)代碼通常無法緩存,瀏覽器也可以緩存響應。如果響應被標記為private,那么這個響應通常只為單個用戶緩存,因此不允許任何中間緩存(CDN)對其進行緩存,private一般用在緩存用戶私人信息頁面。

    • max-age: max-age定義了從請求時間開始,緩存的最長時間,單位為秒。

資源預加載


Pre-fetching是一種提示瀏覽器預先加載用戶之后可能會使用到的資源的方法。

使用dns-prefetch來提前進行DNS解析,以便之后可以快速地訪問另一個主機名(瀏覽器會在加載網頁時對網頁中的域名進行解析緩存,這樣你在之后的訪問時無需進行額外的DNS解析,減少了用戶等待時間,提高了頁面加載速度)。

<link rel="dns-prefetch" href="other.hostname.com">

使用prefetch屬性可以預先下載資源,不過它的優(yōu)先級是最低的。

<link rel="prefetch"  href="/some_other_resource.jpeg">

Chrome允許使用subresource屬性指定優(yōu)先級最高的下載資源(當所有屬性為subresource的資源下載完完畢后,才會開始下載屬性為prefetch的資源)。

<link rel="subresource"  href="/some_other_resource.js">

prerender可以預先渲染好頁面并隱藏起來,之后打開這個頁面會跳過渲染階段直接呈現在用戶面前(推薦對用戶接下來必須訪問的頁面進行預渲染,否則得不償失)。

<link rel="prerender"  >

參考文獻


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

推薦閱讀更多精彩內容