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


简介

Surprisingly, a topic of named function expressions doesn’t seem to be covered well enough on the web. This is probably why there are so many misconceptions floating around. In this article, I’ll try to summarize both - theoretical and practical aspects of these wonderful Javascript constructs; the good, bad and ugly parts of them.

在互联网上很难找到一篇对有名函数表达式讲述的比较全面的文章。这也可能是为什么对此问题有那么多的误解的原因。在这篇文章中,我将试图从理论和实践两个方面总结这些精彩的javascript结构,他的精华、鸡肋和糟粕。

In a nutshell, named function expressions are useful for one thing only - descriptive function names in debuggers and profilers. Well, there is also a possibility of using function names for recursion, but you will soon see that this is often impractical nowadays. If you don’t care about debugging experience, you have nothing to worry about. Otherwise, read on to see some of the cross-browser glitches you would have to deal with and tips on how work around them.

简而言之,有名函数表达式只对一件工作有用,在调试器和性能测试器中描述函数的名称。当然还有可能在递归调用中使用函数名称,但是你将很快看到在当下他通常不大实用。如果你不担心调试的问题,那么你就不用担心了,甚至不去使用有名函数表达式。否则,继续读下去,你将看到你可能已经遇到的跨浏览器问题和一些解决这些问题的方法。

I’ll start with a general explanation of what function expressions are how modern debuggers handle them. Feel free to skip to a final solution, which explains how to use these constructs safely.

下面我们先来介绍一下什么是函数表达式,和现代的调试器如何处理他们呢。你也可以随时跳转到最后的解决方案,那里将说明如何安全的使用这些结构

函数表达式和函数定义

One of the two most common ways to create a function object in ECMAScript is by means of either Function Expression or Function Declaration. The difference between two is rather confusing. At least it was to me. The only thing ECMA specs make clear is that Function Declaration must always have an Identifier (or a function name, if you prefer), and Function Expression may omit it:

在ECMAScript中有两种简单的创建函数对象的方法,一种是函数表达式,一种是函数声明。两者的区别让人比较困惑,至少对我是这样。ECMS规范中唯一澄清了的是函数声明必须含有一个标示符(如果你喜欢,可以称他为函数名),但是函数表达式可以省略他。

FunctionDeclaration :
function Identifier ( FormalParameterList opt ){ FunctionBody }

FunctionExpression :
function Identifier opt ( FormalParameterList opt ){ FunctionBody }

We can see that when identifier is omitted, that “something” can only be an expression. But what if identifier is present? How can one tell whether it is a function declaration or a function expression - they look identical after all? It appears that ECMAScript differentiates between two based on a context. If a function foo(){} is part of, say, assignment expression, it is considered a function expression. If, on the other hand, function foo(){} is contained in a function body or in a (top level of) program itself - it is parsed as a function declaration.

我们可以看到,当标示符被省略的时候,这样的结构只能是一个函数表达式。但是当标示符出现的时候情况是怎样的呢。他究竟是一个函数声明还是一个函数表达式呢,他们看上去是完全一样的。但是ECMAScript是通过上下文来区分两者的。如果function foo(){}是一个赋值表达式的一部分,他就被认为是函数表达式。相反,如果function foo(){}包含在一个函数体中,或者在程序的最上层的代码中,他就被解释成为一个函数声明。

  function foo(){}; // 声明,因为作为最上层程序的一部分declaration, since it's part of a Program
  var bar = function foo(){}; // 表达式,因为是复制表达式的一部分expression, since it's part of an AssignmentExpression

  new function bar(){}; // 表达式,因为他是New表达式的一部分。expression, since it's part of a NewExpression

  (function(){
    function bar(){}; // 声明,因为是函数图的一部分declaration, since it's part of a FunctionBody
  })();

There’s a subtle difference in behavior of declarations and expressions.

声明和表达式在作用上有这细微的差别。

First of all, function declarations are parsed and evaluated before any other expressions are. Even if declaration is positioned last in a source, it will be evaluated foremost any other expressions contained in a scope. The following example demonstrates how fn function is already defined by the time alert is executed, even though it’s being declared right after it:

