1. JScript のバグ
IE の JScript の ECMAScript 実装は、名前付き関数式に深刻な混乱をもたらし、多くの人が名前付き関数式に反対しており、現在も使用されているバージョン (IE8 で使用されているバージョン 5.8) さえも存在しています。次のような質問があります。
IE が実装時に犯した間違いを見てみましょう。よく言われるように、敵を知ることによってのみ無敵になれます。次の例を見てみましょう:
例 1: 外部スコープに漏洩した関数式の識別子
var f = function g(){}; typeof g; // "function"
名前付き関数式の識別子は外部スコープでは無効であると前述しましたが、JScript は明らかにこの仕様に違反しています。上記の例の識別子 g は関数オブジェクトに解析されますが、これは非常に複雑です。 -to-find バグはこの理由によって発生します。
注: この問題は IE9 で修正されたようです
例 2: 名前付き関数式を関数宣言と関数式の両方として扱います
typeof g; // "function" var f = function g(){};
フィーチャー環境では、関数宣言は式の前に解析されます。上記の例は、JScript が実際の宣言の前に g を解析するため、名前付き関数式を実際に関数宣言として扱うことを示しています。
この例は次の例につながります。
例 3: 名前付き関数式は、2 つのまったく異なる関数オブジェクトを作成します。
var f = function g(){}; f === g; // false f.expando = 'foo'; g.expando; // undefined
これを見ると、どのオブジェクトを変更しても他のオブジェクトは変更されないため、誰もが問題は深刻であると考えるでしょう。この例では、2 つの異なるオブジェクトを作成することがわかります。つまり、f の属性を変更して特定の情報を保存し、それを当然のことながら、g の同じ name 属性を参照して使用する場合です。同じオブジェクトの場合、それはまったく不可能であるため、大きな問題が発生します。
もう少し複雑な例を見てみましょう:
例 4: 関数宣言のみを順番に解析し、条件付きステートメント ブロックを無視します
var f = function g() { return 1; }; if (false) { f = function g(){ return 2; }; } g(); // 2
このバグは見つけるのが非常に困難ですが、バグの原因は非常に単純です。まず、g は関数宣言として解析されます。JScript の関数宣言は条件付きコード ブロックの対象ではないため、この厄介な if 分岐では、g は別の関数 function g(){ return 2 } として扱われ、再度宣言されただけです。 。次に、すべての「正規」表現が評価され、新しく作成された別のオブジェクトへの参照が f に与えられます。式が評価されるときに忌まわしい if 分岐 "" が入力されることはないため、 f は最初の関数 function g(){ return 1 } を参照し続けます。これを分析すると、問題は非常に明確になります。十分注意して f で g を呼び出すと、無関係な g 関数オブジェクトが
と呼ばれます。arguments.callee を使用してさまざまなオブジェクトを比較した場合の違いは何なのかと疑問に思われるかもしれません。見てみましょう:
var f = function g(){ return [ arguments.callee == f, arguments.callee == g ]; }; f(); // [true, false] g(); // [false, true]
ご覧のとおり、arguments.callee の参照は常に呼び出される関数です。これは、後で説明するように、実際には良いことです。
もう 1 つの興味深い例は、宣言を含まない代入ステートメントで名前付き関数式を使用することです。
(function(){ f = function f(){}; })();
コード分析によると、最初はグローバル属性 f を作成する必要がありました (名前付き宣言を使用する一般的な匿名関数と混同しないように注意してください)。まず、式が変更されました。式は関数宣言として解析されるため、左側の f はローカル変数として宣言されます (一般的な無名関数の宣言と同じ)。関数が実行されるときには、f はすでに定義されており、関数 f( ) 右側の {} はローカル変数 f に直接割り当てられているため、f はグローバル属性ではありません。
JScript がいかに異常であるかを理解した後、まず、識別子が外部スコープに漏洩しないようにする必要があります。次に、関数名として使用される識別子を引用符で囲んではなりません。グラム? ——gが存在しないふりをすることができれば、どれだけ無用なトラブルを避けることができるでしょうか。したがって、重要なのは、常に f または argument.callee を介して関数を参照することです。名前付き関数式を使用する場合は、デバッグ時にのみその名前を使用する必要があります。最後に、名前付き関数式の宣言中に誤って作成された関数をクリーンアップすることを忘れないでください。
2. JScript のメモリ管理
これらの非標準コード解析のバグを理解した後、それを使用すると、実際にはメモリに問題があることがわかります。例を見てみましょう。
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与内存管理的全部介绍,希望对大家的学习有所帮助。