iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 23
0
Modern Web

教練我想學 JavaScript 系列 第 23

Day 23 函數程式設計

因為 JavaScript 一級函數的特性,函數是一種特殊的物件,函數的參數可以傳入函數,也可在函數中回傳函數,
課堂講師提到身為一個程式設計師,總是懶得打重複的字或做重複的事情,
我們可以利用函數的這些特性來寫出簡潔、方便、可重複使用的程式碼,

我們先來看一個例子,
我有一個陣列,我要將陣列的值處理完後在回傳給一個新陣列時,通常會這麼做,
程式碼如下:

var arr1 = [1, 2, 3];
console.log(arr1);

var arr2 = [];
for(var i = 0; i < arr1.length; i++) {
  arr2.push(arr1[i] * 2);
}

console.log(arr2);

在 Console 中的結果如下:

我們將原來的陣列成員透過跑迴圈乘上兩倍後放進新的陣列裡,

但如果我們現在要比較每個陣列成員是否大於某個數時是不是又要另外寫一次回圈來處理陣列後回傳給新陣列?
因此我們就可以透過將程式碼寫在函數裡來重複使用,
程式碼如下:

function mapForEach(arr, fn) {
  var newArr = [];

  for(var i = 0; i < arr1.length; i++) {
    newArr.push(
        fn(arr[i])
      );
  }

  return newArr;

}

var arr1 = [1, 2, 3];
console.log(arr1);

var arr2 = mapForEach(arr1, function(item) {
  return item * 2;
});

console.log(arr2);

在 Console 中的結果如下:

我們透過函數 mapForEach 傳入原來的陣列,以及匿名函數來處理我們的陣列,
這種方式給我們更多彈性,

如果現在我想要比較原來的陣列成員是否大於特定數值,
可以改寫程這樣,程式碼如下:

function mapForEach(arr, fn) {
  var newArr = [];

  for(var i = 0; i < arr1.length; i++) {
    newArr.push(
        fn(arr[i])
      );
  }

  return newArr;

}

var arr1 = [1, 2, 3];
console.log(arr1);

var arr2 = mapForEach(arr1, function(item) {
  return item > 2;
});

console.log(arr2);

在 Console 中的結果如下:

只有 3 大於 2, 所以 1 跟 2 回傳 false ,3 回傳 true,

但現在我們的計算數值是不可變動的,有沒有辦法讓他可以在呼叫函數的時候可以透過參數來傳入?

我們新增一個匿名函數指派給變數 checkPastLimiter,匿名函數的第一個參數接受一個用來運算的數值,
第二個參數是每個陣列成員,但有個問題是函數 mapForEach 中的迴圈每次在執行傳入的函數時,只接受一個參數,這樣我們該怎麼辦?

可以透過之前說過的函數本身內建的 bind 方法來設定預設參數,我們在將指派給變數 checkPastLimiter 的匿名函數當作函數 mapForEach 的第二個參數傳入時,就可以透過 bind 方法來設置預設參數了,

程式碼如下:

function mapForEach(arr, fn) {
  var newArr = [];

  for(var i = 0; i < arr1.length; i++) {
    newArr.push(
        fn(arr[i])
      );
  }

  return newArr;

}

var arr1 = [1, 2, 3];
console.log(arr1);

var checkPastLimiter = function(limiter, item) {
  return item > limiter;
}

var arr2 = mapForEach(arr1, checkPastLimiter.bind(this, 1));
console.log(arr2);

在 Console 中的結果如下:

這樣就可以每次在呼叫 mapForEach 時,透過指派給變數 checkPastLimiter 的匿名函數本身的 bind 方法,在拷貝一個新的函數時設置不同的預設參數了,

目前是透過指派給變數 checkPastLimiter 的匿名函數本身的 bind 方法拷貝新的函數,
再將 bind 方法拷貝後的新函數當作函數 mapForEach 的傳入參數,

那我們有沒有辦法在呼叫函數 mapForEach 時,
把指派給新的變數的匿名函數在當作函數 mapForEach 的參數時同時呼叫後,
只傳入處理的計算數值,也就是只傳入參數 limiter ,
需要改寫一下程式碼,這邊講者希望我們暫停影片自己先思考一下,試著自己做作看,

我們一樣用匿名函數回傳一個透過 bind 方法處理過的匿名函數並指派給新的變數 checkPastLimiterSimpled,
程式碼如下:

function mapForEach(arr, fn) {
  var newArr = [];

  for(var i = 0; i < arr1.length; i++) {
    newArr.push(
        fn(arr[i])
      );
  }

  return newArr;

}

var arr1 = [1, 2, 3];
console.log(arr1);

var checkPastLimiter = function(limiter, item) {
  return item > limiter;
}

var checkPastLimiterSimpled = function(limiter) {
  return function(limiter, item) {
    return item > limiter;
  }.bind(this, limiter);
}

var arr2 = mapForEach(arr1, checkPastLimiterSimpled(2));
console.log(arr2);

在 Console 中的結果如下:

將指派給變數 checkPastLimiterSimpled 的匿名函數當作函數 mapForEach 的參數時,
並不會把參數 limiter 傳給回傳的匿名函數的參數 limiter,
在將指派給的變數 checkPastLimiterSimpled 的匿名函數當作函數 mapForEach 的參數傳入時,
我們有先呼叫匿名函數,
所以實際上是回傳的匿名函數已經在回傳時先透過本身的 bind 方法在拷貝函數時設置了參數 limter 的固定值,
最後回傳已經被設置好參數 limiter 固定值的匿名函數,

現在我們初步練習完函數程式設計了,
但我們不能只是把程式碼改成寫在函數中,
要去思考如何讓回傳的函數更簡單與如我們所預期的達成我們想做的事,

講者提到不要去改變原來的資料(arr1),透過回傳的函數來處理資料,這是在函數程式設計中需要特別注意的,

雖然 JavaScript 跟 Java聽起來很像,但他們完全不一樣,就像熱狗跟狗的關係,
另外不要把 JavaScript 當成像 PHP 或 C#,
因為 JavaScript 一級函數的概念你可以做一些其他程式語言做不到的事,
你可以寫出更簡潔、方便、易讀、可重用的程式碼,

有一些相當不錯的函數程式設計函數庫非常值得一看:
Underscore.js
Lodash.js

這些都是相當優良的開源教育
課堂講師給 Underscore 非常大的讚賞,
因為 Lodash 是借鏡 Underscore 改良出來的,
先鋒者(Underscore)應該受到讚賞,

如果你想了解它們背後的原理也可以在玩過他們的每個方法、範例後去看他們的原始碼,
記得看開發(Development)版本的原始碼會有很多註解說明,
產品(Production)版的原始碼沒有註解,而且是壓縮過的不好閱讀,

再次強調課堂講師非常推荐我們去看 Underscore 的原始碼,會學到很多函數程式設計的技巧與細節,
這樣對你的函數程式設計功力會有非常大的幫助,
如何透過一級函數寫出更乾淨的程式碼,讓別人也可以使用你開發出來的功能,
會讓你能夠在網路上跟別人合作開發,
函數程式設計讓我們寫出可重複使用、更簡短的程式碼。


上一篇
Day 22 call()、apply() 與 bind()
下一篇
Day 24 Class 和 Prototype 繼承
系列文
教練我想學 JavaScript 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言