>웹 프론트엔드 >JS 튜토리얼 >독점적인 이해: JavaScript 함수형 프로그래밍

독점적인 이해: JavaScript 함수형 프로그래밍

巴扎黑
巴扎黑원래의
2017-04-29 15:48:031126검색

자바스크립트 함수형 프로그래밍은 오랜 시간 동안 화두였던 주제인데, 2016년 이후로 점점 인기가 많아진 것 같습니다. 이는 ES6 구문이 함수형 프로그래밍에 더 친숙하기 때문일 수도 있고 RxJS(ReactiveX)와 같은 함수형 프레임워크의 인기 때문일 수도 있습니다.

함수형 프로그래밍에 대한 설명을 많이 봤지만 대부분은 이론적인 수준이고 일부는 하스켈 같은 순수 함수형 프로그래밍 언어에만 해당됩니다. 이 글의 목적은 내 눈으로 보기에 JavaScript에서의 함수형 프로그래밍의 구체적인 실천에 대해 이야기하는 것입니다. "내 눈으로 보기"라고 하는 이유는 내가 말하는 내용은 단지 내 개인적인 의견일 뿐이며 일부 엄격한 개념과 충돌할 수 있다는 의미입니다.

이 글에서는 형식적인 개념 소개를 많이 생략하고 JavaScript에서 함수형 코드가 무엇인지, 함수형 코드와 일반 작성의 차이점은 무엇인지, 함수형 코드가 우리에게 가져올 수 있는 이점은 무엇인지, 공통 함수형 모델은 무엇인지 보여주는 데 중점을 둘 것입니다.

함수형 프로그래밍에 대해 내가 이해한 것

함수형 프로그래밍은 함수를 메인 캐리어로 사용하는 프로그래밍 방법, 함수를 사용하여 일반 표현식을 분해하고 추상화하는 방법

으로 이해하면 된다고 생각합니다. ​명령형과 비교했을 때, 이렇게 하면 어떤 이점이 있나요? 주요 내용은 다음과 같습니다.

  • 의미가 더 명확해졌습니다


  • 높은 재사용성


  • 유지관리성 향상


  • 범위 제한, 부작용 적음

기본 함수형 프로그래밍

다음 예는 특정 함수식

// 数组中每个单词,首字母大写


// 一般写法
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);


// 函数式写法一
function upperFirst(word) {
  return word[0].toUpperCase() + word.slice(1);
}

function wordToUpperCase(arr) {
  return arr.map(upperFirst);
}

console.log(wordToUpperCase(['apple', 'pen', 'apple-pen']));


// 函数式写法二
console.log(arr.map(['apple', 'pen', 'apple-pen'], word => word[0].toUpperCase() + word.slice(1)));

입니다. 상황이 더 복잡해지면 표현을 쓰는 방식에 여러 가지 문제가 생길 것입니다.

  1. 표현이 명확하지 않고 점차 유지하기 어려워진다


  2. 재사용성이 낮으면 더 많은 코드가 생성됩니다


  3. 중간변수가 많을 겁니다

함수형 프로그래밍은 위의 문제를 매우 잘 해결합니다. 먼저 함수 작성 방법 1을 참조하세요. 함수 캡슐화를 활용하여 함수를 분해하고(세분성은 고유하지 않음) 이를 다른 함수로 캡슐화한 다음 결합된 호출을 사용하여 목적을 달성합니다. 이를 통해 표현이 명확해지고 유지 관리, 재사용 및 확장이 쉬워집니다. 둘째, 고차 함수를 사용하여 Array.map은 배열 탐색을 위해 for...of를 대체하여 중간 변수와 연산을 줄입니다.

함수 작성 방법 1함수 작성 방법 2의 가장 큰 차이점은 함수가 향후 재사용 가능성이 있는지 여부를 고려할 수 있다는 점입니다. 그렇지 않다면 후자가 더 좋습니다.

체인 최적화

위의 함수형 작성 방법 2를 보면 함수형 코드를 작성하는 과정에서 를 가로로 확장하게 하는, 즉 여러 레이어의 중첩을 생성하기 쉽다는 것을 알 수 있습니다. 아래에 좀 더 극단적인 예를 들어보겠습니다.

아아아아

이 예는 수평 확장의 극단적인 경우만을 보여줍니다. 함수의 중첩 수준이 계속 증가할수록 코드의 가독성이 크게 떨어지며 오류가 발생하기 쉽습니다.

​이 경우에는 다음과 같은 체인 최적화 등 다양한 최적화 방법을 고려할 수 있습니다.

아아아아

이런 식으로 다시 작성하면 전체 구조가 더 명확해지고 체인의 각 링크가 수행하는 작업을 쉽게 표시할 수 있습니다. 함수 중첩과 체이닝을 비교한 또 다른 좋은 예는 콜백 함수Promise 패턴입니다.

아아아아

콜백 함수의 중첩 수준과 단일 레이어의 복잡성이 증가하면 비대해지고 유지 관리가 어려워집니다. 그러나 Promise의 체인 구조는 복잡성이 높을 때 여전히 수직으로 확장될 수 있으며 레이어 격리가 매우 명확합니다.

일반적인 함수형 프로그래밍 모델

폐쇄

지역 변수가 해제되는 것을 방지할 수 있는 코드 블록을 클로저라고 합니다

클로저의 개념은 상대적으로 추상적입니다. 누구나 이 기능을 어느 정도 알고 사용하고 있다고 생각합니다.

그렇다면 폐쇄는 우리에게 어떤 이점을 가져다 줄 수 있습니까?

먼저 클로저를 만드는 방법을 살펴보겠습니다.

// 计算数字之和


// 一般写法
console.log(1 + 2 + 3 - 4)


// 函数式写法
function sum(a, b) {
  return a + b;
}

function sub(a, b) {
  return a - b;
}

console.log(sub(sum(sum(1, 2), 3), 4);

makeCounter 함수의 코드 블록은 반환된 함수에서 로컬 변수 k를 참조하므로 함수가 실행된 후 시스템에서 로컬 변수를 재활용할 수 없게 되어 클로저가 생성됩니다. 이 클로저의 기능은 내부 함수가 호출될 때 변수를 재사용할 수 있도록 지역 변수를 "유지"하는 것입니다. 전역 변수와 달리 이 변수는 함수 내부에서만 참조될 수 있습니다.

즉, 클로저는 실제로 함수 전용인 "영구 변수"를 생성합니다.

따라서 이 예에서 클로저 생성 조건은 다음과 같다는 결론을 내릴 수 있습니다.

  1. 두 가지 수준의 기능이 있습니다


  2. 内层函数对外层函数的局部变量进行了引用

  闭包的用途

  闭包的主要用途就是可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量等等。

// 简单的缓存工具
// 匿名函数创造了一个闭包
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

  上面例子是一个简单的缓存工具的实现,匿名函数创造了一个闭包,使得 store 对象 ,一直可以被引用,不会被回收。

  闭包的弊端

  持久化变量不会被正常释放,持续占用内存空间,很容易造成内存浪费,所以一般需要一些额外手动的清理机制。

  高阶函数

接受或者返回一个函数的函数称为高阶函数

  听上去很高冷的一个词汇,但是其实我们经常用到,只是原来不知道他们的名字而已。JavaScript 语言是原生支持高阶函数的,因为 JavaScript 的函数是一等公民,它既可以作为参数又可以作为另一个函数的返回值使用。

  我们经常可以在 JavaScript 中见到许多原生的高阶函数,例如 Array.map , Array.reduce , Array.filter

  下面以 map 为例,我们看看他是如何使用的

  map (映射)

映射是对集合而言的,即把集合的每一项都做相同的变换,产生一个新的集合

  map 作为一个高阶函数,他接受一个函数参数作为映射的逻辑

// 数组中每一项加一,组成一个新数组


// 一般写法
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 循环的方式遍历数组会产生额外的操作,而且有改变原数组的风险

  而 map 函数封装了必要的操作,使我们仅需要关心映射逻辑的函数实现即可,减少了代码量,也降低了副作用产生的风险。

  柯里化(Currying)

给定一个函数的部分参数,生成一个接受其他参数的新函数

  可能不常听到这个名词,但是用过 undescore 或 lodash 的人都见过他。

  有一个神奇的 _.partial 函数,它就是柯里化的实现

// 获取目标文件对基础路径的相对路径

// 一般写法
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 ,我们得到了新的函数 relativeFromBase ,这个函数在调用时就相当于调用 path.relative ,并默认将第一个参数传入 BASE ,后续传入的参数顺序后置。

  本例中,我们真正想完成的操作是每次获得相对于 BASE 的路径,而非相对于任何路径。柯里化可以使我们只关心函数的部分参数,使函数的用途更加清晰,调用更加简单。

  组合(Composing)

将多个函数的能力合并,创造一个新的函数

  同样你第一次见到他可能还是在 lodash 中,compose 方法(现在叫 flow)

// 数组中每个单词大写,做 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 将转大写和转 Base64 的函数的能力合并,生成一个新的函数。方便作为参数函数或后续复用。

 自己的观点

  我理解的 JavaScript 函数式编程,可能和许多传统概念不同。我并不只认为 高阶函数 算函数式编程,其他的诸如普通函数结合调用、链式结构等,我都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

  而我认为函数式编程并不是必须的,它也不应该是一个强制的规定或要求。与面向对象或其他思想一样,它也是其中一种方式。我们更多情况下,应该是几者的结合,而不是局限于概念。

위 내용은 독점적인 이해: JavaScript 함수형 프로그래밍의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.