javascript為什麼用函數語言程式設計

2022-09-30 18:01:52

原因:1、js的語法是從Scheme這種函數語言程式設計語言借鑑而來。2、就瀏覽器端而言,隨著各種單頁框架的發展,使用者端的處理能力不斷提升,越來越多的業務邏輯被放到端,從而導致使用者端要維護的狀態越來越多;隨之而來的問題是,一不小心就會大量使用依賴於外部變數的函數,這些函數隨著業務邏輯不斷增加,從而導致邏輯分支劇增,狀態難以追蹤,程式碼可讀性差,難以維護,而函數語言程式設計有著很好的解決方案。

前端(vue)入門到精通課程:進入學習
API 檔案、設計、偵錯、自動化測試一體化共同作業工具:

本教學操作環境:windows7系統、javascript1.8.5版、Dell G3電腦。

一、什麼是函數語言程式設計?

函數語言程式設計(Functional programming),簡稱 FP,並不是什麼庫或者框架,與程式式程式設計(Procedural programming)相對,而是一種程式設計正規化。FP 通過宣告純函數抽象資料的處理,來避免或儘可能減少函數呼叫對於外部狀態和系統產生的副作用。

所謂副作用,大抵有改變函數外系統狀態,向外丟擲異常,處理使用者操作,修改入參,資料庫查操作,DOM操作等等可能會引起系統錯誤操作。

二、為什麼在 JavaScript 使用函數語言程式設計思想

2.1 從語言特性來看

JavaScript 一開始的語法就是從 Scheme 這種函數語言程式設計語言借鑑而來。隨著語言標準的推進,語言本身的功能性不斷豐富,閉包、箭頭函數、高階函數,陣列迭代等等功能都讓 JavaScript 中實現 FP 變得簡單,簡單講幾個特性:

2.1.1. lambda 表示式

lambda 表示式其實是一個匿名函數,使用箭頭清晰的表示輸入輸出的對映關係,JavaScript 中使用箭頭函數來實現。

const multiply = x => x * x
multiply(6) // 36
登入後複製

2.1.2 高階函數

高階( Higher-order )函數可以接受一個或者多個函數作為入參,輸出一個函數。

簡單寫兩個例子

const add = x => y => x + y
const add10 = add(10)
add10(5) // 15
const compose = (...fns) => x =>  fns.reduce((acc, fn) => fn(acc), x)
const a = x => x + 1
const b = x => x + 2
const composedFn = compose(a, b)
composedFn(1) // 1 + 1 + 2 = 4
登入後複製

2.1.3 filter map forEach reduce 迭代

Array.prototype 下的 filter map forEach reduce 都是高階函數,因為入參是個函數。

const flatten = (arr = []) => arr.reduce(
  (acc, val)=> accconcat(Array.isArray(val) ? flatten(val) : val),
  []
)
let arr = [1,[ 4, 5 ], 2, 3];
flatten(arr)  // [1, 4, 5, 2, 3]
登入後複製

2.2 從實際需求角度來看

就瀏覽器端而言,隨著各種單頁框架的發展,使用者端的處理能力不斷提升,越來越多的業務邏輯被放到端,從而導致使用者端要維護的狀態越來越多。隨之而來的問題是,我們一不小心就會大量使用依賴於外部變數的函數,這些函數隨著業務邏輯不斷增加,從而導致邏輯分支劇增,狀態難以追蹤,程式碼可讀性差,難以維護,而 FP 恰恰有著很好的解決方案。

另外,現在主流的程式語言基本上都引入函數語言程式設計的特性,即使是以物件導向著稱的 java,通過使用 stream + lambda 表示式,依然可以實踐函數語言程式設計思想,而 Spring5 更是將 Reactive 作為主要賣點,總之 FP 近來很火。

而 JS 的函數語言程式設計生態也在不斷豐富, RxJS, circleJS 等框架在前端產線上的應用也越來越廣。

三、使用函數式的優點

使用 FP 程式設計主要有以下幾個優點:

  • 將資料和處理邏輯分離,程式碼更加簡潔,模組化,可讀性好

  • 容易測試,測試環境容易模擬

  • 邏輯程式碼可複用性強

四、函數語言程式設計相關概念

函數語言程式設計的實現主要依賴於:

  • 宣告式程式設計

  • 純函數

  • 不可變資料

4.1 宣告式程式設計

宣告式程式設計 Declarative programming 只描述目標的性質,從而抽象出形式邏輯,告訴計算機需要計算什麼而不是如何一步步計算。例如正則、SQL、 FP 等。

指令式程式設計 Imperative Programming 告訴計算機每一步的計算操作

最簡單的,相同的陣列處理,使用 for 迴圈是指令式,用 map 之類的操作是宣告式。使用宣告式程式設計,簡化了程式碼,提高了複用率,為重構留有餘地。

4.2 純函數

純函數簡要概括有兩個特點:

  • 函數的輸出只與輸入有關,相同輸入產生的輸出一致,並不會不依賴外部條件

  • 函數呼叫不會改變函數域以外的狀態或者變數,不會對系統產生副作用

看個簡單的例子:

let counter = 0
const increment = () => ++counter
const increment = counter => ++counter
登入後複製

前一個函數每次呼叫都會修改外部變數的值,返回值也依賴於外部變數;後一個函數對於同一輸入值每次返回的結果都相同,並且不會對外部狀態造成影響。所以後一個是純函數。

為什麼要追求函數的純度,這就涉及到一個稱為參照透明性的概念。

4.2.1 參照透明性

純函數的這種函數的返回值只依賴於其輸入值的特性,被稱為參照透明性(referential transparency),純函數都是可以進行快取的。

const memorize(pureFn) {
  const _cache = {}
  return (...args) => {
    const key = JSON.stringify(args)
    return _cache[key] || (_cache[key] = purFu.apply(null, args))
  }
}
登入後複製

4.3 Immutable Data

「可變的全域性狀態是萬惡之源」(其實從功能程式碼的角度看,區域性和全域性是相對而言的),簡而言之可變狀態會讓程式的執行變得不可預測,程式碼可讀性差,難以維護。

在 JS 中,當函數入參是物件型別的資料時,我們拿到的其實是個參照,所以即使在函數內部我們也是可以修改物件內部的屬性,這種情景依然會產生副作用。

所以這個時候就需要引入 Immutable 的概念。 Immutable 即 unchangeable, Immutable data在初始化建立後就不能被修改了,每次對於 Immutable data 的操作都會返回一個新的 Immutable data。 所以並不會對原來的狀態形成改變(當然不是簡單的深拷貝再修改)。

Immutable 比較流行的 JS 實現有 immutable-js 和 seamless-immutable; 對於 React 黨來說, immutable-js 一點都不陌生, immutable-js 配合 Redux 就是一種很好的 FP 實踐。

const map1 = Immutable.Map({a:1, b: {d:2}, c:3});
const map2 = map1.set('a', 50);
map1 === map2 // false
const mapb1 = map1.get('b')
const mapb2 = map2.get('b')
mapb1===mapb2 // true
登入後複製

【相關推薦:、】

以上就是javascript為什麼用函數語言程式設計的詳細內容,更多請關注TW511.COM其它相關文章!