JS中的 当即执行函数(IIFE)

在JS中,若是咱们定义了一个函数以下:javascript

function fn(){ /* code */ }
复制代码

或者java

let fn = function(){ /* code */ }
复制代码

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

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

function(){ /* code */ }()
复制代码

若是这样的话,是会报错的,由于当圆括号为了调用函数而出如今函数后面时,不管在全局环境或者局部环境里遇到了这样的function关键字。
默认的,他会将他看成是一个函数声明,而不是函数表达式,若是你不明确的告诉圆括号他是一个表达式,他会将其看成没有名字的函数声明而且抛出一个错误,由于函数声明须要一个名字。闭包

那么若是咱们加上函数名呢?ide

function fn(){ /* code */ }()
复制代码

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

function fn(){ /* code */ }
()
复制代码

当即执行函数(IIFE)

当咱们声明了一个函数,可能不须要调用屡次,而且能够只调用一次获得一个单一的值函数

显然上面的方法是不行的,那么怎么办呢?其实咱们能够这样写:post

(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的属性。

模块模式方法不只至关的厉害并且简单。很是少的代码,你能够有效的利用与方法和属性相关的命名,在一个对象里,组织所有的模块代码,即最小化了全局变量的污染也创造了使用变量。

+_+

相关文章
相关标签/搜索