В этой статье я расскажу о советах по работе с Javascript Pro. Пожалуйста, научитесь писать надежный современный Javascript и избегать плохого кода былых времен.

Отладка

На самом деле речь идет о консольных журналах.

Как вы используете журнал консоли? Есть хорошие и плохие способы консольного журнала. Представьте, что у вас есть три разных объекта. Каждому из них присваивается собственная переменная, вот так.

const foo = { name: 'tom', age: 30, nervous: false } 
const bar = { name: 'dick', age: 40, nervous: false }
const baz = { name: 'harry', age:50, nervous: false }

Очевидный способ их регистрации - просто один за другим.

Например:

console.log(foo);
console.log(bar);
console.log(baz);

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

Итак, вы можете сделать так:

console.log({ foo, bar, baz });

Это не только сокращает объем кода, но и сообщает нам, какая именно переменная определяет эти данные. Одна строка кода, один журнал консоли и вся необходимая информация.

Но, возможно, эти данные особенно важны, поэтому мы хотим выделить их с помощью пользовательского стиля CSS. Вы можете заменить данные, а также стили CSS, используя знак %. Итак, мы добавим %c, а второй аргумент будет нашим фактическим стилем CSS.

Например:

console.log('%c My Friends', 'color: orange;')
console.log({ foo, bar, baz });

Вот так!

Вы могли заметить одну вещь: все объекты имеют общие свойства, поэтому, возможно, нам следует отобразить их в виде таблицы.

console.table([foo, bar, baz]);

Это действительно полезно, когда у вас есть массив объектов. Вы просто создаете консольную таблицу с массивом.

Если вы измеряете производительность, вы можете отслеживать время в консоли.

console.time('looper');
let i = 0;
while (i < 1000000) { i ++ }
console.timeEnd('looper')

Отслеживать время - это здорово, но что, если вам нужно знать, откуда появился console.log.

Давайте представим, что у нас есть действительно важная функция, которая удаляет элементы из нашей базы данных, и мы хотим убедиться, что случайно не вызовем этот метод дважды. В этом случае вы можете добавить трассировку консоли к своей функции, и она предоставит вам трассировку стека для того, где она была вызвана и что ее определило, если мы запустим этот код, мы получим консольный журнал, который сообщает нам, что функция была определена на которой номер строки.

Например, если мы напишем:

const deleteMe = () => console.trace('bye bye database')

Вы можете получить:

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

Здесь я покажу вам несколько различных способов сделать свой код максимально кратким и эффективным. Представим, что у нас есть объект с данными о животных и нам нужна функция, которая сообщит нам, как кормить животное.

Например:

function feed(animal) {
    return `Feed ${animal.name} ${animal.meal} kilos of ${animal.diet}`
}

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

Если у нас есть функция, которая принимает объект, но нам нужно использовать только несколько его свойств. Мы можем опровергнуть их в самом аргументе. Мы просто заключаем его в скобки, а затем передаем имена свойств объекта, которые мы хотим использовать.

function feed({ name, meal, diet }) {
    return `Feed ${name} ${meal} kilos of ${diet}`
}

Итак, теперь мы можем форматировать ту же строку, но нам никогда не придется напрямую использовать слово «животное». это может показаться скромным преимуществом для этой простой функции, но когда у вас есть большой объект с множеством свойств, это может иметь огромное значение.

Если вам не нравится этот синтаксис скобок в аргументе объекта, на самом деле есть другой способ сделать это, который не менее хорош.

function feed(animal) {
    const { name, meal, diet } = animal;
    return `Feed ${name} ${meal} kilos of ${diet}`
}

Теперь вы можете использовать свойства, такие как переменные, во всей функции, и это, как правило, лучший способ, если у вас есть несколько объектов 2d-структуры и одна функция.

Шаблонные литералы

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

Например, если у нас есть:

const horse = {
   name: 'Topher',
   size: 'large',
   skills: ['jousting', 'racing'],
   age: 7
}
let bio = horse.name + ' is a ' + horse.size + ' horse skilled in ' + horse.skills.join(' & ')

Вы увидите много конкатенации строк, как показано выше, где у вас есть переменная плюс строка, и вам нужно управлять пробелами между ними, а также выражением и множеством других вещей. Работа с этим типом кода невероятно раздражает, но литералы шаблонов в современном Javascript полностью решают эту проблему.

Вместо того, чтобы объединять значения вместе, мы можем интерполировать их непосредственно в строку. Вы можете сделать это, определив свою строку с помощью обратных кавычек, а затем используя скобки со знаком доллара, а затем любую переменную или выражение, которое вы хотите внутри. Нравится:

const horse = {
   name: 'Topher',
   size: 'large',
   skills: ['jousting', 'racing'],
   age: 7
}
bio = `${name} is a ${size} skilled in ${skills.join(' & ')}`

Это будет выглядеть более читабельным и более простым в обслуживании.

Но на самом деле вы можете пойти дальше и построить строки чисто функциональным способом. Итак, мы напишем здесь функцию, называемую возрастом лошади, которая принимает массив строк в качестве первого аргумента, а затем она может принимать любые другие аргументы, которые потребуются после этого. Представьте, что у нас есть такая функция:

function horseAge(str, age) {
   const ageStr = age > 5 ? 'old' : 'young';
   return `${str[0]}${ageStr} at ${age} years`;
}

