即时函数

即时函数是一种语法模式,它会使函数在定义后立即执行。看这个例子:

  1. (function () {
  2. alert('watch out!');
  3. }());

这种模式本质上只是一个在创建后就被执行的函数表达式(具名或者匿名)。“即时函数”这种说法并没有在ECMAScript标准中被定义,但它作为一个名词,有助于我们的描述和讨论。

这种模式由以下几个部分组成:

  • 使用函数表达式定义一个函数。(不能使用函数声明。)
  • 在最后加入一对括号,这会使函数立即被执行。
  • 把整个函数包裹到一对括号中(只在没有将函数赋值给变量时需要)。

下面这种语法也很常见(注意右括号的位置),但是JSLint倾向于第一种:

  1. (function () {
  2. alert('watch out!');
  3. })();

这种模式很有用,它为我们提供一个作用域的沙箱,可以在执行一些初始化代码的时候使用。设想这样的场景:当页面加载的时候,你需要运行一些代码,比如绑定事件、创建对象等等。所有的这些代码都只需要运行一次,所以没有必要创建一个带有名字的函数。但是这些代码需要一些临时变量,而这些变量在初始化完之后又不会再次被用到。显然,把这些变量作为全局变量声明是不合适的。正因为如此,我们才需要即时函数。它可以把你所有的代码包裹到一个作用域里面,而不会暴露任何变量到全局作用域中:

  1. (function () {
  2. var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
  3. today = new Date(),
  4. msg = 'Today is ' + days[today.getDay()] + ', ' + today.getDate();
  5. alert(msg);
  6. }()); // "Today is Fri, 13"

如果这段代码没有被包裹到立即执行函数中,那么变量daystodaymsg都会是全局变量,而这些变量仅仅是因为初始化而遗留下来的垃圾,没有任何用处。

即时函数的参数

即时函数也可以接受参数,看这个例子:

  1. // 打印出:
  2. // I met Joe Black on Fri Aug 13 2010 23:26:59 GMT-0800 (PST)
  3. (function (who, when) {
  4. console.log("I met " + who + " on " + when);
  5. }("Joe Black", new Date()));

通常我们会把全局对象当作一个参数传给即时函数,以保证在函数内部也可以访问到全局对象,而不是使用window对象,这样可以使得代码在非浏览器环境中使用时更具可移植性。

值得注意的是,一般情况下尽量不要给即时函数传入太多的参数,否则会有一件麻烦的事情,就是你在阅读代码的时候需要频繁地上下滚动代码。

即时函数的返回值

和其它的函数一样,即时函数也可以返回值,并且这些返回值也可以被赋值给变量:

  1. var result = (function () {
  2. return 2 + 2;
  3. }());

如果省略括号的话也可以达到同样的目的,因为如果需要将返回值赋给变量,那么第一对括号就不是必需的。省略括号的代码是这样子:

  1. var result = function () {
  2. return 2 + 2;
  3. }();

这种写法更简洁,但是同时也容易造成误解。如果有人在阅读代码的时候忽略了最后的一对括号,那么他会以为result指向了一个函数。而事实上result是指向这个函数运行后的返回值,在这个例子中是4。

还有一种写法也可以得到同样的结果:

  1. var result = (function () {
  2. return 2 + 2;
  3. })();

前面的例子中,即时函数返回的是一个基本类型的数值。但事实上,一个即时函数可以返回任意类型的值,甚至返回一个函数都可以。你可以利用即时函数的作用域来存储一些私有的数据,这些数据只能在返回的内层函数中被访问。

在下面的例子中,即时函数的返回值是一个函数,这个函数会简单地返回res的值,并且这个值被赋给了变量getResult。而res是一个预先计算好的变量,它被存储在即时函数的闭包中:

  1. var getResult = (function () {
  2. var res = 2 + 2;
  3. return function () {
  4. return res;
  5. };
  6. }());

在定义一个对象属性的时候也可以使用即时函数。设想一下这样的场景:你需要定义一个对象的属性,这个属性在对象的生命周期中都不会改变,但是在定义之前,你需要做一些计算来得到它的值。这种情况下你就可以使用即时函数来包裹那些额外的计算工作,然后将它的返回值作为对象属性的值。下面是一个例子:

  1. var o = {
  2. message: (function () {
  3. var who = "me",
  4. what = "call";
  5. return what + " " + who;
  6. }()),
  7. getMsg: function () {
  8. return this.message;
  9. }
  10. };
  11. // 使用对象
  12. o.getMsg(); // "call me"
  13. o.message; // "call me"

在这个例子中,o.message是一个字符串,而不是一个函数,但是它需要一个函数在脚本载入后通过计算得到这个属性值。

好处和用法

即时函数应用很广泛。它可以帮助我们做一些不想留下全局变量的工作。所有定义的变量都只是即时函数的本地变量,你完全不用担心临时变量会污染全局对象。

即时函数还有一些名字,比如“自调用函数”或者“自执行函数”,因为这些函数会在被定义后立即执行自己。

这种模式也经常被用到书签代码中,因为书签代码有可能会运行在任何一个页面中,所以需要非常苛刻地保持全局命名空间干净。

这种模式也可以让你包裹一些独立的特性到一个封闭的模块中。设想你的页面是静态的,在没有JavaScript的时候工作正常,然后,本着渐进增强的精神,你给页面加入了一点增强代码。这时候,你就可以把你的代码(也可以叫“模块”或者“特性”)放到一个即时函数中并且保证页面在有没有它的时候都可以正常工作。然后你就可以加入更多的增强特性,或者对它们进行移除、进行独立测试或者允许用户禁用等等。

你可以使用下面的模板定义一段函数代码,我们叫它module1:

  1. // 在module1.js中定义module1
  2. (function () {
  3. // 所有module 1的代码……
  4. }());

你可以套用这个模板来编写其它的模块,然后在发布到线上的时候,再决定在这个时间节点上哪些特性是稳定可用的,然后使用发布脚本将它们打包上线。