關(guān)于前端性能優(yōu)化問題詳解

關(guān)于前端性能優(yōu)化問題詳解

出處:http://segmentfault.com/blogs

前端性能優(yōu)化指南

AJAX優(yōu)化

  • 緩存 AJAX
*   `異步`并不等于 `即時(shí)`。
  • 請求使用 GET
*   當(dāng)使用 `XMLHttpRequest`時(shí),而URL長度不到 `2K`,可以使用 `GET`請求數(shù)據(jù), `GET`相比 `POST`更快速。
    
    
    *   `POST`類型請求要發(fā)送兩個(gè) `TCP`數(shù)據(jù)包。
        
        
        *   先發(fā)送文件頭。
            
            
        *   再發(fā)送數(shù)據(jù)。
            
            
    *   `GET`類型請求只需要發(fā)送一個(gè) `TCP`數(shù)據(jù)包。
        
        
        *   取決于你的 `cookie`數(shù)量。

COOKIE專題

  • 減少 COOKIE的大小。
  • 使用無 COOKIE的域。
*   比如圖片 `CSS`等靜態(tài)文件放在靜態(tài)資源服務(wù)器上并配置單獨(dú)域名,客戶端請求靜態(tài)文件的時(shí)候,減少 `COOKIE`反復(fù)傳輸時(shí)對主域名的影響。

DOM優(yōu)化

  • 優(yōu)化節(jié)點(diǎn)修改。
*   使用 `cloneNode`在外部更新節(jié)點(diǎn)然后再通過 `replace`與原始節(jié)點(diǎn)互換。
  • 優(yōu)化節(jié)點(diǎn)添加

    多個(gè)節(jié)點(diǎn)插入操作,即使在外面設(shè)置節(jié)點(diǎn)的元素和風(fēng)格再插入,由于多個(gè)節(jié)點(diǎn)還是會引發(fā)多次reflow。

*   優(yōu)化的方法是創(chuàng)建 `DocumentFragment`,在其中插入節(jié)點(diǎn)后再添加到頁面。
    
    
    *   如 `JQuery`中所有的添加節(jié)點(diǎn)的操作如 `append`,都是最終調(diào)用 `DocumentFragment`來實(shí)現(xiàn)的,
        
        
        
        <pre>         `createSafeFragment(document) {
                 var list = nodeNames.split( "|" ),
                     safeFrag = document.createDocumentFragment();` </pre>
  • 優(yōu)化 CSS樣式轉(zhuǎn)換。

    如果需要?jiǎng)討B(tài)更改CSS樣式,盡量采用觸發(fā)reflow次數(shù)較少的方式。

*   如以下代碼逐條更改元素的幾何屬性,理論上會觸發(fā)多次 `reflow`。
    
    
    
    <pre>       `    element.style.fontWeight = 'bold' ;
        element.style.marginLeft= '30px' ;
        element.style.marginRight = '30px' ;`</pre>
    
    
*   可以通過直接設(shè)置元素的 `className`直接設(shè)置,只會觸發(fā)一次 `reflow`。
  • 減少 DOM元素?cái)?shù)量
*   在 `console`中執(zhí)行命令查看 `DOM`元素?cái)?shù)量。
    
    
    
    <pre>       `    `document.getElementsByTagName( '*' ).length``</pre>
    
    
*   正常頁面的 `DOM`元素?cái)?shù)量一般不應(yīng)該超過 `1000`。
    
    
*   `DOM`元素過多會使 `DOM`元素查詢效率,樣式表匹配效率降低,是頁面性能最主要的瓶頸之一。
  • DOM操作優(yōu)化。
*   `DOM`操作性能問題主要有以下原因。
    
    
    *   `DOM`元素過多導(dǎo)致元素定位緩慢。
        
        
    *   大量的 `DOM`接口調(diào)用。
        
        
        *   `JAVASCRIPT`和 `DOM`之間的交互需要通過函數(shù) `API`接口來完成,造成延時(shí),尤其是在循環(huán)語句中。
            
            
    *   `DOM`操作觸發(fā)頻繁的 `reflow(layout)`和 `repaint`。
        
        
    *   `layout`發(fā)生在 `repaint`之前,所以layout相對來說會造成更多性能損耗。
        
        
        *   `reflow(layout)`就是計(jì)算頁面元素的幾何信息。
            
            
        *   `repaint`就是繪制頁面元素。
            
            
    *   對 `DOM`進(jìn)行操作會導(dǎo)致瀏覽器執(zhí)行回流 `reflow`。
        
        
*   解決方案。
    
    
    *   純 `JAVASCRIPT`執(zhí)行時(shí)間是很短的。
        
        
    *   最小化 `DOM`訪問次數(shù),盡可能在js端執(zhí)行。
        
        
    *   如果需要多次訪問某個(gè) `DOM`節(jié)點(diǎn),請使用局部變量存儲對它的引用。
        
        
    *   謹(jǐn)慎處理 `HTML`集合( `HTML`集合實(shí)時(shí)連系底層文檔),把集合的長度緩存到一個(gè)變量中,并在迭代中使用它,如果需要經(jīng)常操作集合,建議把它拷貝到一個(gè)數(shù)組中。
        
        
    *   如果可能的話,使用速度更快的API,比如 `querySelectorAll`和 `firstElementChild`。
        
        
    *   要留意重繪和重排。
        
        
    *   批量修改樣式時(shí), `離線`操作 `DOM`樹。
        
        
    *   使用緩存,并減少訪問布局的次數(shù)。
        
        
    *   動畫中使用絕對定位,使用拖放代理。
        
        
    *   使用事件委托來減少事件處理器的數(shù)量。
  • 優(yōu)化 DOM交互

    JAVASCRIPT中, DOM操作和交互要消耗大量時(shí)間,因?yàn)樗鼈兺枰匦落秩菊麄€(gè)頁面或者某一個(gè)部分。

*   最小化 `現(xiàn)場更新`。
    
    
    *   當(dāng)需要訪問的 `DOM`部分已經(jīng)已經(jīng)被渲染為頁面中的一部分,那么 `DOM`操作和交互的過程就是再進(jìn)行一次 `現(xiàn)場更新`。
        
        
        *   `現(xiàn)場更新`是需要針對 `現(xiàn)場`(相關(guān)顯示頁面的部分結(jié)構(gòu))立即進(jìn)行更新,每一個(gè)更改(不管是插入單個(gè)字符還是移除整個(gè)片段),都有一個(gè)性能損耗。
            
            
        *   現(xiàn)場更新進(jìn)行的越多,代碼完成執(zhí)行所花的時(shí)間也越長。
            
            
*   多使用 `innerHTML`。
    
    
    *   有兩種在頁面上創(chuàng)建 `DOM`節(jié)點(diǎn)的方法:
        
        
        *   使用諸如 `createElement()`和 `appendChild()`之類的`DOM`方法。
            
            
        *   使用 `innerHTML`。
            
            
            *   當(dāng)使用 `innerHTML`設(shè)置為某個(gè)值時(shí),后臺會創(chuàng)建一個(gè) `HTML`解釋器,然后使用內(nèi)部的 `DOM`調(diào)用來創(chuàng)建 `DOM`結(jié)構(gòu),而非基于 `JAVASCRIPT`的 `DOM`調(diào)用。由于內(nèi)部方法是編譯好的而非解釋執(zhí)行,故執(zhí)行的更快。
  • 回流 reflow
*   發(fā)生場景。
    
    
    *   改變窗體大小。
        
        
    *   更改字體。
        
        
    *   添加移除stylesheet塊。
        
        
    *   內(nèi)容改變哪怕是輸入框輸入文字。
        
        
    *   CSS虛類被觸發(fā)如 :hover。
        
        
    *   更改元素的className。
        
        
    *   當(dāng)對DOM節(jié)點(diǎn)執(zhí)行新增或者刪除操作或內(nèi)容更改時(shí)。
        
        
    *   動態(tài)設(shè)置一個(gè)style樣式時(shí)(比如element.style.width="10px")。
        
        
    *   當(dāng)獲取一個(gè)必須經(jīng)過計(jì)算的尺寸值時(shí),比如訪問offsetWidth、clientHeight或者其他需要經(jīng)過計(jì)算的CSS值。
        
        
