物件裡面可以包含三種東西
核心物件會有一個記憶體的位址,可以參考到這些電腦記憶體中的屬性(property)和方法(method)的位址空間,如下圖。
JSON => JavaScript Object Notation
補充 XML 早期傳資料的做法
Everything you can do with other types you can do with functions
Assign them to variables, pass them around, create them on the fly.
A unit of code that results in a value
It doesn't have save to a variable
To change something
"Immutable" means it can't be changed.
Collections of anything
arr[0]
arguments
and spread切記我們所撰寫的程式語言並不會直接被電腦執行,而是透過有個中介的程式在我們的程式與電腦之間轉換成電腦可以懂得指令。
這個編譯器裡面非常多功能與東西,其中一個就是我們要講的語法解析器,它會閱讀我們的程式碼是否有效,例如它會一個一個字看程式碼是否有效 return 變成 r-e-t-u-r-n,如果中間變成 r-e-t-r…語法解析器就會不認得你在講什麼,所以要記得這個規則。
否則你會很常看到這傢伙 not defined
。
首先這個危險小叮嚀與 JavaScript 的語法解析器有關係也就是它的自動插入分號,我們前面瞭解那麼多都知道 JavaScript 語法解析器有多強大,它會盡可能幫助我們做一些事情,或許你已經注意到我們有沒有寫分號都沒有差異,甚至不是很必要,但為什麼這樣子?主要原因出在 JavaScript 幫我們自動插入分號,但這個動作有多危險?
一般來講語法解析器在解析語法是一個字一個字去檢查,舉例來講 r-e-t-u-r-n,解析完畢後若我們按下換行(carriage return 又稱 回車鍵、Enter)你會發生一些奇怪的事情 ,所以如果沒有分號或括號,JavaScript 就會幫我們補上它認為應該補的地方,所以我們必須理解到一件事情,並不是它並不需要分號而是 JavaScript 幫我們補上了,所以我們要盡可能避免讓 JavaScript 幫我們做決定補上分號,讓我們來看程式碼瞭解這個恐怖的問題 ↓
立即呼叫的函數表示式又稱為 IIFE,相信許多人已經知道這個東西,但實際 IIFE 是什麼,它是怎樣運作,又有什麼用,這就是本章節要探討的問題。
首先我們都已經知道函數陳述句與函數表示式的差異。
而這就是函數陳述句,當 JavaScript 執行完畢後只會將它放在記憶體中,並不會執行它,除非我們執行了 greet()。
然而函數表示式就像這樣,它一開始並不會被儲存到記憶體內,只有變數會被儲存至記憶體內,因為變數有提升效果,直到 JavaScript 執行這一行時就會立刻創造執行環境,這就是函數表示式。
簡單回顧了之前的函數陳述句與函數表示式後,我們可以對函數做一點特別的動作,讓它變成特別的物件,若我們希望在函數表示式後立刻呼叫它,而不是透過 greetFunction(); 呢?
只需要在後面加上括號就可以了,那如果我們帶入參數應該放哪?一樣在括號裡面帶入就好了。
而這觀念就是它會在函數被創造之後立刻呼叫它(執行),而這就是人家講的立即呼叫的函數表示式又稱 IIFE。
所以我們可以看看這兩者差異在哪裡。
我們可以發現不是使用 IIFE 會得到一整個函數,而 IIFE 直接會得到一個值,這也就是這兩者差異。
當然也有另一種撰寫方式。
這也是人家講的匿名函數,我不需要名稱就可以直接執行的函數。
但若如果這樣呢?
你會得到一個錯誤。
Uncaught SyntaxError: Unexpected token
因為當語法解析器第一行解析到 f-u…. 就會一直解析,而它就會想說 「哦!這是一個函數陳述句,所以等一下應該會有一個名稱」,結果我們並沒有給予函數名稱,所以就會得到一個錯誤。
那如果不要讓語法解析器認為這是一個函數陳述句,那就要在函數陳述句外圍包上括號,當它解析到括號,它就會知道 「哦!這是一個函數表示式」,就像前面講的範例一樣。
但是如果試著將這段程式碼貼上去執行會看到一個滿有趣的狀況。
它直接回傳一個函數,所以如果我們要正確呼叫它就在最後加上括號,而括號內也可以帶上參數。
最後補充一個課程講的地方,這兩者 IIFE 都是一樣合理正確的,純粹只是美感問題。
這章節將會開始講解一個非常恐怖又惡名昭彰的東西「閉包」(closuere),由於我們要深入瞭解這個程式語言,所以閉包就是一個非常重要的觀念,非常多人對於閉包非常感到混淆甚至討厭,因為這真的很難懂。
由於一開始我們必須先撰寫一些程式碼來展示閉包的威力,然後再來瞭解它背後的機制及好用之處 ↓
這時候你可能會說,我該如何呼叫放在 return 中的函數,其實只要這樣就可以了 ↓
雖然內部函數沒有 whattosay 變數,但因為範圍鏈所以會參考外部環境,進而找到 whattosay,這邊目前看起來還算合理。
但是接下來這個範例就比較不太正常了 ↓
為什麼 sayHi 還是知道 whattosay 是什麼?你一定會說我為什麼這樣講,讓我們試著直接輸出 sayHi 你就會知道為什麼了 ↓
我們可以看到只有內部函數,但完全沒有看到 whattosay 的變數,那它到底是怎麼找到的?
首先當 greet 被執行時執行環境被建立,然後 whattosay 變數被傳入到環境內。
接下來當 greet 執行完畢後回傳了一個物件函數,所以 greet 就離開了執行堆並留下了 whattosay 的變數。
但是每一個執行環境都有屬於它自己的記憶體空間,所以若當執行環境消失了,記憶體空間會發生什麼事情呢?
一般情況下會被清除,而這也稱之為垃圾回收 (Garbage collection)。
但是當執行環境結束時記憶體還是會存在那邊,雖然執行環境消失了,但變數一樣存在某處,然後就回到了全域環境狀況下,並開始執行 sayHi('Tony');。
那這時候的 sayHi('Tony'); 是一個匿名函數,類似像這樣 ↓
接下來當我們執行匿名函數後都知道會出現 console.log(whattosay + ' ' + name);,但是我們會注意到裡面並沒有 whattosay 變數,所以 JavaScript 就會往外部環境去找,就是所謂的範圍鏈。
所以課程上有說明 JavaScript 為了避免我們找不到變數,所以會形成一個框框,將這一整個包住關起來,確保我們可以找的到東西。
而這行為就是所謂的閉包。
而閉包只是 JavaScript 中的一個特色,確保我們找的到我們想要的東西而已,與我們何時呼叫函數並沒有任何直接關係。
在網路上搜尋相關閉包知識時,其實都會找到類似的範例程式,但是如果沒有釐清觀念,頓時會覺得很難。
所以在這一堂課算是驗證自己對於閉包的觀念是否已經清楚了,首先先準備一個函數 ↓
接下來將執行 buildFunction() 存入變數 fs,所以這邊我們可以預期得到一個陣列 ↓
所以我們可以這樣呼叫陣列裡面的函數,那預期應該會是什麼?
可以看到答案是 3,絕對不是我下面沒有截圖。
為什麼當它到外部參數找 i 的時候會發現它們都一樣呢?回頭想一下執行堆。
首先在執行堆中 buildFunction() 會開始執行,然後跑三次,然後匿名函數會被建立,可是它們只是建立並沒有被執行,只是將函數放進去陣列中 (arr),接下來執行完畢就回傳 arr,所而 i 在執行完畢後最終停在 3,故而被保存在記憶體中,就像課程簡報一樣。
它們的環境是處於閉包,在匿名函數裡面它們找不到 i,所以會從範圍鏈去取得 i,而 i 目前為 3,這也是為什麼 i 會是 3 的原因。
但是如果我們希望是輸出 0…1…2 呢?那就這樣做。
這樣就可以達到我們要的效果,而這邊主要使用的是 ES6 的 let,let 的特性就是只會存活在這個區塊,所以當這個區塊結束或離開後,就會被消滅,但在這邊 j 會被儲存在不同的記憶體位置中,所以每次 for 迴圈執行一次,就會產生一個新的記憶體位置的 j。
那另一種作法呢?
這邊所活用的方式是先期講過的其中一種方式,因為要讓變數獨一無二那就要有一個唯一的執行環境,所以就是立刻執行環境 IIFE。
但是課程的寫法是這樣。
基本上我覺得課程所講的方式較妥當,因為可以確保有執行環境。
一般來講如果我們在 JavaScript 使用過 setTimeout、jQuery,這些其實都是使用了閉包,所以這章節要來聊聊閉包和回呼 (callback)。
首先我們先試著建立一個簡單的函數範例 ↓
這個範例我們都知道會在三秒後出現 Hi,而這個過程其實我們已經使用了函數表示式及閉包。
而 jQuery 也是一樣,例如 click 事件。
另外這也是一個閉包,從上面這兩個範例我們有可能已經使用過了閉包、函數表示式及一級函數。
而回呼函數是當某個函數執行完你給他的執行函數,然後那個函數再回呼你給他的函數,在白話一點就是當某個函數執行完畢要給該函數執行的函數就稱為回呼。
讓我們從範例來看
我們可以看到呼叫了兩次 tellMeWhenDone,然後參數 function 帶入 callback,當 tellMeWhenDone 執行完之後就會呼叫剛剛帶入的函數,這就是回呼。
簡單來講就是 function 執行完畢後再執行另一個。
這節課將來討論 JavaScript 中的三個方法 call()、apple()、bind(),而這三個東西與我們前面所講的東西有關係。
在探討這三個方法之前必須先瞭解什麼一級函數,一開始我們已經知道一級函數是特殊形態
接下來我們試著跟著課程範例了解。
那 call()、apply()與 bind() 該怎麼使用呢?假設我們有一個 logName 匿名函數
課程來看,其實我們大概都可以知道這樣會出現錯誤,因為 this 指向的是 window,而 window 下並沒有一個函數叫 getFullName。
但是如果今天我們可以控制 this 指向誰的話呢?所以這邊就要試著使用看看 bind
bind 這個方法會回傳新的函數,當 JavaScript 看到 bind 將會重新指向 this 到傳入 bind 方法的東西。
所以 bind 創造任何我們呼叫的函數拷貝,不論我們傳入函數還是物件都可以。
call 與 bind 一樣可以傳入要拷貝的函數,但是可以傳入參數並執行他
bind 只是單純拷貝並沒有執行函數,但 call 可以拷貝並執行函數。
但是如果我們試著跟 call 一樣傳入參數就會出現錯誤。
所以應該怎麼使用 apply?,apply 。主要接受的參數是陣列,而不是普通的字串、數值等等。
我們可以從上面發現 apply 非常強大,在傳入參數後會自動幫我們對應載入相對應的參數。
所以我們這邊也可以透過匿名函數 + apply or call 立刻執行呼叫。
在這個例子我們可以將原有 person 底下的函數 (getFullName) this 轉指到 person2。
而 call 也是一樣。
這個例子與 bind 有關,在前面使用 call 及 apply 傳入參數就只是傳入參數,但是 bind 可以創造函數拷貝。
注意 bind 只會拷貝並不會執行,但給他的參數將會被設定成拷貝函數的永久參數值,所以當我們第一個參數放 2 代表永遠都是 2 , a 永遠都是 2。
此時你可能會問怎麼是 NaN?試著思考一下變數的預設值是什麼 undefined,當 2 * undefined 那當然會是 NaN,這並不是一個數值,所以我們可以在執行函數時傳入數字。
在討論完一級函數及各種 JavaScript 之後準備來討論講師喜歡的主題【函數程式設計 (function Programmin)】
以往我們很多人都會以為 JavaScript 會跟 Java 有關係,甚至看起來很像 C++、C#等,但講師是說 JavaScript 和函數程式語言比較有關係(Lisp、Scheme、ML),所以我們從範例來了解什麼是 函數程式設計 (function Programmin)。
通常身為程式設計師我們都會盡可能減少自己的工作量,所以都會將重複性較高的動作包裝至一個函數內,那如果在沒有一級函數的狀況下我們所做的事情是有限的,所以我們可以利用一級函數來製作函數程式設計,然後將上面這個範例做修改。
我們可以看到 函數程式設計 的強大,所以也可以拿來做比較。
這樣就可以重複不停地利用函數,而這就是 函數程式設計 的經典例子。
那如果拿來檢查是否有超過特定數值呢?
但是有些時候 bind 很煩,所以這邊可以試著不要在 mapForEach(arr1, checkPastLimt.bind(this, 1)) 使用 bind 來限制值。
前面我們已經了解到 函數程式設計 的強大,所以這堂課將會花點時間來講一些資源例子。
underscore.js 是一個滿有名的 JavaScript 資源庫,可以幫助我們處理陣列及物件。
講師也建議我們可以從這些開源資料來了解進而學習,但要挑戰之前我們一定要有觀念,所以這堂課也希望我們可以藉由開源的資料來幫助我們學習了解函數程式設計。
Lodash與 underscore 類似,但執行速度比 underscore 快。
但作者建議我們去 underscore 看一下學習了解 underscore 如何撰寫,這可以協助我們更加深了解函數程式設計。
最後我附上我找的幾個資源
WierdJavascript