Roman Ponomarev
Apr 12, 2017 · 4 min read

Перевод статьи Joel Thoms: Functional JavaScript: Decoupling methods from their objects

В своих проектах я всегда делаю одну вещь: развязку (decoupling) методов из их объектов. map, filter и reduce - не единственные, но, безусловно, одни из первых, кому дается свобода.

Так как же это выглядит?

Чтобы все было проще, давайте ограничимся развязкой map из массива. К счастью, прототипное наследование JavaScript облегчает нашу задачу, поскольку желаемая функция сидит на Array.prototype.map. Одно замечательное свойство JavaScript заключается в том, что мы можем вызвать этот метод напрямую. Нам просто нужно использовать .call, потому что map ожидает параметр this.

Приправьте сюда немного каррирования (будущая статья), и окажется, что нам был нужен всего лишь небольшой однострочник…

const map = f => x => Array.prototype.map.call(x, f)

Теперь мы можем вызывать нашу функцию map вне массива!

Альтернативные способы вызова map

Существует много разных способов вызова map, но из-за оптимизации движком V8 сколько нибудь реальной разницы в производительности нет.

Любая разница в производительности не является существенной, и эти числа будут меняться постоянно. Можно считать производительность этих вызовов равной.

Как развязка методов делает мою жизнь лучше?

Вот это отличный вопрос! Возможно, лучший вопрос. Я думаю, что это лучше всего объяснить кодом, а не словами, так что давайте просто окунемся в него.

document.querySelectorAll (и аналогичные методы) не возвращают Array, они возвращают NodeList, а NodeList не содержит метода map. Есть некоторые фокусы, которые вы можете сделать, чтобы преобразовать NodeList в массив, но преобразование не требуется, поскольку наша map может перебирать NodeList так, как если бы это был массив.

const items = document.querySelectorAll('div')items.map(doSomething)
// => Uncaught TypeError: items.map is not a function
map(doSomething)(items)
// => [<div/>, ..., <div/>]

Мы можем даже осуществлять map по строке без необходимости сначала преобразовывать ее в массив символов.

const value = 'Kitty Cat'value.map(doSomething)
// => Uncaught TypeError: items.map is not a function
map(doSomething)(value)
// => ['K', 'i', 't', 't', 'y', ' ', 'C', 'a', 't']

Развязка позволяет нам легко превратить сопоставление по объекту в сопоставление по списку:

const getFullName = ({ first, last }) => `${first} ${last}`getFullName({ first: 'Max', last: 'Power' })
// => 'Max Power'
map(getFullName)([
{ first: 'Max', last: 'Power' },
{ first: 'Disco', last: 'Stu' },
{ first: 'Joe', last: 'Kickass' }
])
// => ['Max Power', 'Disco Stu', 'Joe Kickass']

Мы можем даже осуществлять map по объектам.

const obj = {
0: 4,
1: 5,
2: 6,
length: 3
}
map(increase)(obj)
// => [5, 6, 7]

Развязка позволяет нам составлять функции:

const mapDoStuff = map(doStuff)
const mapDoSomething = map(doSomething)
// composing 2 mappings
const mapDoSomethingThenStuff =
compose(mapDoStuff, mapDoSomething)

Развязка (вместе с каррированием) позволяет частично применять аргументы функции и создавать новые функции.

const increaseOne = x => x + 1// partially applied map increase
const increaseMany = map(increaseOne)
increaseMany([1, 2, 3])
// => [2, 3, 4]

Попрощайтесь с this!!!

const cat = {
sound: 'meow',
speak: function() {
console.log(this.sound)
}
}
const catSpeak = cat.speakcat.speak()
// => 'meow'
catSpeak()
// => Uncaught TypeError: Cannot read property 'sound' of undefined

В этом примере работает cat.speak, но catSpeak не работает, потому что контекст this изменился. Какой ужас! Вместо этого мы можем развязать метод speak и никогда больше не беспокоиться о this!

сonst cat = {sound: 'meow'}
сonst speak = ({sound}) => console.log (sound)
speak(cat)
// => 'meow'

Затем мы можем создавать новые функции, использующие наши развязанные функции.

const cat = { sound: 'meow' }
const speak = ({ sound }) => console.log(sound)
const speakLoudly = obj =>
speak({ ...obj, sound: obj.sound.toUpperCase() + '!' })
speak(cat)
// => 'meow'
speakLoudly(cat)
// => 'MEOW!'

Итог

Сегодня мы узнали много преимуществ развязки методов и их извлечения из их объектов. Развязка позволяет использовать функцию в большем количестве мест и с различными типами объектов, а также открывать ее для компоновки с другими функциями. Мы также исключаем все ссылки к контексту this - одного этого для меня уже достаточно!

Я знаю, что это мелочь, но это делает мой день, когда я получаю уведомления, что кто-то подписался на меня в Medium или Twitter (@joelnet). Или, если вы думаете, что я несу чушь, скажите это мне в комментариях ниже (прим. пер.: соответствующим образом, дорогой читатель, вы можете поступить и относительно этого или любого другого перевода).

Увидимся!

Связанные статьи


Читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook. Скоро подъедет подкаст, не теряйтесь.

Статья на GitHub

devSchacht

Подкаст. Переводы. Веб-разработка.

Roman Ponomarev

Written by

arrival.com developer, youknow.st activist, medium.com/maria-machine author; github.com/maksugr

devSchacht

Подкаст. Переводы. Веб-разработка.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade