1. JScript Bug
IE's ECMAScript implementation of JScript seriously confuses named function expressions, causing many people to oppose named function expressions, and even the version that is still in use (version 5.8 used in IE8) still exists The following questions.
Let’s take a look at what mistakes IE made in its implementation. As the saying goes, only by knowing the enemy can you be invincible. Let’s take a look at the following examples:
Example 1: Identifier of function expression leaked to outer scope
var f = function g(){}; typeof g; // "function"
We said earlier that the identifier of a named function expression is invalid in the external scope, but JScript obviously violates this specification. The identifier g in the above example is parsed into a function object, which is messy. Yes, many hard-to-find bugs are caused by this reason.
Note: This problem seems to have been fixed in IE9
Example 2: Treat a named function expression as both a function declaration and a function expression
typeof g; // "function" var f = function g(){};
In the feature environment, function declarations will be parsed before any expression. The above example shows that JScript actually treats the named function expression as a function declaration because it parses g before the actual declaration.
This example leads to the next one.
Example 3: Named function expressions create two completely different function objects!
var f = function g(){}; f === g; // false f.expando = 'foo'; g.expando; // undefined
Seeing this, everyone will think that the problem is serious, because modifying any object will not change the other one. This is too evil. Through this example, we can find that creating two different objects, that is to say, if you want to modify the attribute of f to save certain information, and then use it as a matter of course by referencing the same name attribute of g of the same object, then there will be a big problem. Because it's simply impossible.
Let’s look at a slightly more complicated example:
Example 4: Only parse function declarations sequentially and ignore conditional statement blocks
var f = function g() { return 1; }; if (false) { f = function g(){ return 2; }; } g(); // 2
This bug is much harder to find, but the cause of the bug is very simple. First, g is parsed as a function declaration. Since function declarations in JScript are not subject to conditional code blocks, in this nasty if branch, g is treated as another function function g(){ return 2 }, also It was just declared again. Then, all "regular" expressions are evaluated, and f is given a reference to another newly created object. Since the abominable if branch "" will never be entered when the expression is evaluated, f will continue to refer to the first function function g(){ return 1 }. After analyzing this, the problem is very clear: if If you are not careful enough and call g in f, an irrelevant g function object will be called
.You may ask, what are the differences when comparing different objects with arguments.callee? Let’s take a look:
var f = function g(){ return [ arguments.callee == f, arguments.callee == g ]; }; f(); // [true, false] g(); // [false, true]
As you can see, the reference of arguments.callee is always the called function. In fact, this is also a good thing, as will be explained later.
Another interesting example is using a named function expression in an assignment statement that does not contain a declaration:
(function(){ f = function f(){}; })();
According to the code analysis, we originally wanted to create a global attribute f (be careful not to confuse it with the general anonymous function, which uses a named declaration). JScript made a mess here. First, it changed the expression The expression is parsed as a function declaration, so f on the left is declared as a local variable (the same as the declaration in a general anonymous function). Then when the function is executed, f is already defined, and the function f() on the right {} is directly assigned to the local variable f, so f is not a global attribute at all.
After understanding how abnormal JScript is, we must prevent these problems in time. First, prevent identifiers from leaking into external scopes. Secondly, identifiers used as function names should never be quoted; remember the one in the previous example The annoying identifier g? ——If we can pretend that g does not exist, how much unnecessary trouble can be avoided. Therefore, the key is to always refer to functions via f or arguments.callee. If you use named function expressions, you should only use that name when debugging. Finally, remember to clean up functions that were created incorrectly during the declaration of a named function expression.
2. JScript memory management
After knowing these non-standard code parsing bugs, if we use it, we will find that there is actually a problem with memory. Let’s look at an example:
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与内存管理的全部介绍,希望对大家的学习有所帮助。