马上实行函数表达式(IIFE)

原文:Immediately-Invoked Function Expression (IIFE) by Ben Alman
原译:马上实行函数 by Murphywuwu
改增内容: by blanu

或许你没有注重到,我是一个关于专业术语有一点强迫症的人。所以,当我屡次听到盛行却易产生误解的术语「自实行匿名函数」,我终究决议将我的主意写进这篇文章里。

更进一步地说,除了供应关于该形式究竟是如何事情的周全信息,事实上我还发起了我们应当如何称谓这类形式。别的,假如你想跳过这里,你能够直接跳到马上挪用函数表达式举行浏览,然则我发起你读完整篇文章。

请明白这篇文章不是想说「我对了,你错了」。我发自真心肠想协助人们明白看似庞杂的观点,而且我认为运用前后一致的准确术语是有助于人们明白的最简朴的体式格局之一。

它是什么

在JavaScript里,每一个函数,当被挪用时,都邑建立一个新的实行上下文。由于在函数里定义的变量和函数只能在函数内部被接见,外部没法猎取;当挪用函数时,函数供应的上下文就供应了一个异常简朴的要领建立私有变量。

//由于这个函数的返回值是另一个能接见私有变量i的函数,因而返回的函数实际上被提权(privileged)了
function makeCounter() {
    //i只能从`makeConuter`内部接见
    var i = 0;
    return function(){
        console.log(++i);
    };   
}
//记着:`counter`和`counter2`都有他们自身作用域中的变量 `i`
var counter = makeCounter();
counter();//1
counter();//2

var counter2 = makeCounter();
counter2();//1
counter2();//2

i;//ReferenceError: i is not defined(它只存在于makeCounter里)

在很多状况下,你能够并不须要makeWhatever如许的函数返回屡次累加值,而且能够只挪用一次获得一个单一的值,在其他一些状况里,你以至不须要明白的晓得返回值。

它的中心

如今,不管你定义一个函数像如许function foo(){}或许var foo = function(){},挪用时,你都须要在背面加上一对圆括号,像如许foo()

//向下面如许定义的函数能够经由过程在函数名后加一对括号举行挪用,像如许`foo()`,由于foo相干于函数表达式`function(){/* code */}`只是一个援用变量

var foo = function(){/* code */}
//那这能够申明函数表达式能够经由过程在厥后加上一对括号自身挪用自身吗?
function(){ /* code */}();//SyntaxError: Unexpected token (

正如你所看到的,这里捕捉了一个毛病。当圆括号为了挪用函数出如今函数背面时,不管在全局环境或许部分环境里碰到了如许的function关键字,默许的,它会将它看成是一个函数声明,而不是函数表达式,假如你不明白的通知圆括号它是一个表达式,它会将其看成没有名字的函数声明而且抛出一个毛病,由于函数声明须要一个名字。
题目1:这里我么能够思索一个题目,我们是不是是也能够像如许直接挪用函数var foo = function(){console.log(1)}(),答案是能够的。
题目2:一样的,我们还能够思索一个题目,像如许的函数声明在背面加上圆括号被直接挪用,又会涌现什么状况呢?请看下面的解答。

题外话:函数、圆括号和毛病

风趣的是,假如你为一个函数指定一个名字并在它背面放一对圆括号,一样的也会抛出毛病,但这次是由于别的一个缘由。当圆括号放在一个函数表达式背面指清晰明了这是一个被挪用的函数,而圆括号放在一个声明背面便意味着完整的和前面的函数声明分开了,此时圆括号只是一个简朴的代表一个括号(用来掌握运算优先的括号)。

//但是函数声明语法上是无效的,它仍然是一个声明,紧随着的圆括号是无效的,由于圆括号里须要包括表达式
function foo(){ /* code */ }();//SyntaxError: Unexpected token
//如今,你把一个表达式放在圆括号里,没有抛出毛病...然则函数也并没有实行,由于:
function foo(){/* code */}(1)
//它等同于以下,一个函数声明随着一个完整没有关联的表达式:
function foo(){/* code */}
(1);

关于这个细节,你能够浏览Dmitry A. Soshnikov的文章:ECMA-262-3 in detail. Chapter 5. Functions 中文版本

马上实行函数表达式(IIFE)

荣幸的是,修改语法毛病很简朴。最盛行的也最被接收的要领是将函数声明包裹在圆括号里来通知语法分析器去表达一个函数表达式,由于在JavaScript里,圆括号不能包括声明。由于这点,当圆括号为了包裹函数碰上了 function关键词,它便晓得将它作为一个函数表达式去剖析而不是函数声明。注重明白这里的圆括号和上面的圆括号碰到函数时的表现是不一样的,也就是说。

  • 当圆括号出如今匿名函数的末端想要挪用函数时,它会默许将函数当做是函数声明。

  • 当圆括号包裹函数时,它会默许将函数作为表达式去剖析,而不是函数声明。

//这两种形式都能够被用来马上挪用一个函数表达式,应用函数的实行来制造私有变量
(function(){/* code */}());//Crockford recommends this one
(function(){/* code */})();//But this one works just as well

// 由于括号的作用就是为了消弭函数表达式和函数声明之间的差别
// 假如诠释器能预料到这是一个表达式,括号能够被省略
// 不过请拜见下面的「主要笔记」
var i = function(){return 10;}();
true && function(){/*code*/}();
0,function(){}();

//假如你并不体贴返回值,或许让你的代码尽量的易读,你能够经由过程在你的函数前面带上一个一元操作符来存储字节
!function(){/* code */}();
~function(){/* code */}();
-function(){/* code */}();
+function(){/* code */}();

// 这里是别的一种要领
// 我(原文作者)不清晰new要领是不是会影响机能
// 但它倒是见效,拜见http://twitter.com/kuvos/status/18209252090847232

