2012年4月10日 星期二

各式 JavaScript Class 的效能比較

Standard
我們在做 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 倍,記憶體也節省不少。