深入理解JavaScript立即执行函数

我们在很多场景中使用了立即执行函数,或者看到别人写了立即执行函数,但是对它的作用和用法 还有一些疑惑,写这篇文章就是来解决这个问题的。

立即执行函数

IIFE(Immediately-Invoked Function Expression)
立即执行函数模式是一种语法,可以让你的函数在定义后立即被执行。

函数相关的概念

函数声明函数表达式匿名函数

函数声明

function func() { console.log(‘this is a function’) };
首先使用 function 关键字声明一个函数,再执行一个函数名,叫函数声明。

函数表达式
let func = function() { console.log(‘this is a function’) };
使用 function 关键字声明一个函数,但未给函数命名,将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。

匿名函数:
function() { console.log(‘this is a function’) } ;
使用 function 关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。

函数声明和函数表达式区别

1、JavaScript 引擎在解析 JavaScript 代码时会“函数声明提升”当前执行环境(作用域)上的函数声明,而函数表达式必须等到 JavaScript 引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式。

2、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以 func() 形式调用。

我们看一下下面的例子就会明白

函数声明

func(); //正常输出this is a function
function func() {
    console.log('this is a function') 
}; 

函数表达式

func();// Uncaught SyntaxError: Invalid or unexpected token
////函数表达式报错,func未保存对函数的引用,函数调用需放在函数表达式后面
var func = function() { 
    console.log('this is a function') 
};

立即执行函数的两种写法

//第一种写法
(function(){ 
   console.log('this is a function')
})();

//第二种写法
(function(){ 
   console.log('this is a function')
}());

为什么立即执行函数要加(),不加可不可以呢,我们来试一下

function(){ 
   console.log('this is a function')
}()
//Uncaught SyntaxError: Function statements require a function name
//报错,意思是函数声明需要一个函数名

上面这个例子其实是一个匿名函数,
虽然匿名函数属于函数表达式,但未进行赋值,所以javascript解析时将开头的function当做函数声明,故报错提示需要函数名;
立即执行函数里面的函数必须是函数表达式

立即执行函数是否可以有返回值

let func = function() {
    return 1;
}();
console.log(func) //1 

我们在浏览器的控制台可以看到,立即执行函数是可以有返回值的
为什么给一个匿名函数赋值就可以正常了呢?
我们可以理解为在匿名函数前加了 “=” 有了运算符后,将函数声明转化为函数表达式。
我们拿!,+,-,()…等运算符来进行测试:

!function(){
    console.log('this is a function1')
}()
// this is a function1
    
+function(){
    console.log('this is a function2')
}()
// this is a function2
    
-function(){
    console.log('this is a function3')
}()
// this is a function3
    
;(function(){
    console.log('this is a function4')
})()
// this is a function4

由此可见,加运算符确实可将函数声明转化为函数表达式

需要注意的地方

注意在代码console.log(‘this is a function4’)这个函数我在最前面加上了一个“;”(分号),
为什么要加这分号,不加行不行呢,大家可以验证一下。
不加会报错,会报下面的错
(Uncaught TypeError: (intermediate value)(…) is not a function)
上面的代码有一些多,我们截取一部分来讲

-function(){
    console.log('this is a function3')
}()

(function(){
    console.log('this is a function4')
})()

这段代码一样会报错,因为ECMAScript规范具有分号自动插入规则,但是在上面代码中,在第一个立即执行函数末尾却不会插入,因为第二个立即执行函数,会被解释为如下形式:

-function(){
    console.log('this is a function3')
}()(function(){console.log('this is a function4')})()

因此我们在最后一个立即执行函数前面加一个分号;(是为了防止前一个立即函数尾部没有的分号;)
其实还有其它的方式也可以实现,使用void 运算符,个人感觉这种方式更优雅

-function(){
    console.log('this is a function3')
}()

void (function(){
    console.log('this is a function4')
})()

立即执行函数的作用

**创造一个作用域空间,防止变量冲突或覆盖 **

我们下面来看一个精典的面试题

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

输出一个数据,从0-4,按常理会输出//0 1 2 3 4,结果输出了5个5。
这样的需求我们可以用立即执行函数来做,
我们可以把代码稍微改一下可以了,

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

首先 JS中调用函数传递参数都是值传递 ,所以当立即执行函数执行时,首先会把参数 i 的值复制一份,然后再创建函数作用域来执行函数,循环5次就会创建5个作用域,所以1秒后几乎会同时输出 0 1 2 3 4 。
其实还有其它的方式可以来实现,把原代码中的var改成let(ES6的块级作用域)也是可以正常输出 0 1 2 3 4

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

立即执行函数的应用场景

1、代码在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。

2、所有的这些动作只需要执行一次,比如只需要显示一个事件。

3、将代码包裹在它的局部作用域中,不会让任何变量泄漏成全局变量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章