iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 16
0
Modern Web

【這些年我似是非懂的 Javascript】系列 第 16

【這些年我似是非懂的 Javascript】Day 16 - 函式的範疇

前一個章節有提到範疇泡泡,
那這些泡泡除了最外圈的全域變數外,
難道建立範疇泡泡只能透過函式嗎?

讓我們繼續看下去~


函式的範疇

在 JS 的世界中函式會為自己建立一個範疇泡泡,
ES6 以前除了函式外沒有任何結構可以建立他們自己的範疇泡泡。
那函式範疇所造成的影響是什麼?

所有的變數都是屬於函式內部的,
也就是說在全域範疇是無法使用的,如下面的範例。

function foo(){
    const a = 123;
    function boo(){
        const b = 321;
    }
    const c = 567;
}

foo(); // 可呼叫
console.log(boo()); // 呼叫失敗,拋出 ReferenceError
console.log(a,b,c); // 呼叫失敗,拋出 ReferenceError

隱身在普通範疇

使用函式範疇其實就有點像是把你的程式碼包裹起來,而當任何人使用該函式他就會被綁到新的包裹函式的範疇上,而不是之前他的範疇 (真繞口)。
這有什麼好處?
實際來看看

function foo(a){
    function boo(a){
        return a-1;
    }
    const b = a + boo(a*5);
    console.log(b);
}

foo(2); // 11

他可以將 b 以及 boo() 完全區隔開不受外界影響,讓私有的保持私有。
看起來很正常啊,如果不這麼做會發生什麼事
可以看一下下面的範例

function foo(a){
    b = a + boo(a*5);
    console.log(b);
}

function boo(a){
    return a-1;
}

var b;

foo(2); // 11

等等等等...
結果還是一樣啊!?
那感覺沒差吧?

才怪!

你這樣做的話 b 在全域範疇內,誰都有可能造成你無法預料的後果,萬一被存取加減一下,就會造成你內心原本預期的效果,這就可以應證上面所說的"讓私有的保持私有"的好處。

此外因為變數 b 和函式 boo() 被完全阻隔,也可以避免識別字的重複。

隱藏不等於沒污染

剛剛聽起來滿完美的,
但是其實就算是這樣還可能會造成範疇的污染,
例如以下範例

const a = 2;

function foo(){
    const a = 3;
    console.log(a);
}
foo(); // 用了 foo 這個識別字
console.log(a); // 2

foo 這個 function 明顯是要執行,
雖然也用隱藏的方式阻隔了 a 變數,
避免私有變數被存取,
但是本身的 foo 卻污染了該範疇,
那該怎麼做才好?
你可以透過 IIFE(即刻調用的函式運算式)

我怎麼記得我之前文章寫過 xDD
之後回頭再補上連結。

簡單來說他可以將函式透過() 包裹起來,變成一個運算式,並且在最尾段再加上 () 來執行該函式。
拿上面的例子改一下

const a = 2;

(function foo(){
    const a = 3;
    console.log(a);
})(); // 3 直接被調用
console.log(a); // 2
foo(); // ReferenceError 噴爆你

這就可以很明白的知道這樣連他本身都不會污染包含他的範疇了。

IIFE

剛剛在上面提到了 IIFE 書中也提了一些我不知道的事,
我覺得滿有趣的也跟你們分享,

形式

以前的 IIFE 形式某些人會使用以下的方式

(function (){
    console.log(17)
}()); // 17

我個人比較常看到和使用的是下面這種方式

(function (){
    console.log(17)
})(); // 17

這兩種的功能是一模一樣的,純粹只是個人偏好的風格不同。

誓死捍衛 undefined

你渴望爆炸嗎?

還記得之前提過 undefined 有可能會被當成變數被亂搞,當你使用 IIFE 就可以避免悲劇發生保證 undefined 是你認識的 undefined

undefined = true; // 弄爆你

(function foo(){
    let a;
    if (a === undefined){
        console.log('沒爆炸')
    }
})(); // 沒爆炸

傳入參數

他可以透過最後的 () 傳入參數,

(function (num){
    console.log(num)
})(123); // 123

反轉順序

要執行的函式不會先出現,而是在調用和傳入給他參數之後出現,很常被用在 UMD (Universal Module Definition) 專案中。 (完全沒聽過啊!我好菜 Orz)

var a = 2;

( function foo(func) {
  func(window);
})( function boo(global) {
  const a = 3;
  console.log(a); // 3
  console.log(global.a); // 2
}); 

簡單來說他就是透過參數的方式帶入整個 boo function,再傳入 windows 這全域變數作為 global 參數。

以上是今天的分享


啊... 明天已經是中秋連假的開始,
看來我要抱著筆電去台南玩了 QQ

越來越硬的感覺xDD

還記得開賽的前五天...

再看看現在的我...

然後今天準備要開始寫文章的時候發現,
已經有一批人已經完賽惹 Orz
在此敬佩那些完賽的鐵人們。

好啦~ 明天見~


參考來源:

你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))


上一篇
【這些年我似是非懂的 Javascript】Day 15 - 語彙範疇
下一篇
【這些年我似是非懂的 Javascript】Day 17 - 區塊的範疇
系列文
【這些年我似是非懂的 Javascript】34

尚未有邦友留言

立即登入留言