各式 JavaScript Class 的效能比較

我們在做 JavaScript 開發時,常會使用到類別(Class) 的設計方法,但事實上,JavaScript 根本沒有所謂的類別(Class) 概念,此功能完全是靠動態物件(Object) 所模擬出來。因此,在 JavaScript 中,有數種方法可以定義和設計我們自己的類別(Class) 。可是哪一種方法的效能比較好呢?這便是本文的主題。

為免空口白話,筆者在 jsperf.com 建立了一個 Test Case『Prototype Operator Performance』以便實驗,並邀請各方網友使用瀏覽器前來測試,比較使用和未使用 Prototype 兩種情況,以及不同寫法的速度。(若你有興趣幫忙做實驗,進入該網站後, 請點選 Run Tests 開始測試)

註:在測試過程中,請確定你的瀏覽器,沒去跑其他的吃重的網頁,以確保實驗結果的準確性。

你可以在該網站上,觀察其他網友們的測試結果。但是,由於本次目的,主要測試的是同一種 JavaScript 引擎,處理不同建構方法的速度,所以,大家只要專注於在同一個瀏覽器下,其測試結果就可以。不同電腦,不同系統和不同瀏覽器的速度,並不是我們這次比較的重點。此外,因為 Node.js 使用的是 V8 JavaScript Engine,在本文中主要會拿 Chrome (因為 Chrome 使用的是 V8 JavaScript Engine)的結果討論。

這個測試有兩組實驗,第一組是測試呼叫類別(Class) 中方法(Method) 的速度,我們分別測試三種,用不同方法建構出來的類別(Class):
  1. 未使用原型(Prototype),直接在 Constructor 內定義 方法(Method)
    A = function() {
      this.message = function(s) {
        var msg = s + '';
      };
      this.addition = function(i, j) {
        return (i * 2 + j * 2) / 2;
      };
    };
    A_instance = new A();
    A_instance.message('Hi');
    A_instance.addition(i, 2);
  2. 使用原型(Prototype),在 Constructor 外定義 方法(Method)
    B = function() {};
    B.prototype.message = function(s) {
      var msg = s + '';
    };
    B.prototype.addition = function(i, j) {
      return (i * 2 + j * 2) / 2;
    };
    B_instance = new B();
    B_instance.message('Hi');
    B_instance.addition(i, 2);
  3. 使用原型(Prototype),直接在 Constructor 內定義 方法(Method)
    C = function() {
      C.prototype.message = function(s) {
        var msg = s + '';
      };
      C.prototype.addition = function(i, j) {
        return (i * 2 + j * 2) / 2;
      };
    };
    C_instance = new C();
    C_instance.message('Hi');
    C_instance.addition(i, 2);

第二組則是運用 A、B、C 三種類別(Class),測試建立實例(Instance) 的速度:
  1. A Class
    A_instance = new A();
    A_instance.message('Hi');
    A_instance.addition(i, 2);
  2. B Class
    B_instance = new B();
    B_instance.message('Hi');
    B_instance.addition(i, 2);
  3. C Class
    C_instance = new C();
    C_instance.message('Hi');
    C_instance.addition(i, 2);

截至目前為止,V8 JavaScript Engine 的測試結果:

顏色各代表:
  • 藍:Call Methods of A Class
  • 紅:Call Methods of B Class
  • 橘:Call Methods of C Class
  • 綠:Create A Instance
  • 紫:Create B Instance
  • 藍綠:Create C Instance


單純比較 呼叫方法(Call Method) ,不使用 Prototype 略勝一疇

雖然不同瀏覽器上不盡相同,但從測試結果來看,通常呼叫 A Class,不使用原型(Prototype) 所定義出來的方法(Method),速度最快,這通常是因為執行原型(Prototype) 的方法(Method)之前,會先檢查是否有一般的方法(Method),所以 JavaScript Engine 多做了一道檢查拖慢了速度。

比較建立實例(Instance) 的速度,使用 Prototype 快過其他實作近 20 倍

如果是比較建立實例(Instance) 的速度,B Class 『在類別(Class) 外使用原型(Prototype) 去定義方法(Method) 』的方式,速度快過其他方法將近 20 倍,效能差距相當大。這造成效能低落的主要是因為 Constructor 的複雜度,JavaScript 在建立 實例(Instance) 時,每次都會執行 Constructor,所以 Constructor 一旦工作很多,效能理所當然會降低。

而且,從測試結果來看,在 Constructor 外事先使用 原型(Prototype) 的情況下,所有的 實例(Instance) 會共用方法(Method),而不是自己建立自己的,所以除了省記憶體外,理所當然也減少每次建立 方法(Method) 的時間。更重要的是,如果節省記憶體,也意味著 Garbage Collection 的工作越少,效能也會多少有些提升。

總結

整體上來說,使用 Prototype 最好的方式,如果在需要重覆建立多個 實例(Instance) 的情況下,效能可以提升 20 倍,記憶體也節省不少。

留言

  1. Hi, Fred

    最近在看JS效能的文件,想起你這篇文章,只有在constructor之外宣告member function才是正確的做法。

    msdn的這篇文件也建議了同樣的事情,很明確的表示不該在ctor設定method

    這篇文件也有從engine的角度來解釋各種情境,有些地方還挺讓我意外的,尤其是inferred type system那裏,連順序都會有影響

    我稍微改了一下別人的jsperf測試,property順序不同在Chrome效能會相差約30%
    http://jsperf.com/use-the-same-property-order/2

    所以我最近在找Firefox和Chrome(V8)有沒有類似的文件,不過沒有找到,不知道Fred您有沒有些推薦的連結可以看?

    還有剛才發現你的jsperf的i沒有設初值,雖然不曉得這是不是原本的意圖,不過就算這樣測試對每個情境也都是公平的@@

    然後我隨手改了一下,加上測試用Object.defineProperties設定method,結果效能是差不多
    http://jsperf.com/prototype-operator-performance/2

    回覆刪除

張貼留言

這個網誌中的熱門文章

有趣的邏輯問題:是誰在說謊

Web 技術中的 Session 是什麼?

淺談 USB 通訊架構之定義(一)

淺談 USB 通訊架構之定義(二)

Reverse SSH Tunnel 反向打洞實錄