JS中的立即执行函数
JS 立即执行函数可以让函数在创建后立即执行,这种模式本质上就是函数表达式(命名的或者匿名的),在创建后立即执行。
1、立即执行函数的写法
立即执行函数通常有下面两种写法:
//第一种写法 (function(){ ... })(); //第二种写法 (function(){ ... }()); //错误的写法 function (){ ... }(); //报错: Uncaught SyntaxError: Unexpected token (
第三种写法报错的原因是,Javascript引擎看到function关键字之后,认为后面跟的是函数定义语句,而在一条语句后面加上() 会被当做分组操作符,分组操作符里必须要有表达式,所以这里报错,不应该以圆括号结尾。以圆括号开头,引擎就会认为后面跟的是一个表示式,而不是函数定义,所以就避免了错误。
让Javascript引擎认为这是一个表达式的方法还有很多:
!function(){}(); +function(){}(); -function(){}(); ~function(){}(); new function(){ /* code */ } new function(){ /* code */ }() // 只有传递参数时,才需要最后那个圆括号
2、立即执行函数的作用
立即执行函数只有一个作用:创建一个独立的作用域。这个作用域里面的变量,外面访问不到(即避免了「变量污染」)。
面试题:
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 的值复制一份,然后再创建函数作用域来执行函数,循环5次就会创建5个作用域,所以每个 li 元素访问的都是不同作用域的 i 的值 。
3、立即执行函数和闭包的区别
下面的setTimeout 将依次输出 5 0 1 2 3 4
for (var i = 0; i < 5; i++) { (function (i) { setTimeout(function () { console.log(i); }, 1000); })(i); } console.log(i);
因为for循环以后 i 就会变成 5,上面代码最后一行的输出将先执行,所以先输出 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。
立即执行函数和闭包没有什么关系,只是两者会经常结合在一起使用而已,但两者有本质的不同。
立即执行函数和闭包只是有一个共同优点就是能减少全局变量的使用。
立即执行函数只是函数的一种调用方式,只是声明完之后立即执行,这类函数一般都只是调用一次,调用完之后会立即销毁,不会占用内存。
闭包则主要是让外部函数可以访问内部函数的作用域,也减少了全局变量的使用,保证了内部变量的安全,但因被引用的内部变量不能被销毁,增大了内存消耗,使用不当易造成内存泄露。
可参考:https://blog.csdn.net/Liu_yunzhao/article/details/90641956