*   解決問題的關(guān)鍵,就是限制通過DOM操作所引發(fā)回流的次數(shù)。
    
    
    *   在對當(dāng)前DOM進(jìn)行操作之前,盡可能多的做一些準(zhǔn)備工作,保證N次創(chuàng)建,1次寫入。
        
        
    *   在對DOM操作之前,把要操作的元素,先從當(dāng)前DOM結(jié)構(gòu)中刪除:
        
        
        *   通過removeChild()或者replaceChild()實(shí)現(xiàn)真正意義上的刪除。
            
            
        *   設(shè)置該元素的display樣式為“none”。
            
            
    *   每次修改元素的style屬性都會觸發(fā)回流操作。
        
        
        
        <pre>         `element.style.backgroundColor = "blue";` </pre>
        
        
        *   使用更改 `className`的方式替換 `style.xxx=xxx`的方式。
            
            
        *   使用 `style.cssText = '';`一次寫入樣式。
            
            
        *   避免設(shè)置過多的行內(nèi)樣式。
            
            
        *   添加的結(jié)構(gòu)外元素盡量設(shè)置它們的位置為 `fixed`或 `absolute`。
            
            
        *   避免使用表格來布局。
            
            
        *   避免在 `CSS`中使用 `JavaScript expressions(IE only)`。
            
            
    *   將獲取的 `DOM`數(shù)據(jù)緩存起來。這種方法,對獲取那些會觸發(fā)回流操作的屬性(比如 `offsetWidth`等)尤為重要。
        
        
    *   當(dāng)對HTMLCollection對象進(jìn)行操作時(shí),應(yīng)該將訪問的次數(shù)盡可能的降至最低,最簡單的,你可以將length屬性緩存在一個(gè)本地變量中,這樣就能大幅度的提高循環(huán)的效率。

eval優(yōu)化

  • 避免 eval
*   `eval`會在時(shí)間方面帶來一些效率,但也有很多缺點(diǎn)。
    
    
    *   `eval`會導(dǎo)致代碼看起來更臟。
        
        
    *   `eval`會需要消耗大量時(shí)間。
        
        
    *   `eval`會逃過大多數(shù)壓縮工具的壓縮。

HTML優(yōu)化

  • 插入 HTML
*   `JavaScript`中使用 `document.write`生成頁面內(nèi)容會效率較低,可以找一個(gè)容器元素,比如指定一個(gè) `div`,并使用 `innerHTML`來將 `HTML`代碼插入到頁面中。
  • 避免空的 srchref
*   當(dāng) `link`標(biāo)簽的 `href`屬性為空、 `script`標(biāo)簽的 `src`屬性為空的時(shí)候,瀏覽器渲染的時(shí)候會把當(dāng)前頁面的 `URL`作為它們的屬性值,從而把頁面的內(nèi)容加載進(jìn)來作為它們的值。
  • 為文件頭指定 Expires
*   使內(nèi)容具有緩存性,避免了接下來的頁面訪問中不必要的HTTP請求。
  • 重構(gòu)HTML,把重要內(nèi)容的優(yōu)先級提高。
  • Post-load(次要加載)不是必須的資源。
  • 利用預(yù)加載優(yōu)化資源。
  • 合理架構(gòu),使DOM結(jié)構(gòu)盡量簡單。
  • 利用 LocalStorage合理緩存資源。
  • 盡量避免CSS表達(dá)式和濾鏡。
  • 嘗試使用defer方式加載Js腳本。
  • 新特性:will-change,把即將發(fā)生的改變預(yù)先告訴瀏覽器。
  • 新特性Beacon,不堵塞隊(duì)列的異步數(shù)據(jù)發(fā)送。
  • 不同之處:網(wǎng)絡(luò)緩慢,緩存更小,不令人滿意的瀏覽器處理機(jī)制。
  • 盡量多地緩存文件。
  • 使用HTML5 Web Workers來允許多線程工作。
  • 為不同的Viewports設(shè)置不同大小的Content。
  • 正確設(shè)置可Tap的目標(biāo)的大小。
  • 使用響應(yīng)式圖片。
  • 支持新接口協(xié)議(如HTTP2)。
  • 未來的緩存離線機(jī)制:Service Workers。
  • 未來的資源優(yōu)化Resource Hints(preconnect, preload, 和prerender)。
  • 使用Server-sent Events。
  • 設(shè)置一個(gè)Meta Viewport。

JIT與 GC優(yōu)化

  • untyped(無類型)。
*   `JAVASCRIPT`是個(gè)無類型的語言,這導(dǎo)致了如 `x=y+z`這種表達(dá)式可以有很多含義。
    
    
    *   `y`, `z`是數(shù)字,則 `+`表示加法。
        
        
    *   `y`, `z`是字符串,則 `+`表示字符串連接。
        
        
        
        而JS引擎內(nèi)部則使用“ `細(xì)粒度`”的類型,比如:
        
        
    *   32-bit* integer。
        
        
    1.  64-bit* floating-point。
        
        
        
        這就要求js類型-js引擎類型,需要做“boxed/unboxed(裝箱/解箱)”,在處理一次 `x=y+z`這種計(jì)算,需要經(jīng)過的步驟如下。
        
        
    2.  從內(nèi)存,讀取 `x=y+z`的操作符。
        
        
    3.  從內(nèi)存,讀取 `y`, `z`。
        
        
    4.  檢查y,z類型,確定操作的行為。
        
        
    5.  `unbox y,z`。
        
        
    6.  執(zhí)行操作符的行為。
        
        
    7.  `box x`。
        
        
    8.  把 `x`寫入內(nèi)存。
        
        
        
        只有第 `5`步驟是真正有效的操作,其他步驟都是為第 `5`步驟做準(zhǔn)備/收尾, `JAVASCRIPT`的 `untyped`特性很好用,但也為此付出了很大的性能代價(jià)。
  • JIT
*   先看看 `JIT`對 `untyped`的優(yōu)化,在 `JIT`下,執(zhí)行 `x=y+z`流程。
    
    
    1.  從內(nèi)存,讀取 `x=y+z`的操作符。
        
        
    2.  從內(nèi)存,讀取 `y`, `z`。
        
        
    3.  檢查 `y`, `z`類型,確定操作的行為。
        
        
    4.  `unbox y,z`。
        
        
    5.  執(zhí)行 操作符 的行為。
        
        
    6.  `box x`。
        
        
    7.  把 `x`寫入內(nèi)存。
        
        
*   新引擎還對“對象屬性”訪問做了優(yōu)化,解決方案叫 `inline caching`,簡稱: `IC`。簡單的說,就是做 `cache`。但如果當(dāng) `list`很大時(shí),這種方案反而影響效率。
  • Type-specializing JIT

    Type-specializing JIT引擎用來處理 typed類型(聲明類型)變量,但 JAVASCRIPT都是 untype類型的。

*   `Type-specializing JIT`的解決方案是:
    
    
    *   先通過掃描,監(jiān)測類型。
        
        
    *   通過編譯優(yōu)化(優(yōu)化對象不僅僅只是“類型”,還包括對JS代碼的優(yōu)化,但核心是類型優(yōu)化),生成類型變量。
        
        
    *   再做后續(xù)計(jì)算。
        
        
*   `Type-specializing JIT`的執(zhí)行 `x=y+z`流程:
    
    
    *   *   從內(nèi)存,讀取 `x=y+z`的操作符。
            
            
        *   從內(nèi)存,讀取 `y`, `z`。
            
            
        *   檢查 `y`, `z`類型,確定操作的行為。
            
            
        *   `unbox y,z`。
            
            
        *   執(zhí)行操作符的行為。
            
            
        *   `box x`。
            
            
        *   把 `x`寫入內(nèi)存。
            
            
            
            <pre>           `代價(jià)是:` </pre>
            
            
    *   前置的掃描類型
        
        
    *   編譯優(yōu)化。
        
        
        
        <pre>         `所以·Type-specializing JIT·的應(yīng)用是有選擇性,選擇使用這個(gè)引擎的場景包括:`</pre>
        
        
    *   熱點(diǎn)代碼。
        
        
    *   通過啟發(fā)式算法估算出來的有價(jià)值的代碼。
        
        
        
        <pre>         `另外,有2點(diǎn)也需要注意:`</pre>
        
        
    *   當(dāng)變量類型 發(fā)生變化時(shí),引擎有2種處理方式:
        
        
        *   少量變更,重編譯,再執(zhí)行。
            
            
        *   大量變更,交給JIT執(zhí)行。
            
            
    *   `數(shù)組`, `object properties`, 閉包變量 不在優(yōu)化范疇之列。

js載入優(yōu)化

  • 加快JavaScript裝入速度的工具:
*   Lab.js
    
    
    *   借助LAB.js(裝入和阻止JavaScript),你就可以并行裝入JavaScript文件,加快總的裝入過程。此外,你還可以為需要裝入的腳本設(shè)置某個(gè)順序,那樣就能確保依賴關(guān)系的完整性。此外,開發(fā)者聲稱其網(wǎng)站上的速度提升了2倍。
  • 使用適當(dāng)?shù)腃DN:
*   現(xiàn)在許多網(wǎng)頁使用內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)。它可以改進(jìn)你的緩存機(jī)制,因?yàn)槊總€(gè)人都可以使用它。它還能為你節(jié)省一些帶寬。你很容易使用ping檢測或使用Firebug調(diào)試那些服務(wù)器,以便搞清可以從哪些方面加快數(shù)據(jù)的速度。選擇CDN時(shí),要照顧到你網(wǎng)站那些訪客的位置。記得盡可能使用公共存儲庫。
  • 網(wǎng)頁末尾裝入JavaScript:
