FENote— JS 的IIFE 相關

Chien Wei Luo
Aug 25, 2017 · 7 min read

我認為知識點之間是環環相扣的, 所以用 '相關' 這兩個字是因為我懶極了

附上淡水很不錯的一家咖啡廳, 我真的很喜歡這個司康

前言:

js 作為相較隨意的語言, 有些特殊寫法不去查真的看不懂, 最基礎的地方也常常是我debug最久的地方(笑), 雖然有些寫法怪異了點, 但存在必然是有它的道理, 理解它們的功能也可以對js 特性有更進一步的熟練

作為語言中最重要也是最常用的function, 必須清楚的釐清, 所以這篇作為筆記記錄下函式的相關應用


基本函數的種類

  1. 函式聲明: function add(a,b){ return a+b }
  2. 表達式: var add_it = function add(a,b){ return a+b }
  3. 匿名函式: function(a,b){ return a+b }

提一下 3.的最常的用法是

var add = function(a,b){return a+b}

這個寫法是表達式的一種, 其實只是把add 變數跟一個沒有名字(匿名)的 function 結合而已, 但這個特性讓寫法具有彈性

這邊要提到的是函式聲明不管在哪裡都會被提升到頂端 (hoisting), 這個觀念大概是這樣的, 在寫完程式碼給瀏覽器解讀的時候, 會經過一道給引擎compile的程序, 瀏覽器會將程式碼 ”做” 成它的規格

這裡先不細講這件事, 但有了大概的觀念之後呢, 看看下面的程式碼

add(3,5); //8 it worksfunction add(a,b){
return a + b;
}

如果照寫程式的順序來看, 先調用這個add 應該要是錯的, 但hoisting就幫我們做了這件事

But, 如果是表達式的話, 就會報錯

add(3,5); //errorvar add = function(a,b){
return a+b
}

立即調用函式表達式(IIFE)

function(){    ...})()
//這是我比較習慣用的, 也有另外一種但只是括號不同而已就不列出, 其實用處是相同的

如果有參數的話, 後方括號的參數為傳入給第一個括號的值

(function(name, age){return '我叫'+name+ ', 我' + age +'歲'})('Kris', 666)

IIFE算是匿名函式的應用:

例如:(function(){
alert('HELLO')
})()
可以用解釋成兩個步驟1.說哈囉的這個函式;
2.執行它
而原本具有名稱的時候var sayHello = function(){
alert('HELLO')
};
sayHello();
1. 賦值給sayHello變數
2. 執行sayHello

def: 此函式在定義時立即執行, 且這個函數是匿名的, 將運算過程包起來.

  • 因為都是區域變數, 所以可以避免留下全域變數導致混淆, 多人協作的時候尤其明顯
  • 函數執行完就啟動垃圾回收機制, 也代表生命週期結束, 省下一些空間

而為什麼會有這種寫法呢 ?

因為要在函式體後加括號作為立即調用的話, 這個函數必須是表達式

如果是函式聲明像這樣立即調用的話,function(){
console.log('HELLO')
}()
報錯原因: 編譯時遇上function 關鍵字, 引擎會默認這是函式聲明,而函數聲明是需要名字的所以拋出了語法錯誤除此之外, 這也不是表達式---
那如果加個名字...
function hello(){
alert(' Hello World ')
}()
又報錯原因: 引擎會解析成//第一部分: 聲明 function hello(){ alert(‘Hello World’); }//第二部分: () 出現錯誤, 會解讀成不相干的表達式---而立即調用更快的方法就是用()將function包起來, 讓引擎解讀為表達式, 再調用(function(){...})()

其實+-~!運算符甚至是 new 都可以作為宣告立即函式且避免報錯的方法, 但有時運算符可能會和返回值進行非預期的運算, 也較難理解語意, 加括號是最安全的做法

以括號來說, 有點像我們平時運算 (a + b) * c 會用小括號先把 a + b 算出來

也算是一種既定的規則了, 因為大家都這麼用嘛 (?)


延伸(看看就好):

也是因為JS中沒有Private的概念, 全域變量可能會被覆蓋, 導致無法正常運作, 所以根據作用域鍊的特性, 使用這種技術可以模擬一個Private, 內部可以訪問外部的變量, 而外部環境不能訪問內部的, 藉此減少變量的衝突, 有時候也稱作 閉包(closure), 有權訪問另一個作用域中變量的函數

閉包:

// 它的运行原理可能并不像你想的那样,因为`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);
}
註: 這段應用來自[译] JavaScript:立即执行函数表达式(IIFE)

模塊模式:

var a = (function(){
var name = 'Kris'
var age = 666
return{
sayName: function(){
console.log('我叫:' + name)},
howOld: function(){
console.log('我幾歲?' + age)},
yearLater: function(){
age = age +1
console.log('我到底幾歲?' + age)},
}
})();
a.sayName(); //我叫:Kris
a.howOld();//我幾歲?666
a.yearLater();//我到底幾歲?667
a.howOld();//我幾歲?667
a.name; //undefined

相關連結:

  • 閉包closure
  • 模塊(Module)模式
  • 提升(hoisting)

本文參考資料:

(译)详解javascript立即执行函数表达式(IIFE)

[译] JavaScript:立即执行函数表达式(IIFE)

兩篇好像都是翻譯自 原文:Immediately-Invoked Function Expression (IIFE)

)
Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade