предисловие
Функциональное программирование стало очень горячей темой во фронтенде. За последние несколько лет мы видели множество кодовых баз приложений, интенсивно использующих идеи функционального программирования.
В этой статье мы не будем вводить эти малопонятные понятия и сосредоточимся на том, что такое функциональный код в JavaScript, в чем разница между декларативным и императивным кодом и каковы общие функциональные модели?Чтобы прочитать больше качественных статей, нажмитеБлог GitHub
1. Что такое функциональное программирование
Функциональное программирование — это парадигма программирования, которая в основном использует функции для инкапсуляции рабочего процесса и вычисляет результаты, комбинируя различные функции. Функциональное программирование означает, что вы можете написать меньше кода с ошибками за меньшее время. В качестве простого примера предположим, что мы хотим набрать строкуfunctional programming is great
Чтобы сделать первую букву каждого слова заглавной, мы можем сделать это:
var string = 'functional programming is great';
var result = string
.split(' ')
.map(v => v.slice(0, 1).toUpperCase() + v.slice(1))
.join(' ');
В приведенном выше примере сначала используется разделение для преобразования строки в массив, затем первая буква каждого элемента преобразуется в верхний регистр с помощью карты и, наконец, преобразование массива в строку с помощью соединения. Весь процессjoin(map(split(str)))
, в котором воплощена основная идея функционального программирования:Преобразование данных с помощью функций.
Отсюда мы видим, что функциональное программирование имеет две основные характеристики:
- Преобразование данных с помощью функций
- Поиск результата путем объединения нескольких функций
2. Противопоставление декларативного и императивного
-
Императив: мы заставляем компьютер выполнять какое-то действие, записывая одну инструкцию за другой, что обычно включает в себя множество сложных деталей. Операторы часто используются в императивном коде для достижения определенного поведения. Например, для, если, переключатель, бросок и т. д.
-
Декларативный: мы объявляем, что хотим сделать, написав выражения, а не пошаговые инструкции. Выражение обычно представляет собой композицию вызова некоторой функции, некоторого значения и оператора, вычисляющего результирующее значение.
//命令式
var CEOs = [];
for(var i = 0; i < companies.length; i++){
CEOs.push(companies[i].CEO)
}
//声明式
var CEOs = companies.map(c => c.CEO);
Из вышеприведенного примера видно, что декларативное написание — это выражение, и нам не нужно заботиться о том, как перебирать счетчик и как собирать возвращаемый массив, оно указывает, что делать, а не как.Очевидным преимуществом функционального программирования является декларативный код., Для чистых функций без побочных эффектов мы можем полностью игнорировать то, как реализована функция, и сосредоточиться на написании бизнес-кода.
3. Общие черты
Нет побочных эффектов
Это означает, что внешнее состояние не будет изменено при вызове функции, то есть функция по-прежнему будет возвращать тот же результат после n вызовов.
var a = 1;
// 含有副作用,它修改了外部变量 a
// 多次调用结果不一样
function test1() {
a++
return a;
}
// 无副作用,没有修改外部状态
// 多次调用结果一样
function test2(a) {
return a + 1;
}
прозрачная ссылка
Это означает, что функция будет использовать только переданные ей переменные и переменные, созданные ею самой, и не будет использовать другие переменные.
var a = 1;
var b = 2;
// 函数内部使用的变量并不属于它的作用域
function test1() {
return a + b;
}
// 函数内部使用的变量是显式传递进去的
function test2(a, b) {
return a + b;
}
неизменяемая переменная
Это означает, что после создания переменной ее нельзя изменить, и любая модификация приведет к созданию новой переменной. Самым большим преимуществом использования неизменяемых переменных является безопасность потоков. Несколько потоков могут одновременно обращаться к одной и той же неизменяемой переменной, что упрощает достижение параллелизма. Поскольку JavaScript изначально не поддерживает неизменяемые переменные, его необходимо реализовать через стороннюю библиотеку. (например, Immutable.js, Mori и т. д.)
var obj = Immutable({ a: 1 });
var obj2 = obj.set('a', 2);
console.log(obj); // Immutable({ a: 1 })
console.log(obj2); // Immutable({ a: 2 })
Функции являются гражданами первого класса
Мы часто говорим, что функции являются «гражданами первого класса» JavaScript, что означает, что функции находятся наравне с другими типами данных и могут быть назначены другим переменным, переданы в качестве параметров, переданы в другую функцию или использованы как другие типы данных. функции Возвращаемое значение. Замыкания, функции высшего порядка, каррирование функций и композиция функций, описанные ниже, — все это приложения вокруг этой возможности.
4. Общие модели функционального программирования
1. Закрытие
Функция является замыканием, если она ссылается на свободные переменные. Что такое свободные переменные? Свободные переменные относятся к переменным, которые не принадлежат области действия функции (все глобальные переменные являются свободными переменными, строго говоря, функции, которые ссылаются на глобальные переменные, являются замыканиями, но такие замыкания бесполезны, обычно мы говорим, что замыкание относится к функции внутри функция).
Условия формирования замыкания:
- Существуют внутренние и внешние функции.
- Внутренняя функция ссылается на локальные переменные внешней функции.
Использование замыканий:Вы можете определить некоторые постоянные переменные с ограниченной областью действия, которые можно использовать для кэширования или промежуточных вычислений и т. д..
// 简单的缓存工具
// 匿名函数创造了一个闭包
const cache = (function() {
const store = {};
return {
get(key) {
return store[key];
},
set(key, val) {
store[key] = val;
}
}
}());
console.log(cache) //{get: ƒ, set: ƒ}
cache.set('a', 1);
cache.get('a'); // 1
Приведенный выше пример представляет собой реализацию простого инструмента кэширования.Анонимная функция создает замыкание, так что на объект хранилища всегда можно сослаться, и он не будет повторно использован.
Недостатки закрытия:Постоянные переменные не будут нормально освобождаться и будут продолжать занимать место в памяти, что может легко привести к трате памяти., поэтому обычно требуется дополнительный механизм ручной очистки.
2. Функции высшего порядка
Функциональное программирование имеет тенденцию повторно использовать общий набор функциональных возможностей для обработки данных с использованием функций более высокого порядка.Функция высшего порядка относится к функции, которая принимает функцию в качестве аргумента, принимает функцию в качестве возвращаемого значения или принимает функцию в качестве аргумента и функцию в качестве возвращаемого значения..
Функции высшего порядка часто используются для:
- Абстрагируйте или изолируйте поведение, роли, асинхронный поток управления в виде функций обратного вызова, промисов, монад и т. д.
- Создавайте функции, которые можно использовать для различных типов данных.
- Частично применить к аргументам функции (частичное приложение функции) или создать каррированную функцию для повторного использования или композиции функций.
- Принимает список функций и возвращает некоторую составную функцию, состоящую из функций из этого списка.
Язык JavaScript изначально поддерживает функции высшего порядка. Например, Array.prototype.map, Array.prototype.filter и Array.prototype.reduce являются встроенными функциями высшего порядка в JavaScript. Использование функций высшего порядка сделает наш код яснее и короче..
map
Метод map() создает новый массив, результат которого является результатом вызова предоставленной функции для каждого элемента массива. map не изменяет исходный массив.
Предположим, у нас есть массив объектов, содержащих свойства name и kind, мы хотим, чтобы все свойства name в этом массиве были помещены в новый массив, как этого добиться?
// 不使用高阶函数
var animals = [
{ name: "Fluffykins", species: "rabbit" },
{ name: "Caro", species: "dog" },
{ name: "Hamilton", species: "dog" },
{ name: "Harold", species: "fish" },
{ name: "Ursula", species: "cat" },
{ name: "Jimmy", species: "fish" }
];
var names = [];
for (let i = 0; i < animals.length; i++) {
names.push(animals[i].name);
}
console.log(names); //["Fluffykins", "Caro", "Hamilton", "Harold", "Ursula", "Jimmy"]
// 使用高阶函数
var animals = [
{ name: "Fluffykins", species: "rabbit" },
{ name: "Caro", species: "dog" },
{ name: "Hamilton", species: "dog" },
{ name: "Harold", species: "fish" },
{ name: "Ursula", species: "cat" },
{ name: "Jimmy", species: "fish" }
];
var names = animals.map(x=>x.name);
console.log(names); //["Fluffykins", "Caro", "Hamilton", "Harold", "Ursula", "Jimmy"]
filter
Метод filter() создает новый массив, содержащий все элементы, проверенные функцией обратного вызова. filter вызывает функцию обратного вызова один раз для каждого элемента в массиве.Функция обратного вызова возвращает true, если элемент проходит тест и сохраняет элемент, и false, если это не так. filter не изменяет исходный массив, он возвращает новый отфильтрованный массив.
Предположим, у нас есть массив объектов, содержащих свойства name и kind. Мы хотим создать массив, содержащий только собак (вид: «собака»). Как этого добиться?
// 不使用高阶函数
var animals = [
{ name: "Fluffykins", species: "rabbit" },
{ name: "Caro", species: "dog" },
{ name: "Hamilton", species: "dog" },
{ name: "Harold", species: "fish" },
{ name: "Ursula", species: "cat" },
{ name: "Jimmy", species: "fish" }
];
var dogs = [];
for (var i = 0; i < animals.length; i++) {
if (animals[i].species === "dog") dogs.push(animals[i]);
}
console.log(dogs);
// 使用高阶函数
var animals = [
{ name: "Fluffykins", species: "rabbit" },
{ name: "Caro", species: "dog" },
{ name: "Hamilton", species: "dog" },
{ name: "Harold", species: "fish" },
{ name: "Ursula", species: "cat" },
{ name: "Jimmy", species: "fish" }
];
var dogs = animals.filter(x => x.species === "dog");
console.log(dogs); // {name: "Caro", species: "dog"}
// { name: "Hamilton", species: "dog" }
reduce
Метод сокращения выполняет функцию обратного вызова для каждого элемента вызывающего массива, в конечном итоге создавая одно значение и возвращая его. Метод редукции принимает два параметра: 1) функцию редуктора (обратный вызов), 2) необязательное начальное значение.
Предположим, мы хотим суммировать массив:
// 不使用高阶函数
const arr = [5, 7, 1, 8, 4];
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum = sum + arr[i];
}
console.log(sum);//25
// 使用高阶函数
const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue,0);
console.log(sum)//25
Мы можем наглядно показать разницу между этими тремя на следующем рисунке:
3. Каррирование функций
Каррирование также известно как частичная оценка.Каррированная функция получит некоторые параметры, а затем не будет оцениваться немедленно, а продолжит возвращать новую функцию, сохранять входящие параметры в виде замыканий и ждать, пока они будут фактически оценены. При этом все входящие параметры оцениваются одновременно.
// 普通函数
function add(x,y){
return x + y;
}
add(1,2); // 3
// 函数柯里化
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
increment(2);// 3
Здесь мы определяем функцию добавления, которая принимает один аргумент и возвращает новую функцию. После вызова add возвращаемая функция запоминает первый параметр add через замыкание. Итак, как нам реализовать простую каррированную функцию?
function curryIt(fn) {
// 参数fn函数的参数个数
var n = fn.length;
var args = [];
return function(arg) {
args.push(arg);
if (args.length < n) {
return arguments.callee; // 返回这个函数的引用
} else {
return fn.apply(this, args);
}
};
}
function add(a, b, c) {
return [a, b, c];
}
var c = curryIt(add);
var c1 = c(1);
var c2 = c1(2);
var c3 = c2(3);
console.log(c3); //[1, 2, 3]
Из этого мы видим, что каррирование — это метод «предварительной загрузки» функций путем передачи меньшего количества параметров для получения новой функции, которая уже помнит эти параметры. писать функции!
4. Функциональная композиция (Composition)
Как упоминалось ранее, одной из отличительных черт функционального программирования является оценка функций путем их объединения. Однако по мере увеличения количества конкатенированных функций читабельность кода снижается. Композиция функций - это метод, используемый для решения этой проблемы. Предположим, есть функция компоновки, которая принимает несколько функций в качестве аргументов и возвращает новую функцию. Когда мы передаем аргумент этой новой функции, аргумент «проходит» через функцию внутри нее и возвращает результат.
//两个函数的组合
var compose = function(f, g) {
return function(x) {
return f(g(x));
};
};
//或者
var compose = (f, g) => (x => f(g(x)));
var add1 = x => x + 1;
var mul5 = x => x * 5;
compose(mul5, add1)(2);// =>15
Порекомендуйте полезный инструмент мониторинга ошибок для всехFundebug, добро пожаловать, чтобы попробовать это бесплатно!
Добро пожаловать в публичный аккаунт:Мастер по фронтенду, мы будем свидетелями вашего роста вместе!
Справочная статья
- Архитектурный класс Everest (настоятельно рекомендуется)
- Документация MDN
- What is Functional Programming?
- So You Want to be a Functional Programmer
- Понимание функций высшего порядка в JavaScript
- Что я знаю о функциональном программировании
- Руководство по функциональному программированию JS
- Функциональное программирование JavaScript (1)
- Функциональное программирование JavaScript, как я его вижу