javascript 中有三种定义函数的方式,每种方式都有自己的优缺点,这也使得我们在什么时候使用哪种方式成为了一个问题。我希望能够通过一些简单的文字让大家简单的理解一下这三种方式,之后,大家可以看看我在文章末尾提供的一些资料。

首先,我们先把这三种方式罗列出来:

1. 函数声明

function identifier(parameterList) {
    // FunctionBody
}

这是最简单也最容易被理解的方式,它是一种非常简单的语句结构,而且其结构和主流语言C/C++或java非常相似。使用时的唯一要求就是必须指定一个标示符(identifier)。另外注意一点,千万不要把函数声明放在另一个语句中!

2. 函数表达式

function identifier(parameterList) {
    // FunctionBody
}

javascript中,函数声明也可以作为表达式,而且从语法结构上来说,他们是一样的。唯一的区别只有两点:

  1. 函数表达式属于表达式结构; 这是最关键的,javascript之所以能作为一种表达式语言,主要就是因为这点。
  2. 函数表达式的标示符(identifier)是可选的,不指定也没关系(就是匿名函数咯)。而且就算指定了,也只能在这个函数的函数体内有效;如果你需要写个递归函数,那么就给他起个名字吧。

因此区分函数声明和函数表达式的方式就是根据它们是在表达式中定义还是作为语句定义:

// 函数表达式
var i = function fun1() {
    // FunctionBody
};

// 函数声明
function fun2() {
    // FunctionBody
}

3. 创建函数对象

new Function('parameter1', 'parameter2', ..., '//FunctionBody');

javascript中,函数就是对象实例。既然是对象,自然就有类——Function,也自然可以通过Function类创建一个函数对象。

好了,现在来详细地说下它们的区别。

总结

第一,在一个作用域(全局作用域或一个函数作用域)中,函数声明可以在作用域中的任何位置进行定义,而函数表达式和创建函数对象必须在调用之前进行定义。

这和javascript的解析机制有关。在代码执行之前,javascript会创建一个对应的执行环境(就是用来运行代码的一个环境啦),同时在该执行环境中创建一个活动对象;接着找到所有函数声明,将每个函数声明中的标示符作为活动对象中的属性名,并实例化该函数作为其属性值;然后再找到作用域中所有通过var声明的局部变量并同样指定为活动对象中的属性,当然,因为代码还未执行,还不知道它们的值,所以统一设为undefined;之后,在调用局部变量或函数时,实际上都是调用的这个活动对象中的属性。

也就是说,在代码执行前,函数声明所定义的函数已经被创建好了,自然在任何位置都可以调用。而其它两种方式就只能等执行到他们的时候,才会创建函数对象,然后才能够被调用:

// 此时变量fun1和fun2还是undefined,无法调用。
// fun1();
// fun2();

// 代码执行前,fun3对应的函数已经被创建,所以可以调用。
fun3();

// 代码执行到这里,开始创建函数实例,并赋给对应变量。

var fun1 = function() {
    // FunctionBody
}
var fun2 = new Function('//FunctionBody');

fun1();
fun2();
fun3();

function fun3() {
    // FunctionBody
}

第二,通过Function创建的函数好像‘不是’一个闭包。

javascript中的所有函数都是闭包;函数被调用时所创建的执行环境中有一个作用域列表,这个作用域列表源自于函数本身的一个属性——[[scope]],[[scope]] 属性来自该函数被创建时所在的执行环境中的作用域列表。在此属性的顶端添加上该函数执行环境中的活动对象便构成了这个执行环境的作用域列表。这样,当我们调用任何变量或函数时,会从作用域列表顶端开始查找,若直到最低端的全局对象都还没有找打,就报错。

另外,对于全局环境,它也有一个作用域列表,这个列表中只有一个值,就是全局对象。(所以可以将全局环境也看成是一个函数,所有代码都是在这个函数中。)

不过,ECMAScript要求,对于通过Function创建的函数,它的 [[scope]] 属性中也只有一个值,也是全局对象。所以在这种函数执行时,执行环境中的作用域列表中只有全局对象和执行环境中的活动对象,能访问到的,也只有这个函数自己内部的局部变量和函数以及全局对象中的变量和函数。

// 全局环境中的函数A
function A() {
    var num = 10;
    var B1 = function() {
        return num;
    };
    var B2 = new Function(
        'return num;'
    );
    var B3 = new Function(
        'return A;'
    );

    B1();    // 10
    B2();    // error: num is not defined.
    B3();    // [函数A的代码]
}

p.s.

好了,就说这些吧,以后有时间再多说点,下面是扩展阅读时间:

理解 JavaScript 闭包——为之漫笔
命名函数表达式探秘——为之漫笔