0%

JavaScript的立即执行函数

在很多JavaScript代码中都能看到这种写法:

1
2
3
(function () {
//...
})()

或者

1
2
3
!function () {
//...
}()

这种看起来比较奇葩的写法被称为立即执行函数,可以让JavaScript解释器在执行到此处代码时立即执行函数内部的代码。要理解立即执行函数需要区别函数声明和函数表达式的概念。

函数声明:即使用function关键字声明一个函数,为该函数指定一个函数名,如下:

1
2
3
function fn () {
//...
};

函数表达式:使用function关键字声明一个函数,但不指定函数名,将该函数赋值给一个变量,如下:

1
2
3
var fn = function () {
//...
};

若声明一个函数后未赋值给变量,则该函数为一个匿名函数,匿名函数也是函数表达式,如下:

1
2
3
function () {
//...
};

以上两种都是函数表达式的表示。

下面是函数声明和函数表达式的区别:

  1. 函数声明提升:所有的函数声明在JavaScript代码运行前都将被提升到函数体的顶部,但是函数表达式会留在原来的位置。因此,在函数声明提升效果下,函数声明可以位于函数调用之后,如下:
1
2
3
4
5
fn(); // 由于函数声明提升,实际上解释时fn的声明会被“提升”到代码的最顶部
//...coding...
function fn () { // fn函数的声明可以位于调用fn函数的代码之后
//...
}
1
2
3
4
5
fn(); // 报错,函数表达式不会被自动提升,因此函数表达式必须在函数调用之前
//...coding...
var fn = function () { // 函数表达式
//...
}
  1. 函数表达式后加括号可以立即调用该函数,函数声明不可以,只能通过函数名后加括号调用该函数,如下:
1
2
3
4
5
6
7
8
9
10
11
var fn = function () {
//...
} (); // <--在函数表达式后加括号可以在此处立即调用fn函数

function fn () {
//...
} (); // <--在函数声明后加括号不能立即调用该函数

function () {
//...
} (); // <--虽然匿名函数也是表达式,但是JavaScript解释器会将function关键字当做函数声明,因此并不会将匿名函数识别为表达式,效果等同于在函数声明后加括号

可见,只有函数表达式可通过在表达式后加括号即可立即调用该函数,因此,需要在函数体后加括号就能立即调用,则被调用方必须以函数表达式存在。

要将一个函数声明转变成函数表达式外,除了将函数声明以赋值形式变成表达式外,还能通过增加括号、运算符等操作将一个函数声明转化为一个表达式。事实上文章开头的两种形式即是将一个匿名函数以显式转化为函数表达式的形式,告诉解释器该匿名函数为一个表达式,从而使解释器可以立即执行该匿名函数,就像这样:

1
2
3
(function () {
//...
})()

或者

1
2
3
!function () {
//...
}()

由于运算符会与函数的返回值进行运算,因此以括号的形式转换是一种更安全的做法。

那么为什么要将匿名函数包裹起来立即执行呢?由于JavaScript函数作用域的关系,匿名函数内部的作用域是独立于外部的作用域的,因此外部的操作无法访问匿名函数内部的变量,从而在保持函数正常执行的同时,达到了私有作用域的效果。这种做法也被称为“匿名包裹器”。

在jQuery源代码中也使用了这个方法,通过匿名函数包裹jQuery内部的变量和函数,只暴露$jQuery这两个全局变量,就像这样:

1
2
3
4
5
6
(function (window, undefined) {
//...code...
var _jQuery = window.jQuery;
var _$ = window.$;
//...code...
}) (window);