偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

我眼中的JavaScript函數(shù)式編程

開發(fā) 前端
JavaScript 函數(shù)式編程是一個存在了很久的話題,但似乎從 2016 年開始,它變得越來越火熱。這可能是因為 ES6 語法對于函數(shù)式編程更為友好,也可能是因為諸如 RxJS (ReactiveX) 等函數(shù)式框架的流行。

JavaScript 函數(shù)式編程是一個存在了很久的話題,但似乎從 2016 年開始,它變得越來越火熱。這可能是因為 ES6 語法對于函數(shù)式編程更為友好,也可能是因為諸如 RxJS (ReactiveX) 等函數(shù)式框架的流行。

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

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

我理解的函數(shù)式編程

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

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

  • 語義更加清晰
  • 可復(fù)用性更高
  • 可維護(hù)性更好
  • 作用域局限,副作用少

基本的函數(shù)式編程

下面例子是一個具體的函數(shù)式體現(xiàn)

// 數(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)));

當(dāng)情況變得更加復(fù)雜時,表達(dá)式的寫法會遇到幾個問題:

  1. 表意不明顯,逐漸變得難以維護(hù)
  2. 復(fù)用性差,會產(chǎn)生更多的代碼量
  3. 會產(chǎn)生很多中間變量

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

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

鏈?zhǔn)絻?yōu)化

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

// 計算數(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ù)不斷增多,導(dǎo)致代碼的可讀性大幅下降,還很容易產(chǎn)生錯誤。

在這種情況下,我們可以考慮多種優(yōu)化方式,比如下面的 鏈?zhǔn)絻?yōu)化 。

// 優(yōu)化寫法 (嗯,你沒看錯,這就是 lodash 的鏈?zhǔn)綄懛?
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());

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

// 順序請求兩個接口

// 回調(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ù)嵌套層級和單層復(fù)雜度增加,它將會變得臃腫且難以維護(hù),而 Promise 的鏈?zhǔn)浇Y(jié)構(gòu),在高復(fù)雜度時,仍能縱向擴展,而且層次隔離很清晰。

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

閉包(Closure)

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

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

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

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

// 創(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 ,進(jìn)行了引用,導(dǎo)致局部變量無法在函數(shù)執(zhí)行結(jié)束后,被系統(tǒng)回收掉,從而產(chǎn)生了閉包。而這個閉包的作用就是,“保留住“ 了局部變量,使內(nèi)層函數(shù)調(diào)用時,可以重復(fù)使用該變量;而不同于全局變量,該變量只能在函數(shù)內(nèi)部被引用。

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

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

  1. 存在內(nèi)、外兩層函數(shù)
  2. 內(nèi)層函數(shù)對外層函數(shù)的局部變量進(jìn)行了引用

閉包的用途

閉包的主要用途就是可以定義一些作用域局限的持久化變量,這些變量可以用來做緩存或者計算的中間量等等。

// 簡單的緩存工具
// 匿名函數(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ù)作為映射的邏輯

// 數(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ù)組的風(fēng)險

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

柯里化(Currying)

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

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

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

// 獲取目標(biāo)文件對基礎(chǔ)路徑的相對路徑

// 一般寫法
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)用時就相當(dāng)于調(diào)用 path.relative ,并默認(rèn)將***個參數(shù)傳入 BASE ,后續(xù)傳入的參數(shù)順序后置。

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

組合(Composing)

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

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

// 數(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ù)復(fù)用。

自己的觀點

我理解的 JavaScript 函數(shù)式編程,可能和許多傳統(tǒng)概念不同。我并不只認(rèn)為 高階函數(shù) 算函數(shù)式編程,其他的諸如普通函數(shù)結(jié)合調(diào)用、鏈?zhǔn)浇Y(jié)構(gòu)等,我都認(rèn)為屬于函數(shù)式編程的范疇,只要他們是以函數(shù)作為主要載體的。

而我認(rèn)為函數(shù)式編程并不是必須的,它也不應(yīng)該是一個強制的規(guī)定或要求。與面向?qū)ο蠡蚱渌枷胍粯?,它也是其中一種方式。我們更多情況下,應(yīng)該是幾者的結(jié)合,而不是局限于概念。

責(zé)任編輯:張燕妮 來源: 化辰
相關(guān)推薦

2015-07-20 11:32:07

編程語言

2010-06-22 13:32:26

函數(shù)式編程JavaScript

2016-08-11 10:11:07

JavaScript函數(shù)編程

2016-08-11 10:34:37

Javascript函數(shù)編程

2017-10-26 08:53:38

前端JavaScript函數(shù)式編程

2013-10-16 11:26:45

DevOps

2012-03-21 09:30:11

ibmdw

2013-03-21 13:42:55

JSjQYUI

2020-09-23 16:07:52

JavaScript函數(shù)柯里化

2015-05-25 15:06:28

JavaScript函數(shù)式編程

2024-10-15 11:58:31

2009-06-09 13:18:56

Scala函數(shù)式命令式

2016-12-19 14:35:50

軟件系統(tǒng)

2019-08-06 09:00:00

JavaScript函數(shù)式編程前端

2013-09-09 09:41:34

2012-09-21 09:21:44

函數(shù)式編程函數(shù)式語言編程

2016-10-19 14:35:20

JavaScript函數(shù)式編程

2012-12-26 09:20:30

2013-01-17 14:38:37

Fedora 18

2012-12-25 09:43:08

點贊
收藏

51CTO技術(shù)棧公眾號