立即調用函數
本篇文章,主要講解的立即執行函數或自執行匿名函數的含義、用法、以及使用它的主要場景。系列的前面幾篇文章主要講解了作用域、原型、執行上下文,本篇文章一樣起到了承上啓下的作用,如果您感興趣,不妨去看看哦~ 傳送門
目錄
一、瞭解立即調用函數表達式
1.1 思維導圖
1.2 什麼是立即調用?
在詳細瞭解這個之前,我們來談了解一下“自執行”這個叫法,本文對這個功能的叫法也不一定完全對,每個人對他的理解都不一樣,我們在這裏用立即調用
~
立即調用:
- 顧名思義,該表達式一被
創建就立即執行
。 - 是一個在定義時就會立即執行的
JavaScript 函數
。
(function (x) {
console.log('x + x = ', x + x);
})(5) // x + x = 10
這是一個被稱爲 自執行匿名函數
的設計模式,主要包含兩部分:
- 第一部分是包圍在 圓括號運算符
()
裏的一個匿名函數,這個匿名函數擁有獨立的詞法作用域。這不僅避免了外界訪問此 IIFE 中的變量,而且又不會污染全局作用域。 - 第二部分再一次使用
()
創建了一個立即執行函數表達式,JavaScript 引擎到此將直接執行函數。
1.3 核心問題
當你聲明一個函數的時候,通過在後面加括號就可以實現立即執行嗎?
var foo = function(){
console.log('餘光');
}(); // 餘光 成功了!
// ...是不是意味着後面加個括弧都可以自動執行?
function(){
console.log(''餘光);
}(); // Uncaught SyntaxError: Function statements require a function name
// 什麼?還需要一個函數名?不是叫 自執行匿名函數嗎?
// 我加上了函數名
function foo(){
console.log('餘光');
}(); // Uncaught SyntaxError: Unexpected token ')'
很顯然,例子中的第二條和第三條確實報錯了,而且報錯內容不一樣,那麼問題出現在哪呢?
二、立即調用函數表達式報錯了?
有時,我們定義函數之後,立即調用該函數,這時不能在函數的定義後面直接加圓括號,這會產生語法錯誤。產生語法錯誤的原因是,function
這個關鍵字,既可以當做語句,也可以當做表達式,比如下邊:
//語句
function fn() {};
//表達式
var fn = function (){};
爲了避免解析上的歧義,JS引擎規定,如果function出現在行首,一律解析成語句。因此JS引擎看到行首是function關鍵字以後,認爲這一段都是函數定義,不應該以括號結尾
,在它看來括號
只是分組操作符。
// 下面這個function在語法上是沒問題的,但是依然只是一個語句
// 加上括號()以後依然會報錯,因爲分組操作符需要包含表達式
function foo(){ /* code */ }(); // SyntaxError: Unexpected token )
// 但是如果你在括弧()裏傳入一個表達式,將不會有異常拋出
// 但是foo函數依然不會執行
function foo(){ /* code */ }( 1 );
// 因爲它完全等價於下面這個代碼,一個function聲明後面,又聲明瞭一個毫無關係的表達式:
function foo(){ /* code */ }
( 1 );
三、使用立即調用函數的正確姿勢
要解決上述問題,非常簡單。
我們只需要用大括弧
將代碼的代碼全部括住就行了,因爲JavaScript裏括弧()
裏面不能包含語句,所以在這一點上,解析器在解析function關鍵字的時候,會將相應的代碼解析成function表達式,而不是function聲明。
3.1 常見使用姿勢
// 下面2個括弧()都會立即執行
(function () { /* code */ } ()); // 推薦使用這個
(function () { /* code */ })(); // 但是這個也是可以用的
3.2 不常見的使用姿勢(一)
// 由於括弧()和JS的&&,異或,逗號等操作符是在函數表達式和函數聲明上消除歧義的
// 所以一旦解析器知道其中一個已經是表達式了,其它的也都默認爲表達式了
var i = function() {
console.log('餘光')
}(); // 餘光
true && function() {
console.log('餘光')
}(); // 餘光
0, function() { console.log('餘光') }(); // 餘光
3.3 不常見的使用姿勢(二)
// 如果你不在意返回值,或者不怕難以閱讀
// 你甚至可以在function前面加一元操作符號
//轉bool
var res1 = !function () {
console.log('餘光');
}()
console.log('res1:', res1); // 餘光 true
// 轉數字
var res2 = +function () {
console.log('餘光');
}()
console.log('res2:', res2); // 餘光 NaN
// 按位非
var res3 = ~function () {
console.log('餘光');
}()
console.log('res3:', res3); // 餘光 NaN
3.4 不常見的使用姿勢(三)
還有一個情況,使用new和void關鍵字,不過不太常見罷了。
void function() {
console.log('餘光');
}();
new function() {
console.log('餘光');
}();
四、常見使用場景
4.1 隔離作用域
IIFE最常見的功能,就是隔離作用域,在ES6之前JS原生也沒有塊級作用域的概念,所以需要函數作用域來模擬。
舉例:
var currentTime = (function () {
var time = new Date();
var year = time.getFullYear()
var month = time.getMonth()+1;
var date = time.getDate();
var hour = time.getHours();
var min = time.getMinutes();
return year + '-' + month + '-' + date + ' ' + hour + ':' + min;
})()
你仍然可以在其他地方聲明同名變量~
4.2 惰性函數
DOM事件添加中,爲了兼容現代瀏覽器和IE瀏覽器,我們需要對瀏覽器環境進行一次判斷:
var addEvent = (function(){
if(window.addEventListener) {
return function(type, el, fn) {
el.addEventListener(type, fn, false);
}
}
else if(window.attachEvent) {
return function(type, el, fn) {
el.attachEvent('on' + type, fn);
}
}
})();
4.3 用閉包保存狀態
這裏我僅舉個例子,爲我的下一篇文章——《JavaScript中的閉包》賣個關子
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);
}
注意
當函數變成立即執行的函數表達式時,表達式中的變量不能從外部訪問。
(function () {
var name = "Barry";
})();
// 無法從外部訪問變量 name
name // 拋出錯誤:"Uncaught ReferenceError: name is not defined"
將 IIFE 分配給一個變量,不是存儲 IIFE 本身,而是存儲 IIFE 執行後返回的結果。
var result = (function () {
var name = "Barry";
return name;
})();
// IIFE 執行後返回的結果:
result; // "Barry"
參考
寫在最後
JavaScript內功系列:
- this、call、apply詳解,系列(一)
- 從原型到原型鏈,系列(二)
- 從作用域到作用域鏈,系列(三)
- JavaScript中的執行上下文(四)
- JavaScript中的變量對象(五)
- 本文
- JavaScript中的閉包
關於我
- 花名:餘光
- WX:j565017805
- 沉迷JS,水平有限,虛心學習中
其他沉澱
如果您看到了最後,不妨收藏、點贊、評論一下吧!!!
持續更新,您的三連就是我最大的動力,虛心接受大佬們的批評和指點,共勉!