欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript函數(shù)式編程實(shí)現(xiàn)介紹

 更新時(shí)間:2022年09月09日 09:09:46   作者:ExMaterial  
函數(shù)式編程是一種編程范式,將整個(gè)程序都由函數(shù)調(diào)用以及函數(shù)組合構(gòu)成。 可以看成一條流水線,數(shù)據(jù)可以不斷地從一個(gè)函數(shù)的輸出流入另一個(gè)函數(shù)的輸入,最后輸出結(jié)果

為什么要學(xué)習(xí)函數(shù)式編程

Vue進(jìn)入3.*(One Piece 海賊王)世代后,引入的setup語法,頗有向老大哥React看齊的意思,說不定前端以后還真是一個(gè)框架的天下。話歸正傳,框架的趨勢確實(shí)是對(duì)開發(fā)者的js功底要求更為嚴(yán)格了,無論是hooks、setup,都離不開函數(shù)式編程,抽離代碼可復(fù)用邏輯,更好地組織及復(fù)用代碼,有一點(diǎn)我感到很高興的是,終于可以拋棄煩人的this了,當(dāng)然,這也不是我為偷懶而生出這樣的感想,人家道格拉斯老爺子可是在他的新書《JavaScript悟道》里極力吐槽了一下this,所以,也算是像js大佬看齊了。所以,要想不被前端日新月異的新技術(shù)給沖昏頭腦,還是適時(shí)回來重學(xué)一下JavaScript吧。

什么是函數(shù)式編程

函數(shù)式編程(Functional Programming, FP),F(xiàn)P 是編程范式之一,我們常聽說的編程范式還有面向過程編程、面向?qū)ο缶幊獭?/p>

面向?qū)ο缶幊蹋好嫦驅(qū)ο笥腥筇匦?,通過封裝、繼承和多態(tài)來演示事物之間的聯(lián)系,如果更寬泛來說,抽象也應(yīng)該算進(jìn)去,但是由于面向?qū)ο蟮谋举|(zhì)就是抽象,其不算是三大特性也不為過。

函數(shù)式編程:函數(shù)式編程的思想主要就是對(duì)運(yùn)算過程進(jìn)行抽象,它更像一個(gè)黑盒,你給入特定的輸出,進(jìn)過黑盒運(yùn)算后再返回運(yùn)算結(jié)果。你可以將其理解為數(shù)學(xué)中的y = f(x)。

  • 程序的本質(zhì):根據(jù)輸入進(jìn)行某種運(yùn)算得到相應(yīng)的輸出。
  • x -> f(聯(lián)系、映射) -> y, y = f(x)
  • 函數(shù)式編程中的函數(shù)其實(shí)對(duì)應(yīng)數(shù)學(xué)中的函數(shù),即映射關(guān)系。
  • 相同的輸入始終要得到相同的輸出(純函數(shù))
  • 可復(fù)用

前置知識(shí)

函數(shù)是一等公民

作為一名有一定經(jīng)驗(yàn)的前端開發(fā)者,你一定對(duì)JavaScript中“函數(shù)是一等公民”這一說法不陌生。

這里給出權(quán)威文檔MDN的定義:當(dāng)一門編程語言的函數(shù)可以被當(dāng)作變量一樣用時(shí),則稱這門語言擁有頭等函數(shù)。例如,在這門語言中,函數(shù)可以被當(dāng)作參數(shù)傳遞給其他函數(shù),可以作為另一個(gè)函數(shù)的返回值,還可以被賦值給一個(gè)變量。

函數(shù)可以儲(chǔ)存在變量中

let fn = function() {
  console.log('Hello First-class Function')
}
fn()

函數(shù)作為參數(shù)

function foo(arr, fun) {
  for (let i = 0; i < arr.length; i++) {
    fun(arr[i])
  }
}
const array = [1, 2, 3, 4]
foo(array, function(a) { console.log(a) })