*   也可以在頭部分放置需要裝入的一些JavaScript,但是前提是它以異步方式裝入。
  • 異步裝入跟蹤代碼:

    腳本加載與解析會阻塞HTML渲染,可以通過異步加載方式來避免渲染阻塞,步加載的方式很多,比較通用的方法如下。

<pre>     `var _gaq = _gaq || []; 
       _gaq.push(['_setAccount', 'UA-XXXXXXX-XX']); 
       _gaq.push(['_trackPageview']); 
   (function() { 
       var ga = document.createElement('script'); ga.type = 'text/JavaScript'; ga.async = true; 
       ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 
       var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); 
   })();` </pre>

或者

<pre> `function loadjs (script_filename){
var script = document.createElement( 'script' );
script.setAttribute( 'type' , 'text/javascript' );
script.setAttribute( 'src' , script_filename);
script.setAttribute( 'id' , 'script-id' );

     scriptElement = document.getElementById( 'script-id' );
     if (scriptElement){
         document.getElementsByTagName( 'head' )[0].removeChild(scriptElement);
     }
     document.getElementsByTagName( 'head' )[0].appendChild(script);
}
var script = 'scripts/alert.js' ;
loadjs(script);` </pre>
  • 把你的JavaScript打包成PNG文件
*   將JavaScript/css數(shù)據(jù)打包成PNG文件。之后進(jìn)行拆包,只要使用畫布API的getImageData()。可以在不縮小數(shù)據(jù)的情況下,多壓縮35%左右。而且是無損壓縮,對比較龐大的腳本來說,在圖片指向畫布、讀取像素的過程中,你會覺得有“一段”裝入時(shí)間。
  • 設(shè)置Cache-Control和Expires頭
通過Cache-Control和Expires頭可以將腳本文件緩存在客戶端或者代理服務(wù)器上,可以減少腳本下載的時(shí)間。 
> 
Expires格式: 
>



<pre>     `   Expires = "Expires" ":" HTTP-date
   Expires: Thu, 01 Dec 1994 16:00:00 GMT
   Note: if a response includes a Cache-Control field with the max-age directive that directive overrides the
   Expires field.`</pre>



> 
Cache-Control格式: 
>



<pre>     `Cache-Control   = "Cache-Control" ":" 1#cache-directive
   Cache-Control: public` </pre>

具體的標(biāo)準(zhǔn)定義可以參考http1.1中的定義,簡單來說Expires控制過期時(shí)間是多久,Cache-Control控制什么地方可以緩存 。

with優(yōu)化

  • 盡可能地少用 with語句,因?yàn)樗鼤黾?with語句以外的數(shù)據(jù)的訪問代價(jià)。
  • 避免使用 with
<pre>     `>
`with`語句將一個(gè)新的可變對象推入作用域鏈的頭部,函數(shù)的所有局部變量現(xiàn)在處于第二個(gè)作用域鏈對象中,從而使局部變量的訪問代價(jià)提高。` </pre>

變量專題

  • 全局變量
*   當(dāng)一個(gè)變量被定義在全局作用域中,默認(rèn)情況下 `JAVASCRIPT`引擎就不會將其回收銷毀。如此該變量就會一直存在于老生代堆內(nèi)存中,直到頁面被關(guān)閉。
    
    
*   `全局變量`缺點(diǎn)。
    
    
    *   使變量不易被回收。
        
        
        *   多人協(xié)作時(shí)容易產(chǎn)生混淆。
            
            
    *   在作用域鏈中容易被干擾。
        
        
*   可以通過包裝函數(shù)來處理 `全局變量`。
  • 局部變量。
*   盡量選用局部變量而不是全局變量。
    
    
*   局部變量的訪問速度要比全局變量的訪問速度更快,因?yàn)槿肿兞科鋵?shí)是 `window`對象的成員,而局部變量是放在函數(shù)的棧里的。
  • 手工解除變量引用
*   在業(yè)務(wù)代碼中,一個(gè)變量已經(jīng)確定不再需要了,那么就可以手工解除變量引用,以使其被回收。
  • 變量查找優(yōu)化。
*   變量聲明帶上 `var`,如果聲明變量忘記了 `var`,那么 `JAVASCRIPT`引擎將會遍歷整個(gè)作用域查找這個(gè)變量,結(jié)果不管找到與否,都會造成性能損耗。
    
    
    *   如果在上級作用域找到了這個(gè)變量,上級作用域變量的內(nèi)容將被無聲的改寫,導(dǎo)致莫名奇妙的錯(cuò)誤發(fā)生。
        
        
    *   如果在上級作用域沒有找到該變量,這個(gè)變量將自動被聲明為全局變量,然而卻都找不到這個(gè)全局變量的定義。
        
        
*   慎用全局變量。
    
    
    *   全局變量需要搜索更長的作用域鏈。
        
        
    *   全局變量的生命周期比局部變量長,不利于內(nèi)存釋放。
        
        
    *   過多的全局變量容易造成混淆,增大產(chǎn)生bug的可能性。 
        >
        
        
*   具有相同作用域變量通過一個(gè)var聲明。
    
    
    
    <pre>       `      jQuery.extend = jQuery.fn.extend = function () {
              var options, 
                  name, 
                  src, 
                  copy, 
                  copyIsArray, 
                  clone,target = arguments[0] || {},
                  i = 1,
                  length = arguments.length,
                  deep = false ;
          }`</pre>
    
    
*   緩存重復(fù)使用的全局變量。
    
    
    *   全局變量要比局部變量需要搜索的作用域長
        
        
    *   重復(fù)調(diào)用的方法也可以通過局部緩存來提速
        
        
    *   該項(xiàng)優(yōu)化在IE上體現(xiàn)比較明顯
  • 善用回調(diào)。
*   除了使用閉包進(jìn)行內(nèi)部變量訪問,我們還可以使用現(xiàn)在十分流行的回調(diào)函數(shù)來進(jìn)行業(yè)務(wù)處理。
    
    
    
    <pre>       `      function getData(callback) {
            var data = 'some big data';
            callback(null, data);
          }
          getData(function(err, data) {
            console.log(data);
          });`</pre>
    
    
    *   回調(diào)函數(shù)是一種后續(xù)傳遞風(fēng)格( `Continuation Passing Style`, `CPS`)的技術(shù),這種風(fēng)格的程序編寫將函數(shù)的業(yè)務(wù)重點(diǎn)從返回值轉(zhuǎn)移到回調(diào)函數(shù)中去。而且其相比閉包的好處也有很多。
        
        
        *   如果傳入的參數(shù)是基礎(chǔ)類型(如字符串、數(shù)值),回調(diào)函數(shù)中傳入的形參就會是復(fù)制值,業(yè)務(wù)代碼使用完畢以后,更容易被回收。
            
            
        *   通過回調(diào),我們除了可以完成同步的請求外,還可以用在異步編程中,這也就是現(xiàn)在非常流行的一種編寫風(fēng)格。
            
            
        *   回調(diào)函數(shù)自身通常也是臨時(shí)的匿名函數(shù),一旦請求函數(shù)執(zhí)行完畢,回調(diào)函數(shù)自身的引用就會被解除,自身也得到回收。

常規(guī)優(yōu)化

  • 傳遞方法取代方法字符串
一些方法例如 `setTimeout()`、 `setInterval()`,接受 `字符串`或者 `方法實(shí)例`作為參數(shù)。直接傳遞方法對象作為參數(shù)來避免對字符串的二次解析。


*   *   傳遞方法
        
        
        
        <pre>         `setTimeout(test, 1);` </pre>
        
        
*   傳遞方法字符串
  • 使用原始操作代替方法調(diào)用
方法調(diào)用一般封裝了原始操作,在性能要求高的邏輯中,可以使用原始操作代替方法調(diào)用來提高性能。


*   原始操作
    
    
    
    <pre>       `    var min = a<b?a:b;`</pre>
    
    
*   方法實(shí)例
  • 定時(shí)器
如果針對的是不斷運(yùn)行的代碼,不應(yīng)該使用 `setTimeout`,而應(yīng)該是用 `setInterval`。 `setTimeout`每次要重新設(shè)置一個(gè)定時(shí)器。
  • 避免雙重解釋

當(dāng) JAVASCRIPT代碼想解析 JAVASCRIPT代碼時(shí)就會存在雙重解釋懲罰,雙重解釋一般在使用 eval函數(shù)、 new Function構(gòu)造函數(shù)和 setTimeout傳一個(gè)字符串時(shí)等情況下會遇到,如。

<pre> eval("alert('hello world');"); var sayHi = new Function("alert('hello world');"); setTimeout("alert('hello world');", 100); </pre>

