var foo = function () {
alert('foo');
};
foo();
另外一个例子是创建封装的闭包从外部上下文中隐藏辅助性数据(在下面的例子中我们使用FE,它在创建后立即调用):
复制代码 代码如下:
var foo = {};
(function initialize() {
var x = 10;
foo.bar = function () {
alert(x);
};
})();
foo.bar(); // 10;
alert(x); // "x" 未定义
我们看到函数foo.bar(通过[[Scope]]属性)访问到函数initialize的内部变量“x”。同时,“x”在外部不能直接访问。在许多库中,这种策略常用来创建”私有”数据和隐藏辅助实体。在这种模式中,初始化的FE的名称通常被忽略:
复制代码 代码如下:
(function () {
// 初始化作用域
})();
还有一个例子是:在代码执行阶段通过条件语句进行创建FE,不会污染变量对象VO。
复制代码 代码如下:
var foo = 10;
var bar = (foo % 2 == 0
? function () { alert(0); }
: function () { alert(1); }
);
bar(); // 0
关于圆括号的问题
让我们回头并回答在文章开头提到的问题——”为何在函数创建后的立即调用中必须用圆括号来包围它?”,答案就是:表达式句子的限制就是这样的。
按照标准,表达式语句不能以一个大括号{开始是因为他很难与代码块区分,同样,他也不能以函数关键字开始,因为很难与函数声明进行区分。即,所以,如果我们定义一个立即执行的函数,在其创建后立即按以下方式调用:
复制代码 代码如下:
function () {
...
}();
// 即便有名称
function foo() {
...
}();
我们使用了函数声明,上述2个定义,解释器在解释的时候都会报错,但是可能有多种原因。
如果在全局代码里定义(也就是程序级别),解释器会将它看做是函数声明,因为他是以function关键字开头,第一个例子,我们会得到SyntaxError错误,是因为函数声明没有名字(我们前面提到了函数声明必须有名字)。
第二个例子,我们有一个名称为foo的一个函数声明正常创建,但是我们依然得到了一个语法错误——没有任何表达式的分组操作符错误。在函数声明后面他确实是一个分组操作符,而不是一个函数调用所使用的圆括号。所以如果我们声明如下代码:
复制代码 代码如下:
// "foo" 是一个函数声明,在进入上下文的时候创建
alert(foo); // 函数
function foo(x) {
alert(x);
}(1); // 这只是一个分组操作符,不是函数调用!
foo(10); // 这才是一个真正的函数调用,结果是10
上述代码是没有问题的,因为声明的时候产生了2个对象:一个函数声明,一个带有1的分组操作,上面的例子可以理解为如下代码:
复制代码 代码如下:
// 函数声明
function foo(x) {
alert(x);
}
// 一个分组操作符,包含一个表达式1
(1);
复制代码 代码如下:
// 另外一个操作符,包含一个function表达式
(function () {});
// 这个操作符里,包含的也是一个表达式"foo"
("foo");
// 等等
如果我们定义一个如下代码(定义里包含一个语句),我们可能会说,定义歧义,会得到报错:
if (true) function foo() {alert(1)}
根据规范,上述代码是错误的(一个表达式语句不能以function关键字开头),但下面的例子就没有报错,想想为什么?
我们如果来告诉解释器:我就像在函数声明之后立即调用,答案是很明确的,你得声明函数表达式function expression,而不是函数声明function declaration,并且创建表达式最简单的方式就是用分组操作符括号,里边放入的永远是表达式,所以解释器在解释的时候就不会出现歧义。在代码执行阶段这个的function就会被创建,并且立即执行,然后自动销毁(如果没有引用的话)。
复制代码 代码如下:
(function foo(x) {
alert(x);
})(1); // 这才是调用,不是分组操作符
上述代码就是我们所说的在用括号括住一个表达式,然后通过(1)去调用。
注意,下面一个立即执行的函数,周围的括号不是必须的,因为函数已经处在表达式的位置,解析器知道它处理的是在函数执行阶段应该被创建的FE,这样在函数创建后立即调用了函数。
复制代码 代码如下: