>  기사  >  웹 프론트엔드  >  JavaScript 함수형 프로그래밍 이해

JavaScript 함수형 프로그래밍 이해

怪我咯
怪我咯원래의
2017-04-05 13:46:431619검색

 JavaScript 함수FormulaProgramming은 존재다 오랫동안 화제가 되었는데, 2016년부터 점점 인기가 많아진 것 같습니다. 이는 ES6 구문이 함수형 프로그래밍에 더 친숙하기 때문일 수도 있고, RxJS(ReactiveX)와 같은 함수형 프레임워크의 인기 때문일 수도 있습니다.

함수형 프로그래밍에 대한 설명을 많이 봤지만 대부분 이론적인 수준이고 일부는 Haskell과 같은 순수 플레이어만을 위한 것입니다. . 기능적

프로그래밍 언어

. 이 글의 목적은 내 눈으로 보기에 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. 변수

    가 많이 생성됩니다

  4. 함수형 프로그래밍은 위의 문제를 매우 잘 해결합니다. 먼저
함수 작성 방법 1

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

기능적 작성 방법 1

기능적 작성 방법 2의 주요 차이점은 해당 기능이 향후 재사용 가능성이 있는지 여부를 고려할 수 있다는 것입니다. , 그렇다면 후자가 더 좋습니다. 체인 최적화

위의

함수형 작성 방법 2

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

// 计算数字之和


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

수평 확장

의 극단적인 경우만을 보여줍니다. 함수의 중첩 레이어 수가 계속 증가할수록 코드의 가독성이 크게 떨어지며, 실수를 야기합니다. 이 경우 다음

체인 최적화

와 같은 여러 가지 최적화 방법을 고려할 수 있습니다.

// 优化写法 (嗯,你没看错,这就是 lodash 的链式写法)
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());
이렇게 다시 작성하면 전체적인 구조가 더욱 명확해지고, 체인의 각 링크가 무엇을 하는지 쉽게 표시할 수 있습니다. 함수 중첩과 연결의 비교에 대한 또 다른 좋은 예는

콜백 함수Promise 패턴입니다.

// 顺序请求两个接口

// 回调函数
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());
콜백 함수의 중첩 수준과 단일 레이어의 복잡성이 증가함에 따라 비대해지고 유지 관리가 어려워집니다. 그러나 Promise의 체인 구조는 복잡성이 높을 때 여전히 수직으로 확장될 수 있으며, 레벨 구분이 명확합니다.

공통 함수형 프로그래밍 모델

클로저

지역 변수를 유지하고 해제되지 않는 코드 블록을 클로저라고 합니다

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

그렇다면 클로저가 우리에게 어떤 이점을 가져올 수 있을까요?

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

// 创建一个闭包
function makeCounter() {
  let k = 0;

  return function() {
    return ++k;
  };
}

const counter = makeCounter();

console.log(counter());  // 1
console.log(counter());  // 2

  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으로 문의하세요.