上述 alert('hello world');語句包含在字符串中,即在JS代碼運(yùn)行的同時(shí)必須新啟運(yùn)一個(gè)解析器來解析新的代碼,而實(shí)例化一個(gè)新的解析器有很大的性能損耗。

<pre> `我們看看下面的例子:

var sum, num1 = 1, num2 = 2;
/**效率低**/
for(var i = 0; i < 10000; i++){
    var func = new Function("sum+=num1;num1+=num2;num2++;");
    func();
    //eval("sum+=num1;num1+=num2;num2++;");
}
/**效率高**/
for(var i = 0; i < 10000; i++){
    sum+=num1;
    num1+=num2;
    num2++;
}` </pre>

第一種情況我們是使用了new Function來進(jìn)行雙重解釋,而第二種是避免了雙重解釋。

  • 原生方法更快
*   只要有可能,使用原生方法而不是自已用JS重寫。原生方法是用諸如C/C++之類的編譯型語言寫出來的,要比JS的快多了。
  • 最小化語句數(shù)
JS代碼中的語句數(shù)量也會影響所執(zhí)行的操作的速度,完成多個(gè)操作的單個(gè)語句要比完成單個(gè)操作的多個(gè)語句塊快。故要找出可以組合在一起的語句,以減來整體的執(zhí)行時(shí)間。這里列舉幾種模式


*   多個(gè)變量聲明
    
    
    
    <pre>       `/**不提倡**/
          var i = 1;
          var j = "hello";
          var arr = [1,2,3];
          var now = new Date();
          /**提倡**/
          var i = 1,
              j = "hello",
              arr = [1,2,3],
              now = new Date();` </pre>
    
    
*   插入迭代值
    
    
    
    <pre>       `/**不提倡**/
          var name = values[i];
          i++;
          /**提倡**/
          var name = values[i++];` </pre>
    
    
*   使用數(shù)組和對象字面量,避免使用構(gòu)造函數(shù)Array(),Object()
    
    
    
    <pre>       `/**不提倡**/
          var a = new Array();
          a[0] = 1;
          a[1] = "hello";
          a[2] = 45;
          var o = new Obejct();
          o.name = "bill";
          o.age = 13;
          /**提倡**/
          var a = [1, "hello", 45];
          var o = {
              name : "bill",
              age : 13
          };` </pre>
  • 避免使用屬性訪問方法
*   JavaScript不需要屬性訪問方法,因?yàn)樗械膶傩远际峭獠靠梢姷摹?    
    
*   添加屬性訪問方法只是增加了一層重定向 ,對于訪問控制沒有意義。
    
    
    
    <pre>       `使用屬性訪問方法示例
    
         function Car() {
            this .m_tireSize = 17;
            this .m_maxSpeed = 250;
            this .GetTireSize = Car_get_tireSize;
            this .SetTireSize = Car_put_tireSize;
         }
    
         function Car_get_tireSize() {
            return this .m_tireSize;
         }
    
         function Car_put_tireSize(value) {
            this .m_tireSize = value;
         }
         var ooCar = new Car();
         var iTireSize = ooCar.GetTireSize();
         ooCar.SetTireSize(iTireSize + 1);
    
    直接訪問屬性示例
    
         function Car() {
            this .m_tireSize = 17;
            this .m_maxSpeed = 250;
         }
         var perfCar = new Car();
         var iTireSize = perfCar.m_tireSize;
         perfCar.m_tireSize = iTireSize + 1;` </pre>
  • 減少使用元素位置操作
*   一般瀏覽器都會使用增量reflow的方式將需要reflow的操作積累到一定程度然后再一起觸發(fā),但是如果腳本中要獲取以下屬性,那么積累的reflow將會馬上執(zhí)行,已得到準(zhǔn)確的位置信息。

代碼壓縮

  • 代碼壓縮工具
> 精簡代碼就是將代碼中的 `空格`和 `注釋`去除,也有更進(jìn)一步的會對變量名稱 `混淆`、 `精簡`。根據(jù)統(tǒng)計(jì)精簡后文件大小會平均減少 `21%`,即使 `Gzip`之后文件也會減少 `5%`。


*   YUICompressor
    
    
*   Dean Edwards Packer
    
    
*   JSMin
    
    
*   GZip壓縮
    
    
    *   `GZip`縮短在瀏覽器和服務(wù)器之間傳送數(shù)據(jù)的時(shí)間,縮短時(shí)間后得到標(biāo)題是 `Accept-Encoding`: `gzip`, `deflate`的一個(gè)文件。不過這種壓縮方法同樣也有缺點(diǎn)。
        
        
        *   它在服務(wù)器端和客戶端都要占用處理器資源(以便壓縮和解壓縮)。
            
            
        *   占用磁盤空間。
            
            
    *   `Gzip`通常可以減少70%網(wǎng)頁內(nèi)容的大小,包括腳本、樣式表、圖片等任何一個(gè)文本類型的響應(yīng),包括 `XML`和 `JSON`。 `Gzip`比 `deflate`更高效,主流服務(wù)器都有相應(yīng)的壓縮支持模塊。
        
        
    *   `Gzip`的工作流程為
        
        
        *   客戶端在請求 `Accept-Encoding`中聲明可以支持 `Gzip`。
            
            
        *   服務(wù)器將請求文檔壓縮,并在 `Content-Encoding`中聲明該回復(fù)為 `Gzip`格式。
            
            
        *   客戶端收到之后按照 `Gzip`解壓縮。
            
            
*   Closure compiler

代碼優(yōu)化

  • 優(yōu)化原則:
  • JS優(yōu)化總是出現(xiàn)在大規(guī)模循環(huán)的地方:
<pre>     `這倒不是說循環(huán)本身有性能問題,而是循環(huán)會迅速放大可能存在的性能問題,所以第二原則就是以大規(guī)模循環(huán)體為最主要優(yōu)化對象。` </pre>



以下的優(yōu)化原則,只在大規(guī)模循環(huán)中才有意義,在循環(huán)體之外做此類優(yōu)化基本上是沒有意義的。



目前絕大多數(shù)JS引擎都是解釋執(zhí)行的,而解釋執(zhí)行的情況下,在所有操作中,函數(shù)調(diào)用的效率是較低的。此外,過深的prototype繼承鏈或者多級引用也會降低效率。JScript中,10級引用的開銷大體是一次空函數(shù)調(diào)用開銷的1/2。這兩者的開銷都遠(yuǎn)遠(yuǎn)大于簡單操作(如四則運(yùn)算)。
  • 盡量避免過多的引用層級和不必要的多次方法調(diào)用:
<pre>     `特別要注意的是,有些情況下看似是屬性訪問,實(shí)際上是方法調(diào)用。例如所有DOM的屬性,實(shí)際上都是方法。在遍歷一個(gè)NodeList的時(shí)候,循環(huán) 條件對于nodes.length的訪問,看似屬性讀取,實(shí)際上是等價(jià)于函數(shù)調(diào)用的。而且IE DOM的實(shí)現(xiàn)上,childNodes.length每次是要通過內(nèi)部遍歷重新計(jì)數(shù)的。(My god,但是這是真的!因?yàn)槲覝y過,childNodes.length的訪問時(shí)間與childNodes.length的值成正比!)這非常耗費(fèi)。所以 預(yù)先把nodes.length保存到j(luò)s變量,當(dāng)然可以提高遍歷的性能。` </pre>



同樣是函數(shù)調(diào)用,用戶自定義函數(shù)的效率又遠(yuǎn)遠(yuǎn)低于語言內(nèi)建函數(shù),因?yàn)楹笳呤菍σ姹镜胤椒ǖ陌b,而引擎通常是c,c++,java寫的。進(jìn)一步,同樣的功能,語言內(nèi)建構(gòu)造的開銷通常又比內(nèi)建函數(shù)調(diào)用要效率高,因?yàn)榍罢咴贘S代碼的parse階段就可以確定和優(yōu)化。
  • 盡量使用語言本身的構(gòu)造和內(nèi)建函數(shù):

動畫優(yōu)化

  • 動畫效果在缺少硬件加速支持的情況下反應(yīng)緩慢,例如手機(jī)客戶端。
*   特效應(yīng)該只在確實(shí)能改善用戶體驗(yàn)時(shí)才使用,而不應(yīng)用于炫耀或者彌補(bǔ)功能與可用性上的缺陷。
    
    
*   至少要給用戶一個(gè)選擇可以禁用動畫效果。
    
    
*   設(shè)置動畫元素為absolute或fixed。
    
    
    *   `position: static`或 `position: relative`元素應(yīng)用動畫效果會造成頻繁的 `reflow`。
        
        
    *   `position: absolute`或 `position: fixed`的元素應(yīng)用動畫效果只需要 `repaint`。
        
        
*   使用一個(gè) `timer`完成多個(gè)元素動畫。
    
    
    *   `setInterval`和 `setTimeout`是兩個(gè)常用的實(shí)現(xiàn)動畫的接口,用以間隔更新元素的風(fēng)格與布局。。
        
        
