1. JScript-Fehler
Die ECMAScript-Implementierung von JScript durch IE verwirrt benannte Funktionsausdrücke erheblich, was viele Leute dazu veranlasst, benannte Funktionsausdrücke abzulehnen, und selbst die noch verwendete Version (Version 5.8, die in IE8 verwendet wird) existiert noch. Die folgenden Fragen.
Werfen wir einen Blick auf die Fehler, die der IE bei seiner Implementierung gemacht hat. Wie das Sprichwort sagt: Nur wer den Feind kennt, kann unbesiegbar sein. Schauen wir uns die folgenden Beispiele an:
Beispiel 1: Bezeichner eines Funktionsausdrucks, der in den äußeren Bereich gelangt ist
var f = function g(){}; typeof g; // "function"
Wir haben zuvor gesagt, dass der Bezeichner eines benannten Funktionsausdrucks im externen Bereich ungültig ist, aber JScript verstößt offensichtlich gegen diese Spezifikation. Der Bezeichner g im obigen Beispiel wird in ein Funktionsobjekt analysiert, was für viele schwierig ist -to-find-Fehler werden aus diesem Grund verursacht.
Hinweis: Dieses Problem scheint in IE9 behoben worden zu sein
Beispiel 2: Behandeln Sie einen benannten Funktionsausdruck sowohl als Funktionsdeklaration als auch als Funktionsausdruck
typeof g; // "function" var f = function g(){};
In der Feature-Umgebung werden Funktionsdeklarationen vor jedem Ausdruck analysiert. Das obige Beispiel zeigt, dass JScript den benannten Funktionsausdruck tatsächlich als Funktionsdeklaration behandelt, da es g vor der eigentlichen Deklaration analysiert.
Dieses Beispiel führt zum nächsten.
Beispiel 3: Benannte Funktionsausdrücke erzeugen zwei völlig unterschiedliche Funktionsobjekte!
var f = function g(){}; f === g; // false f.expando = 'foo'; g.expando; // undefined
Wenn man das sieht, wird jeder denken, dass das Problem ernst ist, denn die Veränderung eines Objekts wird das andere nicht verändern. Das ist zu schlimm. Anhand dieses Beispiels können wir feststellen, dass zwei verschiedene Objekte erstellt werden müssen, d das gleiche Objekt, dann wird es ein großes Problem geben, weil es einfach unmöglich ist.
Sehen wir uns ein etwas komplizierteres Beispiel an:
Beispiel 4: Funktionsdeklarationen nur sequentiell analysieren und bedingte Anweisungsblöcke ignorieren
var f = function g() { return 1; }; if (false) { f = function g(){ return 2; }; } g(); // 2
Dieser Fehler ist viel schwieriger zu finden, aber die Ursache des Fehlers ist sehr einfach. Erstens wird g als Funktionsdeklaration analysiert, da die Funktionsdeklaration in JScript keinen bedingten Codeblöcken unterliegt. In diesem unangenehmen if-Zweig wird g auch als eine andere Funktion behandelt, die gerade erneut deklariert wurde . Anschließend werden alle „regulären“ Ausdrücke ausgewertet und f erhält eine Referenz auf ein anderes neu erstelltes Objekt. Da der abscheuliche if-Zweig „“ bei der Auswertung des Ausdrucks niemals eingegeben wird, verweist f weiterhin auf die erste Funktion function g(){ return 1 }. Nach der Analyse ist das Problem sehr klar: if Wenn dies nicht der Fall ist Wenn Sie vorsichtig genug sind und g in f aufrufen, wird ein irrelevantes g-Funktionsobjekt
aufgerufen.Sie fragen sich vielleicht: Was sind die Unterschiede beim Vergleich verschiedener Objekte mit arguments.callee? Werfen wir einen Blick darauf:
var f = function g(){ return [ arguments.callee == f, arguments.callee == g ]; }; f(); // [true, false] g(); // [false, true]
Wie Sie sehen, ist die Referenz von arguments.callee immer die aufgerufene Funktion. Tatsächlich ist dies auch eine gute Sache, wie später erklärt wird.
Ein weiteres interessantes Beispiel ist die Verwendung eines benannten Funktionsausdrucks in einer Zuweisungsanweisung, die keine Deklaration enthält:
(function(){ f = function f(){}; })();
Laut Code-Analyse wollten wir ursprünglich ein globales Attribut f erstellen (achten Sie darauf, es nicht mit der allgemeinen anonymen Funktion zu verwechseln, die eine benannte Deklaration verwendet). Zuerst hat es den Ausdruck geändert Der Ausdruck wird als Funktionsdeklaration analysiert, sodass f auf der linken Seite als lokale Variable deklariert wird (dasselbe wie die Deklaration in einer allgemeinen anonymen Funktion). Wenn die Funktion ausgeführt wird, ist f bereits definiert und die Funktion f(). ) auf der rechten Seite {} ist direkt der lokalen Variablen f zugewiesen, daher ist f überhaupt kein globales Attribut.
Nachdem wir verstanden haben, wie abnormal JScript ist, müssen wir verhindern, dass Bezeichner in externe Bereiche gelangen. Zweitens sollten Bezeichner, die als Funktionsnamen verwendet werden, niemals in Anführungszeichen gesetzt werden G? ——Wenn wir so tun können, als ob g nicht existiert, wie viel unnötigen Ärger können wir dann vermeiden? Daher besteht der Schlüssel darin, immer über f oder arguments.callee auf Funktionen zu verweisen. Wenn Sie benannte Funktionsausdrücke verwenden, sollten Sie beim Debuggen nur diesen Namen verwenden. Denken Sie abschließend daran, Funktionen zu bereinigen, die bei der Deklaration eines benannten Funktionsausdrucks falsch erstellt wurden.
2. JScript-Speicherverwaltung
Wenn wir diese nicht standardmäßigen Code-Parsing-Fehler kennen, werden wir feststellen, dass tatsächlich ein Problem mit dem Speicher vorliegt:
var f = (function(){ if (true) { return function g(){}; } return function g(){}; })();
我们知道,这个匿名函数调用返回的函数(带有标识符g的函数),然后赋值给了外部的f。我们也知道,命名函数表达式会导致产生多余的函数对象,而该对象与返回的函数对象不是一回事。所以这个多余的g函数就死在了返回函数的闭包中了,因此内存问题就出现了。这是因为if语句内部的函数与g是在同一个作用域中被声明的。这种情况下 ,除非我们显式断开对g函数的引用,否则它一直占着内存不放。
var f = (function(){ var f, g; if (true) { f = function g(){}; } else { f = function g(){}; } // 设置g为null以后它就不会再占内存了 g = null; return f; })();
通过设置g为null,垃圾回收器就把g引用的那个隐式函数给回收掉了,为了验证我们的代码,我们来做一些测试,以确保我们的内存被回收了。
测试
测试很简单,就是命名函数表达式创建10000个函数,然后把它们保存在一个数组中。等一会儿以后再看这些函数到底占用了多少内存。然后,再断开这些引用并重复这一过程。下面是测试代码:
function createFn(){ return (function(){ var f; if (true) { f = function F(){ return 'standard'; }; } else if (false) { f = function F(){ return 'alternative'; }; } else { f = function F(){ return 'fallback'; }; } // var F = null; return f; })(); } var arr = [ ]; for (var i=0; i < 10000; i++) { arr[i] = createFn(); }
通过运行在Windows XP SP2中的任务管理器可以看到如下结果:
IE7: without `null`: 7.6K -> 20.3K with `null`: 7.6K -> 18K IE8: without `null`: 14K -> 29.7K with `null`: 14K -> 27K
如我们所料,显示断开引用可以释放内存,但是释放的内存不是很多,10000个函数对象才释放大约3M的内存,这对一些小型脚本不算什么,但对于大型程序,或者长时间运行在低内存的设备里的时候,这是非常有必要的。
以上就是关于JScript的Bug与内存管理的全部介绍,希望对大家的学习有所帮助。