JavaScript浅析 -- IIFE立即执行函数表达式
一、什么是IIFE?
所谓IIFE(Immediately-Invoked Function Expression)翻译过来就是立即执行函数表达式。其实就是
- 声明了一个函数。
- 立即执行这个函数。
他有好多种写法,如下代码就是经典的写法:
(function() { console.log('立即执行') })()
如上,前面是一个函数,然后调用函数是‘函数名()’,所以后面加()执行该函数。这个比较好理解,那前面那个为啥要加括号呢?不加可以不?我们来试试:
function() { console.log('立即执行') }() // Uncaught SyntaxError: Unexpected token (
结果是报错Uncaught SyntaxError: Unexpected token (
,为啥呢?其实因为js解释代码的时候遇到function会认为是一个函数声明,而声明函数却没有找到函数名,所以遇到第一个左括号就报错了。接着我们加上函数名修改如下:
function func() { console.log('立即执行') }() // Uncaught SyntaxError: Unexpected token )
还是报错Uncaught SyntaxError: Unexpected token )
,好在错误变了,不过这次又是为啥?其实因为前面的函数声明包含函数名已经是一个完整的语句了,所以后面的()又另做为一个语句。相当于如下代码:
function func() { console.log('立即执行') };
();
而分组操作符()中的表达式又不能为空,所以是遇到最后一个右括号才报的错。那怎样才能让两者并成一个语句从而实现函数的执行呢?
为了解决上面的两个问题,只要让其变成表达式,加个括号就行了,因为括号内部不能有语句所以会认为是一个表达式。有如下两种写法:
- (function func(){})()
- (function func(){}())
其原理都是一样的,都是为了让其变成一个表达式。而其实让他变成表达式的写法不止上面两种,上面两种是比较经典易读的写法。还有如下几种,原理都是一样的:
// 运算符直接转表达式法
var i = function(){ console.log('立即执行') }();
!function(){ console.log('立即执行') }();
~function(){ console.log('立即执行') }();
-function(){ console.log('立即执行') }();
+function(){ console.log('立即执行') }();
// 解析器在遇到& || ,等操作符时,会把两边默认为表达式。
// 但注意交换位置就不行,因为如果把function放在前面则还是会认为是声明
true & function(){ console.log('立即执行') }();
0, function(){ console.log('立即执行') }();
// 以及new
new function(){ console.log('立即执行') }
new function(){ console.log('立即执行') }() // 带参数
二、IIFE有啥用?
先来看个例子,如下声明了一个变量i并输出:
var i = 0;
console.log(i);
但是这样做有时候会有一个问题,在这个全局作用域下声明的i会影响到整个作用域(全局污染),也就是整个全局作用域下的i都变成了0,而我们只是单纯想在这里可用,应该怎么做呢?在Js(ES6之前)里只分为全局作用域和局部作用域 ,局部作用域一般是通过函数来创建一个单独的作用域,这个作用域下只有函数内可用。所以将上面改写如下:
function a() {
var i = 0;
console.log(i);
}
a();
这样的话,i就只在函数a里面才有效。可是。。。却多出来一个变量a,还是污染了全局。其实这个a也是可以避免的,因为我们声明了这个函数只是想利用他的局部作用域而已,声明完就立马执行了,何必起个名字呢。所以再进一步改写如下:
(function() {
var i = 0;
console.log(i);
})()
这样立即执行函数表达式就诞生了,神奇吧?(其实还是挺鸡肋的,代码写的这么复杂只是想创建一个不污染全局的变量而已,在ES6之后有了块级作用域和let等,这些问题得到了改善)。
所以经过上面的例子,我们大概就能体会立即执行函数表达式有啥用了。简单来说:
- IIFE的作用就是创建一个独立的作用域。在这个作用域里面的变量只有其内可以访问,从而避免变量污染。