91

JavaScript 1.8.5 (ECMAScript 5) adds some interesting methods that prevent future modifications of a passed object, with varying degrees of thoroughness:

Presumably the main point of these is to catch mistakes: if you know that you don't want to modify an object after a certain point, you can lock it down so that an error will be thrown if you inadvertently try to modify it later. (Providing you've done "use strict"; that is.)

My question: in modern JS engines such as V8, is there any performance benefit (eg, faster property look-ups, reduced memory footprint) in locking down objects using the above methods?

(See also John Resig's nice explanation – doesn't mention performance, though.)

callum
  • 34,206
  • 35
  • 106
  • 163
  • Possible duplicate of [Difference between freeze and seal in Javascript](http://stackoverflow.com/questions/21402108/difference-between-freeze-and-seal-in-javascript) – maja May 07 '17 at 13:12
  • I posted a new answer with working perf links. I attempted to prevent any dead code elimination, and it seems as though frozen objects are slightly slower for iteration using for-in. https://stackoverflow.com/a/70490675/454780 – trusktr Dec 27 '21 at 00:15

7 Answers7

104

There's been no difference in performance since at least Chrome 47.0.2526.80 (64-bit).

Testing in Chrome 6.0.3359 on Mac OS 10.13.4
-----------------------------------------------
Test               Ops/sec
non-frozen object  106,825,468  ±1.08%  fastest
frozen object      106,176,323  ±1.04%  fastest

Performance test (available at http://jsperf.com/performance-frozen-object):

  const o1 = {a: 1};
  const o2 = {a: 1};

  Object.freeze(o2);

  // Non-frozen object:
  for(var key in o1);

  // Frozen object:
  for(var key in o2);

Update 30.10.2019: There's no difference in performance on Chrome 78.0.3904 (64-bit)

Update 17.09.2019: There's no difference in performance on Chrome 76.0.3809 (64-bit)

Update 03.05.2018: There's no difference in performance on Chrome 66.0.3359 (64-bit)

Update 06.03.2017: There's no difference in performance on Chrome 56.0.2924 (64-bit)

Update 13.12.2015: There's no difference in performance on Chrome 47.0.2526.80 (64-bit)


With Chrome 34, a frozen object performs slightly better than a non-frozen one in @pimvdb's test case (results below). The difference, however doesn't seem to be large enough to justify using this technique for performance benefits.

http://jsperf.com/performance-frozen-object

Testing in Chrome 34.0.1847.116 on OS X 10.9.2
----------------------------------------------
Test               Ops/sec
non-frozen object  105,250,353  ±0.41%  3% slower
frozen object      108,188,527  ±0.55%  fastest

Running @kangax's test cases shows that both versions of the object perform pretty much the same:

http://jsperf.com/performance-frozen-object-prop-access

Testing in Chrome 34.0.1847.116 on OS X 10.9.2
----------------------------------------------
Test               Ops/sec
non-frozen object  832,133,923  ±0.26%  fastest
frozen object      832,501,726  ±0.28%  fastest

http://jsperf.com/http-jsperf-com-performance-frozen-object-instanceof

Testing in Chrome 34.0.1847.116 on OS X 10.9.2
----------------------------------------------
Test               Ops/sec
non-frozen object  378,464,917  ±0.42%  fastest
frozen object      378,705,082  ±0.24%  fastest
Jan Molak
  • 4,426
  • 2
  • 36
  • 32
  • your answer is good, I +1-ed it, but you should have edited the now deprecated answer to do things right. – Nicocube Oct 25 '14 at 06:51
  • Thanks for your feedback, @Nicocube. I wasn't sure if it's better to edit a deprecated answer or to write a new one. I've seen both approaches being used on stackoverflow, but your suggestion makes sense. – Jan Molak Oct 28 '14 at 10:11
  • Your links to jsperf are broken with `something went wrong`, do you have a copy of the code you tested for your claim? – Ferrybig Feb 02 '18 at 15:57
  • Looks like a problem with jsperf as no other links to them work either... I'll try to post the sample here when they're back online. – Jan Molak Feb 02 '18 at 16:21
  • 2
    I had a case where the call of Object.freeze() in itself was negative for performance, because I had many different small objects (think Nodes for a big tree). The construction turned out to be too heavy, so I dropped Object.freeze(). – nalply Apr 25 '19 at 12:15
  • 1
    Testing empty loops doesn't look correct to me, because they could be optimized away by JIT. I've made a new version with [few changes to force actual iteration](https://jsperf.com/performance-frozen-object/85) (at least, I hope that it forces one), but I still looks like all three options (default, frozen, with extensions prevented) show same results in Chrome 79.0.3945 / Mac OS X 10.15.3, but significantly different in Safari 13.0.5 / Mac OS X 10.15.3. – dolphin278 Feb 16 '20 at 10:11
  • I guess freezing is possible to reduce memory usage. like vector.shrink_to_fit() of C++ – rua.kr Mar 15 '21 at 17:24
  • I added a new answer with benchmarks that try to prevent any chance of dead code elimination (i.e. no empty loops, and each loop persists data on the globaThis object). https://stackoverflow.com/a/70490675/454780 – trusktr Dec 27 '21 at 00:09
17

In theory freezing an object allows you to make stronger guarantees about the shape of an object.

This means the VM can compact the memory size.

It means the VM can optimize property lookups in the prototype chain.

It means any live references just became not live because the object cannot change anymore.

In practice JavaScript engines do not make these aggressive optimization yet.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 1
    In practice in most engines there's little to be gained from a memory point of view for any given object. Equally, property lookups from prototypes are already cached (performance of most built-ins would be terrible if it weren't). – gsnedders Dec 08 '11 at 19:06
  • Right. You should be able to have more than just an inline cache, as you can inline the entire read as you have a known value. – gsnedders Dec 08 '11 at 21:11
  • (Note that inlining will only gain a certain amount: you don't want to inline strings, for example, though inlining integers/doubles is something you do want to do.) – gsnedders Dec 09 '11 at 19:09
  • @Raynos unfortunately we can still modify an object’s prototype even after it has been frozen. One way to get a really stable shape is to set the prototype to `null`. Another is to freeze the whole prototype chain, along with `Object.prototype` (sounds scary). – tomekwi Apr 24 '15 at 20:23
  • "It means any live references just became not live because the object cannot change anymore" - I think this is a misunderstanding of what it means for a reference to be _live_. A reference that can't be modified can still be live. – davmac May 17 '16 at 10:52
  • these optimizations are hopefully around these days (10 years later)? – trusktr Dec 27 '21 at 00:11
14

Update: Since this answer was originally written, the bug in V8 that caused this issue has been fixed. See the answer by Jan Molak for more info.


In Google Chrome (so V8, that is), a frozen object iterates 98% slower than a regular object.

http://jsperf.com/performance-frozen-object

Test name*              ops/sec

non-frozen object    32,193,471
frozen object           592,726

Probably this is because those functions are relatively new and probably not optimized yet (but that's just my guess, I honestly don't know the reason).

Anyhow, I really do not recommed using it for performance benefits, as that apparently does not make sense.


* The code for the test is:

var o1 = {a: 1};
var o2 = {a: 1};

Object.freeze(o2);

Test 1 (non-frozen object):

for(var key in o1);

Test 2 (frozen object):

for(var key in o2);
user3840170
  • 26,597
  • 4
  • 30
  • 62
pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • 7
    This sounds like a V8 bug rather then something that's not optimised. Note [Object.keys](http://jsperf.com/performance-frozen-object/2) is only 72% slower – Raynos Dec 08 '11 at 17:55
  • 3
    @Raynos: Good point; also `Object.keys` should not be *slower*. I agree it's more like a bug since frozing should not be a performance hit; rather the opposite. – pimvdb Dec 08 '11 at 17:57
  • 1
    [prototype lookups in the prototype](http://jsperf.com/prototype-property-lookup-frozen) are also 94% slower :\ – Raynos Dec 08 '11 at 17:58
  • 3
    Interesting find. I checked other browsers — nightly FF, WebKit, Opera and none of them have such crazy slow down (see [jsperf](http://jsperf.com/performance-frozen-object)). Definitely looks like a bug. I filed an issue in V8 tracker — http://code.google.com/p/v8/issues/detail?id=1858 – kangax Dec 09 '11 at 04:58
  • 1
    Made couple more tests: http://jsperf.com/performance-frozen-object-prop-access, http://jsperf.com/http-jsperf-com-performance-frozen-object-instanceof Same slowdown in Chrome with property access, but all good with something like `instanceof` check. – kangax Dec 09 '11 at 18:13
  • However, frozen objects seem slightly faster in V8 when checking whether a key belongs to an object (and, apparently, only if the answer is "no"): http://jsperf.com/performance-frozen-object/9 – Ruben Verborgh Jan 14 '13 at 09:26
  • 21
    Now (in Chrome 34 and in the year 2014 that is) the iteration of a frozen object seems to iterates around 24 percent faster. – msung Apr 18 '14 at 13:46
  • 1
    Outdated answer, the given benchmark now shows that being frozen as no impact on enumeration performance. There is no gain, but no loss either. – Nicocube Oct 25 '14 at 06:46
  • 8
    This is outdated now, downvoting. – Emil Eriksson Dec 28 '14 at 13:33
  • 3
    No longer true http://jsperf.com/freeze-vs-seal-vs-normal/19 because of several commits to Chrome over the years. – John Chadwick Mar 28 '15 at 20:22
6

V8 has optimized Object.freeze as of Jun 20, 2013. And Object.seal and Object.preventExtensions as of Dec 10, 2014. See issue https://code.google.com/p/chromium/issues/detail?id=115960

gx0r
  • 4,682
  • 2
  • 23
  • 24
3

jsperf links in the answers are broken, plus an empty for-loop might cause dead code elimination in some engines, so I wrote a new test to ensure code elimination doesn't happen. Most of the time the frozen object wins:

perf.link benchmark

For reference, the code looks like:

const o1 = {a: 1};
const o2 = {a: 1};

Object.freeze(o2);

// store stuff to prevent any sort of theoretical dead code elimination
const buffer = [];

// test 1, unfrozen
for(const key in o1) buffer.push(key);

// test 2, frozen
for(const key in o2) buffer.push(key);

EDIT: I tried to further eliminate the possibility of dead code elimination. Maybe (?) an engine can detect and eliminate the unused buffer array that is never read from, so in the following benchmark I placed the object onto the globalThis object, and the frozen object tends to win more often (edited, it more recently wins more often, but at some point it was slower):

perf.link benchmark

For reference, the code looks like:

const o1 = {a: 1};
const o2 = {a: 1};

Object.freeze(o2);

// store stuff to prevent any sort of theoretical dead code elimination
globalThis._buffer = [];

// test 1, unfrozen
for(const key in o1) globalThis._buffer.push(key);

// test 2, frozen
for(const key in o2) globalThis._buffer.push(key);
trusktr
  • 44,284
  • 53
  • 191
  • 263
  • The method for testing is wrong when the buffer not redefined as empty again. You can try it by switch cases or define buffer as var in both cases. – MMMahdy-PAPION Feb 08 '22 at 18:43
  • @MMMahdy-PAPION While I agree on a theoretical level, in this case it did not seem to have an impact to use a different array (`gloablThis._bufferb = []` `for(const key in o2) globalThis._bufferb.push(key);`, Chrome 108) – Simon Dec 16 '22 at 10:15
2

If you’re interested in the performance of object creation (literal vs frozen vs sealed vs Immutable.Map), I’ve created a test on jsPerf to check that out.

So far I’ve only had the opportunity to test it in Chrome 41 and Firefox 37. In both browsers the creation of a frozen or sealed object takes about three times longer than the creation of a literal – whereas the Immutable.Map performs about 50 times worse than the literal.

tomekwi
  • 2,048
  • 2
  • 21
  • 27
  • FWIW, the 3× penalty is better than I had expected. I’ll happily use frozen objects except in performance-critical situations. – tomekwi May 13 '15 at 20:38
  • It's pretty bad if it takes 3x as long... according to the linked jsperf, this performance bug is still present (Chrome 49). Object.freeze, with a literal in it, should be detected by the compiler as intent that the object be frozen. I.e. it doesn't have to be two separate steps, one of object creation and one of freezing. Creating (and accessing) a frozen object should be the same or faster. Looks like worthy of an issue report. – Robert Monfera Mar 17 '16 at 15:00
  • I can confirm. Was implementing immutable data structures in JavaScript, freezing internal nodes slow things down considerably. It's a bad choice when you have a lot of new objects created all over the place. All the path copying that you do with immutable data structures add up to too much in the end. It's about 2x-3x but more so a 2x factor than a 3x factor in my opinion. – John Leidegren Aug 13 '20 at 14:15
0

The only reason I see for those methods in production code is, that you can have sealed or frozen objects, for integrity purposes.

For instance, I write a little library, which works just great and offers you a set of methods in an object, but I don't want to you to change or overwrite any of my properties or methods. I'm not saying I can prevent you from doing that, but I can try to prevent you do it by accident which maybe is more important.

Also, those methods are easy to 'shim' in environment which doen't know about them, by just returning the original object. Of course it would have no effect then.

I don't see any performance related reasons to do this.

jAndy
  • 231,737
  • 57
  • 305
  • 359