new function(){ /* code */ }
new function(){ /* code */ }() // 只有当传入参数时才须要加括号

关于括号的主要笔记

在一些状况下,当分外的带着歧义的括号围绕在函数表达式四周是没有必要的(由于这时候的括号已将其作为一个表达式去表达),但当括号用于挪用函数表达式时,这仍然是一个好主意。

如许的括号指明函数表达式将会被马上挪用,而且变量将会贮存函数的效果,而不是函数自身。当这是一个异常长的函数表达式时,这能够勤俭其他人浏览你代码的时候,不必滚到页面底部去看这个函数是不是被挪用。

作为划定规矩,当你誊写清晰明了的代码时,有必要阻挠JavaScript抛出毛病的,一样也有必要阻挠其他开发者对你抛出毛病WTFError!

保留闭包的状况

就像当函数经由过程他们的名字被挪用时,参数会被通报,而当函数表达式被马上挪用时,参数也会被通报。一个马上挪用的函数表达式能够用来锁定值而且有效的保留此时的状况,由于任何定义在一个函数内的函数都能够运用表面函数通报进来的参数和变量(这类关联被叫做闭包)。

关于闭包的更多信息,拜见 Closures explained with JavaScript

//它的运转道理能够并不像你想的那样,由于`i`的值从来没有被锁定。相反的,每一个链接,当被点击时(轮回已被很好的实行终了),因而会弹出一切元素的总数,由于这是`i`此时的实在值。
var elems = document.getElementsByTagName('a');
for(var i = 0;i < elems.length; i++ ) {
    elems[i].addEventListener('click',function(e){
        e.preventDefault();
        alert('I am link #' + i)
        },false);
}
//而像下面如许改写,便能够了,由于在IIFE里,`i`值被锁定在了`lockedInIndex`里。在轮回完毕实行时,只管`i`值的数值是一切元素的总和,但每一次函数表达式被挪用时,IIFE里的`lockedInIndex`值都是`i`传给它的值,所以当链接被点击时,准确的值被弹出。
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 link #' + lockedInIndex);
            },false)
    })(i);
}
//你一样能够像下面如许运用IIFE,仅仅只用括号包裹点击处置惩罚函数,并不包括悉数`addEventListener`。不管用哪一种体式格局,这两个例子都能够用IIFE将值锁定,不过我发明前面一个例子更可读
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);
    }

记着,在这末了两个例子里,lockedInIndex能够没有任何题目的接见i,然则作为函数的参数运用一个差别的定名标识符能够使观点越发轻易的被诠释。

马上实行函数一个最明显的上风是就算它没有定名或许说是匿名,函数表达式也能够在没有运用标识符的状况下被马上挪用,一个闭包也能够在没有当前变量污染的状况下被运用。

「自实行匿名函数(Self-executing anonymous function)」有什么题目呢?

你看到它已被提到好几次了,但它仍未被清晰地诠释,我发起将术语改成“Immediately-Invoked Function Expression”,或许,IIFE,假如你喜好缩写的话(发音相似“iffy”)。

什么是Immediately-Invoked Function Expression呢?望文生义,它就是一个被马上挪用的函数表达式。

我想JavaScript社区的成员应当能够在他们的文章里或许陈说里接收术语Immediately-Invoked Function ExpressionIIFE,由于我觉得如许更轻易让这个观点被明白,而且术语”self-executing anonymous function”真的也不够准确。

//下面是个自实行函数,递归的挪用自身自身
function foo(){foo();};
//这是一个自实行匿名函数。由于它没有标识符,它必需是运用`arguments.callee`属性来挪用它自身
var foo = function(){arguments.callee();};
//这或许算是一个自实行匿名函数,然则仅仅当`foo`标识符作为它的援用时,假如你将它换成用`foo`来挪用一样可行
var foo = function(){foo();};
//有些人像如许叫'self-executing anonymous function'下面的函数,纵然它不是自实行的,由于它并没有挪用它自身。然后,它只是被马上挪用了罢了。
(function(){ /*code*/ }());
//为函数表达式增添标识符(也就是说制造一个定名函数)对我们的调试会有很大协助。一旦定名,函数将不再匿名。
(function foo(){/* code */}());
//IIFEs一样也能够自实行,只管,或许他不是最有效的形式
(function(){arguments.callee();}())
(function foo(){foo();}())
// 别的,下面这个表达式竟会在黑莓5上抛出毛病,在一个被定名的函数中,该函数名是undefined。很巧妙吧…
(function foo(){ foo(); }());

愿望上面的例子能够让你越发清晰的晓得术语’self-executing’是有一些误导的,由于他并非实行自身的函数,只管函数已被实行。一样的,匿名函数也没用必要迥殊指出,由于,Immediately Invoked Function Expression,既能够是定名函数也能够匿名函数。

风趣的是:由于arguments.callee在ECMAScript 5 strict mode中被deprecated了,所以在ES5的strict mode中实际上不能够建立一个self-executing anonymous function

末了:模块形式

当我挪用函数表达式时,假如我不最少一次的提示我自身关于模块形式,我便很能够会疏忽它。假如你并不熟习JavaScript里的模块形式,它和我第一个例子很像,然则返回值用对象替代了函数。

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)

模块形式要领不仅相称的凶猛而且简朴。异常少的代码,你能够有效的应用与要领和属性相干的定名,在一个对象里,构造悉数的模块代码即最小化了全局变量的污染也制造了私家变量。

延长浏览

愿望这篇文章能够为你答疑解惑。固然,假如你产生了更多迷惑,你能够浏览下面这些关于函数和模块形式的文章。

    原文作者:blanu
    原文地址: https://segmentfault.com/a/1190000007569312
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