この記事では主に、JS の即時実行関数 (Immediately-Invoked Function Expression) について説明します。これは、関数式の後に () を追加することで関数を自己実行させることができることを意味します。注: 関数宣言の後に括弧 () を追加することはできません。
(function foo(){/* code */}()); //正确,推荐这样使用 (function foo(){/* code */})(); //正确,这样可以 var foo = function foo(){/* code */}(); //正确 function foo(){/* code */}(); //SyntaxError: Unexpected token ( // 但是如果你在括弧()里传入一个表达式,将不会有异常抛出 // 但是foo函数依然不会执行 function foo(){ /* code */ }( 1 ); // 因为它完全等价于下面这个代码,一个function声明后面,又声明了一个毫无关系的表达式: function foo(){ /* code */ } ( 1 ); // 由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的 // 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了 // 不过,请注意下一章节的内容解释 var i = function () { return 10; } (); true && function () { /* code */ } (); 0, function () { /* code */ } (); // 如果你不在意返回值,或者不怕难以阅读 // 你甚至可以在function前面加一元操作符号 !function () { /* code */ } (); ~function () { /* code */ } (); -function () { /* code */ } (); +function () { /* code */ } (); // 还有一个情况,使用new关键字,也可以用,但我不确定它的效率 // http://twitter.com/kuvos/status/18209252090847232 new function () { /* code */ } new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()
IIFE はローカル変数のカプセル化されたスコープを提供でき、IIFE が返す関数でアクセスできます。このアプローチにより、関数が IIFE の字句範囲外で実行される場合でも、関数はこのローカル変数にアクセスできるようになります。 const uniqueId1 = function() {
let count1 = 0;
return function() {
++count1;
return count1;
};
};
uniqueId1(); //ƒ () {++count1;return count1;}
const uniqueId2 = function() {
let count = 0;
return count;
};
uniqueId2(); //0
uniqueId2(); //0
uniqueId2(); //0
const uniqueId3 = (function() {
let count3 = 0;
return function() {
++count3;
return count3;
};
})();
uniqueId3(); //1
uniqueId3(); //2
uniqueId3(); //3
uniqueId3(); //4
const uniqueId4 = function() {
let count4 = 0;
return (function() {
++count4;
return count4;
})();
};
uniqueId4(); //1
uniqueId4(); //1
uniqueId4(); //1
uniqueId4(); //1
カウント
。この変数は、IIEF から返される関数以外は読み書きできません。これにより、制御された方法でのみ変更できる真のプライベート状態が作成されます。 count
。除了从IIEF中返回的函数,别人无法读写该变量。这样就能创建真正的私有状态,它只能以受控的方式进行修改。
Module模式非常依赖IIFE这种机制。当使用IIFE来返回一个”封闭”一些本地变量来管理私有数据的函数时,let
和const
モジュール モードは IIFE メカニズムに大きく依存しています。 IIFE を使用して、プライベート データを管理するためにいくつかのローカル変数を「囲む」関数を返す場合、 let
const
// 创建一个立即调用的匿名函数表达式
// return一个变量,其中这个变量里包含你要暴露的东西
// 返回的这个变量将赋值给counter,而不是外面声明的function自身
var counter = (function () {
var i = 0;
return {
get: function () {
return i;
},
set: function (val) {
i = val;
},
increment: function () {
return ++i;
}
};
} ());
// counter是一个带有多个属性的对象,上面的代码对于属性的体现其实是方法
counter.get(); // 0
counter.set(3);
counter.increment(); // 4
counter.increment(); // 5
counter.i; // undefined 因为i不是返回对象的属性
i; // 引用错误: i 没有定义(因为i只存在于闭包)
通常の関数の実行時にパラメータを渡すのと同じように、自己実行関数式でもこの方法でパラメータを渡すことができます。これは、クロージャが渡されたパラメータを直接参照し、これらを使用できるためです。パラメータをロックされた受信パラメータに変更すると、自己実行関数式が効果的に状態を保存できます。
// 这个代码是错误的,因为变量i从来就没背locked住 // 相反,当循环执行以后,我们在点击的时候i才获得数值 // 因为这个时候i操真正获得值 // 所以说无论点击那个连接,最终显示的都是I am link #10(如果有10个a元素的话) 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'); } // 这个是可以用的,因为他在自执行函数表达式闭包内部 // i的值作为locked的索引存在,在循环执行结束以后,尽管最后i的值变成了a元素总数(例如10) // 但闭包内部的lockedInIndex值是没有改变,因为他已经执行完毕了 // 所以当点击连接的时候,结果是正确的 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); } // 你也可以像下面这样应用,在处理函数那里使用自执行函数表达式 // 而不是在addEventListener外部 // 但是相对来说,上面的代码更具可读性 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'); }
for (var i = 0; i < 5; i++) { setTimeout(function(i) { console.log(i); }, i * 1000); }
for (var i = 0; i < 5; i++) { setTimeout((function(i) { console.log(i); })(i), i * 1000); }
for (var i = 0; i < 5; i++) { setTimeout((function(i) { return function() { console.log(i); } })(i), i * 1000); }
for (var i = 0; i < 5; i++) { setTimeout((function(i) { console.log(i); }).bind(this,i), i * 1000); }
var j = 0; for (i = 0; i < 5; i++) { setTimeout(function() { console.log(j); j++; }, i * 1000); }
window.$ = function somethingElse() { // ... }; (function($) { // ... })(jQuery);
为了解决命名冲突问题,可以将一段代码封装在一个IIEF中,将一个全局变量(比如,jQuery)作为参数传入IIFE。在函数内部,就可以以一个任意的参数名(比如,$)来访问该参数值。
IIFE的这种特性可以用来优化代码,这种方式使代码能够被更有效的压缩。例如:
(function(window, document, undefined) { // ... })(window, document);
可以缩短函数的参数名为单个字母的标识符,更短标识符名会使文件的体积变得更小。
(function(w, d, u) { // ... })(window, document);
2、括号和分号的使用
我们知道,以下两种方法都是立即执行函数的写法:
// 下面2个括弧()都会立即执行 (function () { /* code */ } ()); (function () { /* code */ })();
注意这两种写法:匿名函数上面的写法都存在前后文;问题,所以需要注意的是匿名函数在压缩工具打包压缩后会出现上下文错误合并()的问题,例如第二种写法。如果下面的代码,未压缩之前是正常的,压缩后就不正常了,所以要严格上下文的;问题,而第一种就不会出现类似问题。
var a="bbb" (function(){ alert(1); })();
//var a = function(){} var a="bbb" (function(){ alert(1); }());
上述代码会报""bbb" is not a function"错误,若变量a是一函数,则会报"undefined is not a function",这是因为a变量或a函数会把他后面的匿名函数作为参数传入a中,这就很好的解释了为什么报前面提到的错误,这也很好地解释了为什么有人习惯在匿名函数之前添加;了,就是为了防止上文没有严格遵循javascript语法,漏掉;的问题。
相关推荐:
以上がJSでの即時実行関数の説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。