注:此文只在理解当即执行函数,不在所谓原创,文中大量引用阮一峰的JavaScript标准参考教程、MDN的JavaScript 参考文档和深刻理解JavaScript系列(4):当即调用的函数表达式的内容。javascript
当即执行函数一般有下面两种写法:html
(function(){ ... })();
(function(){ ... }());
在Javascript中,一对圆括号“()”是一种运算符,跟在函数名以后,表示调用该函数。好比,print()就表示调用print函数。java
这个写法和咱们想象的写法不同(知道的人固然已经习觉得常)
不少人刚开始理解当即执行函数的时候,以为应该是这样的:express
function (){ ... }(); //或者 function fName(){ ... }();
然而事实倒是这样:SyntaxError: Unexpected token (
。这是为何呢?闭包
要理解当即执行函数,须要先理解一些函数的基本概念:函数声明
、函数表达式
,由于咱们定义一个函数一般都是经过这两种方式函数
函数声明 (function 语句)优化
function name([param[, param[, ... param]]]) { statements }
name:函数名;
param:被传入函数的参数的名称,一个函数最多能够有255个参数;
statements:这些语句组成了函数的函数体。code
函数表达式 (function expression)htm
函数表达式和函数声明很是相似,它们甚至有相同的语法。对象
function [name]([param] [, param] [..., param]) { statements }
name:函数名,能够省略,省略函数名的话,该函数就成为了匿名函数
;
param:被传入函数的参数的名称,一个函数最多能够有255个参数;
statements:这些语句组成了函数的函数体。
下面咱们给出一些栗子说明:
// 声明函数f1 function f1() { console.log("f1"); } // 经过()来调用此函数 f1(); //一个匿名函数的函数表达式,被赋值给变量f2: var f2 = function() { console.log("f2"); } //经过()来调用此函数 f2(); //一个命名为f3的函数的函数表达式(这里的函数名能够随意命名,能够没必要和变量f3重名),被赋值给变量f3: var f3 = function f3() { console.log("f2"); } //经过()来调用此函数 f3();
上面所起的做用都差很少,但仍是有一些差异
一、函数名和函数的变量存在着差异。函数名不能被改变,但函数的变量却可以被再分配。函数名只能在函数体内使用。假若在函数体外使用函数名将会致使错误:
var y = function x() {}; alert(x); // throws an erro
二、函数声明定义的函数能够在它被声明以前使用
foo(); // alerts FOO! function foo() { alert('FOO!'); }
但函数声明很是容易(常常是意外地)转换为函数表达式。当它再也不是一个函数声明:
成为表达式的一部分
再也不是函数或者script自身的“源元素” (source element)。在script或者函数体内“源元素”并不是是内嵌的语句(statement)
var x = 0; // source element if (x == 0) { // source element x = 10; // 非source element function boo() {} // 非 source element } function foo() { // source element var y = 20; // source element function bar() {} // source element while (y == 10) { // source element function blah() {} // 非 source element y++; //非source element } }
Examples:
// 函数声明 function foo() {} // 函数表达式 (function bar() {}) // 函数表达式 x = function hello() {} if (x) { // 函数表达式 function world() {} } // 函数声明 function a() { // 函数声明 function b() {} if (0) { //函数表达式 function c() {} } }
如今咱们来解释上面的SyntaxError: Unexpected token (
:
产生这个错误的缘由是,Javascript引擎看到function关键字以后,认为后面跟的是函数定义语句,不该该以圆括号结尾。
解决方法就是让引擎知道,圆括号前面的部分不是函数定义语句,而是一个表达式,能够对此进行运算。因此应该这样写:
(function(){ /* code */ }()); // 或者 (function(){ /* code */ })();
这两种写法都是以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义,因此就避免了错误。这就叫作“当即调用的函数表达式”(Immediately-Invoked Function Expression),简称IIFE。
注意,上面的两种写法的结尾,都必须加上分号。
推而广之,任何让解释器以表达式来处理函数定义的方法,都能产生一样的效果,好比下面三种写法。
var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }();
甚至像这样写:
!function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }();
new关键字也能达到这个效果:
new function(){ /* code */ } new function(){ /* code */ }() // 只有传递参数时,才须要最后那个圆括号。
那咱们一般为何使用函数当即表达式呢,以及我如何使用呢?
一般状况下,只对匿名函数使用这种“当即执行的函数表达式”。
它的目的有两个:
一是没必要为函数命名,避免了污染全局变量;
二是IIFE内部造成了一个单独的做用域,能够封装一些外部没法读取的私有变量。
// 写法一 var tmp = newData; processData(tmp); storeData(tmp); // 写法二 (function (){ var tmp = newData; processData(tmp); storeData(tmp); }());
上面代码中,写法二比写法一更好,由于彻底避免了污染全局变量。
最后在举一个真实的栗子:在JavaScript的OOP中,咱们能够经过IIFE来实现一个单例(关于单例的优化再也不此处讨论)
// 建立一个当即调用的匿名函数表达式 // return一个变量,其中这个变量里包含你要暴露的东西 // 返回的这个变量将赋值给counter,而不是外面声明的function自身 var counter = (function () { var i = 0; return { get: function () { return i; }, set: function (val) { i = val; }, increment: function () { return ++i; } }; } ()); // counter是一个带有多个属性的对象,上面的代码对于属性的体现实际上是方法 counter.get(); // 0 counter.set(3); counter.increment(); // 4 counter.increment(); // 5 counter.i; // undefined 由于i不是返回对象的属性 i; // 引用错误: i 没有定义(由于i只存在于闭包)