文章目錄

本篇醞釀的時機灰常的微妙,我親愛的學姐~ > ////< 也(因為五斗米)踏入了JS界了~。

很多的書籍在介紹js的時候、除了一開始的超經典hello world,接著就會開始介紹browser的歷史,然後、什摸什摸的分野
,接著就是ㄜ…什摸沒有型態又有型態的弱型態特性,然後bind event,接著有些也許會深入介紹一下經典特性、例子什
摸的。可是、其實我覺得在弄完hello world後、應該是要先講scope和closure~ XDDD 那可以讓好多人少繞很多彎路。

因為、以前找資料,前輩們說起js時、總是會…『這就是因為scope呀~』『阿這個就是用那個closure喇~』
『這個bug就是因為scope,balabala…』

在剛學JS的時候,這2個名詞對我來說、真的好疑惑好神秘,不懂耶~問了很多次…還是一知半解~ Q 口Q 真的很失敗。
所以、scope…. 其實是我很一開始的時候就想好好記錄下來的一個topic,因為、它對我有很深的意義。是我一開始接觸
JS時,遇到的第一個從完全不知道怎摸下手測試與trace,只好呆呆的硬背下來。也因為正式的面對它、讓我真正邁入JS的
門檻。這..難道就是所謂的、成也蕭何、敗也蕭何 (大誤)

其實、這世界上有很多講解scope的文章和書籍,我也陸陸續續看過很多篇,不過、也許是我不夠聰明、或者和前輩們的觀點
不太一樣,在我trace的過程、總覺得…有些細節怪怪的。不過、我也不敢保證我的說法一定是對的啦~(汗) 也許、這個東
西講起來會有點點抽象,不過、就想做一個紀錄。

一開始、說到scope,我們對於不熟悉的語言都會有點點害怕… 所以、把它翻成中文就是…『作用域』
講到作用域、就很熟了~ = . .= 對、我熟悉的作用域、是數學的作用域~ XDDD 不過、那又怎樣…起碼它給我靈感了。
所謂的『作用域』就是在這個範圍內,是有用的、有意義的。

所以、在有了方向後,接著來看下面這2個框框的東西:

  • 如果、我們在作用域1定義了a、在作用域2也定義了a,還蠻明顯的,有在寫程式的人應該可以區分出來,在作
    用域2中的a和作用域1的a明顯的不同。


    (作用域1)
    var a (<—視為globle)

    (作用域2)
    var a (<—視為local)

  • 一樣、我們在作用域1定義a,不過我們在作用域2沒有定義a,可是一樣使用a,那…很明顯的,有在寫程式的
    人,也一定可以區分出在作用域2的a就是作用域1的a

    (作用域1)
    var <strong>a</strong> (<---視為globle)
    <div style="border:solid red 2px; width: 230px; height: 100px; text-align:left;">
        (作用域2)
        <strong color="red">a</strong>  (<---local找不到,往上層...發現a,所以,這個a是globle的a)
    </div>
    



然後、我自己後來覺得有個比喻很棒耶~ @ @++

就把function當作籠子、變數們當作小動物…關起來、這樣這樣、然後再那樣那樣~ w
好吧、說穿了~ = . .= 我覺得這就是所謂的scope。可是、為什摸還是有很多人敗在scope下,被它耍的團團轉咧~!!
其實、原因也很簡單,歸納如下:

  • 大家其實說scope、說得很順…卻從來沒有認真的搞清楚過JS的作用域的定義。
    JS是functional language,大部分的人都會說、在寫程式的時候,並沒有把function視為一個領域看待,特別是先學
    過其他
    非functional language**的人來說,在最一開始、應該都會把花括號當作一個scope、因此就混亂了。
  • 因為它沒有強制定義型別,JS的變數都可以塞任何型別進去,在交互作用下,早就忘記abcd到底塞了些啥進去了。
  • 當尼的code落落長的時候、慘慘…跑到最上面看不到整個運作、跑到最下面看不到定義… = “””= 更不用說,花括號
    亂亂長,縮排縮到不知道哪兒去了。
  • 不良的寫作習慣。

其實、每種語言的風格都差蠻多的啦、以我來說~ = “””= 真的、都差很多~ w 以前、我也會想,我想要把functional
language搞得很像oo language,後來…想把oo language搞得像functional language~ = . .= 是可以啦~ 不過、我覺得
那是自討苦吃~ XDD 應該這摸說,可以沿用它的精神,也就是…神似而型不似,這是沒有問題的,而不推薦要整個都長得
很像(從定義到實做)。

##作用域 之 初學幼幼班

在最前面、看過作用域的基本定義後,一定要來現學現賣,練習一下…所以、來看小小2段程式。

  • 範例1
    1. 流程:
      1. 在最外面定義變數 a 和function b。
      2. function b中、給予a一個新的value。
      3. 依照順序把a給log出來看。
    2. 說明:
      1. 第一個log:毫無疑問的,因為在最一開始定義了a,遇到第一個之前並沒有任何的操作會影響,因此、肯定是123。
      2. 第二個log:執行b function,同樣的、在b裡面,a被影響到了,應該也不會遲疑a=456這個答案。
      3. 有疑問的第三個log來了:前面有提過、JS的作用域的基本單位是function,所以、在function內有定義的、會
        和外面不同、不然就是同一個。而在b中,我們的a並沒有另外定義,因此、b內的變數a就是外面的那個a。然後、
        因為在b裡面已經被改過了,所以、第三個log的結果就會是… 456囉~
範例1
1
2
3
4
5
6
7
8
var a = 123,
b = function() {
a = 456;
console.log("a = " + a);
};
console.log(a); // 123
b(); // a = 456
console.log(a); // 456

這個、和上面那個差不多同樣的方法推演喇~ = . .= 就跳過了~ w

範例2
1
2
3
4
5
6
7
8
var a = 123,
b = function() {
var a = 456;
console.log("a = " + a);
};
console.log(a); // 123
b(); // a = 456
console.log(a); // 123

##作用域 之 超常見陷阱題

嗯嗯、這一段的程式,相信是大家很常見的一個類型,然後… 大家的答案近乎一致性的都是『阿這就是scope(closure)~』
小的駑鈍~ = “””= 當時的我沒有辦法用這種話就可以明白。現在的我、用我所理解的來解釋一下。

  • 範例3:

    1. 流程:相信、有些許程式經驗的人,一般來說,都會覺得、下面是沒有什摸問題的code,就是把function塞進去空
      陣列a,然後…再接著執行陣列a的每一個function,然後… function只是很單純的把index給log出來。

      是~~~~這摸想的對吧~ = . .=
      沒錯、初學JS,而又有一點基礎的人,十之八九都會這摸寫。因為、在我們心裡,潛藏著花括號就是作用域的刻板印象。
      而…這就是造成,為什摸大家會說『js很難懂』『js很怪』『js根本就不是人學的』『js很討厭』『js…balabala』的
      原因。

    2. 解析:在解析前,請大家認真的把function當作個柵欄,把變數們當作小動物…關起來(圍毆…XDD!?)。

      1. 首先、我們這一段code,作用域1。
      2. 接著定義了空陣列a。
      3. 用一個loop定義了…3個小作用域。且、小作用域的i…沒有使用定義,因此…參考至外部的…i。因此、i就會
        隨著loop的i改變而改變。(←注意到了嗎? 這就是,人家說的… = . .= js的陷阱)
      4. 執行塞到a陣列的function。

      5. 執行階段:根據上述的解析,在執行a陣列的各個function的時候,此時的i=3 (←這個道理、應該…不用詳述了吧~
        = . .=)、而小function的i沿用外部的定義,因此….這個i,log出來不等於3要等於啥摸咧~ 懂了吧、懂了吧~
        就就就是降~~~~ w

範例3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = []
for (i = 0; i < 3; i++) {
a.push(function() {
console.log("i = " + i);
});
}
a.forEach(function(fn_i) {
fn_i();
})
// result
// i = 3
// i = 3
// i = 3
  • 範例4:那…如果想達到原本上面我們所想的,該怎摸做咧~看接下來的這個就對了~ w
    1. 解析:
      • 同樣的定義空陣列a。
      • 一樣遇到loop了,為了解決因為作用域引起的問題,也就是說…我們只要讓function裡面的i可以跟外面的i有所區別
        就可以解決了對吧~ 0 因此在這邊、我們在原本的function在包上一層。也就是說,我們使用了一個馬上執行的匿名
        function,把i塞進去。這裡花幾個步驟,稍微解釋一下囉~ w
        1. 所謂的匿名function,白話的說、就是,這個function是個沒有名字的function,除了沒名字以外,跟一般
          的function都一樣。所以、我們可以得到如下的訊息:
          • function是個作用域。
          • function的參數,是有被定義的。
          • 根據上面2點、我們可以知道,有被參數定義過的,會和外層同樣名字的意義是不一樣的。
        2. 因此、根據上面那點,再更詳細的描述一下,這一小段的意義。我們要的是真正想要的是return回來的
          function那一段,而為了i,我們幫它包上一層function、以隔絕掉內外的差別,而這個i必須給予定義,
          如果、不以參數的形式,那…就沒意義了,因此、帶入參數…。
          • 最後把a陣列的function都執行一次出來看… 歐耶~結果對了~ w
範例4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = []
for (i = 0; i < 3; i++) {
a.push((function(i) { // 匿名function
return function() { // 真正想要的function
console.log("index = " + i);
}
})(i));
}
a.forEach(function(fn_i) {
fn_i();
})
// result
// i = 0
// i = 1
// i = 2

聰明的尼,一定會想說… 阿~ i來i去的~ = . .= 匿名function的參數我可不可以不用i… 可~~以,來對照一下吧

範例4只有loop
1
2
3
4
5
6
7
for (i = 0; i < 3; i++) {
a.push((function(j) { // 匿名function
return function() { // 真正想要的function
console.log("index = " + j);
}
})(i));
}

##總結

其實、因為js定義範圍的基本單位是function,因此、有些寫法,也許會讓人覺得多餘…,可是,其實我覺得很有道理耶~
比起會被block住的其他語言來說,其實…functional language更能說服我,我覺得…那很直覺、很蘇湖的想法~ @ 3@


嗯嗯、如果尼對scope很熟悉…那真的是太好了~ 我一點都幫不上忙~(攤手)
如果、尼是scope苦手,那…請試著用我有點點彆腳、又需要一點點想像力的描述~ (Q 口Q 我很努力了~)
看看自己的code、別人的code…。當尼越來越熟悉這樣的方式時,尼會發現,好多js的code都隱藏了不少的bug(大誤)

本篇、也將開啟一個系列文~ Q 口Q 怎摸辦、我又挖坑了。為什摸咧~因為、有太多的東西…都跟這個作用域有關西,
什摸memory leak、recursive、tail recursive…一堆堆balabala很難懂的、隱藏性bug的地方。其實、說穿了都沒啥~ w
藉此、也想強迫自己認真的用自己的話,為自己的頓悟下個註記。

文章目錄