*   動畫效果的幀率最優(yōu)化的情況是使用一個(gè) `timer`完成多個(gè)對象的動畫效果,其原因在于多個(gè) `timer`的調(diào)用本身就會損耗一定性能。
    
    
    
    <pre>       `setInterval(function() {
            animateFirst('');
          }, 10);
          setInterval(function() {
            animateSecond('');
          }, 10);
    
     使用同一個(gè)`timer`。` </pre>
  • 以腳本為基礎(chǔ)的動畫,由瀏覽器控制動畫的更新頻率。

對象專題

  • 減少不必要的對象創(chuàng)建:
*   創(chuàng)建對象本身對性能影響并不大,但由于 `JAVASCRIPT`的垃圾回收調(diào)度算法,導(dǎo)致隨著對象個(gè)數(shù)的增加,性能會開始嚴(yán)重下降(復(fù)雜度 `O(n^2)`)。
    
    
    *   如常見的字符串拼接問題,單純的多次創(chuàng)建字符串對象其實(shí)根本不是降低性能的主要原因,而是是在對象創(chuàng)建期間的無謂的垃圾回收的開銷。而 `Array.join`的方式,不會創(chuàng)建中間字符串對象,因此就減少了垃圾回收的開銷。
        
        
*   復(fù)雜的 `JAVASCRIPT`對象,其創(chuàng)建時(shí)時(shí)間和空間的開銷都很大,應(yīng)該盡量考慮采用緩存。
    
    
*   盡量作用 `JSON`格式來創(chuàng)建對象,而不是 `var obj=new Object()`方法。前者是直接復(fù)制,而后者需要調(diào)用構(gòu)造器。
  • 對象查找
*   避免對象的嵌套查詢,因?yàn)?`JAVASCRIPT`的解釋性, `a.b.c.d.e`嵌套對象,需要進(jìn)行 `4`次查詢,嵌套的對象成員會明顯影響性能。
    
    
*   如果出現(xiàn)嵌套對象,可以利用局部變量,把它放入一個(gè)臨時(shí)的地方進(jìn)行查詢。
  • 對象屬性
*   訪問對象屬性消耗性能過程( `JAVASCRIPT`對象存儲)。
    
    
    *   先從本地變量表找到 `對象`。
        
        
    *   然后遍歷 `屬性`。
        
        
    *   如果在 `當(dāng)前對象`的 `屬性列表`里沒找到。
        
        
    *   繼續(xù)從 `prototype`向上查找。
        
        
    *   且不能直接索引,只能遍歷。

服務(wù)端優(yōu)化

  • 避免404。
*   更改404錯(cuò)誤響應(yīng)頁面可以改進(jìn)用戶體驗(yàn),但是同樣也會浪費(fèi)服務(wù)器資源。
    
    
*   指向外部 `JAVASCRIPT`的鏈接出現(xiàn)問題并返回404代碼。
    
    
    *   這種加載會破壞并行加載。
        
        
    *   其次瀏覽器會把試圖在返回的404響應(yīng)內(nèi)容中找到可能有用的部分當(dāng)作JavaScript代碼來執(zhí)行。
  • 刪除重復(fù)的 JAVASCRIPTCSS
*   重復(fù)調(diào)用腳本缺點(diǎn)。
    
    
    *   增加額外的HTTP請求。
        
        
    *   多次運(yùn)算也會浪費(fèi)時(shí)間。在IE和Firefox中不管腳本是否可緩存,它們都存在重復(fù)運(yùn)算 `JAVASCRIPT`的問題。
  • ETags配置 Entity標(biāo)簽。
*   `ETags`用來判斷瀏覽器緩存里的元素是否和原來服務(wù)器上的一致。
    
    
    *   與 `last-modified date`相比更靈活。
  • 權(quán)衡DNS查找次數(shù)
*   減少主機(jī)名可以節(jié)省響應(yīng)時(shí)間。但同時(shí)也會減少頁面中并行下載的數(shù)量。
    
    
    *   `IE`瀏覽器在同一時(shí)刻只能從同一域名下載兩個(gè)文件。當(dāng)在一個(gè)頁面顯示多張圖片時(shí), `IE`用戶的圖片下載速度就會受到影響。
  • 通過Keep-alive機(jī)制減少TCP連接。
  • 通過CDN減少延時(shí)。
  • 平行處理請求(參考BigPipe)。
  • 通過合并文件或者Image Sprites減少HTTP請求。
  • 減少重定向( HTTP 301和40x/50x)。

類型轉(zhuǎn)換專題

  • 把數(shù)字轉(zhuǎn)換成字符串。
*   應(yīng)用 `""+1`,效率是最高。
    
    
    *   性能上來說: `""+字符串`> `String()`> `.toString()`> `new String()`。
        
        
        *   `String()`屬于內(nèi)部函數(shù),所以速度很快。
            
            
        *   `.toString()`要查詢原型中的函數(shù),所以速度略慢。
            
            
        *   `new String()`最慢。
  • 浮點(diǎn)數(shù)轉(zhuǎn)換成整型。
*   錯(cuò)誤使用使用 `parseInt()`。
    
    
    *   `parseInt()`是用于將 `字符串`轉(zhuǎn)換成 `數(shù)字`,而不是 `浮點(diǎn)數(shù)`和`整型`之間的轉(zhuǎn)換。
        
        
*   應(yīng)該使用 `Math.floor()`或者 `Math.round()`。
    
    
    *   `Math`是內(nèi)部對象,所以 `Math.floor()`其實(shí)并沒有多少查詢方法和調(diào)用的時(shí)間,速度是最快的。

邏輯判斷優(yōu)化

  • switch語句。
*   若有一系列復(fù)雜的 `if-else`語句,可以轉(zhuǎn)換成單個(gè) `switch`語句則可以得到更快的代碼,還可以通過將 `case`語句按照最可能的到最不可能的順序進(jìn)行組織,來進(jìn)一步優(yōu)化。

內(nèi)存專題

  • JAVASCRIPT的內(nèi)存回收機(jī)制
*   以Google的 `V8`引擎為例,在 `V8`引擎中所有的 `JAVASCRIPT`對象都是通過 `堆`來進(jìn)行內(nèi)存分配的。當(dāng)我們在代碼中 `聲明變量`并 `賦值`時(shí), `V8`引擎就會在 `堆內(nèi)存`中分配一部分給這個(gè) `變量`。如果已申請的 `內(nèi)存`不足以存儲這個(gè) `變量`時(shí), `V8`引擎就會繼續(xù)申請 `內(nèi)存`,直到 `堆`的大小達(dá)到了 `V8`引擎的內(nèi)存上限為止(默認(rèn)情況下, `V8`引擎的 `堆內(nèi)存`的大小上限在 `64位系統(tǒng)`中為 `1464MB`,在 `32位系統(tǒng)`中則為 `732MB`)。
    
    
*   另外, `V8`引擎對 `堆內(nèi)存`中的 `JAVASCRIPT`對象進(jìn)行 `分代管理`。
    
    
    *   新生代。
        
        
        *   新生代即存活周期較短的 `JAVASCRIPT`對象,如臨時(shí)變量、字符串等
            
            
    *   老生代。
        
        
        *   老生代則為經(jīng)過多次垃圾回收仍然存活,存活周期較長的對象,如主控制器、服務(wù)器對象等。
  • 垃圾回收算法。
*   垃圾回收算法一直是編程語言的研發(fā)中是否重要的??一環(huán),而 `V8`引擎所使用的垃圾回收算法主要有以下幾種。
    
    
    *   `Scavange`算法:通過復(fù)制的方式進(jìn)行內(nèi)存空間管理,主要用于新生代的內(nèi)存空間;
        
        
    *   `Mark-Sweep`算法和 `Mark-Compact`算法:通過標(biāo)記來對堆內(nèi)存進(jìn)行整理和回收,主要用于老生代對象的檢查和回收。
  • 對象進(jìn)行回收。
*   `引用`。
    
    
    *   當(dāng)函數(shù)執(zhí)行完畢時(shí),在函數(shù)內(nèi)部所聲明的對象 `不一定`就會被銷毀。
        
        
    *   引用( `Reference`)是 `JAVASCRIPT`編程中十分重要的一個(gè)機(jī)制。
        
        
        *   *   是指 `代碼對對象的訪問`這一抽象關(guān)系,它與`C/C++`的指針有點(diǎn)相似,但并非同物。引用同時(shí)也是 `JAVASCRIPT`引擎在進(jìn)行 `垃圾回收`中最關(guān)鍵的一個(gè)機(jī)制。
                
                
                
                <pre>             `var val = 'hello world';
                    function foo() {
                      return function() {
                        return val;
                      };
                    }
                    global.bar = foo();` </pre>
                
                
        *   當(dāng)代碼執(zhí)行完畢時(shí),對象 `val`和 `bar()`并沒有被回收釋放, `JAVASCRIPT`代碼中,每個(gè) `變量`作為單獨(dú)一行而不做任何操作, `JAVASCRIPT`引擎都會認(rèn)為這是對 `對象`的訪問行為,存在了對 `對象的引用`。為了保證 `垃圾回收`的行為不影響程序邏輯的運(yùn)行, `JAVASCRIPT`引擎不會把正在使用的 `對象`進(jìn)行回收。所以判斷 `對象`是否正在使用中的標(biāo)準(zhǔn),就是是否仍然存在對該 `對象`的 `引用`。
  • JAVASCRIPT引用是可以進(jìn)行 轉(zhuǎn)移的,那么就有可能出現(xiàn)某些引用被帶到了全局作用域,但事實(shí)上在業(yè)務(wù)邏輯里已經(jīng)不需要對其進(jìn)行訪問了,這個(gè)時(shí)候就應(yīng)該被回收,但是 JAVASCRIPT引擎仍會認(rèn)為程序仍然需要它。
  • IE下閉包引起跨頁面內(nèi)存泄露。
  • JAVASCRIPT的內(nèi)存泄露處理
*   給 `DOM`對象添加的屬性是一個(gè)對象的引用。
    
    
    
    <pre>       `var MyObject = {};
        document.getElementByIdx_x('myDiv').myProp = MyObject;
    
    解決方法:在window.onunload事件中寫上: 
    
        document.getElementByIdx_x('myDiv').myProp = null;` </pre>
    
    
*   DOM對象與JS對象相互引用。
    
    
    
    <pre>       `function Encapsulator(element) {
            this.elementReference = element;
            element.myProp = this;
         }
         new Encapsulator(document.getElementByIdx_x('myDiv'));
    
    解決方法:在onunload事件中寫上: 
    
        document.getElementByIdx_x('myDiv').myProp = null;` </pre>
    
    
*   給DOM對象用attachEvent綁定事件。
    
    
    
    <pre>       `function doClick() {}
         element.attachEvent("onclick", doClick);
    
    解決方法:在onunload事件中寫上: 
    
        element.detachEvent('onclick', doClick);` </pre>
    
    
*   從外到內(nèi)執(zhí)行appendChild。這時(shí)即使調(diào)用removeChild也無法釋放。
    
    
    
    <pre>       `var parentDiv =   document.createElement_x("div");
         var childDiv = document.createElement_x("div");
         document.body.appendChild(parentDiv);
         parentDiv.appendChild(childDiv);
    
    解決方法:從內(nèi)到外執(zhí)行appendChild:
    
         var parentDiv =   document.createElement_x("div");
         var childDiv = document.createElement_x("div");
         parentDiv.appendChild(childDiv);
         document.body.appendChild(parentDiv);` </pre>
    
    
*   反復(fù)重寫同一個(gè)屬性會造成內(nèi)存大量占用(但關(guān)閉IE后內(nèi)存會被釋放)。
    
    
    
    <pre>       `for(i = 0; i < 5000; i++) {
            hostElement.text = "asdfasdfasdf";
         }` </pre>
  • 內(nèi)存不是 緩存
*   不要輕易將 `內(nèi)存`當(dāng)作 `緩存`使用。
    
    
*   如果是很重要的資源,請不要直接放在 `內(nèi)存`中,或者制定 `過期機(jī)制`,自動銷毀 `過期緩存`。
  • CollectGarbage
*   `CollectGarbage`是 `IE`的一個(gè)特有屬性,用于釋放內(nèi)存的使用方法,將該變量或引用對象設(shè)置為 `null`或 `delete`然后在進(jìn)行釋放動作,在做 `CollectGarbage`前,要必需清楚的兩個(gè)必備條件:(引用)。
    
    
    *   一個(gè)對象在其生存的上下文環(huán)境之外,即會失效。
        
        
    *   一個(gè)全局的對象在沒有被執(zhí)用(引用)的情況下,即會失效

事件優(yōu)化

  • 使用事件代理
*   當(dāng)存在多個(gè)元素需要注冊事件時(shí),在每個(gè)元素上綁定事件本身就會對性能有一定損耗。
    
    
*   由于DOM Level2事件模 型中所有事件默認(rèn)會傳播到上層文檔對象,可以借助這個(gè)機(jī)制在上層元素注冊一個(gè)統(tǒng)一事件對不同子元素進(jìn)行相應(yīng)處理。

捕獲型事件先發(fā)生。兩種事件流會觸發(fā)DOM中的所有對象,從document對象開始,也在document對象結(jié)束。

<pre> <ul id="parent-list"> <li id="post-1">Item 1 <li id="post-2">Item 2 <li id="post-3">Item 3 <li id="post-4">Item 4 <li id="post-5">Item 5 <li id="post-6">Item 6 </li></ul> // Get the element, add a click listener... document.getElementById("parent-list").addEventListener("click",function(e) { // e.target is the clicked element! // If it was a list item if(e.target && e.target.nodeName == "LI") { // List item found! Output the ID! console.log("List item ",e.target.id.replace("post-")," was clicked!"); } });</pre>

數(shù)組專題

  • 當(dāng)需要使用數(shù)組時(shí),可使用 JSON格式的語法
*   即直接使用如下語法定義數(shù)組: `[parrm,param,param...]`,而不是采用 `new Array(parrm,param,param...)`這種語法。使用 `JSON`格式的語法是引擎直接解釋。而后者則需要調(diào)用 `Array`的構(gòu)造器。
  • 如果需要遍歷數(shù)組,應(yīng)該先緩存數(shù)組長度,將數(shù)組長度放入局部變量中,避免多次查詢數(shù)組長度。
*   根據(jù)字符串、數(shù)組的長度進(jìn)行循環(huán),而通常這個(gè)長度是不變的,比如每次查詢 `a.length`,就要額外進(jìn)行一個(gè)操作,而預(yù)先把 `var len=a.length`,則每次循環(huán)就少了一次查詢。

同域跨域

  • 避免跳轉(zhuǎn)
*   同域:注意避免反斜杠 “/” 的跳轉(zhuǎn);
    
    
*   跨域:使用Alias或者mod_rewirte建立CNAME(保存域名與域名之間關(guān)系的DNS記錄)

性能測試工具

  • js性能優(yōu)化和內(nèi)存泄露問題及檢測分析工具
*   *   性能優(yōu)化ajax工具 `diviefirebug`
        
        
    *   [web性能分析工具YSlow]
        
        
        *   `performance`性能評估打分,右擊箭頭可看到改進(jìn)建議。
            
            
        *   `stats`緩存狀態(tài)分析,傳輸內(nèi)容分析。
            
            
        *   `components`所有加載內(nèi)容分析,可以查看傳輸速度,找出頁面訪問慢的瓶頸。
            
            
        *   `tools`可以查看js和css,并打印頁面評估報(bào)告。
            
            
    *   內(nèi)存泄露檢測工具 `sIEve`
        
        
        *   `sIEve`是基于 `IE`的內(nèi)存泄露檢測工具,需要下載運(yùn)行,可以查看dom孤立節(jié)點(diǎn)和內(nèi)存泄露及內(nèi)存使用情況。
            
            
            1.  列出當(dāng)前頁面內(nèi)所有dom節(jié)點(diǎn)的基本信息(html id style 等)
                
                
            2.  頁面內(nèi)所有dom節(jié)點(diǎn)的高級信息 (內(nèi)存占用,數(shù)量,節(jié)點(diǎn)的引用)
                
                
            3.  可以查找出頁面中的孤立節(jié)點(diǎn)
                
                
            4.  可以查找出頁面中的循環(huán)引用
                
                
            5.  可以查找出頁面中產(chǎn)生內(nèi)存泄露的節(jié)點(diǎn)
                
                
    *   內(nèi)存泄露提示工具 `leak monitor`
        
        
        *   `leak monitor`在安裝后,當(dāng)離開一個(gè)頁面時(shí),比如關(guān)閉窗口,如果頁面有內(nèi)存泄露,會彈出一個(gè)文本框進(jìn)行即時(shí)提示。
            
            
    *   代碼壓縮工具
        
        
        *   YUI壓縮工具
            
            
        *   Dean Edwards Packer
            
            
        *   JSMin
            
            
*   `Blink/Webkit`瀏覽器
    
    
    *   在 `Blink/Webkit`瀏覽器中( `Chrome`, `Safari`, `Opera`),我們可以借助其中的 `Developer Tools`的 `Profiles`工具來對我們的程序進(jìn)行內(nèi)存檢查。
  • Node.js中的內(nèi)存檢查
*   在 `Node.js`中,我們可以使用 `node-heapdump`和 `node-memwatch`模塊進(jìn)??行內(nèi)存檢查。
    
    
    
    <pre>       `var heapdump = require('heapdump');
        var fs = require('fs');
        var path = require('path');
        fs.writeFileSync(path.join(__dirname, 'app.pid'), process.pid);` </pre>
    
    
    
    在業(yè)務(wù)代碼中引入 `node-heapdump`之后,我們需要在某個(gè)運(yùn)行時(shí)期,向 `Node.js`進(jìn)程發(fā)送 `SIGUSR2`信號,讓 `node-heapdump`抓拍一份堆內(nèi)存的快照。
    
    
    
    <pre>       `$ kill -USR2 (cat app.pid)` </pre>
  • 分析瀏覽器提供的Waterfall圖片來思考優(yōu)化入口。
  • 新的測試手段(Navigation, Resource, 和User timing。

循環(huán)專題

  • 循環(huán)是一種常用的流程控制。
*   `JAVASCRIPT`提供了三種循環(huán)。
    
    
    *   `for(;;)`。
        
        
        *   推薦使用for循環(huán),如果循環(huán)變量遞增或遞減,不要單獨(dú)對循環(huán)變量賦值,而應(yīng)該使用嵌套的 `++`或 `–-`運(yùn)算符。
            
            
        *   代碼的可讀性對于for循環(huán)的優(yōu)化。
            
            
        *   用 `-=1`。
            
            
        *   從大到小的方式循環(huán)(這樣缺點(diǎn)是降低代碼的可讀性)。
            
            
    *   `while()`。
        
        
        *   `for(;;)`、 `while()`循環(huán)的性能基本持平。
            
            
    *   `for(in)`。
        
        
        *   在這三種循環(huán)中 `for(in)`內(nèi)部實(shí)現(xiàn)是構(gòu)造一個(gè)所有元素的列表,包括 `array`繼承的屬性,然后再開始循環(huán),并且需要查詢hasOwnProperty。所以 `for(in)`相對 `for(;;)`循環(huán)性能要慢。
  • 選擇正確的方法
*   避免不必要的屬性查找。
    
    
    *   訪問 `變量`或 `數(shù)組`是 `O(1)`操作。
        
        
    *   訪問 `對象`上的 `屬性`是一個(gè) `O(n)`操作。
        
        
        
        <pre>         `對象上的任何屬性查找都要比訪問變量或數(shù)組花費(fèi)更長時(shí)間,因?yàn)楸仨氃谠玩溨袑碛性撁Q的屬性進(jìn)行一次搜索,即屬性查找越多,執(zhí)行時(shí)間越長。所以針對需要多次用到對象屬性,應(yīng)將其存儲在局部變量。` </pre>
        
        
*   優(yōu)化循環(huán)。
    
    
    *   減值迭代。
        
        
        *   大多數(shù)循環(huán)使用一個(gè)從0開始,增加到某個(gè)特定值的迭代器。在很多情況下,從最大值開始,在循環(huán)中不斷減值的迭代器更加有效。
            
            
    *   簡化終止條件。
        
        
        *   由于每次循環(huán)過程都會計(jì)算終止條件,故必須保證它盡可能快,即避免屬性查找或其它O(n)的操作。
            
            
    *   簡化循環(huán)體。
        
        
        *   循環(huán)體是執(zhí)行最多的,故要確保其被最大限度地優(yōu)化。確保沒有某些可以被很容易移出循環(huán)的密集計(jì)算。
            
            
    *   使用后測試循環(huán)。
        
        
        *   最常用的for和while循環(huán)都是前測試循環(huán),而如do-while循環(huán)可以避免最初終止條件的計(jì)算,因些計(jì)算更快。
            
            
*   展開循環(huán)。
    
    
    *   當(dāng)循環(huán)的次數(shù)確定時(shí),消除循環(huán)并使用多次函數(shù)調(diào)用往往更快。
        
        
    *   當(dāng)循環(huán)的次數(shù)不確定時(shí),可以使用Duff裝置來優(yōu)化。
        
        
        *   Duff裝置的基本概念是通過計(jì)算迭代的次數(shù)是否為8的倍數(shù)將一個(gè)循環(huán)展開為一系列語句。
  • 避免在循環(huán)中使用 try-catch
*   `try-catch-finally`語句在catch語句被執(zhí)行的過程中會動態(tài)構(gòu)造變量插入到當(dāng)前域中,對性能有一定影響。
    
    
*   如果需要異常處理機(jī)制,可以將其放在循環(huán)外層使用。
    
    
    *   *   循環(huán)中使用try-catch
            
            
            
            <pre>           `for ( var i = 0; i < 200; i++) {
                  try {} catch (e) {}
                }` </pre>
            
            
    *   循環(huán)外使用try-catch
        
        
        
        <pre>         `try {
                   for ( var i = 0; i < 200; i++) {}
                 } catch (e) {}` </pre>
  • 避免遍歷大量元素:
*   避免對全局 `DOM`元素進(jìn)行遍歷,如果 `parent`已知可以指定 `parent`在特定范圍查詢。
    
    
    
    <pre>       `var elements = document.getElementsByTagName( '*' );
         for (i = 0; i < elements.length; i++) {
            if (elements[i].hasAttribute( 'selected' )) {}
         }
    
    如果已知元素存在于一個(gè)較小的范圍內(nèi),` </pre>

原型優(yōu)化

  • 通過原型優(yōu)化方法定義。
*   如果一個(gè)方法類型將被頻繁構(gòu)造,通過方法原型從外面定義附加方法,從而避免方法的重復(fù)定義。
    
    
*   可以通過外部原型的構(gòu)造方式初始化值類型的變量定義。(這里強(qiáng)調(diào)值類型的原因是,引用類型如果在原型中定義,一個(gè)實(shí)例對引用類型的更改會影響到其他實(shí)例。)
    
    
    *   這條規(guī)則中涉及到 `JAVASCRIPT`中原型的概念,構(gòu)造函數(shù)都有一個(gè) `prototype`屬性,指向另一個(gè)對象。這個(gè)對象的所有屬性和方法,都會被構(gòu)造函數(shù)的實(shí)例繼承。可以把那些不變的屬性和方法,直接定義在 `prototype`對象上。
        
        
        *   可以通過對象實(shí)例訪問保存在原型中的值。
            
            
        *   不能通過對象實(shí)例重寫原型中的值。
            
            
        *   在實(shí)例中添加一個(gè)與實(shí)例原型同名屬性,那該屬性就會屏蔽原型中的屬性。
            
            
        *   通過delete操作符可以刪除實(shí)例中的屬性。

運(yùn)算符專題

  • 使用運(yùn)算符時(shí),盡量使用 +=-=*=\=等運(yùn)算符號,而不是直接進(jìn)行賦值運(yùn)算。
  • 位運(yùn)算
*   當(dāng)進(jìn)行數(shù)學(xué)運(yùn)算時(shí) `位運(yùn)算`較快, `位運(yùn)算`操作要比任何 `布爾運(yùn)算`或 `算數(shù)運(yùn)算`快,如 `取模`, `邏輯與`和 `邏輯或`也可以考慮用 `位運(yùn)算`來替換。

重繪專題

  • 減少頁面的 重繪
*   減少頁面 `重繪`雖然本質(zhì)不是 `JAVASCRIPT`優(yōu)化,但 `重繪`往往是由`JAVASCRIPT`引起的,而 `重繪`的情況直接影響頁面性能。
    
    
    
    <pre>       `var str = "<div>這是一個(gè)測試字符串</div>";
         /**效率低**/
         var obj = document.getElementsByTagName("body");
         for(var i = 0; i < 100; i++){
             obj.innerHTML += str + i;
         }
         /**效率高**/
         var obj = document.getElementsByTagName("body");
         var arr = [];
         for(var i = 0; i < 100; i++){
             arr[i] = str + i;
         }
         obj.innerHTML = arr.join("");` </pre>
    
    

一般影響頁面重繪的不僅僅是innerHTML,如果改變元素的樣式,位置等情況都會觸發(fā)頁面重繪,所以在平時(shí)一定要注意這點(diǎn)。
  • 使用HTML5和CSS3的一些新特性。
  • 避免在HTML里面縮放圖片。
  • 避免使用插件。
  • 確保使用正確的字體大小。
  • 決定當(dāng)前頁面是不是能被訪問。

字符串專題

  • 對字符串進(jìn)行循環(huán)操作。
*   替換、查找等操作,使用正則表達(dá)式。
    
    
    *   因?yàn)?`JAVASCRIPT`的循環(huán)速度較慢,而正則表達(dá)式的操作是用 `C`寫成的 `API`,性能比較好。
  • 字符串的拼接。
