溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JavaScript函數(shù)式編程的示例分析

發(fā)布時間:2022-03-23 09:44:06 來源:億速云 閱讀:123 作者:小新 欄目:web開發(fā)

這篇文章給大家分享的是有關JavaScript函數(shù)式編程的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

看過許多關于函數(shù)式編程的講解,但是其中大部分是停留在理論層面,還有一些是僅針對 Haskell 等純函數(shù)式編程語言的。而本文旨在聊一聊我眼中的函數(shù)式編程在 JavaScript 中的具體實踐,之所以是 “我眼中的” 即我所說的僅代表個人觀點,可能和部分 嚴格概念 是有沖突的。

本文將略去一大堆形式化的概念介紹,重點展示在 JavaScript 中到底什么是函數(shù)式的代碼、函數(shù)式代碼與一般寫法有什么區(qū)別、函數(shù)式的代碼能給我們帶來什么好處以及常見的一些函數(shù)式模型都有哪些。

我理解的函數(shù)式編程
我認為函數(shù)式編程可以理解為,以函數(shù)作為主要載體的編程方式,用函數(shù)去拆解、抽象一般的表達式

與命令式相比,這樣做的好處在哪?主要有以下幾點:

語義更加清晰
可復用性更高
可維護性更好
作用域局限,副作用少
基本的函數(shù)式編程
下面例子是一個具體的函數(shù)式體現(xiàn)

Javascript代碼

// 數(shù)組中每個單詞,首字母大寫  // 一般寫法  const arr = ['apple', 'pen', 'apple-pen'];  for(const i in arr){  
  const c = arr[i][0];  
  arr[i] = c.toUpperCase() + arr[i].slice(1);  }  
  console.log(arr);  
  
  // 函數(shù)式寫法一  function upperFirst(word) {  
  return word[0].toUpperCase() + word.slice(1);  }  
  function wordToUpperCase(arr) {  
  return arr.map(upperFirst);  }  
  console.log(wordToUpperCase(['apple', 'pen', 'apple-pen']));  
  
  // 函數(shù)式寫法二  console.log(arr.map(['apple', 'pen', 'apple-pen'], word => word[0].toUpperCase() + word.slice(1)));

當情況變得更加復雜時,表達式的寫法會遇到幾個問題:

表意不明顯,逐漸變得難以維護
復用性差,會產(chǎn)生更多的代碼量
會產(chǎn)生很多中間變量
函數(shù)式編程很好的解決了上述問題。首先參看 函數(shù)式寫法一,它利用了函數(shù)封裝性將功能做拆解(粒度不唯一),并封裝為不同的函數(shù),而再利用組合的調(diào)用達到目的。這樣做使得表意清晰,易于維護、復用以及擴展。其次利用 高階函數(shù),Array.map 代替 for…of 做數(shù)組遍歷,減少了中間變量和操作。

而 函數(shù)式寫法一 和 函數(shù)式寫法二 之間的主要差別在于,可以考慮函數(shù)是否后續(xù)有復用的可能,如果沒有,則后者更優(yōu)。

鏈式優(yōu)化

從上面 函數(shù)式寫法二 中我們可以看出,函數(shù)式代碼在寫的過程中,很容易造成 橫向延展,即產(chǎn)生多層嵌套,下面我們舉個比較極端點的例子。

Javascript代碼