Но интересно здесь то, что вместо передачи обычного аргумента этой функции мы можем просто прикрепить его к литералу шаблона, и он проанализирует содержащиеся в нем аргументы. Например:

const bio2 = horseAge`This horse is ${horse.age}`;

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

Распространение синтаксиса

Давайте представим, что у нас есть один объект для покемона, а другой - для характеристик, которые определяют его различные атрибуты.

const pikachu = { name: 'Pikachu' }
const stats = { hp: 40, attack: 60, defense: 45 }

Допустим, мы хотим присвоить свойства объекта статистики объекту Pikach. Один из способов сделать это - просто переопределить их одну за другой на исходном объекте Пикачу.

pikachu['hp] = stats.hp
pikachu['attack'] = stats.attack
pikachu['defense'] = stats.defense

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

Мы могли бы использовать здесь Object.assign(), взять исходный объект и объединить его со статистикой, и это объединит их вместе слева направо.

const lv10 = Object.assign(pikachu, stats)
const lvl1 = Object.assign(pikachu, { hp: 45 })

Это неплохо, но есть более лаконичный способ сделать это с помощью синтаксиса распространения. Создавая новый объект и помещая в него наши существующие объекты с тремя точками перед ними. Например:

const lvl0 = { ...pikachu, ...stats }
const lvl1 = { ...pikachu, hp: 45 }

Это создаст новый объект слева направо. Таким образом, собственность, которая находится справа, будет иметь приоритет. Опять же, это в основном просто синтаксический сахар, который просто делает ваш код более читабельным и легким в обслуживании.

А также можно использовать синтаксис распространения для массивов. Представим, что у нас есть массив строк, и нам нужно добавить в этот массив дополнительные элементы.

let pokemon = ['Arbok', 'Raichu', 'Sandshrew']

Старый школьный способ сделать это - просто помещать новые элементы в массив один за другим. Например:

pokemon.push('Bulbasaur')
pokemon.push('Metapod')
pokemon.push('Weedle')

Но в современном мире мы можем сократить эти три строки кода до одной, определив массив с новыми элементами и в синтаксисе распространения исходного массива. Нравится:

pokemon = [...pokemon, 'Bulbasaur', 'Metapod', 'Weedle']
pokemon = ['Bulbasaur', ...pokemon, 'Metapod', 'Weedle']

Петли

Представим, что у нас есть массив чисел, которые представляют остальные итоги.

const orders = [500, 30, 99, 15, 223]

Теперь нам нужно вычислить некоторые значения на основе этого массива, например общую сумму заказа. Возможно, нам нужно добавить налог к ​​каждому из них и отфильтровать заказы с высокой стоимостью, которые будут рассмотрены менеджером. Один из вариантов - просто использовать классический цикл for, который есть практически во всех языках программирования. Например:

const total = 0;
const withTax = [];
const highValue = [];
for(i = 0; i < orders.length; i ++) {
  // Reduce
  total += orders[i];
  // Map
  withTax.push(orders[i] * 1.1);
  // Filter
  if (orders[i] > 100) {
    highValue.push(orders[i])
  }
}

Этот код очень уродлив, и его изменяющиеся значения могут сделать наш код более непредсказуемым. К счастью, мы можем сократить это до трех строк кода, используя современные методы массива Javascript. Нравится:

// Reduce
const total = orders.reduce((acc, cur) => acc + cur)
// Map
const withTax = orders.map(v => v * 1.1)
// Filter
const highValue = orders.filter(v => v > 100)

асинхронный / ожидание

Давайте создадим метод с именем random, который возвращает обещание, которое асинхронно преобразуется в случайное число.

const random = () => {
  return Promise.resolve(Math.random())
}

Теперь мы хотим получить три разных асинхронных числа одно за другим, а затем сложить их все вместе в конце. Это может показаться глупым примером, но на самом деле именно так часто все работает в реальном мире, когда вам нужно получить один элемент из базы данных, получить некоторые данные, извлекая другой элемент из API, и так далее.

С обещаниями вы ждете, пока асинхронное значение разрешится, а затем обрабатываете его с помощью функции обратного вызова внутри then. Например:

const sumRandomAsyncNums = () => {
  let first;
  let second;
  let third;
return random()
       .then(v => {
           first = v;
           return random();
       })
       .then(v => {
           second = v;
           return random();
       })
       .then(v => {
           third = v;
           return first + second + third;
       })
}

Мы можем переписать нашу цепочку обещаний. Единственное отличие - это добавление async перед функцией, которая заставит ее вернуть обещание. Нравится:

const sumRandomAsyncNums = async () => {
  
    const first = await random();
    const second = await random();
    const third = await random();
    return first + second + third;
}

Настоящее преимущество здесь в том, что мы можем использовать ожидание перед нашими обещаниями и разрешить им преобразование в фактическое значение переменной. Поэтому вместо использования этих обратных вызовов мы можем просто сказать const first = await random(); и сделать то же самое для второго и третьего числа. Теперь этот код намного легче читать и понимать, потому что мы можем просто пойти строка за строкой и увидеть, что мы ждем один номер в ожидании другого номера и так далее.

Эта концепция async / await - одна из самых замечательных вещей, которые когда-либо происходили с Javascript!

На сегодня все.

Спасибо, что прочитали!