首先,函数声明是在所有表达式之前被解析和求值。即使声明被放在代码的最后,他也会在包含它的作用域内的所有表达式之前被求值。下面的例子将说明即使fn函数在alert执行之后才被声明,但是在alert被执行的时候fn函数已经被定义了。

  alert(fn());

  function fn() {
    return 'Hello world!';
  }

Another important trait of function declarations is that declaring them conditionally is non-standardized and varies across different environments. You should never rely on functions being declared conditionally and use function expressions instead.

函数声明的另外一个重要的特点是,在条件语句中声明他们的情况在ECMA标准中是没有说明的,并且在不同的环境中(也就是不同的浏览器)中是不同的。因此你不能依赖函数的条件声明,而是应该使用函数表达式来代替。

  // 千万不要这样做Never do this!
  // 有些浏览器会声返回“first”的foo函数Some browsers will declare `foo` as the one returning 'first',
  // 有些浏览器则会声明返回“second”的foo函数while others - returning 'second'

  if (true) {
    function foo() {
      return 'first';
    }
  }
  else {
    function foo() {
      return 'second';
    }
  }
  foo();

  //作为替代你可以使用函数表达式 Instead, use function expressions:
  var foo;
  if (true) {
    foo = function() {
      return 'first';
    }
  }
  else {
    foo = function() {
      return 'second';
    }
  }
  foo();

Function expressions can actually be seen quite often. A common pattern in web development is to “fork” function definitions based on some kind of a feature test, allowing for the best performance. Since such forking usually happens in the same scope, it is almost always necessary to use function expressions. After all, as we know by now, function declarations should not be executed conditionally:

函数表达式可能更常见一些。在web开发中的一个普通的模式就是根据一些某种功能测试的情况来fork函数定义,允许达到最好的性能。既然这个fork通常发生在同一个作用域内,所以他需要使用函数表达式。毕竟,如我们所知,函数定义不应该被条件执行

  var contans = (function() {
    var docEl = document.documentElement;

    if (typeof docEl.compareDocumentPosition != 'undefined') {
      return function(el, b) {
        return (el.compareDocumentPosition(b) & 16) !== 0;
      }
    }
    else if (typeof docEl.contains != 'undefined') {
      return function(el, b) {
        return el !== b && el.contains(b);
      }
    }
    return function(el, b) {
      if (el === b) return false;
      while (el != b && (b = b.parentNode) != null);
      return el === b;
    }
  })();

有名函数表达式

Quite obviously, when a function expression has a name (technically - Identifier), it is called a named function expression. What you’ve seen in the very first example - var bar = function foo(){}; - was exactly that - a named function expression with foo being a function name. An important detail to remember is that this name is only available in the scope of a newly-defined function; specs mandate that an identifier should not be available to an enclosing scope:

非常显然那,当一个函数表达式含有函数名(从技术上讲就是一个标示符),他就被称作有名函数表达式。在你看到的第一个例子中 var bar=function foo(){}就是这样,他是一个以foo作为函数名的有名函数表达式。最重要的一个细节就是这个函数名只有在新定义的函数的作用域内才可访问,ECMA要求一个标示符不应该被一个封闭的作用域所访问

  var f = function foo(){
    return typeof foo; // “foo”在内部作用域可以被访问 "foo" is available in this inner scope
  };
  //‘foo’在外部不可见 `foo` is never visible "outside"
  typeof foo; // "undefined"
  f(); // "function"

So what’s so special about these named function expressions? Why would we want to give them names at all?

It appears that named functions make for a much more pleasant debugging experience. When debugging an application, having a call stack with descriptive items makes a huge difference.

因此,为什么这些有名函数表达式如此特别?为什么我们想要给他们名字呢。

可以看到有命名的函数在调试过程中可能更加方便一些。当调试一个应用程序的时候拥有一个含有描述项的调用栈会有很大的不同


« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3