iT邦幫忙

0

Javascript書本中關於'閉包'的問題

在"Javascript學習手冊"中,P.127,有個範例不大懂怎麼運作的,程式如下:

const f = (function(){
    let count = 0;
    return function(){
        return `I have been called ${++count} time(s).`;
    }
})();
f();  //I have been called 1 time(s).
f();  //I have been called 2 time(s).

想問的是:
Q.為什麼第二次呼叫時.會得到結果"I have been called 2 time(s)."
而不是"I have been called 1 time(s)."
我的理解是,第二次呼叫後,在let count = 0,不是就重0開始了嗎?怎麼會跳過這行呢?

因為小弟剛學習JavaScript,請各路好手多多見諒...

6
黃彥儒
iT邦好手 1 級 ‧ 2019-06-23 11:50:18
最佳解答

因為閉包……
實際上你第一個f();跑完之後,這個f就已經是一個物件,內容是

function(){
    return `I have been called ${++count} time(s).`;
}

往後的程式中,count會變成只有f這個物件能存取的私有變數,因為已經建立,所以並不會再被建立一次。

O口O iT邦新手 5 級 ‧ 2019-06-23 17:03:22 檢舉

ok,好像有點懂了,感謝您的認真回覆.

1
小魚
iT邦高手 1 級 ‧ 2019-06-23 12:01:28

比較一下

const f = (function(){
    let count = 0;
    return function(){
        return `I have been called ${++count} time(s).`;
    }
})();
function g ()
{
    let count = 0;
	return `I have been called ${++count} time(s).`;
}
console.log(f());
console.log(f());
console.log(g());
console.log(g());
dragonH iT邦研究生 3 級 ‧ 2019-06-23 12:06:02 檢舉

他 care 的點應該是

同樣都有 let count = 0;

為什麼閉包不會每次都執行吧XD

小魚 iT邦高手 1 級 ‧ 2019-06-23 14:26:54 檢舉

是啊,
這兩個執行起來的結果就會不一樣.

13
marlin12
iT邦新手 3 級 ‧ 2019-06-23 19:51:23

(function () { ... })(); 是一個[自調用函數],是會即時執行的。

[自調用函數]做了2個動作:-

  1. 建立一個全局變量count,並且預設為0。 ([全局變量]是相對於返回的函數而言)
  2. 返回 function() { return 'I have been called ${++count} time(s).'; }; 這個函數。

const f = (function () { ... })(); 就是把[自調用函數]的[返回值],給予常數f。效果相等於 const f = function() { return 'I have been called ${++count} time(s).'; };

因此,這個範例的效果,就相等於

let count = 0;
const f = function() { return `I have been called ${++count} time(s).`; };
f();  //I have been called 1 time(s).
f();  //I have been called 2 time(s).
看更多先前的回應...收起先前的回應...

這才是正解

thwu iT邦新手 5 級 ‧ 2019-06-25 09:32:33 檢舉

感謝詳細的解說,這樣很清楚整段程式的運作方式

fillano iT邦超人 1 級 ‧ 2019-06-26 09:36:20 檢舉

global在Javascript中有特定含意,可以的話還是不要把local的變數因為在內層函數的scope chain中,就叫他global變數比較好。雖然意思都明白XD

黃彥儒 iT邦好手 1 級 ‧ 2019-06-30 10:33:13 檢舉

因为我不会Jvascript,所以这提是短暂的Google后用Python的概念回应的。
还是大大回答的比较详尽,可惜回的太晚XD。

marlin12 iT邦新手 3 級 ‧ 2019-07-13 15:34:53 檢舉

你沒答錯,那個範例確實是用上[閉包],其實不同程式語言在概念上都有很多共通點。誰早回答不是問題,大家學會就好,你不用放在心裏。

0
I code so I am
iT邦新手 1 級 ‧ 2019-06-24 11:51:39

這一篇解釋的不錯,我節錄一小段。

  1. 傳統的 JS:把i傳入閉包,setTimeout內的變數才會更新
for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, j * 1000);
  })(i);
}
  1. ES:依原作者說明『let 會在每次迭代時重新宣告變數 i,並將上一次迭代的結果作為這一次的初始值』。
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i);
  }, i * 1000);
}
2
浩瀚星空
iT邦大師 1 級 ‧ 2019-06-24 15:56:33

@marlin12 已經是正解了。

剛好有人有點誤會變數的宣告意義。我就再說明一下。
認真來說。變數宣告中常用的就是 var 這個。其實還有另外let、const很少人會用到。
var的變數宣告會比較自由性。一般我會將其視為廣域性宣告。

javascript有個特性。其實無論是if、for等有應用到{}的。都可以將其視為一個區域。
var的特性可以各自在各自的區域宣告其對應的延伸區城

let的特性其實就像是一個私域性宣告的特性。它擁有保留私域變數及單獨使用的特性。

const則是常數宣告的意思。相信有在使用後端語言的人對於常數並不陌生才對。
也就是一但第一次宣告後。同時擁有全域性及不可變動的特性存在。

以下幾個例子說明。

// 宣告變數 a
var a = 10;

if(true){
  //  在if(block scope)中宣告變數 b
  var b = 20;
}
console.log(a) // 輸出:10
console.log(b);// 輸出:20

以下是let的特性

// 宣告變數 a
var a = 10;

if(true){
  //  在if(block scope)中宣告變數 b
  let b = 20;
  console.log(b);// 輸出:20
}
console.log(a) // 輸出:10
console.log(b);// 無值,且會得到一個 is not defined 的錯誤

以下為 const

const PI = 3.1415926;

$(".diameter").keyup(function(){
  console.log(PI) // 輸出:3.1415926
});
PI = 123456; //會直接報錯
console.log(PI) // 輸出:3.1415926
看更多先前的回應...收起先前的回應...
dragonH iT邦研究生 3 級 ‧ 2019-06-24 17:03:48 檢舉

其實如果有再用 eslint 之類的 linter 的話

如果用 var

很多都會建議你改用 let 或 const

google曾經推一個 js 的 strong mode

直接把 var Deprecated

如果在這 mode 底下使用 var

會直接 throw error

因為大部分情況來說

var 所能做的

let/const 也能做

甚至會比var做得更好

也不會有一些使用 var 可能會有的問題

這篇也把一些問題點出

let/const 應該只會越來越常見

var的使用則會越來越少

froce iT邦大師 6 級 ‧ 2019-06-24 17:21:17 檢舉

沒ie的話,我應該早就把var廢掉了。

dragonH iT邦研究生 3 級 ‧ 2019-06-24 17:33:55 檢舉

ie 11 以下的瀏覽器應該越來越難用了XD

我完全無視ie了。
目前如果有客戶要求ie也要能用的話。我直接將開發價格提高至少2倍以上。
再問客戶要不要。
我也直接跟客戶說,要ie能用,我就得花2倍以上的時間。拿2倍以上的開發金是很正常的事。ie就是這樣機車麻煩。
不是不做,只是更麻煩開發。所以費用不同。

fillano iT邦超人 1 級 ‧ 2019-06-26 09:43:24 檢舉

let跟const都是block scope,只是const是宣告常數,let宣告變數,作用範圍都是在block(大括號)之內。當然,如果是在global scope宣告,作用就是在global scope。

{const a = 'test'}
console.log(a);

這樣會拋出例外(ReferenceError: a is not defined)。

喔!!原來const還是拖離不了js的block特性啊。
看樣子我又誤解了。

可能我都是當全域宣告。都是直接在最外層宣告。沒去注意這件事。
這下又導正我一個觀念了。

我要發表回答

立即登入回答