Dlaczego „użycie ścisłego” poprawia wydajność 10x w tym przykładzie?

128

Idąc za pytaniem Extending String.prototype performance jestem naprawdę zaintrygowany, bo samo dodanie "use strict"do String.prototypemetody poprawiło wydajność 10 razy. Wyjaśnienie przez Bergi jest krótki i nie mi to wyjaśnić. Dlaczego jest tak dramatyczna różnica między dwoma prawie identycznymi metodami, które różnią się tylko "use strict"u góry? Czy możesz wyjaśnić bardziej szczegółowo i za pomocą teorii stojącej za tym?

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');

Wynik:

proto: 101 ms
proto-strict: 7.5 ms
exebook
źródło
1
Czy możesz wykonać test this[i] === chari sprawdzić, czy zauważysz tę samą różnicę?
Niet the Dark Absol,
1
Testowałem this[i] === charw środowisku DOM i wynik jest taki sam
Cristian Traìna,
2
Wyjaśnienie Bergi mówi, że kiedy wywołujesz countfunkcję, thisparametr musi być rzutowany na obiekt typu string zamiast na literał łańcuchowy, podczas gdy w trybie ścisłym nie musi to działać, aby działać poprawnie. Dlaczego tak się dzieje, jest poza mną, jestem bardzo zainteresowany odpowiedzią.
Nick Larsen,
3
@NickLarsen: Tak właśnie określono język. Tradycyjnie JS upewniłby się, że zawsze masz obiekt jako this, ale w trybie ścisłym pomija ten krok, więc otrzymujesz prymitywny ciąg lub cokolwiek innego, co zostało przewidziane this.
6
Czas umieścić "use strict";wszędzie chłopców! Goooold
Jonathan

Odpowiedzi:

155

W trybie ścisłym thiskontekst nie musi być obiektem. Jeśli wywołasz funkcję na obiekcie niebędącym obiektem, thisbędzie to tylko ten obiekt niebędący obiektem.

W przeciwieństwie do tego, w trybie nieścisłości, thiskontekst jest zawsze najpierw zawijany w obiekt, jeśli nie jest jeszcze obiektem. Na przykład, (42).toString()pierwsze okłady 42w Numberobiekt, a następnie zwraca Number.prototype.toStringsię do Numberobiektu jako thiskontekst. W trybie ścisłym The thiskontekst pozostało nietknięte i tylko rozmowy Number.prototype.toStringz 42jak thiskontekst.

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

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

W twoim przypadku wersja w trybie nieostrym spędza dużo czasu na zawijaniu i rozpakowywaniu prymitywów strings do Stringopakowań obiektów iz powrotem. Z drugiej strony wersja trybu ścisłego działa bezpośrednio na prymitywach string, co poprawia wydajność.

Mattias Buelens
źródło
1
Usunięcie withrównież trochę pomaga przy każdym wyszukiwaniu zmiennych iirc.
zzzzBov,
2
@zzzzBov niepoprawne. Usunięcie zmiennej withpomaga niezmiernie, ponieważ pozwala przeglądarce określić, które wyrażenie zmiennej odnosi się do której zmiennej.
John Dvorak
2
Wydaje mi się nieintuicyjne, że nie-przedmiot thisjest „bardziej rygorystyczny” niż zawsze-przedmiot this.
IllidanS4 obsługuje Monikę
2
@ IllidanS4: Dotyczy to głównie przypadków, w których thisjest nulllub undefined, który byłby globalnym obiektem w trybie niechlujstwa.
Bergi,
6
@ IllidanS4: Pomyśl o tym jako o „rzeczywistym this” kontra „opakowujący this”, jeśli chcesz. Opakowania obiektów to kludge, które nigdy nie powinny istnieć, więc sensowne jest, aby tryb ścisły unikał ich bardziej, gdy to możliwe.
Ry-