代码之家  ›  专栏  ›  技术社区  ›  Marco Demaio

Javascript原型操作符性能:节省内存,但速度更快吗?

  •  43
  • Marco Demaio  · 技术社区  · 15 年前

    我读过 here (Douglas Crockford) 还可以节省内存 .

    this John Resig's article 非常,非常,非常快 ,但他是在谈论以标准的方式使用原型,还是在谈论他在文章中的具体例子?

    function Class1()
    {
       this.showMsg = function(string) { alert(string); }
    }
    var c = new Class1();
    c.showMsg();
    

    慢于

    function Class1() {}
    Class1.prototype.showMsg = function(string) { alert(string); }
    var c = new Class1();
    c.showMsg();
    

    我知道prototype用于创建继承和单例对象等,但这个问题与这些主题没有任何关系。


    编辑:它可能也对谁感兴趣 JS对象与JS静态对象的性能比较 this answer below . ,显然,只有当您不需要对象的多个实例时,才可以使用它们。

    9 回复  |  直到 8 年前
        1
  •  64
  •   Jorge Fuentes González    5 年前

    这是一个有趣的问题,所以我运行了一些非常简单的测试(我应该重新启动浏览器来清除内存,但我没有;把这个值多少钱)。至少在Safari和Firefox上, prototype 运行速度明显加快[编辑:不是前面提到的20倍]。我敢肯定,使用功能齐全的对象进行真实世界的测试会是一个更好的比较。我运行的代码是这样的(我分别运行了几次测试):

    var X,Y, x,y, i, intNow;
    
    X = function() {};
    X.prototype.message = function(s) { var mymessage = s + "";}
    X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; }
    
    Y = function() {
      this.message = function(s) { var mymessage = s + "";}
      this.addition = function(i,j) { return (i *2 + j * 2) / 2; }
    };
    
    
    intNow = (new Date()).getTime();
    for (i = 0; i < 10000000; i++) {
      y = new Y();
      y.message('hi');
      y.addition(i,2)
    }
    console.log((new Date()).getTime() - intNow); //FF=5206ms; Safari=1554
    
    intNow = (new Date()).getTime();
    for (i = 0; i < 10000000; i++) {
      x = new X();
      x.message('hi');
      x.addition(i,2)
    }
    console.log((new Date()).getTime() - intNow);//FF=3894ms;Safari=606

    真的很遗憾,因为我真的很讨厌用 原型 . 我喜欢我的目标代码是自我封装,不允许漂移。不过,我想当速度很重要的时候,我别无选择。该死的。

    [编辑]非常感谢@Kevin,他指出了我以前的代码是错误的,这大大提高了报告的速度

        2
  •  32
  •   CaffeineAddiction    9 年前

    我猜这取决于你想要创建的对象的类型。我和Andrew做了一个类似的测试,但是使用了一个静态对象,静态对象轻而易举地就赢了。下面是测试:

    var X,Y,Z,x,y,z;
    
    X = function() {};
    X.prototype.message = function(s) { var mymessage = s + "";}
    X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; }
    
    Y = function() {
        this.message = function(s) { var mymessage = s + "";}
        this.addition = function(i,j) { return (i *2 + j * 2) / 2; }
    };
    
    Z = {
     message: function(s) { var mymessage = s + "";}
     ,addition: function(i,j) { return (i *2 + j * 2) / 2; }
    }
    
    function TestPerformance()
    {
      var closureStartDateTime = new Date();
      for (var i = 0; i < 100000; i++)
      {
     y = new Y();
        y.message('hi');
        y.addition(i,2);
      }
      var closureEndDateTime = new Date();
    
      var prototypeStartDateTime = new Date();
      for (var i = 0; i < 100000; i++)
      {
        x = new X();
        x.message('hi');
        x.addition(i,2);
      }
      var prototypeEndDateTime = new Date();
    
      var staticObjectStartDateTime = new Date();
      for (var i = 0; i < 100000; i++)
      {
     z = Z; // obviously you don't really need this
        z.message('hi');
        z.addition(i,2);
      }
      var staticObjectEndDateTime = new Date();
      var closureTime = closureEndDateTime.getTime() - closureStartDateTime.getTime();
      var prototypeTime = prototypeEndDateTime.getTime() - prototypeStartDateTime.getTime();
      var staticTime = staticObjectEndDateTime.getTime() - staticObjectStartDateTime.getTime();
      console.log("Closure time: " + closureTime + ", prototype time: " + prototypeTime + ", static object time: " + staticTime);
    }
    
    TestPerformance();
    

    此测试是对我在以下位置找到的代码的修改:

    http://blogs.msdn.com/b/kristoffer/archive/2007/02/13/javascript-prototype-versus-closure-execution-speed.aspx

    结果:

    IE6:关闭时间:1062,原型时间:766,静态对象时间:406

    IE8:关闭时间:781,原型时间:406,静态对象时间:188

    Safari:关闭时间:152,原型时间:12,静态对象时间:6

    我们得到的教训是 需要实例化同一个类中的许多不同对象,然后将其创建为静态对象就可以轻而易举地获得成功。所以仔细想想你真正需要什么样的课程。

        3
  •  6
  •   user1822264    13 年前

    c:\ABoxAbove>mocha test/test_andrew.js
    
    Fast Allocation took:170 msec
    ·Fast Access took:826 msec
    state[0] = First0
    Free Memory:5006495744
    
    ·Slow Allocation took:999 msec
    ·Slow Access took:599 msec
    state[0] = First0
    Free Memory:4639649792
    
    Mem diff:358248k
    Mem overhead per obj:366.845952bytes
    
    ? 4 tests complete (2.6 seconds)
    

    var assert = require("assert"), os = require('os');
    
    function Fast (){}
    Fast.prototype = {
        state:"",
        getState:function (){return this.state;},
        setState:function (_state){this.state = _state;},
        name:"",
        getName:function (){return this.name;},
        setName:function (_name){this.name = _name;}
    };
    
    function Slow (){
        var state, name;
        return{
            getState:function (){return this.state;},
            setState:function (_state){this.state = _state;},
            getName:function (){return this.name;},
            setName:function (_name){this.name = _name;}
        };
    }
    describe('test supposed fast prototype', function(){
        var count = 1000000, i, objs = [count], state = "First", name="Test";
        var ts, diff, mem;
        it ('should allocate a bunch of objects quickly', function (done){
            ts = Date.now ();
            for (i = 0; i < count; ++i){objs[i] = new Fast ();}
            diff = Date.now () - ts;
            console.log ("Fast Allocation took:%d msec", diff);
            done ();
        });
        it ('should access a bunch of objects quickly', function (done){
            ts = Date.now ();
            for (i = 0; i < count; ++i){
                objs[i].setState (state + i);
                assert (objs[i].getState () === state + i, "States should be equal");
                objs[i].setName (name + i);
                assert (objs[i].getName () === name + i, "Names should be equal");
            }
            diff = Date.now() - ts;
            console.log ("Fast Access took:%d msec", diff);
            console.log ("state[0] = " + objs[0].getState ());
            mem = os.freemem();
            console.log ("Free Memory:" + mem + "\n");
            done ();
        });
        it ('should allocate a bunch of objects slowly', function (done){
            ts = Date.now ();
            for (i = 0; i < count; ++i){objs[i] = Slow ();}
            diff = Date.now() - ts;
            console.log ("Slow Allocation took:%d msec", diff);
            done ();
        });
        it ('should access a bunch of objects slowly', function (done){
            ts = Date.now ();
            for (i = 0; i < count; ++i){
                objs[i].setState (state + i);
                assert (objs[i].getState () === state + i, "States should be equal");
                objs[i].setName (name + i);
                assert (objs[i].getName () === name + i, "Names should be equal");
            }
            diff = Date.now() - ts;
            console.log ("Slow Access took:%d msec", diff);
            console.log ("state[0] = " + objs[0].getState ());
            var mem2 = os.freemem();
            console.log ("Free Memory:" + mem2 + "\n");
            console.log ("Mem diff:" + (mem - mem2) / 1024 + "k");
            console.log ("Mem overhead per obj:" + (mem - mem2) / count + 'bytes');
            done ();
        });
    });
    

    结论:这证实了本文中其他人的发现。如果您不断地创建对象,那么原型机制显然更快。如果代码将大部分时间花在访问对象上,那么模块模式会更快。如果您对内存使用很敏感,那么原型机制会减少每个对象约360字节的内存使用量。

        4
  •  3
  •   harto    15 年前

    直观地说,在原型上创建函数会更高效、更快:函数只创建一次,而不是每次创建一个新实例。

    然而,当它的时候会有一个轻微的性能差异 接近 函数。什么时候? c.showMsg c . 如果找不到, 然后检查的原型。

    因此,在实例上创建属性将导致访问时间稍微加快—但这可能只是非常深层的原型层次结构的问题。

        5
  •  2
  •   Vakhtang    8 年前

    我们需要将对象的构造和用法分开。

    在原型上声明函数时,它在所有实例之间共享。在构造函数中声明函数时,每次创建新实例时都会重新创建函数。有鉴于此,我们需要分别对构造和使用进行基准测试,以获得更好的结果。这就是我所做的,我想和你们分享结果。这个基准不测试施工速度。

    function ThisFunc() {
        this.value = 0;
        this.increment = function(){
            this.value++;
        }
    }
    
    function ProtFunc() {
        this.value = 0;
    }
    
    ProtFunc.prototype.increment = function (){
        this.value++;
    }
    
    function ClosFunc() {
        var value = 0;
    
        return {
            increment:function(){
                value++;
            }
        };
    }
    
    var thisInstance = new ThisFunc;
    
    var iterations = 1000000;
    var intNow = (new Date()).getTime();
    for (i = 0; i < iterations; i++) {
        thisInstance.increment();
    }
    console.log(`ThisFunc: ${(new Date()).getTime() - intNow}`); // 27ms node v4.6.0
    
    var protInstance = new ProtFunc;
    intNow = (new Date()).getTime();
    for (i = 0; i < iterations; i++) {
        protInstance.increment();
    }
    console.log(`ProtFunc: ${(new Date()).getTime() - intNow}`); // 4ms node v4.6.0
    
    var closInstance = ClosFunc();
    intNow = (new Date()).getTime();
    for (i = 0; i < iterations; i++) {
        closInstance.increment();
    }
    console.log(`ClosFunc: ${(new Date()).getTime() - intNow}`); // 7ms node v4.6.0
    

    从这些结果我们可以看到原型版本是最快的(4ms),但是闭包版本非常接近(7ms)。您可能仍然需要为您的特定案例设定基准。

    • 当我们需要每一点性能或在实例之间共享功能时,我们可以使用原型版本。
    • 我们可以使用其他版本,当我们需要的是他们提供的功能(私有状态封装、可读性等)

    附:我用安德鲁的回答作为参考。使用相同的循环和符号。

        6
  •  1
  •   Benno    10 年前

    I ran my own tests .

    the Version 23 of this test 它有一个有缺陷的原型(变量X),它只是一次又一次地返回完全重写的原型对象,当我创建测试时,这个原型仍然比我的“真实原型”测试慢。

    不管怎样, 答案是什么 :除非我的测试有缺陷,否则它表明真正的原型是最快的。当忽略实例化时,它胜过或至少等于静态对象。实例化和私有变量的赋值都要慢得多。我没想到私有变量会这么慢。

    我用jQuery.extend 在和之间,它和直接分配的速度差不多。当然,扩展超出了测试本身。至少这是一种避免编写烦人的“.prototype.”部件的方法。

        7
  •  1
  •   Community CDub    5 年前

    高分辨率浏览器性能API测试

    这里的测试都没有利用 performance API 对于高分辨率测试,所以我写了一个,它将显示许多不同场景的当前最快结果,包括2个比大多数运行中的任何其他答案都快的结果。

    • 仅限财产访问 (~0.5毫秒) { __proto__: Type }
    • 使用属性访问循环创建对象 : Object.create(Type)

    function profile () {
      function test ( name
                    , define
                    , construct
                    , { index = 0
                      , count = 10000
                      , ordinals = [ 0, 1 ]
                      , constructPrior = false
                      } = {}
                    ) {
        performance.clearMarks()
        performance.clearMeasures()
        const symbols = { type: Symbol('type') }
        const marks = (
          { __proto__: null
          , start: `${name}_start`
          , define: `${name}_define`
          , construct: `${name}_construct`
          , end: `${name}_end`
          }
        )
    
        performance.mark(marks.start)
        let Type = define()
        performance.mark(marks.define)
    
        let obj = constructPrior ? construct(Type) : null
        do {
          if(!constructPrior)
            obj = construct(Type)
          if(index === 0)
            performance.mark(marks.construct)
    
          const measureOrdinal = ordinals.includes(index)
          if(measureOrdinal)
              performance.mark(`${name}_ordinal_${index}_pre`)
    
          obj.message('hi')
          obj.addition(index, 2)
    
          if(measureOrdinal)
            performance.mark(`${name}_ordinal_${index}_post`)
        } while (++index < count)
        performance.mark(marks.end)
    
        const measureMarks = Object.assign (
          { [`${name}_define`]: [ marks.start, marks.define ]
          , [`${name}_construct`]: [ marks.define, marks.construct ]
          , [`${name}_loop`]: [ marks.construct, marks.end ]
          , [`${name}_total`]: [ marks.start, marks.end ]
          }
        , ordinals.reduce((reduction, i) => Object.assign(reduction, { [`${name}_ordinal_${i}`]: [ `${name}_ordinal_${i}_pre`, `${name}_ordinal_${i}_post` ] }), {})
        )
    
        Object.keys(measureMarks).forEach((key) => performance.measure(key, ...measureMarks[key]))
    
        const measures = performance.getEntriesByType('measure').map(x => Object.assign(x, { endTime: x.startTime + x.duration }))
        measures.sort((a, b) => a.endTime - b.endTime)
        const durations = measures.reduce((reduction, measure) => Object.assign(reduction, { [measure.name]: measure.duration }), {})
    
        return (
          { [symbols.type]: 'profile'
          , profile: name
          , duration: durations[`${name}_total`]
          , durations
          , measures
          }
        )
      }
    
      const refs = (
        { __proto__: null
        , message: function(s) { var mymessage = s + '' }
        , addition: function(i, j) { return (i *2 + j * 2) / 2 }
        }
      )
    
      const testArgs = [
        [ 'constructor'
        , function define() {
            return function Type () {
              this.message = refs.message
              this.addition = refs.addition
            }
          }
        , function construct(Type) {
            return new Type()
          }
        ]
      , [ 'prototype'
        , function define() {
            function Type () {
            }
            Type.prototype.message = refs.message
            Type.prototype.addition = refs.addition
            return Type
          }
        , function construct(Type) {
            return new Type()
          }
        ]
      , [ 'Object.create'
        , function define() {
            return (
              { __proto__: null
              , message: refs.message
              , addition: refs.addition
              }
            )
          }
        , function construct(Type) {
            return Object.create(Type)
          }
        ]
      , [ 'proto'
        , function define() {
            return (
              { __proto__: null
              , message: refs.message
              , addition: refs.addition
              }
            )
          }
        , function construct(Type) {
            return { __proto__: Type }
          }
        ]
      ]
    
      return testArgs.reduce(
        (reduction, [ name, ...args ]) => (
          Object.assign( reduction
          , { [name]: (
                { normal: test(name, ...args, { constructPrior: true })
                , reconstruct: test(`${name}_reconstruct`, ...args, { constructPrior: false })
                }
              )
            }
          )
        )
      , {})
    }
    
    let profiled = profile()
    const breakdown = Object.keys(profiled).reduce((reduction, name) => [ ...reduction, ...Object.keys(profiled[name]).reduce((r, type) => [ ...r, { profile: `${name}_${type}`, duration: profiled[name][type].duration } ], []) ], [])
    breakdown.sort((a, b) => a.duration - b.duration)
    try {
      const Pre = props => React.createElement('pre', { children: JSON.stringify(props.children, null, 2) })
      
      ReactDOM.render(React.createElement(Pre, { children: { breakdown, profiled } }), document.getElementById('profile'))
    } catch(err) {
        console.error(err)
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
    
    <div id="profile"></div>
        8
  •  0
  •   SBUJOLD    15 年前

    我敢肯定,只要实例化对象,它的速度会更快,占用的内存也会更少,这一点毫无疑问,但我认为javascript引擎需要遍历对象的所有属性,以确定调用的属性/方法是否是该对象的一部分,如果不是,则检查原型。我不是100%确定,但我假设它就是这样工作的,如果是这样的话,那么在某些情况下,如果你的对象添加了很多方法,只实例化了一次并且大量使用,那么它可能会慢一点,但这只是一个假设,我没有测试任何东西。

    但最后,我还是同意,作为一般规则,使用原型会更快。

        9
  •  -3
  •   Piotr    15 年前

    因此,在实例上创建属性将导致访问时间稍微加快—但这可能只是非常深层的原型层次结构的问题。

    实际上,结果与我们预期的不同——访问原型方法的时间比访问完全附加到对象的方法(FF测试)的时间要快。