高性能JavaScript(1)

来源:https://www.cnblogs.com/zhangzhengsmiling/archive/2019/04/11/high_performanceJS.html
-Advertisement-
Play Games

又有好長時間沒有寫博客了,今天想起來之前的那篇博客還沒有寫完,然後就開始接著寫,本來想把《高性能JavaScript》這本書的知識都羅列進來的,但是......太多了,哎,還是慢慢來,於是就打算分開來寫。 本人JavaScript水平並不是特別高,也只是把自己閱讀《高性能JavaScript》的部分 ...


 

-------------------------------------------------------------------------------------------------------

  又有好長時間沒有寫博客了,今天想起來之前的那篇博客還沒有寫完,然後就開始接著寫,本來想把《高性能JavaScript》這本書的知識都羅列進來的,但是......太多了,哎,還是慢慢來,於是就打算分開來寫。

  本人JavaScript水平並不是特別高,也只是把自己閱讀《高性能JavaScript》的部分想法記錄一下,難免會有錯誤哈。如果對JavaScript的性能優化有興趣的話,不妨親自讀一讀。這本書雖然已經出版很多年了,不過裡面的部分思想能夠有助於我們理解JavaScript的運行機制以及瓶頸所在。

-------------------------------------------------------------------------------------------------------

載入和執行

  1. 腳本阻塞

    由於javascript語言是單線程的,決定了javascript語言是單線程的,因此在瀏覽器上UI的刷新與javascript代碼的邏輯執行就無法同步執行。

    瀏覽器中遇到界面上的<script> 標簽,瀏覽器無法判斷script標簽中的代碼是否會更行dom節點(若需要更新dom則會引起界面的二次重繪)。因此在瀏覽器中,遇script標簽的時候,將會暫停dom的繪製和渲染以及界面的交互響應,直到javascript被載入(包括外鏈下載的過程),執行。

    腳本的合併能夠減少系統發送http請求的次數,能夠減少建立http連接的次數,從而減少阻塞的時間。

  2. 實現無阻塞載入的方法(nonblocking script)

    • 使用script標簽的defer和async屬性,顯式通知瀏覽器引入的script標簽不會改變dom結構,瀏覽器在載入的時候將會非同步載入

      • defer與async載入的比較:兩者都能夠實現腳本的非同步載入,即下載的過程中不會阻塞頁面的渲染。區別在於執行的時機,帶有async屬性的script標簽在下載完成後將會自動執行,而然帶有defer屬性的script標簽則要等到頁面載入完成後才執行。

      • defer屬性只有在script標簽存在src屬性時才會生效,即僅對外聯腳本有效

    • 動態腳本載入,通過JavaScript代碼生成一個頁面script標簽,併為其指定src,文件的下載將不會阻塞頁面

       let script = document.createElement('script');
       script.type = 'text/javascript';
       script.src = `${src}`;
       document.getElementsByTagName('head')[0].appendChild(script);
      • 使用動態方式載入腳本時,當js文件下載完成後將會自動執行,但如果有多個腳本文件註入時,由於js文件的下載速度不同,可能導致文件執行的順序錯亂,尤其當後一個腳本文件依賴於上一個腳本文件的執行時將會出錯。

      // 解決方法:通過監聽script標簽的載入狀態並觸發回調,在回調函數中載入依賴的javascript文件
       ​
       // 載入腳本的方法
       var loadScript(url, callback) {
           var script = document.createElement('script');
           script.src = url;
           // 在非IE瀏覽器中(主流瀏覽器),script標簽載入完成時會觸發onload事件,而IE瀏覽器中script的載入狀態通過readyState實現,readyState有以下  值:'uninitialized'(初始狀態),'loading'(下載中),'loaded'(下載完成),'interactive'(數據下載完成但尚不可用),'complete'(所有數據準備就緒),在IE中需要監聽readyState的狀態改變,以便在合適的時機觸發回調
           if(script.readyState) { // IE舊版本相容
               script.onreadyStatechange = function(){
                   if(script.readyState === 'loaded' || readyState === 'complete') {
                       script.onreadyState = null;
                       callback();
                   }
               }
           } else {
               script.onload = function () {
                   callback()
               }
           }
       }
       // callback中可以載入下一個順序執行的javascript文件
      

      註意:當有多個文件需要順序執行時,將會有多層回調函數的嵌套,容易形成回調地獄

      loadScript('file1.js', function(){
           loadScript('file2.js', function(){
               loadScript('file3.js', function () {
                   loadScript('files.js');
               })
           })
       })
       // 載入順序: file1.js --> file2/js --> file3.js --> file4.js
       // 當多文件嵌套時,儘可能將多個文件按照載入順序合併成一個文件
    • XMLHttpRequest腳本註入

      • 通過XHR對象非同步下載javascript文件

         var xhr = new XMLHttpRequest();
         xhr.open('get', `${url}`);
         shr.onreadystatechange = function () {
             if(xhr.readyState===4) {
                 if(xhr.state>=200 && xhr.state < 300 || xhr.state == 304) {
                     var script = document.createElement('script');
                     script.type = 'text/javascript';
                     script.text = xhr.responseText;
                     document.getElementsByTagName('head')[0].appendChild(script);
                 }
             }
         }
         xhr.send(null); 

        註意:該方法不能夠進行跨域操作

