128

Following the question Extending String.prototype performance I am really intrigued, because just adding "use strict" to a String.prototype method improved performance 10 times. The explanation by bergi is short and does not explain it to me. Why there is such a dramatic difference between two almost identical methods, that only differ in "use strict" at the top? Can you explain in more detail and with the theory behind this?

String.prototype.count = function(char) {
  var n = 0;
  for (var i = 0; i < this.length; i++)
    if (this[i] == char) n++;
  return n;
};

String.prototype.count_strict = function(char) {
  "use strict";
  var n = 0;
  for (var i = 0; i < this.length; i++)
    if (this[i] == char) n++;
  return n;
};
// Here is how I measued speed, using Node.js 6.1.0

var STR = '0110101110010110100111010011101010101111110001010110010101011101101010101010111111000';
var REP = 1e4;

console.time('proto');
for (var i = 0; i < REP; i++) STR.count('1');
console.timeEnd('proto');

console.time('proto-strict');
for (var i = 0; i < REP; i++) STR.count_strict('1');
console.timeEnd('proto-strict');

Result:

proto: 101 ms
proto-strict: 7.5 ms
Community
  • 1
  • 1
exebook
  • 27,243
  • 27
  • 105
  • 196
  • 1
    Can you do a test with `this[i] === char` and see if you get the same difference? – Niet the Dark Absol Jul 16 '16 at 13:12
  • 1
    I tested with `this[i] === char` in a DOM environment and the result is the same – Cristian Traìna Jul 16 '16 at 13:28
  • 2
    bergi's explanation says that when you call the `count` function, the `this` parameter has to be cast to a string object instead of a string literal whereas in strict mode it does not have to in order to operate correctly. Why this is the case is beyond me, I'm very interested in the answer. – Nick Larsen Jul 16 '16 at 13:28
  • 3
    @NickLarsen: It's just how the language was spec'd. Traditionally JS would make sure you always had an object as `this`, but in strict mode it skips that step, so you get the *primitive* string, or whatever was provided for `this`. –  Jul 16 '16 at 13:33
  • 6
    It's time to put `"use strict";` everywhere boys! Goooold – Jonathan Jul 16 '16 at 13:36
  • 1
    Have you had a look at [What does “use strict” do in JavaScript?](http://stackoverflow.com/q/1335851/1048572) and [How does the “this” keyword work?](http://stackoverflow.com/q/3127429/1048572)? – Bergi Jul 16 '16 at 15:50
  • Also http://stackoverflow.com/questions/13055/what-is-boxing-and-unboxing-and-what-are-the-trade-offs – Jared Smith Jul 16 '16 at 23:59

1 Answers1

158

In strict mode, the this context is not forced to be an object. If you call a function on a non-object, this will just be that non-object.

In contrast, in non-strict mode, the this context is always first wrapped in an object if it's not already an object. For example, (42).toString() first wraps 42 in a Number object and then calls Number.prototype.toString with the Number object as this context. In strict mode, the this context is left untouched and just calls Number.prototype.toString with 42 as this context.

(function() {
  console.log(typeof this);
}).call(42); // 'object'

(function() {
  'use strict';
  console.log(typeof this);
}).call(42); // 'number'

In your case, the non-strict mode version spends a lot of time wrapping and unwrapping primitive strings into String object wrappers and back. The strict mode version on the other hand directly works on the primitive string, which improves performance.

JLRishe
  • 90,548
  • 14
  • 117
  • 150
Mattias Buelens
  • 17,720
  • 4
  • 40
  • 49
  • 1
    And the removal of `with` also helps a bit for every variable lookup iirc. – zzzzBov Jul 16 '16 at 13:41
  • 2
    @zzzzBov incorrect. Removal of `with` helps _immensely_ as it allows the browser to reason which variable expression refers to which variable. – John Dvorak Jul 16 '16 at 18:21
  • Theoretically, `this.length` and `this[i]` should also create a string exotic object (string primitives don't have properties), but probably browsers don't do it. – Oriol Jul 16 '16 at 19:33
  • 1
    I tested with `String` objects and the time was the same for each function (after allowing for experimental error). They were also six times faster than the non-strict version and 17 times slower than the strict version were on raw strings on my system. I also added `var self = String(this);` etc. to the non-strict version and this actually brought the speed up to the same as the strict version for `String` objects but raw strings were still 50% slower, presumably due to the `String` construction overhead. – Neil Jul 17 '16 at 09:18
  • 2
    Seems unintuitive to me that non-object `this` is "stricter" than always-object `this`. – IS4 Jul 17 '16 at 10:33
  • 2
    @IllidanS4: This is mostly about cases where `this` is `null` or `undefined`, which would be the global object in sloppy mode. – Bergi Jul 17 '16 at 10:57
  • 6
    @IllidanS4: Think of it as "actual `this`" vs. "wrapper `this`" if you like. Object wrappers are a kludge that should never have existed, so it makes sense that strict mode would avoid them more when possible. – Ry- Jul 18 '16 at 05:37
  • So should we always use `strict mode` if we work with `this`? – Black Jul 26 '16 at 12:13
  • @EdwardBlack Strict mode fixes some mistakes in JavaScript's 'old' semantics, helps you prevent bugs by turning silent errors into actual errors, and enables browsers to better optimize your code. You should *always* use strict mode if you're writing new JavaScript code today. However, enabling strict mode may break legacy code, so be careful when you're transitioning old code to modern JavaScript. – Mattias Buelens Jul 26 '16 at 17:22