JS中的立即执行函数

494 阅读3分钟

js立即执行函数可以让函数在创建后立即执行,这种模式本质上就是函数表达式(命名的或者匿名的),在创建后立即执行。

立即执行函数的写法

立即执行函数通常有下面两种写法:

//第一种写法
(function(){
    ...
})();
//第二种写法
(function(){
    ...
}());
//错误写法
function(){
    ...
}()
//报错: Uncaught SyntaxError: Unexpected token (

第三种写法报错的原因是,JavaScript引擎看到function关键字之后,认为后面跟的是函数定义语句,而在一条语句后面加上()会被当做分组操作符,分组操作符里必须要有表达式,所以这里报错,不应该以圆括号结尾。以圆括号开头,引擎就会认为后面跟的是一个表达式,而不是函数定义,所以就避免了错误。

让JavaScript引擎认为这是一个表达式的方法还有很多:

!function(){}(); //true
+function(){}(); //NaN
-function(){}(); //NaN
~function(){}(); //-1
new function(){/* code */}
new function(){/* code */}()// 只有传递参数时,才需要最后那个圆括号

立即执行函数的作用

立即执行函数只有一个作用:创建一个独立的作用域。这个作用域里面的变量,外面访问不到(避免了变量污染)。

var liList = ul.getElementsByTagName('li')
for(var i=0; i<6; i++){
  liList[i].onclick = function(){
    alert(i) // 为什么 alert 出来的总是 6,而不是 0、1、2、3、4、5
  }
}

因为输出的i是全局作用域的,当循环结束后i的值是6,所以输出的i就是6。

用立即执行函数可以解决这个问题。

var liList = ul.getElementsByTagName('li')
for(var i=0; i<6; i++){
  (function(j){
    liList[j].onclick = function(){
      alert(j) // 0、1、2、3、4、5
    }
  })(i)
}

因为js中调用函数传递参数都是值传递,所以当立即执行函数执行时,首先会把参数i的值复制一份,然后在创建函数作用域执行函数,循环6次就创建5个作用域,所以每个li元素访问的都是不同作用域的i的值。

立即执行函数和闭包的区别

setTimeout一次输出0 1 2 3 4 5

for (var i = 0; i < 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i);
        }, 1000);
    })(i);
}
console.log(i);

第一个5很好输出,因为for循环以后i就会变成5,剩下的我们可以使用立即执行函数来做。首先,js中调用函数参数都是值传递,所以当立即执行函数执行时,首先会把参数i的值复制一份,然后在创建函数作用域来执行函数,循环5次就会创建5个作用域,所以1秒后几乎会同时输出 0 1 2 3 4

上面的现象也可以说是闭包,因为在外层的function里面还包含着setTimeout里面的function函数,而里面的function函数就访问了外层function的i的值,由此就形成了一个闭包。每次循环时,将i的值保存在一个闭包中,当setTimeout中定义了操作执行时,就会访问对应闭包保存的i值,所以输出 0 1 2 3 4。

立即执行函数和闭包没有什么关系,只是两者会经常结合在一起使用而已,但两者有本质的不同。

立即执行函数和闭包只是有一个共同优点就是能减少全局变量的使用。

立即执行函数只是函数的一种调用方式,只是声明完之后立即执行,这类函数一般都只是调用一次,调用完之后会立即销毁,不会占用内存。

闭包则主要是让外部函数可以访问内部函数的作用域,也减少了全局变量的使用,保证了内部变量的安全,但因被引用的内部变量不能被销毁,增大了内存消耗,使用不当容易造成内存泄漏。