數據存取

  1. 明確javascript中有四種數據存取位置

    • 字面量 --> 相當於常量吧,例如 var a = 100,其中100就是字面量,javascript中的字面量包括:字元串,數字,布爾值,對象字面量,數組字面量,函數,正則字面量以及null,undefined,他們只代表字面量本身的值而不具備特定的存儲位置

    • 本地變數,使用var,let等關鍵字聲明的變數,將字面量存儲在特定的變數上,如var a = 100,其中a就是本地變數

    • 數組元素,存儲在javascript數組中的成員,通過下標獲取。註意:此處的數組元素與數組字面量是不同的概念,如[1, 2, 3, 4],整體表示的是是數組字面量,而不涉及到數組內部元素的存取;[1,2,3,4] [0]則涉及到數組內部元素的訪問

    • 對象成員,對象與對象字面量的區別與數組類似


    通常情況下,訪問字面量或者局部變數的性能消耗相對較小,而對於數組元素和對象成員的訪問代價相對較大,如果對於運行速度有較高的要求,那就儘可能使用字面量和局部變數,減少使用數組和對象,當然當下主流瀏覽器對於變數的訪問進行了優化,對數組和對象的訪問速度的差異已經沒有那麼明顯了。

  2. 函數作用域鏈對變數訪問的影響

    • javascript中是通過鏈的方式來保存作用域的,當一個函數被創建時,該函數的執行期上下文將會被添加到作用域鏈的頂端。

     const name = 'zhang';
     let test = function () {
         let a = 100;
         console.log(name);
     }
     test (); 
     // test執行時需要訪問變數name,而在自身的函數作用域中不存在變數name,因此會向上層作用域進行回溯。當然作用域鏈的層數不一定只有兩層,當多層作用域連接時,訪問變數會一次沿著作用域鏈向上搜索。
    // 當然也有可能訪問完所有的作用域,依然找不到變數,此時就會拋出一個ReferenceError,也就是說,當你在函數中訪問一個不存在的變數時,此時將會沿著作用域鏈向上搜索,直至頂層作用域,效率很低。
    • 由於全局變數時存放在全局作用域中的,位於作用域鏈的最底部,因此對於全局變數的訪問將會很大程度上影響性能 ,在javascript中考慮性能優化時需要儘量避免訪問全局變數,當需要多次訪問全局變數時,可以將其保存到局部變數之中,以加快訪問的速度

  3.  let visitDom () {
         const dom1 = document.getElementById('test1');
         const dom2 = document.getElementById('test2');
         const dom3 = document.getElementById('test3');
         const dom4 = document.getElementById('test4');
         .......
     }
     // 其中,document就是一個全局變數,當訪問document時,函數將需要沿著作用域鏈層層回溯,如果函數所在的層次更深一些,那麼訪問的效率就更低
     let visitDom2 () {
         const doc = docuemnt;
         const dom1 = doc.getElementById('test1');
         const dom2 = doc.getElementById('test2');
         const dom3 = doc.getElementById('test3');
         const dom4 = doc.getElementById('test4');
     }
     // 在上述方法中,使用一個局部變數將需要頻繁訪問的變數document保存起來,此時只需要沿著作用域鏈搜索一次,其餘的操作只要訪問當前作用域即可得到
     // 通過以局部變數保存全局變數的方式,減小搜索的深度。
    改變作用域鏈
    
    with()語句
    
     const visitDom () {
         const a = 100,
               b = 200,
               c = 300;
         with (document) {
             // with語句強制性將document對象添加到函數的作用域鏈的頂部,即在當前with函數域中document是頂級作用域
             const dom1 = getElementById('test1');
             const dom2 = getElementById('test2');
             const dom3 = getElementById('test3');
             const dom4 = getElementById('test4');
             console.log(a);
             console.log(b);
             console.log(c);
         }
     // with()語句執行結束時,document對象將會從作用域鏈頂部移除
     }
     // 看似訪問效率提高了,可是在[[with()函數作用域]]中,對於局部變數的訪問效率將會降低
     // 此處個人的理解可能存在偏差,特此更新,為記錄自己的錯誤理解,因此採用更新的方式糾正
     // 在with語句中訪問a,b,c時,若沒有with語句,就是訪問局部變數;可是有了with語句,由於當前的作用域被document代替,因此,需要向上層作用域進行搜索,此時是的對於局部變數的訪問成本增加。
     // 當然,在上面這個案例中,完全可以避開這種缺陷,即將console.log語句移動到with之外,就可以避免局部變數的訪問,但有些時候是無法將其分離的,比如說在getelementById()函數中使用局部變數,此時就無法分離開來。
    

      

    • with()語句可能會增加對局部變數的訪問成本,因此需要謹慎使用,而且在ES5嚴格模式中已不推薦使用with()語句改變作用域鏈了。

      ****************************************************************************************

      ①今天看了《JavaScript高級程式設計》,裡面有一節延長作用域鏈,看了裡面的代碼,運行了一下,結果是能跑通的,於是乎,就感覺自己之前的理解有誤

      with()語句看起來像一個函數,但其本質上與函數作用域是存在區別的,其本質上是一條語句

      function buildUrl () {
          var qs = '?name=zhang';
          with (location) {
              var url = href + qs; 
          }
          return url; // 如果with()是一個函數作用域的話,此處訪問url將會報錯,但實際上並不會,因此,with()語句是不會識別為作用域的
      }
      document.write(buildUrl());

      更新時間:2019-04-15  10:52:52

  1.   ****************************************************************************************

    1. catch()語句

      catch語句中需要接受一個異常參數,並對異常進行處理,但cath語句會將異常對象推入到作用域鏈頂端,就如同with一樣,會造成局部變數訪問的性能開銷

     try {
         do something ...
     } catch (ex) {
         // 此時ex對象作為catch函數作用域鏈中的頂層作用域
         handle exception ...
     }
     // 不同於with()語句,有些時候catch語句的作用是無法被替代的,因而很難避免使用catch語句,我們需要獲取ex異常對象,進行一些異常處理,那麼方法來了,可以聲明一個專門的異常處理函數,將ex對象作為參數傳入
     try {
         do something ...
     } catch (ex) {
         handerExp(ex);
     }
             
     const handlerExp =  function (ex) {
         const a = 100,
               b = 200,
               c = 300;
         handle exception ...
     }
     // 此時訪問局部變數就不會存在回溯作用域鏈的情況了
    • 動態作用域

      with(),catch()以及eval()語句都屬於動態作用域,動態作用域只存在於代碼執行的過程中,因此無法通過靜態分析檢測

       function excute (code) {
           eval (code);
           function subroutine () {
               return window;
           }
           var w = subroutine;
           console.log(w);
       }
       // 此時w的值其實是無法靜態分析的,只有在code值確定時方能確定
       excute('var window = 100');
       // w --> 100
       excute('var a = 100');
       // w --> window(全局對象)

      案例引自《高性能JavaScript》 作者:[美]Nicholas C.Zakas; 丁琛譯

      使用eval()會使得瀏覽器自身的靜態分析優化失效

      (部分瀏覽器自身實現了通過代碼分析確定哪些變數會被訪問,從而避開搜索作用域鏈,進而達到優化變數搜索的功能)

      因而,一般儘量避免使用動態作用域

  2. 對象成員的訪問

    • 對象原型

      javascript是通過原型的方式實現繼承

      hasOwnProperty()與in操作符的比較:

      兩者都是用來判斷某一個key是否存在於對象內部

      不同點在於:hasOwnProperty()方法只會遍歷對象本身的屬性,而in操作符會遍歷對象原型上的屬性

      因此,頻繁的使用in操作符會降低搜索的效率

    • 原型鏈

       function Book (title, publisher) {
           this.title = title;
           this.publisher = publisher;
       }
       ​
       Book.prototype.sayTitle () {
           alert(this.title);
       }
       ​
       const book1 = new Book('High Performance JavaScript', 'Yahoo! Press');
       const book2 = new Book('JavaScript:The Good Parts', 'Yahoo! Press');
       alert(book1 instanceof Book); // true
       alert(book2 instanceof Object); // true
       ​
       book1.sayTitle(); // High Performance JavaScript
       alert(book1.toString()); // [object Object]
      

      當需要訪問原型鏈上某個對象上的變數時,同樣需要類似於作用域鏈那樣一層層向上搜索。對於原型鏈的搜索會帶來類似於函數作用域鏈那樣的開銷。

    • 對象的嵌套訪問

      a.b.c.d --> 對象成員嵌套的層數越多,訪問的開銷越大

    • 對於一個多次訪問但是不發生修改更新操作的對象,可以將其緩存為局部變數,以減小性能的開銷

       const a = {
           b: {
               c: {
                   d: {
                       method () {
                           do something ...
                       }
                   }
               }
           }
       }
           
       // 當我們需要頻繁使用method方法時,就需要重覆沿著嵌套對象一層一層往下找
      適
       const d = a.b.c.d
       d.method();
       d.method();
       // 這是一種折中的方案,此時method方法中的this指向d,同時也避免了不必要的嵌套搜索