// 計算數(shù)字之和  
  // 一般寫法  console.log(1 + 2 + 3 - 4)  
  
  // 函數(shù)式寫法  function sum(a, b) {  
  return a + b;  }  
  function sub(a, b) {  
  return a - b;  }  
  console.log(sub(sum(sum(1, 2), 3), 4);  本例僅為展示 橫向延展 的比較極端的情況,隨著函數(shù)的嵌套層數(shù)不斷增多,導致代碼的可讀性大幅下降,還很容易產(chǎn)生錯誤。 

在這種情況下,我們可以考慮多種優(yōu)化方式,比如下面的 鏈式優(yōu)化 。 
// 優(yōu)化寫法 (嗯,你沒看錯,這就是 lodash 的鏈式寫法) Javascript代碼 


const utils = {  
  chain(a) {  
    this._temp = a;  
    return this;  
  },  
  sum(b) {  
    this._temp += b;  
    return this;  
  },  
  sub(b) {  
    this._temp -= b;  
    return this;  
  },  
  value() {  
    const _temp = this._temp;  
    this._temp = undefined;  
    return _temp;  
  }  };  
  console.log(utils.chain(1).sum(2).sum(3).sub(4).value());

這樣改寫后,結構會整體變得比較清晰,而且鏈的每一環(huán)在做什么也可以很容易的展現(xiàn)出來。函數(shù)的嵌套和鏈式的對比還有一個很好的例子,那就是 回調(diào)函數(shù) 和 Promise 模式。

Javascript代碼

// 順序請求兩個接口  
  
  // 回調(diào)函數(shù)  import $ from 'jquery';  $.post('a/url/to/target', (rs) => {  
  if(rs){  
    $.post('a/url/to/another/target', (rs2) => {  
      if(rs2){  
        $.post('a/url/to/third/target');  
      }  
    });  
  }  });  
  
  // Promise  import request from 'catta';  // catta 是一個輕量級請求工具,支持 fetch,jsonp,ajax,無依賴  request('a/url/to/target')  
  .then(rs => rs ? $.post('a/url/to/another/target') : Promise.reject())  
  .then(rs2 => rs2 ? $.post('a/url/to/third/target') : Promise.reject());

隨著回調(diào)函數(shù)嵌套層級和單層復雜度增加,它將會變得臃腫且難以維護,而 Promise 的鏈式結構,在高復雜度時,仍能縱向擴展,而且層次隔離很清晰。

常見的函數(shù)式編程模型

閉包(Closure)

可以保留局部變量不被釋放的代碼塊,被稱為一個閉包

閉包的概念比較抽象,相信大家都或多或少知道、用到這個特性

那么閉包到底能給我們帶來什么好處?

先來看一下如何創(chuàng)建一個閉包:

Javascript代碼

// 創(chuàng)建一個閉包  function makeCounter() {  
  let k = 0;  
  
  return function() {  
    return ++k;  
  };  }  
  const counter = makeCounter();  
  console.log(counter());  // 1  console.log(counter());  // 2

makeCounter 這個函數(shù)的代碼塊,在返回的函數(shù)中,對局部變量 k ,進行了引用,導致局部變量無法在函數(shù)執(zhí)行結束后,被系統(tǒng)回收掉,從而產(chǎn)生了閉包。而這個閉包的作用就是,“保留住“ 了局部變量,使內(nèi)層函數(shù)調(diào)用時,可以重復使用該變量;而不同于全局變量,該變量只能在函數(shù)內(nèi)部被引用。

換句話說,閉包其實就是創(chuàng)造出了一些函數(shù)私有的 ”持久化變量“。

所以從這個例子,我們可以總結出,閉包的創(chuàng)造條件是:

存在內(nèi)、外兩層函數(shù)
內(nèi)層函數(shù)對外層函數(shù)的局部變量進行了引用
閉包的用途
閉包的主要用途就是可以定義一些作用域局限的持久化變量,這些變量可以用來做緩存或者計算的中間量等等。

Javascript代碼

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

上面例子是一個簡單的緩存工具的實現(xiàn),匿名函數(shù)創(chuàng)造了一個閉包,使得 store 對象 ,一直可以被引用,不會被回收。

閉包的弊端
持久化變量不會被正常釋放,持續(xù)占用內(nèi)存空間,很容易造成內(nèi)存浪費,所以一般需要一些額外手動的清理機制。

高階函數(shù)

接受或者返回一個函數(shù)的函數(shù)稱為高階函數(shù)

聽上去很高冷的一個詞匯,但是其實我們經(jīng)常用到,只是原來不知道他們的名字而已。JavaScript 語言是原生支持高階函數(shù)的,因為 JavaScript 的函數(shù)是一等公民,它既可以作為參數(shù)又可以作為另一個函數(shù)的返回值使用。

我們經(jīng)常可以在 JavaScript 中見到許多原生的高階函數(shù),例如 Array.map , Array.reduce , Array.filter

下面以 map 為例,我們看看他是如何使用的

map (映射)

映射是對集合而言的,即把集合的每一項都做相同的變換,產(chǎn)生一個新的集合

map 作為一個高階函數(shù),他接受一個函數(shù)參數(shù)作為映射的邏輯

Javascript代碼

// 數(shù)組中每一項加一,組成一個新數(shù)組  
  // 一般寫法  const arr = [1,2,3];  const rs = [];  for(const n of arr){  
  rs.push(++n);  }  console.log(rs)  
  
  // map改寫  const arr = [1,2,3];  const rs = arr.map(n => ++n);

上面一般寫法,利用 for…of 循環(huán)的方式遍歷數(shù)組會產(chǎn)生額外的操作,而且有改變原數(shù)組的風險

而 map 函數(shù)封裝了必要的操作,使我們僅需要關心映射邏輯的函數(shù)實現(xiàn)即可,減少了代碼量,也降低了副作用產(chǎn)生的風險。

柯里化(Currying)

給定一個函數(shù)的部分參數(shù),生成一個接受其他參數(shù)的新函數(shù)

可能不常聽到這個名詞,但是用過 undescore 或 lodash 的人都見過他。

有一個神奇的 _.partial 函數(shù),它就是柯里化的實現(xiàn)

Javascript代碼

// 獲取目標文件對基礎路徑的相對路徑  
  
  // 一般寫法  const BASE = '/path/to/base';  const relativePath = path.relative(BASE, '/some/path');  
  
  // _.parical 改寫  const BASE = '/path/to/base';  const relativeFromBase = _.partial(path.relative, BASE);  
  const relativePath = relativeFromBase('/some/path');

通過 _.partial ,我們得到了新的函數(shù) relativeFromBase ,這個函數(shù)在調(diào)用時就相當于調(diào)用 path.relative ,并默認將第一個參數(shù)傳入 BASE ,后續(xù)傳入的參數(shù)順序后置。

本例中,我們真正想完成的操作是每次獲得相對于 BASE 的路徑,而非相對于任何路徑??吕锘梢允刮覀冎魂P心函數(shù)的部分參數(shù),使函數(shù)的用途更加清晰,調(diào)用更加簡單。

組合(Composing)

將多個函數(shù)的能力合并,創(chuàng)造一個新的函數(shù)

同樣你第一次見到他可能還是在 lodash 中,compose 方法(現(xiàn)在叫 flow)

Javascript代碼

// 數(shù)組中每個單詞大寫,做 Base64  
  
  // 一般寫法 (其中一種)  const arr = ['pen', 'apple', 'applypen'];  const rs = [];  for(const w of arr){  
  rs.push(btoa(w.toUpperCase()));  }  console.log(rs);  
  
  // _.flow 改寫  const arr = ['pen', 'apple', 'applypen'];  
  const upperAndBase64 = _.partialRight(_.map, _.flow(_.upperCase, btoa));  
  console.log(upperAndBase64(arr));

_.flow 將轉(zhuǎn)大寫和轉(zhuǎn) Base64 的函數(shù)的能力合并,生成一個新的函數(shù)。方便作為參數(shù)函數(shù)或后續(xù)復用。

自己的觀點
我理解的 JavaScript 函數(shù)式編程,可能和許多傳統(tǒng)概念不同。我并不只認為 高階函數(shù) 算函數(shù)式編程,其他的諸如普通函數(shù)結合調(diào)用、鏈式結構等,我都認為屬于函數(shù)式編程的范疇,只要他們是以函數(shù)作為主要載體的。

而我認為函數(shù)式編程并不是必須的,它也不應該是一個強制的規(guī)定或要求。與面向?qū)ο蠡蚱渌枷胍粯樱彩瞧渲幸环N方式。我們更多情況下,應該是幾者的結合,而不是局限于概念。

感謝各位的閱讀!關于“JavaScript函數(shù)式編程的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI