有名函数表达式全面解析(翻译教程)(3)

http://www.itjxue.com  2015-08-06 23:10  来源:未知  点击次数: 

Example #1: Function expression identifier leaks into an enclosing scope

实例1:函数表达式标示符渗进了外围作用域

    var f = function g(){};
    typeof g; // "function"

Remember how I mentioned that an identifier of named function expression is not available in an enclosing scope? Well, JScript doesn’t agree with specs on this one - g in the above example resolves to a function object. This is a most widely observed discrepancy. It’s dangerous in that it inadvertedly pollutes an enclosing scope - a scope that might as well be a global one - with an extra identifier. Such pollution can, of course, be a source of hard-to-track bugs.

我刚才提到过一个有名函数表达式的标示符不能在外部作用域中被访问。但是,JScript在这点上和标准并不相符,在上面的饿例子中g却是一个函数对象。这个是一个可以广泛观察到的差异。这样它就用一个多余的标示符污染了外围作用域,这个作用域很有可能是全局作用域,这样是很危险的。当然这个污染可能是一个很难去处理和跟踪的bug的根源

Example #2: Named function expression is treated as BOTH - function declaration AND function expression

实例2:有名函数表达式被进行了双重处理,函数表达式和函数声明

    typeof g; // "function"
    var f = function g(){};

As I explained before, function declarations are parsed foremost any other expressions in a particular execution context. The above example demonstrates how JScript actually treats named function expressions as function declarations. You can see that it parses g before an “actual declaration” takes place.

正如我前面解释的,在一个特定的执行环境中,函数声明是在所有的表达式之前被解释。上面的例子说明JScript实际上把有名函数表达式作为一个函数声明来对待。我们可以看到他在一个实际的声明之前就被解释了。

This brings us to a next example:

在此基础上我们引入了下面的一个例子。

Example #3: Named function expression creates TWO DISCTINCT function objects!

实例3:有名函数表达式创建两个不同的函数对象。

    var f = function g(){};
    f === g; // false

    f.expando = 'foo';
    g.expando; // undefined

This is where things are getting interesting. Or rather - completely nuts. Here we are seeing the dangers of having to deal with two distinct objects - augmenting one of them obviously does not modify the other one; This could be quite troublesome if you decided to employ, say, caching mechanism and store something in a property of f, then tried accessing it as a property of g, thinking that it is the same object you’re working with.

在这里事情变得更加有趣了,或者是完全疯掉。这里我们看到必须处理两个不同的对象的危险,当扩充他们当中的一个的时候,另外一个不会相应的改变。如果你打算使用cache机制并且在f的属性中存放一些东西,只有有试图在g的属性中访问,你本以为他们指向同一个对象,这样就会变得非常麻烦

Let’s look at something a bit more complex.

让我们来看一些更复杂的例子。

Example #4: Function declarations are parsed sequentially and are not affected by conditional blocks

实例4:函数声明被顺序的解释,不受条件块的影响

    var f = function g() {
        return 1;
    };
    if (false) {
        f = function g(){
            return 2;
        }
    };
    g(); // 2

An example like this could cause even harder to track bugs. What happens here is actually quite simple. First, g is being parsed as a function declaration, and since declarations in JScript are independent of conditional blocks, g is being declared as a function from the “dead” if branch - function g(){ return 2 }. Then all of the “regular” expressions are being evaluated and f is being assigned another, newly created function object to. “dead” if branch is never entered when evaluating expressions, so f keeps referencing first function - function g(){ return 1 }. It should be clear by now, that if you’re not careful enough, and call g from within f, you’ll end up calling a completely unrelated g function object.

像这样的一个例子可能会使跟踪bug非常困难。这里发生的问题却非常简单。首先g被解释为一个函数声明,并且既然JScript中的声明是和条件块无关的,g就作为来自于已经无效的if分支中的函数被声明function g(){ return 2 }。之后普通的表达式被求值并且f被赋值为另外一个新创建的函数对象。当执行表达式的时候,由于if条件分支是不会被进入的,因此f保持为第一函数的引用 function g(){ return 1 }。现在清楚了如果不是很小心,而且在f内部调用g,你最终将调用一个完全无关的g函数对象。

