js:淺談函數式編程

背景

函數式編程是一種編程思想,是編程範式的一種,其餘的還有命令式(指令式)編程和麪向對象編程。

函數式舉例

假設我們要把字符串 functional programming is great 變成每個單詞首字母大寫

var string = 'functional programming is great';
var result = string
  .split(' ')
  .map(v => v.slice(0, 1).toUpperCase() + v.slice(1))
  .join(' ');

整個過程就是 join(map(split(str))),體現了函數式編程的核心思想: 通過函數對數據進行轉換。
函數式編程有兩個基本特點:

  • 通過函數來對數據進行轉換
  • 通過串聯多個函數來求結果

命令式舉例

我們通過編寫一條又一條指令去讓計算機執行一些動作,這其中一般都會涉及到很多繁雜的細節。命令式代碼中頻繁使用語句,來完成某個行爲。比如 for、if、switch、throw 等這些語句。

var CEOs = [];
for(var i = 0; i < companies.length; i++){
    CEOs.push(companies[i].CEO)
}

函數式的特性

1、無副作用

var a = 1;
// 含有副作用,它修改了外部變量 a
// 多次調用結果不一樣
function test1() {
  a++
  return a;
}

// 無副作用,沒有修改外部狀態
// 多次調用結果一樣
function test2(a) {
  return a + 1;
}

2、透明引用
指一個函數只會用到傳遞給它的變量以及自己內部創建的變量,不會使用到其他變量。

var a = 1;
var b = 2;
// 函數內部使用的變量並不屬於它的作用域
function test1() {
  return a + b;
}
// 函數內部使用的變量是顯式傳遞進去的
function test2(a, b) {
  return a + b;
}

3、不可變變量
指的是一個變量一旦創建後,就不能再進行修改,任何修改都會生成一個新的變量。使用不可變變量最大的好處是線程安全。多個線程可以同時訪問同一個不可變變量,讓並行變得更容易實現。 由於 JavaScript 原生不支持不可變變量,需要通過第三方庫來實現。 (如 Immutable.js,Mori 等等)

var obj = Immutable({ a: 1 });
var obj2 = obj.set('a', 2);
console.log(obj);  // Immutable({ a: 1 })
console.log(obj2); // Immutable({ a: 2 })

4、函數是一等公民
我們常說函數是JavaScript的"第一等公民",指的是函數與其他數據類型一樣,處於平等地位,可以賦值給其他變量,也可以作爲參數,傳入另一個函數,或者作爲別的函數的返回值。下文將要介紹的閉包、高階函數、函數柯里化和函數組合都是圍繞這一特性的應用

常見的函數式編程模型

1、閉包

// 簡單的緩存工具
// 匿名函數創造了一個閉包
const cache = (function() {
  const store = {};
  
  return {
    get(key) {
      return store[key];
    },
    set(key, val) {
      store[key] = val;
    }
  }
}());
console.log(cache) //{get: ƒ, set: ƒ}
cache.set('a', 1);
cache.get('a');  // 1

2、高階函數
高階函數指的是一個函數以函數爲參數,或以函數爲返回值,或者既以函數爲參數又以函數爲返回值。
JavaScript 語言是原生支持高階函數的, 例如Array.prototype.map,Array.prototype.filter 和 Array.prototype.reduce 是JavaScript中內置的一些高階函數,使用高階函數會讓我們的代碼更清晰簡潔。
3、函數柯里化
柯里化又稱部分求值,柯里化函數會接收一些參數,然後不會立即求值,而是繼續返回一個新函數,將傳入的參數通過閉包的形式保存,等到被真正求值的時候,再一次性把所有傳入的參數進行求值。

// 普通函數
function add(x,y){
    return x + y;
}
add(1,2); // 3
// 函數柯里化
var add = function(x) {
  return function(y) {
    return x + y;
  };
};
var increment = add(1);
increment(2);// 3

4、函數組合

//兩個函數的組合
var compose = function(f, g) {
    return function(x) {
        return f(g(x));
    };
};

//或者
var compose = (f, g) => (x => f(g(x)));
var add1 = x => x + 1;
var mul5 = x => x * 5;
compose(mul5, add1)(2);// =>15 

redux 源碼上的 compose 函數:

// compose(f, g, h)(...args) => f(g(h(...args)))
function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章