網(wǎng)站加載性能

參考文檔為google developers中web性能部分的加載性能章節(jié)琼蚯。大家可以不看這篇文章酬凳,直接去看官網(wǎng)文檔。
官網(wǎng)地址
本文為一些總結(jié)和個(gè)人觀點(diǎn)遭庶。

目的

目的當(dāng)然是快宁仔。對(duì)于快,官方也給予了定義(RAIL)峦睡,滿足這個(gè)要求即可翎苫,避免過(guò)度優(yōu)化。
RAIL 是一種以用戶為中心的性能模型榨了。是Response煎谍、Animation、Idle龙屉、Load四個(gè)單詞的縮寫(xiě)呐粘。

  • 以用戶為中心;最終目標(biāo)不是讓您的網(wǎng)站在任何特定設(shè)備上都能運(yùn)行很快,而是使用戶滿意作岖。
  • Response 立即響應(yīng)用戶唆垃;在 100 毫秒以內(nèi)確認(rèn)用戶輸入。
  • Animation 設(shè)置動(dòng)畫(huà)或滾動(dòng)時(shí)鳍咱,在 10 毫秒以內(nèi)生成幀降盹。
  • Idle 最大程度增加主線程的空閑時(shí)間。
  • Load 持續(xù)吸引用戶谤辜;在 1000 毫秒以內(nèi)呈現(xiàn)交互內(nèi)容蓄坏。

本文只涉及Load部分:如果網(wǎng)站可以秒開(kāi),那就完美了丑念。

關(guān)鍵渲染路徑 CRP(Critical Rendering Path)

網(wǎng)站加載性能跟這個(gè)概念密切相關(guān)涡戳,這個(gè)概念是指當(dāng)瀏覽器接收到html后,到完成第一次繪制到屏幕所需要的步驟脯倚。原文地址

  1. 構(gòu)建DOM樹(shù)(Constructing the DOM Tree)
  2. 構(gòu)建CSSOM樹(shù) (Constructing the CSSOM Tree)
  3. 運(yùn)行JS (Running JavaScript)
  4. 創(chuàng)建渲染樹(shù) (Creating the Render Tree)
  5. 生成布局 (Generating the Layout)
  6. 繪制 (Painting)
image.png

1. 構(gòu)建DOM樹(shù)

DOM(Document Object Model)樹(shù)包含了頁(yè)面的所有節(jié)點(diǎn)的對(duì)象渔彰。
HTML可以被部分執(zhí)行,意思是不需要完整地構(gòu)建完DOM樹(shù)才展示到頁(yè)面推正,可以一部分一部分展示恍涂。但是,CSS和JS會(huì)阻塞這個(gè)過(guò)程植榕。

2. 構(gòu)建CSSOM樹(shù)

CSSOM(CSS Object Model)是包含了與DOM相關(guān)的樣式對(duì)象再沧。和DOM相似,但是包含了所有節(jié)點(diǎn)的樣式信息尊残,不管是明確聲明的還是繼承的(附:原文沒(méi)提炒瘸,還有瀏覽器默認(rèn)的)。
CSS是阻塞渲染的資源寝衫,意思是在CSS資源被解析前顷扩,渲染樹(shù)不會(huì)被創(chuàng)建。這是因?yàn)镃SS的級(jí)聯(lián)特性慰毅、后面加載的樣式會(huì)覆蓋前面的隘截。
CSS同樣會(huì)阻塞腳本執(zhí)行,因?yàn)镴S文件必須得等CSSOM構(gòu)建完才能運(yùn)行汹胃。

3. 運(yùn)行JS

JS是阻塞解析(HTML)的資源婶芭,意思是解析HTML文檔會(huì)被JS阻塞。
當(dāng)解析器遇到script標(biāo)簽统台,會(huì)停止解析雕擂,去獲取執(zhí)行JS啡邑。

4. 創(chuàng)建渲染樹(shù)

通過(guò)結(jié)合DOM和CSSOM贱勃,創(chuàng)建最終顯示在頁(yè)面上的渲染樹(shù)(例如display為none的元素,就不會(huì)出現(xiàn)在渲染樹(shù)中)。

5. 生成布局

這個(gè)我沒(méi)看太懂贵扰,講的是viewport大小仇穗,意思是整個(gè)視窗的大小,作為CSS樣式的上下文戚绕。跟平時(shí)講的layout有所不同纹坐。

6. 繪制

將可見(jiàn)元素(附:渲染樹(shù))轉(zhuǎn)換成像素顯示到屏幕上。
繪制的時(shí)間取決于DOM和樣式的復(fù)雜程度舞丛。

文章最后的調(diào)試圖片跟目前Chrome DevTools顯示出入比較大耘子,但是挨個(gè)展開(kāi)event log中的task,基本是一致的球切。

測(cè)試工具

  • Lighthouse
  • PageSpeed Insights
  • WebPageTest
  • Pingdom

這幾個(gè)工具都是可以出性能報(bào)告的谷誓。單個(gè)工具都不是十分準(zhǔn)確,詳盡的吨凑,要結(jié)合使用捍歪。
其中Lighthouse可以直接在DevTools中Audits面板中打開(kāi),下圖是報(bào)告(低性能模式)


Lighthouse報(bào)告

其中First Meaningful Paint(FMP)和Time to Interactive(TTI)是兩個(gè)很重要的指標(biāo)鸵钝。

下圖是WebPageTest出的報(bào)告:

WebPageTest報(bào)告

總結(jié)

為什么總結(jié)要寫(xiě)在這里呢糙臼,因?yàn)槭聼o(wú)巨細(xì),官方文檔寫(xiě)得方方面面涉及太多了恩商。數(shù)了一下变逃,細(xì)節(jié)點(diǎn)大概五六百個(gè),所以先總結(jié)理清一下思路痕届。

概覽

文章最后還介紹了webpack韧献,webpack解決了大部分問(wèn)題。
下面就按照這幾個(gè)方面闡述(與官方文檔順序不一樣)研叫。

減少資源體積

文本

JS锤窑、HTML、CSS文件都屬于文本資源嚷炉。

1. 開(kāi)發(fā)和生產(chǎn)代碼分離

現(xiàn)代框架都提供build腳本構(gòu)建生產(chǎn)版本代碼渊啰。

2. 最小化

即刪除注釋、空格申屹、換行等無(wú)用的東西绘证。

3.源碼壓縮

使用UglifyJS壓縮ES5代碼。使用babel-minify或uglify-es壓縮ES2015+代碼哗讥。

4.傳輸壓縮

即采用Gzip壓縮傳輸文本嚷那。Brotli~q11更好。

5.減少庫(kù)的使用

有些庫(kù)很大的杆煞,譬如jQuery魏宽,壓縮后也有100K左右腐泻,如果只用個(gè)選擇器,完全可以用瀏覽器自帶的querySelector等方法队询,或者使用jQuery核心庫(kù)Sizzle派桩。

6.Tree Shaking(刪除未使用代碼)

前提是使用ES6模塊,webpack在打包時(shí)候會(huì)刪除未使用的代碼蚌斩。
然后在引用代碼的時(shí)候铆惑,按需引用。
例如

import * as utils from "../../utils/utils";

改成

import { simpleSort } from "../../utils/utils";

注意:

  • babel會(huì)將ES6模塊轉(zhuǎn)成CommonJS模塊送膳,要關(guān)閉此功能员魏。
{
  "presets": [
    ["env", {
      "modules": false
    }]
  ]
}

特殊情況1(例lodash)

有些包不是按照ES6模塊開(kāi)發(fā)的,按照上面格式引用依然無(wú)用叠聋,譬如lodash

// This still pulls in all of lodash even if everything is configured right.
import { sortBy } from "lodash";

要解決此問(wèn)題逆趋,有兩種方式

  • 改用lodash-es庫(kù)
// This will only pull in the sortBy routine.
import sortBy from "lodash-es/sortBy";
  • 安裝babel-plugin-lodash插件

特殊情況2 (例moment.js)

moment庫(kù)壓縮后有223KB大小,其中170KB是語(yǔ)言包晒奕,不優(yōu)化的話闻书,會(huì)全部被打包到生產(chǎn)環(huán)境代碼去。
要解決此問(wèn)題脑慧,使用moment-locales-webpack-plugin插件魄眉。

其他特殊情況

https://github.com/GoogleChromeLabs/webpack-libs-optimizations
列舉了很多特殊情況(包括上面兩種)

圖片、視頻

優(yōu)化

一堆專業(yè)性的壓縮圖片技術(shù)和工具就不講了闷袒,就是減少圖片和視頻體積(在不影響網(wǎng)頁(yè)觀看效果前提下)坑律。

  • 選擇合適圖片格式(例:從png 232K 到 jpg 42K)
  • 刪除元數(shù)據(jù)(即一些“無(wú)用信息”:相機(jī)、拍照日期囊骤、位置等晃择,例:363K刪到325K)
  • 裁剪圖片尺寸;提供展示縮略圖也物;降低圖片質(zhì)量(227K到20K)
  • 壓縮圖片(551K到133K)

gif動(dòng)畫(huà)轉(zhuǎn)視頻

gif動(dòng)畫(huà)非常大的宫屠,可以利用工具轉(zhuǎn)換成視頻,并設(shè)置自動(dòng)播放滑蚯。
例:將一個(gè)13.7MB的gif動(dòng)畫(huà)轉(zhuǎn)換成了867K的mp4視頻浪蹂。
webm更好,但沒(méi)有mp4兼容性好告材。

<video autoplay loop muted playsinline>
  <source src="oneDoesNotSimply.webm" type="video/webm">
  <source src="oneDoesNotSimply.mp4" type="video/mp4">
</video>

根據(jù)設(shè)備信息請(qǐng)求不同圖片

srcset坤次,和媒體查詢類似,不同設(shè)備請(qǐng)求不同質(zhì)量圖片

<picture>
  <source media="(min-width: 800px)" srcset="head.jpg, head-2x.jpg 2x">
  <source media="(min-width: 450px)" srcset="head-small.jpg, head-small-2x.jpg 2x">
  <img src="head-fb.jpg" srcset="head-fb-2x.jpg 2x" alt="a head carved out of wood">
</picture>

圖像CDN

專業(yè)圖像CDN站會(huì)幫你自動(dòng)處理好圖片斥赋,但收費(fèi)缰猴。

字體

字體的優(yōu)化也講了很多,有格式問(wèn)題疤剑,有Gzip壓縮問(wèn)題滑绒。
值得注意的是 unicode-range 描述符胰舆,可以指定加載部分子集(不知道要不要服務(wù)器支持)。
例如蹬挤,您可以將 Awesome Font 系列拆分成拉丁文和日文子集,其中的每個(gè)子集將由瀏覽器根據(jù)需要下載:

@font-face {
  font-family:'Awesome Font';
  font-style: normal;
  font-weight: 400;
  src: local('Awesome Font'),
       url('/fonts/awesome-l.woff2') format('woff2'),
       url('/fonts/awesome-l.woff') format('woff'),
       url('/fonts/awesome-l.ttf') format('truetype'),
       url('/fonts/awesome-l.eot') format('embedded-opentype');
  unicode-range:U+000-5FF; /* Latin glyphs */
}

@font-face {
  font-family:'Awesome Font';
  font-style: normal;
  font-weight: 400;
  src: local('Awesome Font'),
       url('/fonts/awesome-jp.woff2') format('woff2'),
       url('/fonts/awesome-jp.woff') format('woff'),
       url('/fonts/awesome-jp.ttf') format('truetype'),
       url('/fonts/awesome-jp.eot') format('embedded-opentype');
  unicode-range:U+3000-9FFF, U+ff??; /* Japanese glyphs */
}