----------------------------------------------------------------------------

好了,今天《JS高程》又到了,感覺自己水平還是不太夠,有些基礎還有待加強哈!!

自勉

-----------------------------------------------------------------------------

2019-04-11    15:34:23


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 示例代碼托管在: "http://www.github.com/dashnowords/blogs" 博客園地址: "《大史住在大前端》原創博文目錄" 華為雲社區地址: "【你要的前端打怪升級指南】" [TOC] 一. 任務說明 使用原生 繪製水球圖,這將是一個非常有意思的挑戰任務。水球圖是一種常見 ...
  • 本篇文檔主要是利用echarts實現可拖動節點的折現圖,在echarts中找到了一個demo,傳送門:https://echarts.baidu.com/examples/editor.html?c=line-draggable,但是不是用vue寫的,並且在改寫為vue組件的過程中遇到了很多問題,在 ...
  • 面試高頻之js的非同步載入 講這個問題之前, 我們從另一個面試高頻問題來切入, 我們的web頁面從開始解析到頁面渲染完成都經歷了什麼 ? 1 , 創建document對象, 開始解析頁面, 此時document.readyState = 'loading' 2 , 遇到link標簽引入的css文件, ...
  • 一. 框架 選用express框架 二. 簡單測試請求 在當前目錄新建index.js文件 複製代碼在終端輸入: node index.js 在瀏覽器中打開 127.0.0.1:3000 <! more 三.使用form上傳圖片 將index.js中的介面更新成 註意:index.js中的文件只要改 ...
  • 一、媒體(介)查詢 1.1 基本語法 媒體查詢由媒體類型和一個或多個檢測媒體特性的條件表達式組成。媒體查詢中可用於檢測的媒體特性有:width、height和color(等)。使用媒體查詢可以在不改變頁面內容的情況下,為特性的一些輸出設備定製顯示效果。 使用 @media 查詢,你可以針對不同的媒體 ...
  • CSS3的出現給網站頁面增加了活力,網站增色不少,有這麼小小的一款插件就能做出很多動畫效果。 最重要的是它:簡單易用、輕量級、無需 jQuery......他就是wow.js 地址:https://daneden.github.io/animate.css/ 也可以在這個地方看各種演示 下麵就讓我們 ...
  • 因為是Node伺服器端的,怎樣實現前臺和後臺請求以及回應 URL(由什麼組成的 ),傳輸的內容:表單數據 文件數據 【圖片、壓縮包、各種尾碼文件】 URL的組成 URL由三部分組成: 協議類型 , 主機名 和 路徑及文件名 。通過URL可以指定的主要有以下幾種:http、ftp、gopher、tel ...
  • getter 和 setter: 1、ES5 里,屬性值可以用一個或兩個方法代替,這兩個方法就是 getter 和 setter,它們使用 get 和 set 進行定義而不是通過 function 2、由 getter 和 setter 定義的屬性稱作 “存取器屬性”,它不同於“數據屬性”,數據屬性 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...