Hi,很高興我們又見面了。
昨日提到了函數的寫法以及閉包的概念,那今日就把剩下的應用完成吧!
昨日提到了 閉包(closure)
特性,需要記住的最主要觀念就是「它擁有自由變數」。
那這個特性就能夠用來建構屬於它自己的私有環境與私有變數,怎麼說呢?
讓我們看一下 MDN 上的範例,
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); // 0
Counter.increment();
Counter.increment();
console.log(Counter.value()); // 2
Counter.decrement();
console.log(Counter.value()); // 1
看出來了嗎,變數 privateCounter
無法被直接修改,也不會和外部環境互相汙染。
上方的例子中,將變數 Counter
的值設為一個自調用函數,直接建立了一個閉包,將變數 privateCounter
關入,然後回傳了一個物件。
裡面有 3 個方法,分別是將自由變數(私有變數)的值 +1
、-1
、印出
。
再來也可以用於嵌套 callback function,將 onclick
事件的 callback function 設為建立的閉包。index.html
<a href="#" id="black">blck</a>
<a href="#" id="orange">orange</a>
script.js
function setBGColor(color){
return function(){
document.body.style.backgroundColor = color;
};
}
var bgBlack = setBGColor('black');
var bgOrange = setBGColor('orange');
document.getElementById('black').onclick = bgBlack;
document.getElementById('orange').onclick = bgOrange;
還記得我們 Day5 提到的非同步事件嗎?
先來看一下這段程式碼,請問 1 秒後會印出 0~4
嗎?
for(var i = 0; i < 5; i++){
setTimeout(function () {
console.log(i);
}, 1000);
}
答案是不會,因為 setTimeout
會被拉出倒數,等到時間到才會放進 Event Queue
。
所以等到實際執行時,參考的 i
是跑完迴圈後的,所以會印出 5 個 5
!
這個問題有兩個解法,一個是用閉包,也就是上面提到的,把它直接拿來當 callback function。
這樣 1 秒後,就會依序印出 0~4
。
function printI(myNum){
return function(){
console.log(myNum);
}
}
for(var i = 0; i < 5; i++){
setTimeout(printI(i), 1000);
}
第二個解法就是 Day2 提到的 let
。
for(let i = 0; i < 5; i++){
setTimeout(function(){
console.log(i)
}, 1000);
}
let
能夠使建立的變數具有區塊(block)的作用域,所以 callback function 執行時都會使用個別的變數 i
,而不會使用原先全域變數 i
。
看完了以上三個範例,再來聊聊實際開發時 js 載入的狀況,index.html
<html>
<head></head>
<body>
...
<script src="./js/getData.js"></script>
<script src="./js/renderUI.js"></script>
</body>
</html>
假設開發時寫了兩個 js,其中一個負責取得資料,而另外一個負責渲染畫面。
但是載入 js 後,裡面的 變數
、函數
都會跑到全域中,
像是,兩個 js 中都有一個叫 myData
的變數,這樣會發生什麼事?getData.js
let myData = {};
function ...
renderUI.js
let myData = {};
function ...
沒錯,它們彼此衝突/汙染了,可能前後兩者的變數 myData
裝的是不同的東西;又或者有兩個相同名稱的 function
,但是裡面做的是不一樣的事情。
這種狀況在載入其他第三方套件、與他人共同開發時更是需要小心。
因此在開發時可以考慮使用模組化的方式,它的原理就是上面提到的閉包,calendar.js
var myCalendar = (function () {
var allData;
var show_div = document.getElementById("show_div");
function _getData() {
...
}
function _eventBind() {
show_div.addEventListener("click", function (e) {
_renderData(this.value);
});
}
function _renderData(val) {
...
}
function init() {
_getData();
_eventBind();
}
return {
init
}
})();
上方的 calendar.js
就改為模組化的形式開發,裡面的變數、函數都不會和外部環境汙染,要啟用它也很簡單,只需要執行傳出的方法: init
。
PS1. 這個寫法是縮寫,也就是屬性名
和函數名
一樣時,可以縮寫成這樣。
return {
init
}
// 等於
return {
init: init
}
PS2. 模組內部的函數可以在函數名前面加上 _
,方便和傳出的函數做區隔。
使用方法也很簡單index.html
<html>
<head></head>
<body>
...
<script src="./js/calnedar.js"></script>
<script>
window.onload = function(){
myCalendar.init();
}
</script>
</body>
</html>
還不錯吧,把功能模組化,除了能夠重用還可以避免開發時的混亂或是環境互相汙染!
那今日的分享就到這,我們明天見