減少請(qǐng)求次數(shù)

這里的減少請(qǐng)求次數(shù)是指減少網(wǎng)絡(luò)請(qǐng)求次數(shù)棘幸,所以緩存也算在這個(gè)優(yōu)化里焰扳。

原因

1. 一個(gè)請(qǐng)求的生命周期

請(qǐng)求生命周期

可以看到,一個(gè)請(qǐng)求要經(jīng)過(guò)隊(duì)列等待误续、DNS查找吨悍、初始化連接、SSL蹋嵌、發(fā)送請(qǐng)求育瓜、等待、下載等過(guò)程栽烂。
每一步都是需要時(shí)間和資源的躏仇。

2. Queueing(隊(duì)列)

  • 存在更高優(yōu)先級(jí)的請(qǐng)求
  • 此源已打開(kāi)六個(gè) TCP 連接,達(dá)到限值腺办。 僅適用于 HTTP/1.0 和 HTTP/1.1
  • 瀏覽器正在短暫分配磁盤(pán)緩存中的空間
    這里的優(yōu)先級(jí)需要講一下焰手,瀏覽器擁有自己的啟發(fā)式算法,自動(dòng)為各個(gè)資源評(píng)級(jí)怀喉,等級(jí)高的會(huì)優(yōu)先去獲取資源书妻,低的會(huì)等待」#看下圖Priority列


    請(qǐng)求優(yōu)先級(jí)

    瀏覽器用自己的啟發(fā)式算法為各種資源分配了默認(rèn)級(jí)別:

  • 給了html highest級(jí)別躲履,這是肯定的
  • 給了css文件highest級(jí)別,這是因?yàn)閏ss會(huì)阻礙渲染(后面會(huì)講)
  • 然后給了js文件high級(jí)別(圖中有一些low的js文件是懶加載的)
  • 其他一些不重要的資源例如圖片都給了low級(jí)別

3. 網(wǎng)絡(luò)連接

http連接是基于TCP的聊闯,關(guān)于http和TCP及網(wǎng)絡(luò)帶寬工猜、延遲對(duì)加載網(wǎng)頁(yè)影響,可以看下面這篇文章菱蔬。
Primer on Web Performance
我的個(gè)人理解是域慷,tcp連接是一個(gè)復(fù)雜的過(guò)程,要消耗性能和資源汗销,而http/1的底層設(shè)計(jì)更是讓這個(gè)過(guò)程很耗時(shí)犹褒,多個(gè)請(qǐng)求要建立多次tcp連接(關(guān)于http/2是如何優(yōu)化的后面會(huì)講)。而且建立一個(gè)tcp連接后弛针,會(huì)根據(jù)資源的大小叠骑,往返多次。

4. 綜上所述

請(qǐng)求次數(shù)越少越好削茁,力求主頁(yè)加載的都是自己所需要的資源宙枷。

合并文本資源

就是將多個(gè)css文件合并成一個(gè)掉房,多個(gè)js文件合并成一個(gè),webpack打包工具就可以做到慰丛。

合并圖片

即將多張背景圖片合并成一張卓囚,然后通過(guò)背景圖片位置顯示不同圖片。

JS位置

關(guān)于如何加載JavaScript诅病,又是一個(gè)復(fù)雜的哪亿,歷史性的問(wèn)題,可以參照這篇文章Deep dive into the murky waters of script loading贤笆。
其復(fù)雜性在于蝇棉,要想達(dá)到如下目的,很麻煩芥永。

  • 不阻塞渲染
  • 不導(dǎo)致重復(fù)下載
  • 兼容各瀏覽器
    簡(jiǎn)單的解決方案是

1. 將script引用標(biāo)簽放在body底部

2. 如果有js腳本必須先執(zhí)行且代碼較少篡殷,可以直接硬寫(xiě)到html中,這樣可以減少請(qǐng)求次數(shù)

緩存

  • Memory Cache
  • Service Worker Cache
  • HTTP Cache (Disk Cache)
  • Push Cache
    一個(gè)請(qǐng)求會(huì)按順序讀取這幾層緩存埋涧,一旦命中就返回資源板辽。
  1. Memory Cache
    不是我們可以控制的,瀏覽器會(huì)緩存一些圖片等資源到內(nèi)存中棘催,再次遇到請(qǐng)求直接返回戳气,一個(gè)例外是preload的資源也會(huì)放在Memory Cache中。
  2. Service Worker Cache
    可以說(shuō)給我們開(kāi)了一個(gè)口子巧鸭,去定制緩存的行為瓶您,完成一些用http緩存完全做不到的事情。Service Worker完全沒(méi)有任何規(guī)則纲仍,就看我們?cè)趺慈ゾ幋a控制了呀袱,自由度極高,緩存的地方也自由:LocalStorage郑叠、IndexedDB夜赵、FilesSystem、Caches Api乡革。每個(gè)存儲(chǔ)類型都有各自的特點(diǎn)及使用場(chǎng)景寇僧。
    官方列舉了一些建議規(guī)則及實(shí)現(xiàn)代碼及其使用場(chǎng)景
    • Cache only (僅緩存)
    • Network only(僅網(wǎng)絡(luò))
    • Cache,falling back to network(優(yōu)先緩存,網(wǎng)絡(luò)備用)
    • Cache & network race(緩存和網(wǎng)絡(luò)競(jìng)賽)
    • Network falling back to cache(優(yōu)先網(wǎng)絡(luò)沸版,緩存?zhèn)溆茫?/li>
    • Cache then network(優(yōu)先緩存嘁傀,同時(shí)去請(qǐng)求網(wǎng)絡(luò)(下次請(qǐng)求可用))
    • Generic fallback(常規(guī)回退,譬如自定義一個(gè)字符串)
    • ServiceWorker-side templating(模板)
  3. HTTP Cahce(Disk Cache)
    很古老的東西了
  4. Push Cache
    這是結(jié)合HTTP/2協(xié)議的緩存视粮,簡(jiǎn)言之就是细办,請(qǐng)求index.html時(shí)候,服務(wù)器不僅返回了index.html蕾殴,也推送了其他關(guān)鍵資源笑撞,這些資源被放在Push Cache中岛啸。

HTTP/2

HTTP/2協(xié)議是好東西,如果有條件使用一定要使用茴肥,以二進(jìn)制分幀為核心從底層解決了HTTP/1的一些令人詬病的問(wèn)題坚踩。使用了HTTP2,上面的一些優(yōu)化手段可以舍棄了(主要是代碼合并方面的瓤狐,例如級(jí)聯(lián)文件瞬铸、圖片精靈)

1. 連接復(fù)用

在 HTTP/1.x 中,如果客戶端要想發(fā)起多個(gè)并行請(qǐng)求以提升性能芬首,則必須使用多個(gè) TCP 連接,不僅耗性能逼裆,浪費(fèi)資源郁稍,而且會(huì)造成隊(duì)首阻塞,效率低下胜宇。
HTTP/2 利用二進(jìn)制分幀層解決了這個(gè)問(wèn)題耀怜,可以在一個(gè)TCP連接中并行發(fā)送多個(gè)請(qǐng)求和響應(yīng),應(yīng)用速度更快桐愉、開(kāi)發(fā)更簡(jiǎn)單财破、部署成本更低。

2. 標(biāo)頭壓縮

HTTP/2 使用HPACK壓縮請(qǐng)求和響應(yīng)標(biāo)頭(header)从诲,極大減少了標(biāo)頭大凶罅 (減小85%-88%)。

我們發(fā)現(xiàn)系洛,僅僅由于標(biāo)頭壓縮俊性,頁(yè)面加載時(shí)間就減少了 45 - 1142 毫秒

3. 服務(wù)器推送

一般打開(kāi)一個(gè)網(wǎng)站,是先向服務(wù)器請(qǐng)求一個(gè)index.html文件描扯,然后解析html定页,再去加載js、css等資源绽诚,這是兩個(gè)串行過(guò)程典徊。
而HTTP/2提供了服務(wù)器推送技術(shù),打開(kāi)一個(gè)網(wǎng)站恩够,服務(wù)器知道你需要什么資源卒落,一起把index.html及js、css等靜態(tài)資源全部推送給你蜂桶,當(dāng)瀏覽器解析完html导绷,請(qǐng)求js和css時(shí)候,直接在緩存中取就可以了屎飘。減少了一次網(wǎng)絡(luò)請(qǐng)求妥曲。
有好處就有弊端:處理的不好的話贾费,重復(fù)發(fā)送資源,浪費(fèi)了緩存這一功能檐盟。

優(yōu)化關(guān)鍵渲染路徑

關(guān)鍵渲染路徑 CRP(Critical Rendering Path)的概念在最開(kāi)始就講了褂萧。
下面說(shuō)一下各個(gè)過(guò)程簡(jiǎn)介及調(diào)試方法(因官網(wǎng)的配圖都比較老了)。

文件列表

index.html

<!DOCTYPE html>
<html>
<head>
  <title>Understanding the Critical Rendering Path</title>
  <link rel="stylesheet" href="main.css">
</head>
<body>
  <header>
      <h1>Understanding the Critical Rendering Path</h1>
  </header>
  <main>
      <h2>Introduction</h2>
      <p>Lorem ipsum dolor sit amet</p>
      <div>
        <img src="./images/panda.jpeg" />
      </div>
  </main>
  <footer>
      <small>Copyright 2017</small>
  </footer>
  <script src="main.js"></script>
</body>
</html>

main.css

body { font-size: 18px; }

header { color: plum; }
h1 { font-size: 28px; }

main { color: firebrick; }
h2 { font-size: 20px; }

footer { display: none; }

main.js

function abc() {
  console.log("just a simple js");
}
abc();

panda.jpeg

panda

1.構(gòu)建對(duì)象模型(DOM)

Bytes -> Characters -> Tokens -> Nodes -> DOM
四個(gè)過(guò)程

  1. 根據(jù)編碼將字節(jié)轉(zhuǎn)換成字符串
  2. 令牌化:將字符串轉(zhuǎn)換成各個(gè)html標(biāo)簽
  3. 詞法分析:將標(biāo)簽轉(zhuǎn)換成節(jié)點(diǎn)對(duì)象(包含屬性和規(guī)則)
  4. 構(gòu)建DOM樹(shù)

調(diào)試查看(找到Parse HTML)

打開(kāi)Chrome DevTools -> Audits,評(píng)估完點(diǎn)擊 View Trace葵萎,就會(huì)進(jìn)入Perfermance面板导犹。

Parse Html

主線程Main中的火焰圖和Event Log中是一一對(duì)應(yīng)的。
我們一個(gè)一個(gè)展開(kāi)EventLog中的Task羡忘,找到Parse HTML谎痢,看到解析HTML,構(gòu)建DOM樹(shù)卷雕,話費(fèi)了1.1ms节猿。

提示:totaltime是這一過(guò)程總時(shí)間,selftime是自身執(zhí)行時(shí)間漫雕,totaltime - selftime就是其調(diào)用的子任務(wù)的執(zhí)行時(shí)間滨嘱。

2. 構(gòu)建CSS對(duì)象模型(CSSOM)

Bytes -> Characters -> Tokens -> Nodes -> DOM
與構(gòu)建DOM類似,不過(guò)產(chǎn)出是CSSOM浸间。

小細(xì)節(jié):瀏覽器會(huì)有一組默認(rèn)樣式(User Agent)太雨,也可以說(shuō)會(huì)有一個(gè)默認(rèn)CSSOM樹(shù),解析樣式其實(shí)是去替換默認(rèn)樣式魁蒜。

調(diào)試查看(找到Parse Stylesheet)

這一個(gè)我調(diào)試驗(yàn)證了很久囊扳,我覺(jué)得官網(wǎng)說(shuō)的是對(duì)應(yīng)DevTools中Recalculate Style過(guò)程,并不對(duì)兜看,應(yīng)該是Parse Stylesheet宪拥,理由如下:

  • CSSOM構(gòu)建會(huì)阻塞js執(zhí)行,main.js必須等到CSSOM構(gòu)建好铣减,才能執(zhí)行她君。這一點(diǎn)在Recalculate Style上并不能體現(xiàn),反而在Parse Stylesheet上得到了證明:main.js百分百在Parse Stylesheet后執(zhí)行
  • DOM解析構(gòu)建對(duì)應(yīng)DevTools中的Parse HTML葫哗,是藍(lán)色的缔刹。Parse Stylesheet也是藍(lán)色的,有理由相信就是CSSOM的解析構(gòu)建劣针。

官方上這樣寫(xiě)校镐,可能是之前的DevTools并沒(méi)有如此細(xì)分。網(wǎng)上查不到相關(guān)信息捺典,所以個(gè)人理解可能也不對(duì)鸟廓。

Parse Stylesheet

3. 構(gòu)建渲染樹(shù)(Render tree)

前面根據(jù)HTML構(gòu)建了完整的DOM樹(shù)、根據(jù)CSS構(gòu)建了完整的CSSOM樹(shù)。
這是兩個(gè)獨(dú)立的樹(shù)引谜,根據(jù)這兩個(gè)樹(shù)牍陌,合并計(jì)算出渲染樹(shù)。
這個(gè)過(guò)程大概是這樣:

  1. 遍歷DOM樹(shù)中可見(jiàn)節(jié)點(diǎn)(不可見(jiàn)節(jié)點(diǎn)有:script员咽、meta標(biāo)簽等毒涧;display:none的元素等,注意visibiility:hidden的不同贝室,會(huì)依然占據(jù)布局)
  2. 對(duì)每個(gè)可見(jiàn)節(jié)點(diǎn)契讲,找到匹配的CSSOM規(guī)則并應(yīng)用。
  3. 發(fā)射可見(jiàn)節(jié)點(diǎn)滑频,連同其內(nèi)容和計(jì)算的樣式(我的理解就是發(fā)射渲染樹(shù))

調(diào)試查看(找到Recalculate Style)

同上捡偏,跟官網(wǎng)有出入。而且我認(rèn)為構(gòu)建渲染樹(shù)對(duì)應(yīng)Recalculate Style理由如下:

  • 如果在main.js中調(diào)用getComputedStyle獲取樣式峡迷,會(huì)強(qiáng)制提前執(zhí)行Recalculate Style過(guò)程银伟,所以Recalculate Style應(yīng)該就是構(gòu)建渲染樹(shù)。


    Recalculate Style

    Recalculate Style還列出了相關(guān)信息:上圖是影響了8個(gè)元素

4. 布局(layout或者叫reflow)

根據(jù)渲染樹(shù)和視口(viewport)凉当,計(jì)算所有可見(jiàn)元素相對(duì)視口的絕對(duì)像素位置和像素尺寸枣申,輸出盒模型(box model)售葡。

調(diào)試查看(找到Layout)

Layout

Layout也列出了相關(guān)信息:上圖是需要布局11個(gè)元素

5. 繪制(painting或者叫rasterizing)

就是輸出到屏幕了

調(diào)試查看(找到Update Layer Tree看杭、Paint、Composite Layers挟伙、Rasterize Paint)

  • 其中Update Layer Tree我也不知道是歸于Layout還是歸于painting好楼雹。
  • 拿剪貼畫(huà)為例,要做一個(gè)三層的剪貼畫(huà)尖阔,先在一張紙上畫(huà)上草地贮缅,然后剪一個(gè)小人并畫(huà)上衣服,然后剪一個(gè)鼻子涂上紅色介却,最后一個(gè)個(gè)貼上去谴供。Paint就是畫(huà)畫(huà)的過(guò)程,Composite Layers就是貼的過(guò)程齿坷,Rasterize Paint就是最后成果圖輸出到屏幕了桂肌。(剪多大,貼在哪兒就是前面Layout的過(guò)程了)
  • 注意這里的Rasterize Paint已經(jīng)不再主線程(Main)里了永淌。
Update Layer Tree崎场、Paint、Composite Layers遂蛀、Rasterize Paint

優(yōu)化關(guān)鍵路徑

優(yōu)化關(guān)鍵路徑就是縮短1-5消耗的總時(shí)間谭跨。盡快將網(wǎng)頁(yè)渲染到屏幕上。還能縮短首次渲染后屏幕刷新的事件,為交互式內(nèi)容實(shí)現(xiàn)更高刷新率(不懂)螃宙。

CSS會(huì)阻塞渲染

由上可知蛮瞄,同時(shí)具有 DOM 和 CSSOM 才能構(gòu)建渲染樹(shù),所以要精簡(jiǎn)CSS污呼,并盡快提供裕坊。而且要用好媒體查詢,例如指定打印的css文件就不會(huì)阻塞渲染燕酷。

驗(yàn)證

為了驗(yàn)證CSS會(huì)阻塞渲染籍凝,需要讓css文件延遲響應(yīng),我搭建了一個(gè)Go服務(wù)苗缩,延遲2秒返回css文件饵蒂。


Go文件目錄

wiki.go

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "path/filepath"
    "time"
)

func main() {
    // 延遲css文件
    http.HandleFunc("/main.css", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(2000 * time.Millisecond)
        // fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
        absPath, _ := filepath.Abs("./static/main.css")
        maincss, _ := ioutil.ReadFile(absPath)
        w.Header().Set("Content-Type", "text/css")
        fmt.Fprintf(w, string(maincss))
    })

    // 其他文件直接讀取
    http.Handle("/", http.FileServer(http.Dir("static/")))
    http.ListenAndServe(":80", nil)
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="main.css" rel="stylesheet">
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <footer>footer</footer>
  </body>
</html>

新打開(kāi)一個(gè)瀏覽器標(biāo)簽頁(yè),打開(kāi)localhost酱讶,會(huì)發(fā)現(xiàn)頁(yè)面空白2秒后才顯示內(nèi)容退盯。


延遲2秒加載css文件

打開(kāi)performance調(diào)試,會(huì)發(fā)現(xiàn)有2秒的空白時(shí)間在等待css文件泻肯,然后才進(jìn)行執(zhí)行腳本渊迁,頁(yè)面渲染。

使用JS添加交互

引用:JavaScript 允許我們修改網(wǎng)頁(yè)的方方面面:內(nèi)容灶挟、樣式以及它如何響應(yīng)用戶交互琉朽。 不過(guò),JavaScript 也會(huì)阻止 DOM 構(gòu)建和延緩網(wǎng)頁(yè)渲染稚铣。 為了實(shí)現(xiàn)最佳性能箱叁,可以讓您的 JavaScript 異步執(zhí)行,并去除關(guān)鍵渲染路徑中任何不必要的 JavaScript惕医。

    1. JavaScript 可以查詢和修改 DOM 與 CSSOM
    1. JavaScript 執(zhí)行會(huì)阻止 CSSOM
    1. 除非將 JavaScript 顯式聲明為異步耕漱,否則它會(huì)阻止構(gòu)建 DOM

1. JavaScript 可以查詢和修改 DOM 與 CSSOM

這一條不用講什么了

2. JavaScript 執(zhí)行會(huì)阻塞 CSSOM

這一句我驗(yàn)證時(shí)候繞了很多彎彎,開(kāi)始我猜測(cè)是JavaScript的加載執(zhí)行結(jié)束抬伺,才會(huì)進(jìn)行CSSOM構(gòu)建螟够,多方驗(yàn)證失敗(在服務(wù)端延遲2秒返回js文件峡钓,瀏覽器會(huì)先渲染js之前的妓笙,加載執(zhí)行js后進(jìn)行第二次渲染)。后來(lái)感覺(jué)作者的意思應(yīng)該很直白椒楣,就是:JavaScript執(zhí)行和CSSOM都是在主線程執(zhí)行的给郊,JavaScript執(zhí)行時(shí)候,CSSOM構(gòu)建肯定不能執(zhí)行捧灰,就這么簡(jiǎn)單淆九。
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="main.css" rel="stylesheet">
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <script>
      var str = "";
      for(let i=0; i < 10000000; i++) {
        str += "a";
      }
    </script>
    <footer>footer</footer>
  </body>
</html>
JS執(zhí)行會(huì)阻塞CSSOM

可以看到统锤,我寫(xiě)了一個(gè)執(zhí)行長(zhǎng)達(dá)941ms的腳本,在這段腳本執(zhí)行結(jié)束后炭庙,才進(jìn)行了CSSOM構(gòu)建饲窿。

3. 除非將 JavaScript 顯式聲明為異步,否則它會(huì)阻止構(gòu)建 DOM焕蹄。

這個(gè)意思是逾雄,html中遇到j(luò)s,將會(huì)停止解析DOM,等待js下載執(zhí)行完畢繼續(xù)解析后面的DOM。
在Go服務(wù)器中將main.js延遲20ms返回状共,查看performance镇草。
index.html

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="main.css" rel="stylesheet">
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <script src="main.js"></script>
    <footer>footer</footer>
  </body>
</html>
延遲20ms返回js虱歪,無(wú)async

可以很明顯看到html被分成2段執(zhí)行了,js后面的頁(yè)面等待js加載執(zhí)行完畢才解析。
<script src="main.js"></script>改成<script src="main.js" async></script>

延遲20ms返回js,有async

加上async后钾麸,瀏覽器不等待js文件加載執(zhí)行,就解析完了dom炕桨。

4. JavaScript 執(zhí)行將暫停饭尝,直至 CSSOM 就緒

參照上面的CSS會(huì)阻塞渲染配置,JS會(huì)等待CSS加載解析完畢献宫,才會(huì)執(zhí)行钥平。
同樣在Go服務(wù)器將css延遲2秒返回

CSSOM會(huì)阻礙JavaScript執(zhí)行

這里講幾個(gè)注意點(diǎn)

  • 4和5肯定是在6之前執(zhí)行的。6和8其實(shí)并不一定嚴(yán)格按順序執(zhí)行遵蚜,如果你在6里讀取了元素樣式帖池,那么8就會(huì)在6之前執(zhí)行奈惑,因?yàn)闉g覽器必須計(jì)算完樣式(Recalculate Stylesheet)吭净,js才能獲取正確的樣式
    例如在main.js中加段代碼:
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
var spanStyle = getComputedStyle(span, null);
console.log(spanStyle.display);
js中讀取了樣式,導(dǎo)致立即計(jì)算樣式

可以看到肴甸,因?yàn)閖s讀取了樣式寂殉,導(dǎo)致了瀏覽器立即計(jì)算樣式返回給js。

這個(gè)在網(wǎng)站運(yùn)行性能里有詳細(xì)講解原在,并且如果代碼寫(xiě)得不好友扰,會(huì)導(dǎo)致反復(fù)計(jì)算樣式,性能奇差庶柿。

  • 接上條村怪,如果讀取了元素的尺寸相關(guān),還會(huì)觸發(fā)布局(Layout)
    加上代碼,讀取了元素高度
  var span = document.getElementsByTagName('span')[0];
  console.log(span.clientHeight);
讀取了元素尺寸
  • style和getComputedStyle的區(qū)別
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
console.log(span.style.display)

