Pull to refresh

5 возможностей JavaScript, без которых я не мог бы писать код

Reading time 6 min
Views 13K
Доброго времени суток, друзья!

Прежде чем мы начнем, позвольте мне немного рассказать о коде, который я пишу. Почти весь мой код состоит из Javascript, плюс немного HTML и CSS. Я пишу как клиентский, так и серверный JS. Я тестирую свой код. Я создаю и распространяю библиотеки с открытым исходным кодом, которые используются тысячами разработчиков по всему миру. Для фронтенда я использую React, для бэкенда — Express или бессерверные вычисления.

Вот 5 особенностей JS, без которых я не мог бы писать код. В произвольном порядке. Разумеется, «без которых я не мог бы писать код» — это гипербола. Это «фичи», которые мне по-настоящему нравятся и используются мной постоянно.

1. Деструктуризация


Я использую эту фичу почти во всех файлах. Неважно, о React-компоненте идет речь или о функции, принимающей аргументы, деструктуризация — прикольная штука.

Вот несколько примеров:
const address = {
  city: 'Salt Lake City',
  state: 'UT',
  zip: 84115,
  coords: {
    lat: 40.776608,
    long: -111.920485,
  },
}

// допустим, вы хотите сделать это:
const city = address.city
const state = address.state
const zip = address.zip

// используя деструктуризацию, вы можете сделать так:
const {city, state, zip} = address 

Вот как это выглядит в React:
// без деструктуризации:
function UserName(props) {
  return (
    <div>
       
    </div>
  )
}

// с использованием деструктуризации:
function UserName() {
  return (
    <div>
       
    </div>
  )
}

// с использованием вложенной деструктуризации:
function UserName({name: {first, last}}) {
  return (
    <div>
       
    </div>
  )
}

Эта фича позволяет делать некоторые интересные вещи:
const info = {
  title: 'Once Upon a Time',
  protagonist: {
    name: 'Emma Swan',
    enemies: [
      {name: 'Regina Mills', title: 'Evil Queen'},
      {name: 'Cora Mills', title: 'Queen of Hearts'},
      {name: 'Peter Pan', title: `The boy who wouldn't grow up`},
      {name: 'Zelena', title: 'The Wicked Witch'},
    ],
  },
}

// при правильном форматировании вложенная деструктуризация довольно проста,
// хотя не все разделяют мое мнение на этот счет
const {
  title,
  protagonist: {
    name: name,
    enemies: [, , , {title: enemyTitle, name: enemyName}],
  },
} = info

console.log(`${enemyTitle} (${enemyName}) is an enemy to ${name} in "$5 JavaScript Features I Couldn't Code Without"`)

2. Модули


Это еще одна фича, которую я использую практически в каждом файле. До того как модули стали частью языка, приходилось использовать странные библиотеки и инструменты для работы с большими проектами. С модулями (и сборщиками, такими как Rollup или Webpack) у нас появилась отличная возможность делиться кодом с другими.

Вот парочка примеров:
// a.js
// экспорт
function add(a, b) {
  return a + b
}

const foo = 'bar'

const theAnswer = 42
const theQuestion = 'who knows'
   
// синтаксис модулей позволяет экспортировать их во время создания, но
// мне нравится размещать все "экспорты" в одном месте
export default add
export {foo, theAnswer, theQuestion}

// b.js
// импорт
// 1. импортируем модуль
import './a'

// 2. импортируем по умолчанию
import add from './a'

// 3. импортируем `theAnswer` и `theQuestion` из './a'
import {theAnswer, theQuestion} from './a'

// 4. импортируем `theAnswer` и переименовываем его в `fortyTwo`
import {theAnswer as fourtyTwo} from './a'

// 5. импортируем `add` (по умолчанию) и `theQuestion`
import {default as add, theQuestion} from './a'

// 6. импортируем `add` и `theQuestion` без указания импорта по умолчанию
import add, {theQuestion} from './a'
   
// 7. импортируем все в единое "пространство имен" под названием `allTheThings`
import * as allTheThings from './a'

Если Вы хотите узнать больше о модулях, можете посмотреть мое видео на youtube — «More than you want to know about ES6 Modules».

3. Параметры по умолчанию


Я люблю и использую эту фичу все время. Это относится как к аргументам функции, так и к деструктуризации. Вот как это используется при деструктуризации объекта:
const bench = {type: 'Piano', adjustable: false}
const {legs = 4} = bench
// `The bench has ${legs} legs`
// -> The bench has 4 legs
// bench - скамья, leg - ножка

Обратите внимание, что у объекта bench нет свойства legs. Без использования синтаксиса параметров по умолчанию значением legs будет undefined.

Вы также можете использовать деструктурирующее присваивание с этой фичей:
const bench = {type: 'Piano', adjustable: false}
const {legs: legCount = 4} = bench
// `The bench has ${legCount} legs`
// -> The bench has 4 legs

Вот как это выглядит в списке параметров:
function getDisplayName(firstName = 'Unknown', lastName = 'Unknown') {
  return `${firstName} ${lastName}`
}

// getDisplayName()
// -> Unknown Unknown

// getDisplayName('Andrew')
// -> Andrew Unknown

// getDisplayName(undefined, 'Yang')
// -> Unknown Yang

// getDisplayName('Andrew', 'Yang')
// -> Andrew Yang

Эта фича также позволяет делать некоторые довольно интересные вещи, поскольку значение справа от знака "=" вычисляется только при необходимости. Это означает, что Вы можете использовать ее для проверки наличия обязательных параметров:
function getCandy(
  kind = requiredParam('kind'),
  size = requiredParam('size'),
  upperKind = kind.toUpperCase(),
  callback = function noop() {},
) {
  const result = {kind, size, upperKind}
  callback(result)
  return result
}

function requiredParam(argName) {
  throw new Error(`${requiredParam} is required`)
}

// getCandy('twix', 'king')
// -> {kind: 'twix', size: 'king', upperKind: 'TWIX'}
// getCandy('twix')
// -> ошибка: 'size is required'

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

О, а Вы заметили, что мы можем использовать предыдущие аргументы как часть параметров по умолчанию для следующих аргументов (как в случае с upperKind)? Здорово, правда?

4. Стрелочные функции


Я часто использую стрелочные функции. Мне нравятся функциональные выражения, но если, например, мне нужна анонимная функция обратного вызова (которой я не хочу придумывать имя), или я хочу получить неявные возвращаемые значения, то стрелочные функции — это как раз то, что мне нужно.

Вот несколько примеров использования стрелочных функций:
const divide = (a, b) => a / b
const getFive = () => 5
const identity = i => i
const asArray = (...args) => args

// обычно, я не именую стрелочные функции с несколькими аргументами
// (в таких случаях я использую обычные функциональные выражения),
// но Вы можете их именовать, если хотите:
const tryInvoke = (obj, fn, ...args) => {
  try {
    return obj[fn](...args)
  } catch (e) {
    return undefined
  }
}
   
// для того, чтобы вернуть объект, необходимо заключить его в круглые скобки
const getObject = favoriteCandy => ()

// многострочные JSX (расширение JS в React) также должны быть заключены в круглые скобки
const MyComponent = () => (
  <div>
    Hello world! I am a function and I return <strong>JSX!</strong>
  </div>
)

5. Промисы и async/await


JS является однопоточным и построен на системе событий (стеке вызовов). Я большой поклонник разговоров типа Что такое event loop? (с русскими субтитрами). Промисы и async/await являются отличными инструментами для управления этим. Большая часть моего кода является асинхронной и названные инструменты значительно упрощают мою работу. Честно говоря, промисы являются серьезной темой и требуют некоторого «привыкания», но они классные.

Я часто использую async/await в тестах и бэкенде. Вот пример асинхронного теста:
test('Can fill out a form across multiple pages', async () => {
  mockSubmitForm.mockResolvedValueOnce({success: true})
  const testData = {food: 'test food', drink: 'test drink'}
  const {findByLabelText, findByText} = render(<App />)

  user.click(await findByText(/fill.*form/i))

  user.type(await findByLabelText(/food/i), testData.food)
  user.click(await findByText(/next/i))

  user.type(await findByLabelText(/drink/i), testData.drink)
  user.click(await findByText(/review/i))

  expect(await findByLabelText(/food/i)).toHaveTextContent(testData.food)
  expect(await findByLabelText(/drink/i)).toHaveTextContent(testData.drink)

  user.click(await findByText(/confirm/i, {selector: 'button'}))

  expect(mockSubmitForm).toHaveBeenCalledWith(testData)
  expect(mockSubmitForm).toHaveBeenCalledTimes(1)

  user.click(await findByText(/home/i))

  expect(await findByText(/welcome home/i)).toBeInTheDocument()
})

А вот пример использования async\await в Express:
async function getListItems(req, res) {
  const listItems = await listItemsDB.query({ownerId: req.user.id})
  res.json({listItems: await expandBookDataMultiple(listItems)})
}

Любопытно, что я не часто использую async/await в своем React-коде (по крайней мере, напрямую). Так происходит потому, что я стараюсь делать большую часть «асинхронной логики» вне моих компонентов. Поэтому если я, например, делаю что-то асинхронное при вызове useEffect в React, я ограничиваюсь одним вызовом асинхронной функции, поскольку считаю, что с промисами работать легче:
React.useEffect(() => {
  getUser().then(
    user => setState({status: 'success', error: null, user}),
    error => setState({status: 'error', error, user: null}),
  )
}, [])

Советую прочитать статью Anthony Chu «Async/Await in Node».

Заключение


Существует множество других фич, которые я регулярно использую и которые могли бы войти в этот список. Приведенные особенности — мои любимые, я все время к ним обращаюсь. Имеются также некоторые свежие дополнения к языку, которые еще не вошли в мою мышечную память. Сейчас самое время стать JS-разработчиком! Надеюсь, эта статья была Вам полезной! Удачи!
Tags:
Hubs:
+15
Comments 23
Comments Comments 23

Articles