iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 7
1
Modern Web

從0.5開始的JavaScript系列 第 7

Day7 工具包: 函數&模組化(2)

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

再來也可以用於嵌套 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>

還不錯吧,把功能模組化,除了能夠重用還可以避免開發時的混亂或是環境互相汙染!


那今日的分享就到這,我們明天見/images/emoticon/emoticon51.gif


上一篇
Day6 工具包: 函數&模組化(1)
下一篇
Day8 工具包: Array(1) & 碎碎念
系列文
從0.5開始的JavaScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言