這段代碼并不會(huì)觸發(fā)瀏覽器計(jì)算樣式浮庐,猜測(cè)是因?yàn)檫@樣寫(xiě)(這個(gè)屬性)是讀取DOM屬性甚负。

5. 將js聲明成異步(async)

<script src="main.js" async></script>

這樣就不會(huì)阻塞構(gòu)建DOM了,不過(guò)不能保證js的執(zhí)行順序,而且各個(gè)瀏覽器實(shí)現(xiàn)的不一致梭域。

延遲加載(懶加載)斑举、預(yù)加載

大概分了幾種情況

  • 針對(duì)圖片視頻,可見(jiàn)范圍外的不加載
  • 代碼拆分病涨,首屏只加載必須的資源富玷,發(fā)生路由跳轉(zhuǎn)時(shí)候加載其他資源
  • 首屏加載必須的資源后,主動(dòng)預(yù)加載一些其他資源既穆,優(yōu)化后續(xù)體驗(yàn)

1. 延遲加載(懶加載)圖片赎懦、視頻

初始只加載屏幕內(nèi)的圖片、視頻幻工,其他用占位圖片代替铲敛。待用戶操作(滾動(dòng)頁(yè)面)到相關(guān)位置,再去獲取圖片会钝。

實(shí)現(xiàn)方式

a. 使用Intersection Observer(最優(yōu))

b. 使用Intersection Observer polyfill

npm install intersection-observer

c. 使用事件處理程序(兼容性最好的方法)

利用scroll伐蒋、resize事件;利用getBoundingClientRect方法與orientationchange事件

d. 使用庫(kù)

CSS中的圖片

CSS中的圖片有一個(gè)特點(diǎn):瀏覽會(huì)計(jì)算需要哪些背景圖焙畔,才會(huì)去加載,所以可以生命兩種樣式串远,設(shè)置不同的背景圖片宏多,通過(guò)切換樣式來(lái)達(dá)到延遲加載

視頻

視頻需要設(shè)定preload="none"屬性阻止加載視頻資源,設(shè)定poster="one-does-not-simply-placeholder.jpg"屬性來(lái)指定占位圖片澡罚。

視頻自動(dòng)播放

延遲加載視頻伸但,又要像gif那樣自動(dòng)播放。
設(shè)定autoplay后留搔,通過(guò)poster屬性設(shè)定占位符更胖,data-src設(shè)定真實(shí)資源,需要時(shí)候?qū)rc改成data-src隔显。

JavaScript 代碼拆分和延遲加載

這主要牽扯到工程化却妨,一般工程化會(huì)把單頁(yè)面應(yīng)用打包成一個(gè)js文件,這樣首屏消耗是很高的括眠,可以利用代碼拆分讓首屏只加載必須的資源彪标。

  • 動(dòng)態(tài)import
onShowCommentsClick(() => {
  import('./comments').then((comments) => {
    comments.renderComments();
  });
});

這樣Webpack就會(huì)將comments組件單獨(dú)打包,首屏不會(huì)加載掷豺,交互時(shí)候才加載

  • 單頁(yè)面應(yīng)用:按路由拆分代碼
    跟動(dòng)態(tài)import類似捞烟,不過(guò)是路由級(jí)別的拆分
  • 多頁(yè)面應(yīng)用
    按照Webpack配置不同的入口文件
// webpack.config.js
module.exports = {
  entry: {
    home: './src/Home/index.js',
    article: './src/Article/index.js',
    profile: './src/Profile/index.js'
  },
};

3. 預(yù)加載

這主要是優(yōu)化交互體驗(yàn)了账锹,跟首屏關(guān)系不大,預(yù)先加載一些即將使用的資源(譬如代碼拆分后的首屏外的其他資源)坷襟,后續(xù)操作就不會(huì)卡頓了奸柬。

資源優(yōu)先級(jí)

上面講隊(duì)列的時(shí)候稍微提到了下。
瀏覽器加載資源會(huì)有不同優(yōu)先級(jí)婴程,查看方法:DevTools -> Network廓奕,右擊表格的標(biāo)題欄,勾選Priority選項(xiàng):


優(yōu)先級(jí)

瀏覽器會(huì)采用啟發(fā)式算法档叔,對(duì)不同資源有不同的優(yōu)先級(jí)桌粉。因?yàn)榫W(wǎng)絡(luò)連接是有限的,所以會(huì)為優(yōu)先級(jí)高的資源請(qǐng)求衙四,剩下的排隊(duì)等待铃肯。
可以通過(guò)如下方式改變一些行為:

1. 預(yù)加載 preload

<link rel="preload" as="script" href="super-important.js">
<link rel="preload" as="style" href="critical.css">

preload可以提高資源的優(yōu)先級(jí),使用場(chǎng)景有:字體的加載優(yōu)先級(jí)是很低的传蹈,可以通過(guò)preload來(lái)提高押逼;preload可以加載CSS或者JS中定義使用的資源;html中引用css會(huì)阻礙渲染惦界,可以通過(guò)preload來(lái)避免挑格;

2. 預(yù)連接 preconnect

<link rel="preconnect" >

一個(gè)請(qǐng)求要經(jīng)過(guò)很多步驟,preconnect可以預(yù)先進(jìn)行一些步驟:DNS 解析沾歪,TLS 協(xié)商漂彤,TCP 握手。大大改善了一些情況下的延遲問(wèn)題灾搏。
也可以使用dns-prefetch挫望,僅僅進(jìn)行DNS查詢。

3. 預(yù)提取 prefetch

這個(gè)跟preload很容易弄混淆狂窑,prefetch是去預(yù)提取一些資源到緩存中媳板,以后會(huì)用到。

4. 預(yù)渲染 prerender

和prefetch相似蕾域,但是prerender會(huì)在后臺(tái)渲染頁(yè)面拷肌。(不知道怎么測(cè)試到旦,我測(cè)試了好像沒(méi)什么效果)

頁(yè)面繪制

這一部分不單單是針對(duì)加載頁(yè)面了旨巷,關(guān)于js執(zhí)行效率和頁(yè)面渲染效率等,打算另開(kāi)一個(gè)文章寫(xiě)添忘。

其他

性能API

Navigation Timing和Resource Timing幫我們收集性能相關(guān)信息采呐。
使用這些API有如下特點(diǎn)

  • 數(shù)據(jù)化:不用再在DevTools中一點(diǎn)點(diǎn)調(diào)試了,可以直接讀取相關(guān)數(shù)據(jù)搁骑,做出自己的性能分析報(bào)告(很多性能測(cè)試工具就是基于此API)斧吐;
  • 更準(zhǔn)確:打開(kāi)DevTools調(diào)試其實(shí)也是消耗性能的又固,加載比直接打開(kāi)網(wǎng)頁(yè)要慢
  • 可以收集用戶真實(shí)的數(shù)據(jù):直接在客戶端收集信息后上傳服務(wù)器,統(tǒng)計(jì)分析

Navigation Timing

performance.getEntriesByType("navigation");

導(dǎo)航時(shí)間線收集HTML文檔性能指標(biāo)


Navigation Timing

Resource Timing

資源時(shí)間線收集了文檔依賴的各種資源的性能指標(biāo)

performance.getEntriesByType("resource");
Resource Timing

main.js的時(shí)間線

其他相關(guān)用法

  • getEntriesByName:收集某個(gè)資源的性能指標(biāo)
  • getEntries:收集所有性能指標(biāo)
  • PerformanceObserver:自己循環(huán)遍歷性能指標(biāo)煤率,會(huì)帶來(lái)些問(wèn)題仰冠,可以實(shí)現(xiàn)這個(gè)對(duì)象實(shí)例來(lái)遍歷
  • navigator.sendBeacon:如果想將用戶性能指標(biāo)數(shù)據(jù)發(fā)給服務(wù)器,可以寫(xiě)在unload事件中蝶糯,但是一般寫(xiě)法(ajax洋只,fetch等)會(huì)阻塞瀏覽器≈绾矗可以使用navigator.sendBeacon避免這個(gè)問(wèn)題识虚。

新的時(shí)間API:performance.now

相關(guān)文檔
上面的時(shí)間線中的時(shí)間都是通過(guò)這個(gè)方法處理處理的。

背景

之前處理時(shí)間一般都是用Date相關(guān)方法妒茬。有很多缺陷

  1. 不夠精確担锤,只能到毫秒數(shù)
  2. 不僅不夠精確,還不準(zhǔn)(受制于系統(tǒng)時(shí)鐘偏移)
    例如以前自己用Date獲取時(shí)刻相減少乍钻,提示一段代碼執(zhí)行時(shí)間肛循,發(fā)現(xiàn),有時(shí)候執(zhí)行16ms银择,有時(shí)候執(zhí)行60多ms育拨,顯然是不對(duì)的。(當(dāng)然也不會(huì)偏差的過(guò)分)欢摄。

所以有了新規(guī)范熬丧,目前是High Resolution Time Level 2。
具體實(shí)現(xiàn)就是通過(guò)實(shí)現(xiàn)performance相關(guān)屬性和方法怀挠。

使用

performance.now(); // 7219725.014999989

輸出了一個(gè)相對(duì)于初始時(shí)間的亞毫秒數(shù)(sub-millisecond析蝴,其實(shí)就是高精度的毫秒數(shù))。

初始時(shí)間

可以通過(guò)

performance.timeOrigin // 1558493857670.848

獲取绿淋。這個(gè)屬性值闷畸,可以是瀏覽上下文創(chuàng)建時(shí)間,可以是新文檔對(duì)象創(chuàng)建時(shí)間吞滞,可以是worker創(chuàng)建時(shí)間佑菩。不用關(guān)心初始時(shí)間,只是個(gè)參照而已裁赠。性能分析分析的都是時(shí)間段殿漠。

requestAnimationFrame

以前寫(xiě)動(dòng)畫(huà)都是利用setTimeout來(lái)處理的,有諸多弊端佩捞,性能也不好绞幌。這個(gè)方法可以改善這個(gè)問(wèn)題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末一忱,一起剝皮案震驚了整個(gè)濱河市莲蜘,隨后出現(xiàn)的幾起案子谭确,更是在濱河造成了極大的恐慌,老刑警劉巖票渠,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逐哈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡问顷,警方通過(guò)查閱死者的電腦和手機(jī)鞠眉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)择诈,“玉大人械蹋,你說(shuō)我怎么就攤上這事⌒呱郑” “怎么了哗戈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)荷科。 經(jīng)常有香客問(wèn)我唯咬,道長(zhǎng),這世上最難降的妖魔是什么畏浆? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任胆胰,我火速辦了婚禮,結(jié)果婚禮上刻获,老公的妹妹穿的比我還像新娘蜀涨。我一直安慰自己,他們只是感情好蝎毡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布厚柳。 她就那樣靜靜地躺著,像睡著了一般沐兵。 火紅的嫁衣襯著肌膚如雪别垮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天扎谎,我揣著相機(jī)與錄音碳想,去河邊找鬼。 笑死毁靶,一個(gè)胖子當(dāng)著我的面吹牛胧奔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播老充,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼葡盗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了啡浊?” 一聲冷哼從身側(cè)響起觅够,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎巷嚣,沒(méi)想到半個(gè)月后喘先,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廷粒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年窘拯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坝茎。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涤姊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗤放,到底是詐尸還是另有隱情思喊,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布次酌,位于F島的核電站恨课,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏岳服。R本人自食惡果不足惜剂公,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吊宋。 院中可真熱鬧纲辽,春花似錦、人聲如沸璃搜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腺劣。三九已至绿贞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間橘原,已是汗流浹背籍铁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趾断,地道東北人拒名。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像芋酌,于是被迫代替她去往敵國(guó)和親增显。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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