函數(shù)作為返回值

function fun() {
  return function () {
    consoel.log('哈哈哈')
  }
}
const fn = fun()
fn()

高階函數(shù)

什么是高階函數(shù)

高階函數(shù)

  • 可以把函數(shù)作為參數(shù)傳遞給另外一個(gè)函數(shù)
  • 可以把函數(shù)作為另外一個(gè)函數(shù)的返回結(jié)果

函數(shù)作為參數(shù)(為了避免文章篇幅過長,后面的演示代碼就不給出測試代碼了,讀者可自行復(fù)制文章代碼在本地編輯器上調(diào)試)

function filter(array, fn) {
    let results = []
    for (let i = 0; i < array.length; i++) {
        if (fn(array[i])) {
            results.push(array[i])
        }
    }
    return results
}
// 測試
let arr = [1, 3, 4, 7, 8]
const results = filter(arr, function(num) {
    return num > 7
})
console.log(results) // [8]

函數(shù)作為返回值

// 考慮一個(gè)場景,在網(wǎng)絡(luò)延遲情況下,用戶點(diǎn)擊支付,你一定不想要用戶點(diǎn)完支付沒反應(yīng)后點(diǎn)擊下一次支付再重新支付一次,不然,你的公司就離倒閉不遠(yuǎn)了。
// 所以考慮一下once函數(shù)
function once(fn) {
    let done = false
    return function() {
        if (!done) {
            done = true
            return fn.apply(this, arguments)
        }
    }
}
let pay = once(function (money) {
    console.log(`支付: ${money} RMB`)
})
pay(5)
pay(5)
pay(5)
pay(5)
// 5

使用高階函數(shù)的意義

  • 抽象可以幫我們屏蔽細(xì)節(jié),只需要關(guān)注目標(biāo)
  • 高階函數(shù)是用來抽象通用的問題

常用高階函數(shù)

  • forEach(已實(shí)現(xiàn))
  • map
const map = (array, fn) => {
  let results = []
  for (let value of array) {
    results.push(fn(value))
  }
  return results
}
  • filter
  • every
const every = (array, fn) => {
  let result = true
  for (let value of array) {
    result = fn(value)
    if (!result) {
      break
    }
  }
  return result
}
  • some
const some = (array, fn) => {
  let result = false
  for (let value of array) {
    result = fn(value)
    if (result) {
      break
    }
  }
  return result
}
  • find/findIndex
  • reduce
  • sort

閉包

閉包 (Closure):函數(shù)和其周圍的狀態(tài)(詞法環(huán)境)的引用捆綁在一起形成閉包。

閉包的本質(zhì):函數(shù)在執(zhí)行的時(shí)候會(huì)放到一個(gè)執(zhí)行棧上當(dāng)函數(shù)執(zhí)行完畢之后會(huì)從執(zhí)行棧上移除,但是堆上的作用域成員因?yàn)楸煌獠恳貌荒茚尫?,因此?nèi)部函數(shù)依然可以訪問外部函數(shù)的成員。

function makePower(power) {
    return function (num) {
        return Math.pow(num, power)
    }
}
// 求平方及立方
let power2 = makePower(2)
let power3 = makePower(3)
console.log(power2(4)) // 16
console.log(power2(5)) // 25
console.log(power3(4)) // 64
function maekSalary(base) {
    return function (performance) {
        return base + performance
    }
}
let salaryLevel1 = makeSalary(12000)
let salaryLevel2 = makeSalary(15000)
console.log(salaryLevel1(2000)) // 14000
console.log(salaryLevel2(3000)) // 18000

其實(shí)上面這兩個(gè)函數(shù)都是差不多的,都是通過維持對(duì)原函數(shù)內(nèi)部成員的引用。具體可以通過瀏覽器調(diào)試工具自行了解。

純函數(shù)

純函數(shù)概念

純函數(shù):相同的輸入永遠(yuǎn)會(huì)得到相同的輸出

