本文译自Dmitry Soshnikov的《ECMA-262-3 in detail》系列教程。其中会加入一些个人见解以及配图举例等等,来帮助读者更好的理解JavaScript。
前言
在本文中,我们将讨论一个通用的ECMAScript对象——关于函数。特别是,我们将讨论各种类型的函数,定义每种类型如何影响上下文的变量对象,以及每个函数的作用域链中包含什么。我们会回答一些常见的问题,例如:以下所创建的函数之间有什么不同(如果有,是什么不同?)
var foo = function () {
...
};
通过习惯的方式定义的函数:
function foo() {
...
}
又或者说,为什么在下面这个调用,函数被括号包裹:
(function () {
...
})();
由于这些文章依赖于前面的章节,为了完全理解这部分内容,请先阅读前几篇文章(变量对象和作用域链),因为我们将使用这些文章中的术语
但是一口吃不成胖子,我们一个接一个来讲这些知识点,首先我们从函数类型开始
函数类型
在ECMAScript中,有三种函数类型并且他们都有自身的特性
1、函数声明
函数声明(Function Declaration, FD)是这样一个函数:
- 一个必须的名字;
- 它所处在源代码中的位置:要么在程序级别,要么在另一个函数体中(函数体FunctionBody);
- 在进入上下文时期被创建;
- 影响变量对象;
- 并且以如下方式声明:
示例:
function exampleFunc() {
...
}
这种类型的函数最主要的特性是只有他们影响变量对象(他们存储在上下文的变量对象中)。这个特性决定了第二个重要的点(变量对象性质的结果)——在代码执行阶段他们是可变的(因为函数声明FD在进入上下文阶段存储在变量对象VO中——在执行期开始之前)
例如(在函数声明源代码之前的位置调用函数):
foo();
function foo() {
console.log('foo');
}
同样重要的是函数在源代码中定义的位置(参见函数声明定义的第二项):
// 函数声明
// 1) 直接在全局上下文中
function globalFD() {
// 2) 或在其他函数体内
function innerFD() {
}
}
代码中只有这两个地方可以声明函数(不可能在函数表达式中或者在一个代码块中)。
函数的声明(这里函数的声明(function declarations)不等同于函数声明(FD))还有一种方式叫做函数表达式,我们接下来会涉及这个。
2、函数表达式:
函数表达式(Function Expression,FE),是这样的函数:
- 在源代码中只能被定义在表达式的位置;
- 可以有一个可选的名字(可以有名字,也可以没有);
- 它的定义不影响变量对象;
- 在代码执行阶段被创建
这种类型函数的主要特性是在源代码中他们总是在表达式位置,这儿有个简单的例子,比如赋值表达式:
var foo = function () {
...
};
这个例子展示了匿名函数表达式(FE)赋值给变量foo,之后函数可以通过foo名称来获取到——foo()。
在定义阶段这种类型的函数可以拥有一个可选的名字:
var foo = function _foo() {
...
};
在这里一个需要提的重点是,从函数表达式(FE)的外部可以通过变量foo来获得到—foo(),当在函数内部中可以通过_foo名称来获取(比如,在回调中)。
当一个函数表达式赋给一个名称它很难被与函数声明(FD)区分开来,但是,如果你知道定义的话,很容易说出他们的区别:函数表达式总是在表达式的位置,在下面的例子中我们可以看到多种ECMASCript表达式,这种情况下的函数是函数表达式(FE):
// 在括号中(这里应该叫做分组操作符)只能是一个表达式
(function foo() {
});
// 数组初始化中只能是表达式
[function bar() {
}];
// 逗号操作符情况也是表达式
1, function baz() {
};
这种定义方式还表明函数表达式FE在代码执行阶段被创建并且不存在变量对象中。让我们在一个例子中来看一看这种行为:
// 函数表达式在定义之前不可获得,因为它在代码执行阶段被创建
console.log(foo); // "foo" is not defined
(function foo() {
});
// 之后也不存在,因为它不在变量对象中
console.log(foo); // "foo" is not defined
现在有个逻辑性的问题是我们为什么需要这种类型的函数呢?答案很明显—在表达式中使用它们并且不“污染”变量对象,可以通过将一个函数作为参数传递给另外一个函数来演示这个:
function foo(callback)