目 录CONTENT

文章目录

JS 立即执行函数(IIFE)

俊阳IT知识库
2023-02-15 / 0 评论 / 1 点赞 / 410 阅读 / 1,644 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-02-15,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。
广告

文章已同步至掘金:https://juejin.cn/post/6844903929633832974
欢迎访问😃,有任何问题都可留言评论哦~

在JS中,如果我们定义了一个函数如下:

function fn(){ /* code */ }

或者

let fn = function(){ /* code */ }

当我们在调用时,都需要在后面加上一对圆括号,像这样:fn()

正如上面所写的那样,fn相对于函数表达式function(){ /* code */ }只是引用的变量
那么我们可以可以这样写吗?

function(){ /* code */ }()

如果这样的话,是会报错的,因为当圆括号为了调用函数而出现在函数后面时,无论在全局环境或者局部环境里遇到了这样的function关键字。
默认的,他会将他当作是一个函数声明,而不是函数表达式,如果你不明确的告诉圆括号他是一个表达式,他会将其当作没有名字的函数声明并且抛出一个错误,因为函数声明需要一个名字。

那么如果我们加上函数名呢?

function fn(){ /* code */ }()

依然报错,因为这对圆括号和前面的声明语句没有任关系,而只是一个分组操作符,用来控制运算的优先级,这里的意思是小括号里面优先计算,所以上面代码等同于:

function fn(){ /* code */ }
()

立即执行函数(IIFE)

当我们声明了一个函数,可能不需要调用多次,并且可以只调用一次得到一个单一的值

显然上面的方法是不行的,那么怎么办呢?其实我们可以这样写:

(function () { /* code */ }());

或者这样

(function () { /* code */ })();

那么这两者又有什么区别呢?

其实这两者形式就是最开始写的那两种函数的形式:

  • 函数声明:function fn(){ /* code */ }
  • 函数表达式:let fn = function(){ /* code */ }

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

所以我们可以这样写立即执行函数

写法

(function () { /* code */ }());
(function () { /* code */ })();
let i = function(){ /* code */ }();
let j = (function(){ /* code */ }());
true && function () { /* code */ }();
0, function(){ /* code */ }();
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
new function(){ /* code */ };
new function(){ /* code */ }(); // 带参数

可以看到,在 function 前面加 ! 、+ 、- 甚至是逗号等都可以起到立即执行的效果,而 () 、! 、+ 、- 、= 等运算符,都将函数声明转换成函数表达式,消除了 javascript 引擎识别函数表达式和函数声明的歧义,告诉 javascript 引擎这是一个函数表达式,不是函数声明,可以在后面加括号,并立即执行函数的代码。

其实加括号是最安全的做法,因为 ! 、+ 、- 等运算符还会和函数的返回值进行运算,有时造成不必要的麻烦。

IIFE 与 闭包

说到立即执行函数的话,顺便扯一下闭包

和普通函数传参一样,立即执行函数也可以传递参数。如果在函数内部定一个函数,而里面的那个函数能引用外部的变量和参数(闭包),我们就能用立即执行函数锁定变量保存状态。

下面用代码来做测试:

<div>
    <ul>
        <li><a>第一个超链接</a></li>
        <li><a>第二个超链接</a></li>
    </ul>
</div>
    var elems = document.getElementsByTagName('a');
    for(var i=0; i<elems.length; i++) {
      elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am click Link ' + i);
      }, 'false')
    }

这段代码意图是点击第一个超链接提示“I am click Link 0”,点击第二个提示“I am click Link 1”。真的是这样吗? 不是,每一次都是“I am click Link 2”

为什么?因为i的值没有被锁住,当我们点击链接的时候其实for循环早已经执行完了,于是在点击的时候i的值已经是elems.length了。

修改代码:

var elems = document.getElementsByTagName('a');
    for(var i=0; i < elems.length; i++){
      (function (LockedInIndex) {
        elems[i].addEventListener('click', function (e) {
          e.preventDefault();
          alert('I am cliick Link ' + i);
        }, 'false')
      })(i)
    }

这次可以正确的输出结果,i的值被传给了LockedIndex,并且被锁定在内存中,尽管for循环之后i的值已经改变,但是立即执行函数内部的LockedIndex的值并不会改变。

当然也可以这样写:

var elems = document.getElementsByTagName('a');
    for ( var i = 0; i < elems.length; i++ ) {
      elems[ i ].addEventListener( 'click', (function( lockedInIndex ){
        return function(e){
          e.preventDefault();
          alert( 'I am link ' + lockedInIndex );
        };
      })( i ), 'false' );
    }

最好的方法就是用let,如下:

var elems = document.getElementsByTagName('a');
    for(let i=0; i<elems.length; i++) {
      elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am click Link ' + i);
      }, 'false')
    }

关于let具体的使用,请参考我的文章:
《JavaScript 变量的使用》
《ES6 新增内容总结》

模块模式

立即执行函数在模块化的时候也有用,用立即执行函数处理模块可以减少全局变量造成的空间污染,而是使用私有变量。

如下创建一个立即执行的匿名函数,该函数返回一个对象,包含要暴露给外部的属性i,如果不实用立即执行函数就要多定义一个属性i了,这个i就会显示的暴露给外部,这样:counter.i,这种方式明显不太安全。

var counter = (function(){
    var i = 0;
    return {
        get: function(){
            return i;
        },
        set: function(val){
            i = val;
        },
        increment: function(){
            return ++i;
        }
    }
    }());
    counter.get();//0
    counter.set(3);
    counter.increment();//4
    counter.increment();//5

    conuter.i;//undefined (`i` is not a property of the returned object)
    i;//ReferenceError: i is not defined (it only exists inside the closure)

这里如果使用counter.i来访问这个内部变量,会报错undefined,因为i并不是counter的属性。

模块模式方法不仅相当的厉害而且简单。非常少的代码,你可以有效的利用与方法和属性相关的命名,在一个对象里,组织全部的模块代码,即最小化了全局变量的污染也创造了使用变量。

+_+

1

评论区