You might be wondering how all this mess with different function objects compares to arguments.callee. Does callee reference f or g? Let’s take a look:

你可能在想不从的函数对象和arguments.callee相比较的结果会是怎样呢?callee是引用f还是g?让我们来看一下

  var f = function g(){
    return [
      arguments.callee == f,
      arguments.callee == g
    ];
  };
  f(); // [true, false]

As you can see, arguments.callee references same object as f identifier. This is actually good news, as you will see later on.

我们可以看到arguments.callee引用的是和f标示符一样的对象,就像稍后你会看到的,这是个好消息

Looking at JScript deficiencies, it becomes pretty clear what exactly we need to avoid. First, we need to be aware of a leaking identifier (so that it doesn’t pollute enclosing scope). Second, we should never reference identifier used as a function name; A troublesome identifier is g from the previous examples. Notice how many ambiguities could have been avoided if we were to forget about g’s existance. Always referencing function via f or arguments.callee is the key here. If you use named expression, think of that name as something that’s only being used for debugging purposes. And finally, a bonus point is to always clean up an extraneous function created erroneously during NFE declaration.

既然看到了JScript的缺点,我们应该避免些什么就非常清楚了。首先,我们要意识到标示符的渗出(以使得他不会污染外围作用域)。第二点,我们不应该引用作为函数名的标示符;从前面的例子可以看出g是一个问题多多的标示符。请注意,如果我们忘记g的存在,很多歧义就可以被避免。通常最关键的就是通过f或者argument.callee来引用函数。如果你使用有名的表达式,记住名字只是为了调试的目的而存在。最后,额外的一点就是要经常清理有名函数表达式声明错误创建的附加函数

I think last point needs a bit of an explanation:

我想最有一点需要一些更多解释

JScript 内存管理

Being familiar with JScript discrepancies, we can now see a potential problem with memory consumption when using these buggy constructs. Let’s look at a simple example:

熟悉了JScript和规范的差别,我们可以看到当使用这些有问题的结构的时候,和内存消耗相关的潜在问题

  var f = (function(){
    if (true) {
      return function g(){};
    }
    return function g(){};
  })();

We know that a function returned from within this anonymous invocation - the one that has g identifier - is being assigned to outer f. We also know that named function expressions produce superfluous function object, and that this object is not the same as returned function. The memory issue here is caused by this extraneous g function being literally “trapped” in a closure of returning function. This happens because inner function is declared in the same scope as that pesky g one. Unless we explicitly break reference to g function it will keep consuming memory.

我们发现从匿名调用中返回的一个函数,也就是以g作为标示符的函数,被复制给外部的f。我们还知道有名函数表达式创建了一个多余的函数对象,并且这个对象和返回的对象并不是同一个函数。这里的内存问题就是由这个没用的g函数在一个返回函数的闭包中被按照字面上的意思捕获了。这是因为内部函数是和可恶的g函数在同一个作用域内声明的。除非我们显式的破坏到g函数的引用,否则他将一直占用内存。

  var f = (function(){
    var f, g;
    if (true) {
      f = function g(){};
    }
    else {
      f = function g(){};
    }
    //给g赋值null以使他不再被无关的函数引用。
        //null `g`, so that it doesn't reference extraneous function any longer
       
    g = null;
    return f;
  })();

Note that we explicitly declare g as well, so that g = null assignment wouldn’t create a global g variable in conforming clients (i.e. non-JScript ones). By nulling reference to g, we allow garbage collector to wipe off this implicitly created function object that g refers to.

注意,我们又显式的声明了g,所以g=null赋值将不会给符合规范的客户端(例如非JScirpt引擎)创建一个全局变量。通过给g以null的引用,我们允许垃圾回收来清洗这个被g所引用的,隐式创建的函数对象。

When taking care of JScript NFE memory leak, I decided to run a simple series of tests to confirm that nulling g actually does free memory.

当考虑JScript的有名函数表达式的内存泄露问题时,我决定运行一系列简单的测试来证实给g函数null的引用实际上可以释放内存

(责任编辑:IT教学网)

更多

推荐Javascript/Ajax文章