lodash 是一個(gè)純函數(shù)的功能庫,提供了對(duì)數(shù)組、數(shù)字、對(duì)象、字符串、函數(shù)等操作的一些方法。有人可能會(huì)有這樣的疑惑,隨著ECMAScript的演進(jìn),lodash中很多方法都已經(jīng)在ES6+中逐步實(shí)現(xiàn)了,那么學(xué)習(xí)其還有必要嗎?其實(shí)不然,lodash中還是有很多很好用的工具函數(shù)的,比如說,防抖節(jié)流是前端工作中經(jīng)常用到的,你可不想每次都手寫一個(gè)函數(shù)吧?更何況沒有一點(diǎn)js功底還寫不出來呢。

話歸正傳,來看看數(shù)組的兩個(gè)方法:slice和splice。

  • slice 返回?cái)?shù)組中的指定部分,不會(huì)改變原數(shù)組
  • splice 對(duì)數(shù)組進(jìn)行操作返回該數(shù)組,會(huì)改變原數(shù)組
let array = [1, 2, 3, 4, 5]
// 純函數(shù)
console.log(array.slice(0, 3))
console.log(array.slice(0, 3))
console.log(array.slice(0, 3))
// 不純的函數(shù)
console.log(array.splice(0, 3))
console.log(array.splice(0, 3))
console.log(array.splice(0, 3))

純函數(shù)的好處

可緩存

因?yàn)榧兒瘮?shù)對(duì)相同的輸入始終有相同的結(jié)果,所以可以把純函數(shù)的結(jié)果緩存起來

function getArea(r) {
    console.log(r)
    return Math.PI * r * r
}
function memoize(f) {
    let cache = {}
    return function() {
        let key = JSON.stringify(arguments)
        cache[key] = cache[key] || f.apply(f, arguments)
        return cache[key]
    }
}
let getAreaWithMemory = memoize(getArea)
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
// 4
// 50.26548245743669
// 50.26548245743669
// 50.26548245743669

可測試

純函數(shù)讓測試更方便

并行處理

在多線程環(huán)境下并行操作共享的內(nèi)存數(shù)據(jù)很可能會(huì)出現(xiàn)意外情況

純函數(shù)不需要訪問共享的內(nèi)存數(shù)據(jù),所以在并行環(huán)境下可以任意運(yùn)行純函數(shù) (Web Worker)

副作用

// 不純的
let mini = 18
function checkAge (age) {
  return age >= mini
}
// 純的(有硬編碼,后續(xù)可以通過柯里化解決)
function checkAge (age) {
  let mini = 18
  return age >= mini
}

副作用讓一個(gè)函數(shù)變的不純(如上例),純函數(shù)的根據(jù)相同的輸入返回相同的輸出,如果函數(shù)依賴于外部的狀態(tài)就無法保證輸出相同,就會(huì)帶來副作用。

柯里化

柯里化的概念:當(dāng)一個(gè)函數(shù)有多個(gè)參數(shù)的時(shí)候先傳遞一部分參數(shù)調(diào)用它(這部分參數(shù)以后永遠(yuǎn)不變),然后返回一個(gè)新的函數(shù)接收剩余的參數(shù),返回結(jié)果。

柯里化就可以解決上面代碼中的硬編碼問題

// 普通的純函數(shù)
function checkAge(min, age) {
    return age >= min
}
// 函數(shù)的柯里化
function checkAge(min) {
    return function(age) {
        return age >= min
    }
}
// 當(dāng)然,上面的代碼也可以用ES6中的箭頭函數(shù)來改造
const checkAge = (min) => (age => age >= min)

下面來手寫一個(gè)curry函數(shù)

function curry(func) {
  return function curriedFn(...args) {
    if (args.length < func.length) {
      return function() {
        return curriedFn(...args.concat(Array.from(arguments)))
      }
    }
    return func(...args)
  }
}

