深入理解JavaScript-立即执行函数(IIFE)

简介: 深入理解JavaScript-立即执行函数(IIFE)

一句话解释


  1. 立即执行函数是什么?
    立即执行函数就是声明一个匿名函数,并马上调用这个匿名函数
  2. 立即执行函数有什么用途
    创建一个独立的作用域,这个作用域里面的变量,外面访问不到(即避免"变量污染")


我们先问自己一个问题:立即执行函数是闭包吗?如果你不能马上回答这个问题,那么不妨往下看看


什么是立即执行函数


来自 MDN[1] 的回答是


IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数)。


这样就能形成一个 块级作用域 效果

(function () {
    // 块级作用域
})();

这在没有块级作用域的 ES3 时代,是相当普遍的做法


以前有个有名的面试题,如下所示:

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


结果是什么,5,5,5,5,5,而且是在每 1 秒打印一个 5

问,有什么方法让它的结果是 1,2,3,4,5

我们分析一下为什么会在一开始的时候打印 5,这是因为 setTimeout 是异步,要塞进异步队列中,所以一开始先循环,循环完了再执行 setTimeout(func, wait)。

所以执行顺序是


for (var i = 0; i < 5; i++) {
    // 赋值 setTimeout(function() { console.log(i) }, 1000 * i)
    // i 1,2,3,4,5
}
// setTimeout 延迟执行,var i被统一赋值为5
setTimeout(function () {
    console.log(5);
}, 1000 * 1);
setTimeout(function () {
    console.log(5);
}, 1000 * 2);
setTimeout(function () {
    console.log(5);
}, 1000 * 3);
setTimeout(function () {
    console.log(5);
}, 1000 * 4);
setTimeout(function () {
    console.log(5);
}, 1000 * 5);


又因为,for() {} 不会形成块级作用域,所以会拿最后的值也就是 5 来给每一个 func 中的 console.log(i) 赋值,最后导致了这样的打印结果

分析完后,我们要思考一下,怎么保住 setTimeout 中的变量 i,通常的办法是通过作用域来保护,例如用块级作用域来保护 i ,方法是用 let 代替 var。


for (let i = 0; i < 5; i++) {
    // 将 var 改成 let 即可
    setTimeout(function () {
        console.log(i);
    }, 1000 * i);
}


或者用函数作用域来保护,因为函数作用域内的变量,函数外不能访问


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


用 let 的方法的伪代码类似于立即执行函数。代码理解为:

每传入一个变量 i,并立即执行 setTimeout ,执行完毕一次后,for 循环中的 i 变为 1,再执行 setTimeout,这样就达到了效果


其原面试题为什么会出现这种结果,本质是 JavaScript 中的 for 循环不能保护 i 被改变,即 for 循环不能形成块级作用域。


通过这题我们能清晰的认知到立即执行函数的用处:定义时就会立即执行的函数


IIFE的延展形态


我们常见的 IIFE 是这样的:

;(function() {
   ...
})()


但是不乏看到这样的代码

(function (window) {
    console.log(window);
})(window);


刚开始我们会很懵逼,这是可以传值?

先看看普通函数怎么运行的

var foo = function (name) {
    console.log(name);
};
foo('johan');


我们可以在任何地方调用 foo 函数,

之所以要创造“IIFE”,是因为它们是立即调用的函数表达式,这意味着它们会在运行时立即被调用,且我们不会再去调用它,它只运行一次,如下所示:

var foo = (function (name) {
    console.log(name);
})('johan');


甚至,我们可以不用赋值给 foo,因为我们并不会使用 foo

(function (name) {
    console.log(name);
})('johan');


以上例子很好理解吧,自己定义一个匿名函数并且自己传入参数调用


为什么要有 IIFE


原因很简单,为了让一块代码执行且不被其他库影响。在 ES6 的模块出现之前,我们写 JavaScript 不是在 HTML 的 script 标签中书写,就是在 javascript 文件中写代码再通过 script 标签引入。当你写的(全局)方法和别人(第三方库也好,同事的代码也好)相同时,就会有方法覆盖的 bug 存在


所以用 IIFE,保证了每一个 IIFE 中的代码变量不会在全局作用域下被访问,也就起到了变量保护的作用


适用场景


UMD 打包

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof exports === 'object') {
        module.exports = factory;
    } else {
        root.MYMODULE = factory();
    }
})(this, function () {
    //...
});

本质就是把 AMD 和 CommonJS 结合在一起


源码中的立即执行函数


jQuery 中的:

(function(window, undefined) {
    ...
})(window);


underscore 中的:

(function(global, factory) {
    ...
}(this, (function () {
    ...
})));


这些大佬库中都用到了立即执行函数来保护库中变量


立即执行函数是闭包吗


回到一开始的问题,IIFE 是闭包吗?

肯定不是,IIFE 是立即执行函数,执行完就被垃圾回收了,怎么会是闭包呢?

什么是闭包?闭包是要利用作用域机制把控私有变量

两者为什么会被人搞混?


因为 IIFE 能起到隔离变量的作用,为模块化没出来前而做的 hack 变量保护机制。而闭包恰恰也能起到隔离变量的效果。所以这两者会被人搞混


那有立即执行函数实现闭包的场景吗?

var Module = (function () {
    var private = '私有变量';
    var foo = function () {
        console.log(private);
    };
    return {
        foo: foo,
    };
})();
Module.foo(); // 私有变量
Module.private; // undefined


立即执行函数不是闭包,但是它可以做出闭包效果


三题见真章


第一题

(function () {
    if (typeof name === 'undefined') {
        console.log('Goodbye ' + name);
    } else {
        var name = 'Jack';
        console.log('Hello ' + name);
    }
})();


   答案Goodbye undefined

解题思路:

我们都知道一个函数定义并立即执行就是立即执行函数,既然是函数,就形成了作用域,在这个题目中,函数内有变量提升,即var name 被提到函数顶部,且默认为 undefined,所以 typeof name === 'undefined' 时,console.log('Goodbye undefined')


第二题

var _fn = function () {
    console.log(1);
};
(function () {
    var _fn = function () {
        console.log(2);
    };
    var fn1 = function () {
        this._fn.apply(this);
    };
    var obj = {
        _fn: function () {
            console.log(3);
        },
        fn2: fn1.bind({
            _fn: function () {
                console.log(4);
            },
        }),
        fn3: fn1,
    };
    var fn4 = obj.fn3;
    var fn5 = obj.fn2;
    fn1();
    obj.fn2();
    obj.fn3();
    fn4();
    this.fn5();
})();

   答案

1

4

3

1

报错 this.fn5 not function


第三题

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
    };
}

   答案

为什么 alert 的值总是 6,因为 i 是贯穿整个作用域的,而不是给每个 li 分配一个 i 解决方案有很多,例如用 let 代替 var。或者是用 IIFES


参考资料


  • 揭秘 IIFE 语法[2]


[1] MDN: https://developer.mozilla.org/zh-CN/docs/Glossary/IIFE

[2] 揭秘 IIFE 语法: https://juejin.cn/post/6844903429735727

相关文章
|
13天前
|
JavaScript 前端开发 Java
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
本文介绍了JavaScript中常用的函数和方法,包括通用函数、Global对象函数以及数组相关函数。详细列出了每个函数的参数、返回值及使用说明,并提供了示例代码。文章强调了函数的学习应结合源码和实践,适合JavaScript初学者和进阶开发者参考。
25 2
[JS]同事:这次就算了,下班回去赶紧补补内置函数,再犯肯定被主管骂
|
11天前
|
前端开发 JavaScript 开发者
除了 Generator 函数,还有哪些 JavaScript 异步编程解决方案?
【10月更文挑战第30天】开发者可以根据具体的项目情况选择合适的方式来处理异步操作,以实现高效、可读和易于维护的代码。
|
26天前
|
JavaScript 前端开发
JavaScript 函数语法
JavaScript 函数是使用 `function` 关键词定义的代码块,可在调用时执行特定任务。函数可以无参或带参,参数用于传递值并在函数内部使用。函数调用可在事件触发时进行,如用户点击按钮。JavaScript 对大小写敏感,函数名和关键词必须严格匹配。示例中展示了如何通过不同参数调用函数以生成不同的输出。
|
28天前
|
存储 JavaScript 前端开发
JS函数提升 变量提升
【10月更文挑战第6天】函数提升和变量提升是 JavaScript 语言的重要特性,但它们也可能带来一些困惑和潜在的问题。通过深入理解和掌握它们的原理和表现,开发者可以更好地编写和维护 JavaScript 代码,避免因不了解这些机制而导致的错误和不一致。同时,不断提高对执行上下文等相关概念的认识,将有助于提升对 JavaScript 语言的整体理解和运用能力。
|
2月前
|
JavaScript 前端开发 安全
JavaScript函数详解
JavaScript函数的详细解析,包括函数的定义和调用方式(如一般格式、匿名函数、构造函数、自调用函数、箭头函数和严格模式)、函数参数(arguments对象、可变参数、默认参数值)、闭包的概念和应用实例。
JavaScript函数详解
|
1月前
|
JavaScript 前端开发
js教程——函数
js教程——函数
32 4
|
1月前
|
存储 JavaScript 前端开发
js中函数、方法、对象的区别
js中函数、方法、对象的区别
16 2
|
1月前
|
JavaScript 前端开发 Java
【javaScript数组,函数】的基础知识点
【javaScript数组,函数】的基础知识点
23 5
|
1月前
|
JavaScript 前端开发
Node.js 函数
10月更文挑战第5天
22 3
|
1月前
|
前端开发 JavaScript
探索JavaScript函数基础
探索JavaScript函数基础
18 3