*   字符串的拼接在我們開發(fā)中會經(jīng)常遇到,所以我把其放在首位,我們往往習(xí)慣的直接用 `+=`的方式來拼接字符串,其實(shí)這種拼接的方式效率非常的低,我們可以用一種巧妙的方法來實(shí)現(xiàn)字符串的拼接,那就是利用數(shù)組的 `join`方法,具體請看我整理的: [Web前端開發(fā)規(guī)范文檔](http://kang.cool/modules/web_develop_standard/index.html%20)中的 `javaScript書寫規(guī)范`倒數(shù)第三條目。
    
    
*   不過也有另一種說法,通常認(rèn)為需要用 `Array.join`的方式,但是由于 `SpiderMonkey`等引擎對字符串的“ `+`”運(yùn)算做了優(yōu)化,結(jié)果使用`Array.join`的效率反而不如直接用“ `+`”,但是如果考慮 `IE6`,則其他瀏覽器上的這種效率的差別根本不值一提。具體怎么取舍,諸君自定。

作用域鏈和閉包優(yōu)化

  • 作用域。
*   作用域( `scope`)是 `JAVASCRIPT`編程中一個(gè)重要的 `運(yùn)行機(jī)制`,在 `JAVASCRIPT`同步和異步編程以及 `JAVASCRIPT`內(nèi)存管理中起著至關(guān)重要的作用。
    
    
*   在 `JAVASCRIPT`中,能形成作用域的有如下幾點(diǎn)。
    
    
    *   函數(shù)的調(diào)用
        
        
    *   with語句
        
        
        *   `with`會創(chuàng)建自已的作用域,因此會增加其中執(zhí)行代碼的作用域的長度。
            
            
    *   全局作用域。
        
        
        
        <pre>         `以下代碼為例:
        
             var foo = function() {
               var local = {};
             };
             foo();
             console.log(local); //=undefined
        
             var bar = function() {
               local = {};
             };
             bar();
             console.log(local); //={}
        
            /**這里我們定義了foo()函數(shù)和bar()函數(shù),他們的意圖都是為了定義一個(gè)名為local的變量。在foo()函數(shù)中,我們使用var語句來聲明定義了一個(gè)local變量,而因?yàn)楹瘮?shù)體內(nèi)部會形成一個(gè)作用域,所以這個(gè)變量便被定義到該作用域中。而且foo()函數(shù)體內(nèi)并沒有做任何作用域延伸的處理,所以在該函數(shù)執(zhí)行完畢后,這個(gè)local變量也隨之被銷毀。而在外層作用域中則無法訪問到該變量。而在bar()函數(shù)內(nèi),local變量并沒有使用var語句進(jìn)行聲明,取而代之的是直接把local作為全局變量來定義。故外層作用域可以訪問到這個(gè)變量。**/` </pre>
  • 作用域鏈
*   在 `JAVASCRIPT`編程中,會遇到多層函數(shù)嵌套的場景,這就是典型的作用域鏈的表示。
    
    
    
    <pre>       `function foo() {
           var val = 'hello';
           function bar() {
             function baz() {
               global.val = 'world;'
             };
             baz();
             console.log(val); //=hello
           };
           bar();
         };
         foo();` </pre>
  • 減少作用域鏈上的查找次數(shù)
*   `JAVASCRIPT`代碼在執(zhí)行的時(shí)候,如果需要訪問一個(gè)變量或者一個(gè)函數(shù)的時(shí)候,它需要遍歷當(dāng)前執(zhí)行環(huán)境的作用域鏈,而遍歷是從這個(gè)作用域鏈的前端一級一級的向后遍歷,直到全局執(zhí)行環(huán)境。
  • 閉包
*   `JAVASCRIPT`中的標(biāo)識符查找遵循從內(nèi)到外的原則。
    
    
    
    <pre>       `      function foo() {
            var local = 'Hello';
            return function() {
              return local;
            };
          }
          var bar = foo();
          console.log(bar()); //=Hello
    
          /**這里所展示的讓外層作用域訪問內(nèi)層作用域的技術(shù)便是閉包(Closure)。得益于高階函數(shù)的應(yīng)用,使foo()函數(shù)的作用域得到`延伸`。foo()函數(shù)返回了一個(gè)匿名函數(shù),該函數(shù)存在于foo()函數(shù)的作用域內(nèi),所以可以訪問到foo()函數(shù)作用域內(nèi)的local變量,并保存其引用。而因這個(gè)函數(shù)直接返回了local變量,所以在外層作用域中便可直接執(zhí)行bar()函數(shù)以獲得local變量。**/`</pre>
    
    
    *   閉包是 `JAVASCRIPT`的高級特性,因?yàn)榘褞в??內(nèi)部變量引用的函數(shù)帶出了函數(shù)外部,所以該作用域內(nèi)的變量在函數(shù)執(zhí)行完畢后的并不一定會被銷毀,直到內(nèi)部變量的引用被全部解除。所以閉包的應(yīng)用很容易造成內(nèi)存無法釋放的情況。
        
        
    *   良好的閉包管理。
        
        
        *   循環(huán)事件綁定、私有屬性、含參回調(diào)等一定要使用閉包時(shí),并謹(jǐn)慎對待其中的細(xì)節(jié)。
            
            
            *   循環(huán)綁定事件,我們假設(shè)一個(gè)場景:有六個(gè)按鈕,分別對應(yīng)六種事件,當(dāng)用戶點(diǎn)擊按鈕時(shí),在指定的地方輸出相應(yīng)的事件。
  • 避開閉包陷阱
*   閉包是個(gè)強(qiáng)大的工具,但同時(shí)也是性能問題的主要誘因之一。不合理的使用閉包會導(dǎo)致內(nèi)存泄漏。
    
    
*   閉包的性能不如使用內(nèi)部方法,更不如重用外部方法。
    
    
    *   由于 `IE 9`瀏覽器的 `DOM`節(jié)點(diǎn)作為 `COM`對象來實(shí)現(xiàn), `COM`的`內(nèi)存管理`是通過引用計(jì)數(shù)的方式,引用計(jì)數(shù)有個(gè)難題就是循環(huán)引用,一旦 `DOM`引用了閉包(例如 `event handler`),閉包的上層元素又引用了這個(gè) `DOM`,就會造成循環(huán)引用從而導(dǎo)致內(nèi)存泄漏。
  • 善用函數(shù)
*   使用一個(gè)匿名函數(shù)在代碼的最外層進(jìn)行包裹。
    
    
    
    <pre>       `;(function() {
       // 主業(yè)務(wù)代碼
     })();` </pre>

有的甚至更高級一點(diǎn):

<pre> ;(function(win, doc, $, undefined) { // 主業(yè)務(wù)代碼 })(window, document, jQuery); </pre>

甚至連如RequireJS, SeaJS, OzJS 等前端模塊化加載解決方案,都是采用類似的形式:

<pre> /**RequireJS**/ define(['jquery'], function($) { // 主業(yè)務(wù)代碼 }); /**SeaJS**/ define('m??odule', ['dep', 'underscore'], function($, _) { // 主業(yè)務(wù)代碼 }); </pre>

被定義在全局作用域的對象,可能是會一直存活到進(jìn)程退出的,如果是一個(gè)很大的對象,那就麻煩了。比如有的人喜歡在JavaScript中做模版渲染:

<pre> <?php $db = mysqli_connect(server, user, password, 'myapp'); $topics = mysqli_query($db, "SELECT * FROM topics;"); ?> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>你是猴子請來的逗比么?</title> </head> <body> <ul id="topics"></ul> <script type="text/tmpl" id="topic-tmpl"> <li class="topic"> <h1><%=title%></h1> <p><%=content%></p> </li> </script> <script type="text/javascript"> var data = <?php echo json_encode($topics); ?>; var topicTmpl = document.querySelector('#topic-tmpl').innerHTML; var render = function(tmlp, view) { var complied = tmlp .replace(/\n/g, '\\n') .replace(/<%=([\s\S]+?)%>/g, function(match, code) { return '" + escape(' + code + ') + "'; }); complied = [ 'var res = "";', 'with (view || {}) {', 'res = "' + complied + '";', '}', 'return res;' ].join('\n'); var fn = new Function('view', complied); return fn(view); }; var topics = document.querySelector('#topics'); function init() data.forEach(function(topic) { topics.innerHTML += render(topicTmpl, topic); }); } init(); </script> </body> </html> </pre>

在從數(shù)據(jù)庫中獲取到的數(shù)據(jù)的量是非常大的話,前端完成模板渲染以后,data變量便被閑置在一邊。可因?yàn)檫@個(gè)變量是被定義在全局作用域中的,所以 JAVASCRIPT引擎不會將其回收銷毀。如此該變量就會一直存在于老生代堆內(nèi)存中,直到頁面被關(guān)閉。可是如果我們作出一些很簡單的修改,在邏輯代碼外包裝一層函數(shù),這樣效果就大不同了。當(dāng)UI渲染完成之后,代碼對data的引用也就隨之解除,而在最外層函數(shù)執(zhí)行完畢時(shí), JAVASCRIPT引擎就開始對其中的對象進(jìn)行檢查,data也就可以隨之被回收。

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