函數(shù)組合

看了這么多代碼,你肯定會(huì)覺得函數(shù)里面有很多return看起來不是很好看,事實(shí)也確是如此,所以這就要引出函數(shù)組合這個(gè)概念。

純函數(shù)和柯里化很容易寫出洋蔥代碼 h(g(f(x)))

獲取數(shù)組的最后一個(gè)元素再轉(zhuǎn)換成大寫字母, .toUpper(.first(_.reverse(array))) (這些都是lodash中的方法)

函數(shù)組合可以讓我們把細(xì)粒度的函數(shù)重新組合生成一個(gè)新的函數(shù)

你可以把其想象成一根管道,你將fn管道拆分成fn1、fn2、fn3三個(gè)管道,即將不同處理邏輯封裝在不同的函數(shù)中,然后通過一個(gè)compose函數(shù)進(jìn)行整合,將其變?yōu)橐粋€(gè)函數(shù)。

fn = compose(f1, f2, f3)
b = fn(a)

Functor(函子)

什么是Functor

  • 容器:包含值和值的變形關(guān)系(這個(gè)變形關(guān)系就是函數(shù))
  • 函子:是一個(gè)特殊的容器,通過一個(gè)普通的對(duì)象來實(shí)現(xiàn),該對(duì)象具有 map 方法,map 行一個(gè)函數(shù)對(duì)值進(jìn)行處理(變形關(guān)系)
// Functor 函子 一個(gè)容器,包裹一個(gè)值
class Container {
    constructor(value) {
        this._value = value
    }
    // map 方法,傳入變形關(guān)系,將容器里的每一個(gè)值映射到另一個(gè)容器
    map(fn) {
        return new Container(fn(this._value))
    }
}
let r = new Container(5)
    .map(x => x + 1)
    .map(x => x * x)

console.log(r)  // 36

總結(jié)

  • 函數(shù)式編程的運(yùn)算不直接操作值,而是由函子完成
  • 函子就是一個(gè)實(shí)現(xiàn)了 map 契約的對(duì)象
  • 我們可以把函子想象成一個(gè)盒子,這個(gè)盒子里封裝了一個(gè)值
  • 想要處理盒子中的值,我們需要給盒子的 map 方法傳遞一個(gè)處理值的函數(shù)(純函數(shù)),由這個(gè)函數(shù)來對(duì)值進(jìn)行處理
  • 最終 map 方法返回一個(gè)包含新值的盒子(函子)

可能你不習(xí)慣在代碼中看到new關(guān)鍵字,所以可以在容器中實(shí)現(xiàn)一個(gè)of方法。

class Container {
    static of (value) {
        return new Container(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return Container.of(fn(this._value))
    }
}

MayBe 函子

上面的代碼中如果傳入一個(gè)null 或 undefined的話,代碼就會(huì)拋出錯(cuò)誤,所以需要再實(shí)現(xiàn)一個(gè)方法

class MayBe {
    static of(value) {
        return new MayBe(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
    }
    isNothing() {
        return this._value == null // 此處雙等號(hào)等價(jià)于this._value === null || this._value === undefined
    }
}

你看下上面的代碼,是不是健壯性就好一點(diǎn)了呢?

Either函子

在MayBe函子中,很難確認(rèn)哪一步產(chǎn)生的空值問題。所以就有了Either

class Left {
    static of(value) {
        return new Left(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return this
    }
}
class Right {
    static of(value) {
        return new Right(value)
    }
    constructor(value) {
        this._value = value
    }
    map(fn) {
        return Right.of(fn(this._value))
    }
}
function parseJSON(str) {
    try {
        return Right.of(JSON.parse(str))
    } catch (e) {
        return Left.of({ error: e.message })
    }
}

到此這篇關(guān)于JavaScript函數(shù)式編程實(shí)現(xiàn)介紹的文章就介紹到這了,更多相關(guān)JS函數(shù)式編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論