有名函数表达式全面解析(翻译教程)(4)
测试
The test was simple. It would simply create 10000 functions via named function expressions and store them in an array. I would then wait for about a minute and check how high the memory consumption is. After that I would null-out the reference and repeat the procedure again. Here’s a test case I used:
这个测试非常简单。他将通过有名函数表达式创建1000个函数,并将它们储存在一个数组中。我等待了大约一分钟,并查看内存使用有多高。只有我们加上null引用,重复上述过程。下面就是我使用的一个简单的测试用例
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();
}
Results as seen in Process Explorer on Windows XP SP2 were:
结果是在Windows XP SP2进行的,通过进程管理器得到的
IE6:
without `null`: 7.6K -> 20.3K
with `null`: 7.6K -> 18K
IE7:
without `null`: 14K -> 29.7K
with `null`: 14K -> 27K
The results somewhat confirmed my assumptions - explicitly nulling superfluous reference did free memory, but the difference in consumption was relatively insignificant. For 10000 function objects, there would be a ~3MB difference. This is definitely something that should be kept in mind when designing large-scale applications, applications that will run for either long time or on devices with limited memory (such as mobile devices). For any small script, the difference probably doesn’t matter.
结果在一定程度上证实了我的假设,显示的给无用的参考以null值确实会释放内存,但是在内寸的消耗的区别上貌似不是很大。对于1000个函数对象,大约应该有3M左右的差别。但是有一些是明确的,在设计大规模的应用的时候,应用要不就是要运行很长时间的或者要在一个内存有限的设备上(例如移动设备)。对于任何小的脚本,差别可能不是很重要。
You might think that it’s all finally over, but we are not just quite there yet :) There’s a tiny little detail that I’d like to mention and that detail is Safari 2.x
你可以认为这样就可以结束了,但是还没到结束的时候。我还要讨论一些小的细节,而且这些细节是在Safari 2.x下的
Safari bug
Even less widely known bug with NFE is present in older versions of Safari; namely, Safari 2.x series. I’ve seen some claims on the web that Safari 2.x does not support NFE at all. This is not true. Safari does support it, but has bugs in its implementation which you will see shortly.
虽然没有被人们发现在早期的Safari版本,也就是Safari 2.x版本中有名函数表达式的bug。但是我在web上看到一些声称Safari 2.x根本不支持有名函数表达式。这不是真的。Safari的确支持有名函数表达式,但是稍后你将看到在它的实现中是存在bug的
When encountering function expression in a certain context, Safari 2.x fails to parse the program entirely. It doesn’t throw any errors (such as SyntaxError ones). It simply bails out:
在某些执行环境中遇到函数表达式的时候,Safari 2.x 将解释程序整体失败。它不抛出任何的错误(例如SyntaxError)。展示如下
(function f(){})(); // <== 有名函数表达式 NFE
alert(1); //因为前面的表达式是的整个程序失败,本行将无法达到, this line is never reached, since previous expression fails the entire program
After fiddling with various test cases, I came to conclusion that Safari 2.x fails to parse named function expressions, if those are not part of assignment expressions. Some examples of assignment expressions are:
在用一些测试用例测试之后,我总结出,如果有名函数表达式不是赋值表达式的一部分,Safari解释有名函数表达式将失败。一些赋值表达式的例子如下
// 变量声明part of variable declaration
var f = 1;
//简单的赋值 part of simple assignment
f = 2, g = 3;
// 返回语句part of return statement
(function(){
return (f = 2);
})();
This means that putting named function expression into an assignment makes Safari “happy”:
这就意味着把有名函数表达式放到赋值表达式中会让 Safari非常“开心”
(function f(){}); // fails 失败
var f = function f(){}; // works 成功
(function(){
return function f(){}; // fails 失败
})();
(function(){
return (f = function f(){}); // works 成功
})();
setTimeout(function f(){ }, 100); // fails
It also means that we can’t use such common pattern as returning named function expression without an assignment:
这也意味着我们不能使用这种普通的模式而没有赋值表达式作为返回有名函数表达式
//要取代这种Safari2.x不兼容的情况 Instead of this non-Safari-2x-compatible syntax:
(function(){
if (featureTest) {
return function f(){};
}
return function f(){};
})();
// 我们应该使用这种稍微冗长的替代方法we should use this slightly more verbose alternative:
(function(){
var f;
if (featureTest) {
f = function f(){};
}
else {
f = function f(){};
}
return f;
})();
// 或者另外一种变形or another variation of it:
(function(){
var f;
if (featureTest) {
return (f = function f(){});
}
return (f = function f(){});
})();
/*
Unfortunately, by doing so, we introduce an extra reference to a function
which gets trapped in a closure of returning function. To prevent extra memory usage,
we can assign all named function expressions to one single variable.
不幸的是 这样做我们引入了对函数的另外一个引用
他将被包含在返回函数的闭包中
为了防止多于的内存使用,我们可以吧所有的有名函数表达式赋值给一个单独的变量
*/
var __temp;
(function(){
if (featureTest) {
return (__temp = function f(){});
}
return (__temp = function f(){});
})();
...
(function(){
if (featureTest2) {
return (__temp = function g(){});
}
return (__temp = function g(){});
})();
/*
Note that subsequent assignments destroy previous references,
preventing any excessive memory usage.
注释:后面的赋值销毁了前面的引用,防止任何过多的内存使用
*/
If Safari 2.x compatibility is important, we need to make sure “incompatible” constructs do not even appear in the source. This is of course quite irritating, but is definitely possible to achieve, especially when knowing the root of the problem.
如果Safari2.x的兼容性非常重要。我们需要保证不兼容的结构不再代码中出现。这当然是非常气人的,但是他确实明确的可以做到的,尤其是当我们知道问题的根源。
It’s also worth mentioning that declaring a function as NFE in Safari 2.x exhibits another minor glitch, where function representation does not contain function identifier:
还值得一提的是在Safari中声明一个函数是有名函数表达式的时候存在另外一个小的问题,这是函数表示法不含有函数标示符(估计是toString的问题)
var f = function g(){};
// Notice how function representation is lacking `g` identifier
String(g); // function () { }
This is not really a big deal. As I have already mentioned before, function decompilation is something that should not be relied upon anyway.
这不是个很大的问题。因为之前我已经说过,函数反编译在任何情况下都是不可信赖的。
解决方案
var fn = (function(){
//声明一个变量,来给他赋值函数对象 declare a variable to assign function object to
var f;
// 条件的创建一个有名函数 conditionally create a named function
// 并把它的引用赋值给f and assign its reference to `f`
if (true) {
f = function F(){ }
}
else if (false) {
f = function F(){ }
}
else {
f = function F(){ }
}
//给一个和函数名相关的变量以null值 Assign `null` to a variable corresponding to a function name
//这可以使得函数对象(通过标示符的引用)可以被垃圾收集所得到This marks the function object (referred to by that identifier)
// available for garbage collection
var F = null;
//返回一个条件定义的函数 return a conditionally defined function
return f;
})();
Finally, here’s how we would apply this “techinque” in real life, when writing something like a cross-browser addEvent function:
最后,当我么一个类似于跨浏览器addEvent函数的类似函数时,下面就是我们如何在真实的应用中使用这个技术
// 1) 用一个分离的作用域封装声明 enclose declaration with a separate scope
var addEvent = (function(){
var docEl = document.documentElement;
// 2)声明一个变量,用来赋值为函数 declare a variable to assign function to
var fn;
if (docEl.addEventListener) {
// 3) 确保给函数一个描述的标示符 make sure to give function a descriptive identifier
fn = function addEvent(element, eventName, callback) {
element.addEventListener(eventName, callback, false);
}
}
else if (docEl.attachEvent) {
fn = function addEvent(element, eventName, callback) {
element.attachEvent('on' + eventName, callback);
}
}
else {
fn = function addEvent(element, eventName, callback) {
element['on' + eventName] = callback;
}
}
// 4)清除通过JScript创建的addEvent函数 clean up `addEvent` function created by JScript
// 保证在赋值之前加上varmake sure to either prepend assignment with `var`,
// 或者在函数顶端声明 addEvent or declare `addEvent` at the top of the function
var addEvent = null;
// 5)最后通过fn返回函数的引用 finally return function referenced by `fn`
return fn;
})();