!function(){)}() 是什么玩意儿?

264 阅读2分钟

1. 写法

立即执行函数的英文名字叫做: immediately-inovked-function expression, 简称 IIFE. 基本形式 ^^函数表达式 + () = IIFE^^ 所以出现了如下这些立即执行函数的方法:

(function(){alert('我是匿名函数')} ()) // 用括号把整个表达式包起来
(function(){alert('我是匿名函数')}) () // 用括号把函数包起来
!function(){alert('我是匿名函数')}() // 求反,我们不在意值是多少,只想通过语法检查。
+function(){alert('我是匿名函数')}()
-function(){alert('我是匿名函数')}()
~function(){alert('我是匿名函数')}()
void function(){alert('我是匿名函数')}()
new function(){alert('我是匿名函数')}()

我是这样子来认识这个问题的:

当JS引擎在解析代码的时候,碰到function就认为你是函数声明.但是找不到必要的函数名, 那就回出现挂载不到内存的情况.所以会直接报错,如function(){}()这个样子.

报错提示语: Function statements require a function name

而在匿名函数外面加上()之后,JS引擎将这玩意儿认知为一个整体. 直接在堆内存开辟一个空间放它.

同理, 匿名函数前面加上 ! + - ~就是让JS引擎将认为匿名函数().比如const a = 1 + function()(return 3)()中的a为4

越写越觉得不对. 这部分的理解可能需要我了解了JS引擎编译原理之后才能够解决的问题了. 这一段的疑惑暂时存在脑海中.

W3C推荐的写法: (function(){}()).

2. 有什么用

🚩: 创建私有作用域 就是避免变量污染.典型的场景如下:

  const LiDiv = ul.getElementsByTagName('li')
  for (var i = 0; i < 5; i++) {
    LiDiv[i].onclick = function() {
      console.log(i)
    }
  }

打印出都是5. for循环结束之后, 用户可以有进行操作的时候,i已经是5了.

而前面的LiDiv[i]. 是可以正确的渠道LiDiv中对应的div的.是因为它是一个同步任务. 而用户点击这种IO操作明显就是一个宏任务.

还有这么一个典型的考题:

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

执行结果就是,在一毫秒之后开始输入5,之后大概是每隔1000ms执行一次5.

上面的1000 * i能够执行预想的0ms, 1000ms, 2000ms, 3000ms, 4000ms.也是因为它在把setTimeout任务丢入宏任务队列的时候是同步任务. 这里涉及到了 事件循环的问题. 要解决上面的这两个问题很简单. 既然变量污染了,就让变量不污染.那么IIFE就是这个作用的.

  for(var i = 0; i < 5; i++){
    !function(j){
      liList[ii].onclick = function() {
        console.log(i) // 0、1、2、3、4
      }
    }(i)
  }
  for (var i = 0; i < 5; i++) {
        ;(function(i){
        setTimeout(() => {
          console.log(i)
        }, 1000 * i)
      })(i)
  }

3. 一道似是而非的题目

  ;(function fun() {
      fun = 2 
      console.log(fun)
  })()        

上面的输出是什么?

[Function fun]

原因:

  • 具名函数表达式的名称是不能修改的

  • 非严格模式会静默处理,严格模式会报错