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

时间:2010-08-16 12:52:13

标签: javascript performance function-prototypes

我使用原型运算符读取here (Douglas Crockford)以向Javascript类添加方法保存内存

然后我读了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();

P.S。

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


编辑:对于 JS对象和JS静态对象之间的性能比较,它可能感兴趣的人可以阅读this answer below静态对象肯定更快,显然只有在您不需要多个对象实例时才可以使用它们。

10 个答案:

答案 0 :(得分:59)

这是一个有趣的问题,所以我运行了一些非常简单的测试(我应该重新启动我的浏览器以清除内存,但我没有;拿它来获取它的价值)。看起来至少在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 < 1000000; 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 < 1000000; i++) {
    x = new X();
    x.message('hi');
    x.addition(i,2)
}
console.log((new Date()).getTime() - intNow);//FF=3894ms;Safari=606

这真是一种耻辱,因为我真的讨厌使用prototype。我喜欢我的目标代码是自封装的,不允许漂移。我想当速度很重要时,我没有选择。该死。

[编辑]非常感谢@Kevin指出我之前的代码错误,大大提升了prototype方法的报告速度。在修复之后,原型仍然明显加快,但差异并不是那么大。

答案 1 :(得分:31)

我猜这取决于你想要创建的对象的类型。我和安德鲁进行了类似的测试,但是使用静态对象,静态对象赢了。这是测试:

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

FF:关闭时间:233,原型时间:141,静态物体时间:94

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

Chrome:关闭时间:13,原型时间:8,静态对象时间:3

所学到的经验教训是,如果不要需要从同一个类中实例化许多不同的对象,那么将其创建为静态对象会失败。所以要仔细考虑你真正需要什么样的课程。

答案 2 :(得分:6)

所以我决定也测试一下。我测试了创建时间,执行时间和内存使用情况。我使用Nodejs v0.8.12和在Mac Book Pro上运行的mocha测试框架启动到Windows 7.“快速”结果使用原型,“慢”结果使用模块模式。我创建了100万种每种类型的对象,然后在每个对象中访问了4种方法。结果如下:

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个字节。

答案 3 :(得分:3)

直观地说,在原型上创建函数似乎更节省内存和更快:函数只创建一次,而不是每次创建新实例。

但是,当访问该功能时,会有轻微的性能差异。引用c.showMsg时,JavaScript运行时首先检查c上的属性。如果找不到,则检查c的原型。

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

答案 4 :(得分:2)

我们需要分离对象构造和用法。

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

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)。您可能仍需要针对特定​​情况进行基准测试。

所以:

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

PS:我用Andrew的答案作为参考。使用相同的循环和符号。

答案 5 :(得分:1)

I ran my own tests

第一个结论是,静态访问实际上比实际原型更慢。有趣的是,the Version 23 of this test中有一个有缺陷的原型(变量X),它只是一遍又一遍地返回完全被覆盖的原型对象,当我创建我的测试时,这个原型设计仍比我的“真实原型”测试慢

无论如何,回答:除非我的测试有缺陷,否则它表明真正的原型设计是最快的。忽略实例化时,它比静态对象更好或至少等于静态对象。实例化和私有变量的这个赋值都要慢得多。我不会猜到私有变量会这么慢。

我可能感兴趣的是我用jQuery.extend扩展了原型Object,它与直接赋值的速度大致相同。当然,延伸不在测试范围内。至少这是一种规避令人讨厌的“.prototype”的方法。 - 一直是零件。

答案 6 :(得分:1)

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

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

在每个类别(10,000次迭代)中禁食

  • 仅限属性访问(~0.5ms){ __proto__: Type }
  • 使用属性访问(&lt; 3ms)循环创建对象:Object.create(Type)

代码使用ES6而不进行babel转换以确保准确性。它适用于当前的chrome。运行下面的测试以查看故障。

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>

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

不过很有趣。这与您创建的对象类型无关,而重要的是您如何编写示例。同样,我运行了与 shmuel613 类似的测试,后者编写了与 Andrew 类似的测试。第一个测试是创建构造函数、类和对象字面量的单个实例,然后从构造函数的实例函数、类的原型方法和对象字面量的静态函数测量执行速度:

var Y, Z, x, y, z;

class X {
    message(s) {
        var mymessage = s + "";
    };
    addition(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(s) {
        var mymessage = s + "";
    },
    addition(i, j) {
        return (i * 2 + j * 2) / 2;
    }
}

function TestPerformance() {
    console.time("Closure time:");
    y = new Y(); // create a single instance
    for (var i = 0; i < 100000; i++) {
        // I am comparing a single instance with the other single instances
        y.message('hi');
        y.addition(i, 2);
    }
    console.timeEnd("Closure time:");

    console.time("Prototype time:");
    x = new X(); // create a single instance
    for (var i = 0; i < 100000; i++) {
        // I am comparing a single instance with the other single instances
        x.message('hi');
        x.addition(i, 2);
    }
    console.timeEnd("Prototype time:");

    console.time("Static object time:");
    for (var i = 0; i < 100000; i++) {
        z = Z; // obviously you don't really need this
        z.message('hi');
        z.addition(i, 2);
    }
    console.timeEnd("Static object time:");
}

TestPerformance();

第二个测试测量创建构造函数、类和对象文字的多个实例的执行速度,然后执行实例函数、原型方法和静态方法:

var Y, x, y, z;

class X {
    message(s) {
        var mymessage = s + "";
    };
    addition(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;
    };
};

function TestPerformance() {
    console.time("Closure time:");
    //y = new Y()
    for (var i = 0; i < 100000; i++) {
        y = new Y(); // creating an instance
        y.message('hi');
        y.addition(i, 2);
    }
    console.timeEnd("Closure time:");

    console.time("Prototype time:");
    //x = new X();
    for (var i = 0; i < 100000; i++) {
        x = new X(); // creating an instance
        x.message('hi');
        x.addition(i, 2);
    }
    console.timeEnd("Prototype time:");

    console.time("Static object time:");
    for (var i = 0; i < 100000; i++) {
        z = { 
            message(s) {
                var mymessage = s + "";
            },
            addition(i, j) {
                return (i * 2 + j * 2) / 2;
            }
        }; // creating an instance such as from factory functions
        z.message('hi');
        z.addition(i, 2);
    }
    console.timeEnd("Static object time:");
}

TestPerformance();

吸取的教训是,不要在不彻底的情况下盲目地对某事产生偏见。构造函数(ES2016 之前的类)的实例函数的执行速度和类的原型方法的执行速度实际上与对象的静态函数的执行速度一样快。然而,具有实例函数的构造函数实例的创建速度与执行速度、具有原型方法的类实例的创建速度与具有静态方法的对象字面量的创建速度表明,具有原型方法的类在 Chrome 上创建和执行速度更快、Microsoft edge 和 Opera。使用静态方法创建对象字面量的速度仅在 Mozilla firefox 中更快

答案 9 :(得分:-3)

  

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

实际上结果与我们预期的不同 - 原型方法的访问时间比访问精确附加到对象的方法(FF测试)更快。