「前端每日一问(22)」说一下你对 JS 立即执行函数(IIFE)的理解

816 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

本题难度:⭐ ⭐

答:立即执行函数可以立即执行一段代码逻辑,一般一些独立的代码逻辑都会封装成一个函数,如果不想先封装,再调用,不想起函数名,就可以使用立即执行函数来写。

立即执行函数的写法

有很多稀奇古怪的写法,这里只罗列四种,其实一般用的都是前两种:

// 第一种,用一个括号把自执行函数整体包起来
;(function sayName () {
  console.log('阿林')
}())
// 第二种,和第一种是类似的,这种写法用得比较多
;(function sayName () {
  console.log('阿林')
})()
// 第三种,函数表达式立即执行
const sayName = function(){
  console.log('阿林')
}()
// 第四种,函数表达式作为一元操作符的参数立即调用
!function(){
  console.log('阿林')
}()

+function(){
  console.log('阿林')
}()

-function(){
  console.log('阿林')
}()

~function(){
  console.log('阿林')
}()

第四种方式会和函数返回值进行运算,且代码不易懂,不推荐使用。

你可能注意到了立即执行函数前面加了个 ;,为啥要这么写呢?

a = b + c
;(function() {  // 故意将分号放在这里,防止语句被理解为对函数c的调用
  // 代码
})();

立即执行函数的一些用途

立即执行一些代码逻辑

一般一些独立的代码逻辑都会封装成一个函数,如果不想先封装,再调用,不想起函数名,就可以使用立即执行函数来写。

比如:

兼容 ie 浏览器

这种系统只需要判断一次的逻辑,不需要复用,就可以用自执行函数来写

;(function () {
    var userAgent = navigator.userAgent //取得浏览器的userAgent字符串

    var isIe =
      (userAgent.indexOf('compatible') > -1 &&
        userAgent.indexOf('MSIE') > -1) ||
      (userAgent.indexOf('Trident') > -1 &&
        userAgent.indexOf('rv:11.0') > -1)
    if (isIe) {
      window.location.href = 'IEBack.html'
    }
  })()

给系统注入一些三方功能模块

比如,阿里的性能监控系统 arms 给出的引用示例代码, 就是用的立即执行函数。

!(function (c, b, d, a) {
    c[a] || (c[a] = {})
    c[a].config = {
      pid: 'xxx',
      appType: 'web',
      imgUrl: 'https://arms-retcode.aliyuncs.com/r.png?',
      sendResource: true,
      enableLinkTrace: true,
      behavior: true,
      enableSPA: true
    }
    with (b)
      with (body)
        with (insertBefore(createElement('script'), firstChild))
          setAttribute('crossorigin', '', (src = d))
  })(window, document, 'https://retcode.alicdn.com/retcode/bl.js', '__bl')

模拟块级作用域,创建函数的私有变量

其实这是闭包的功能,但是可以用立即执行函数少写一点代码。

ES6 之前,JS 没有块级作用域,要想把某些变量隔离,可以使用闭包来实现。

var fn = function () {
  var i = 0

  return { // fn 函数返回一个对象,有 get、set 等方法,来操作私有变量 i
    get: function () {
      return i
    },
    set: function (val) {
      i = val
    },
    increment: function () {
      return ++i
    }
  }
}

const counter = fn() // 这里执行 fn 函数,拿到 counter 对象
counter.get() // 0
counter.set(3)
counter.increment() // 4
counter.increment() // 5

如果我们试图从全局作用域直接访问 counter.i ,会得到 undefined,因为 i 是定义在函数 fn 作用域内的变量,它并不是 counter的属性。同样的,如果我们试图访问 i 也会收到错误,因为 i 并没有在全局作用域中定义。

既然这个函数 fn 不是为了复用,只是为了在自己作用域里创建一些私有变量,就只执行一次就好了,不需要专门封装,也不需要特别地给他起一个函数名,就用立即执行函数就行。

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

网络上很多文章把立即执行函数和闭包混淆在一起,从上面的例子可以看出,不使用立即执行函数,也能实现闭包的功能。

所以说,模拟块级作用域,创建函数的私有变量这个是闭包的使用场景,和立即执行函数半毛钱关系没有,立即执行函数做的事情就是让函数可以立即执行,让代码少写一点而已。

结尾

如果我的文章对你有帮助,你的👍就是对我的最大支持^_^

我是阿林,输出洞见技术,再会!

上一篇:

「前端每日一问(21)」说一下 var、let 和 const 的区别

下一篇:

「前端每日一问(23)」实现一个函数,每调用一次,函数输出值 +1