JavaScript простой! Кто сказал?

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

JavaScript всего лишь еще один язык программирования. Что может пойти не так?

1. Вы когда-нибудь пытались отсортировать массив чисел?

sort() в JavaScript по умолчанию использует буквенно-цифровой порядок сортировки.

Так [1,2,5,10].sort() вернет [1, 10, 2, 5] .

Чтобы получить ожидаемый результат при сортировке массива вы можете использовать следующую конструкцию [1,2,5,10].sort((a, b) => a — b)

Простое решение для заранее известной проблемы :)

2. new Date() просто прекрасна

new Date() работает следующим образом:

  • без аргументов: возвращает текущую дату.
  • один аргумент x : возвращает 1 января 1970 + x миллисекунд. Люди со знанием Unix знают причину.
  • new Date(1, 1, 1) возвращает 1 февраля 1901 года. Все потому что первая цифра означает 1 год после 1900, вторая цифра это месяц в году, в нашем случае второй месяц — февраль. Действительно, кто в своем уме начнет нумерацию с цифры 1? :) И третья цифра означает первый день месяца — потому что иногда индексы все же начинаются с 1.
  • О, а запись new Date(2016, 1, 1) не прибавит 2016 лет к 1900. Просто вернет 2016 год.

3. Replace не заменяет

Мне кажется, что это хорошо, поскольку я не люблю функции, которые мутируют вводные данные. Вам так же стоит знать, что replace заменит только первое совпадение:

let s = "bob";
const replaced = s.replace("b", "l");
console.log(replaced); // lob — заменено только первое совпадение
console.log(s); // bob — изначальная переменная осталась прежней

Если вы хотите изменить все вхождения, то можете использовать регулярное выражение /g :

"bob".replace(/b/g, 'l') === 'lol' // заменены все вхождения

4. Аккуратнее со сравнениями

// Тут все ок
'abc' === 'abc'; // true
1 === 1; // true
// А вот тут уже не ок
[1, 2, 3] === [1, 2, 3]; // false
{a: 1} === {a: 1}; // false
{} === {}; // false

Причина: [1, 2, 3] и [1, 2, 3] являются двумя отдельными массивами. Просто по счастливому стечению обстоятельств они содержат одинаковые данные. Оба массива имеют разные ссылки и не могут сравниваться при помощи === .

5. Массив не является примитивным типом

typeof {} === 'object'; // true
typeof 'a' === 'string'; // true
typeof 1 === number; // true
// Но...
typeof [] === 'object'; // true

Для того, чтобы узнать является ли ваша переменная массивом вы можете использовать Array.isArray(myVar) .

6. Замыкания

Есть один вопрос по JavaScript, который очень любят задавать на собеседовании:

const Greeters = [];
for (var i = 0; i < 10; i++) {
Greeters.push(function () {
return console.log(i)
})
}
Greeters[0]() // 10
Greeters[1]() // 10
Greeters[2]() // 10

Уверен, вы ожидали, что выведется 0, 1, 2… Вы знаете, почему этого не произошло? Как бы вы исправили это?

Рассмотрим два из возможных решений этой проблемы:

  • Используйте let вместо var . Бум! Решено.
“Разница [между let и var ] заключается в области видимости. Область видимости var ограничена ближайшим блоком функции. А область видимости let ограничена ближайшей областью использования, которая может быть гораздо меньше блока функции. (Обе являются глобальными если объявлены за пределами какого-либо блока).”
Источник
  • Альтернатива: используйте bind :
Greeters.push(console.log.bind(null, i));

Это только мой топ-2 из огромного количества решений.

7. Поговорим о bind

Как вы думаете, что получится на выходе?

class Foo {
constructor (name) {
this.name = name;
}
greet () {
console.log('hello, this is ', this.name);
}
someThingAsync () {
return Promise.resolve();
}
asyncGreet () {
this.someThingAsync()
.then(this.greet)
}
}
new Foo('dog').asyncGreet();

Одно очко в вашу пользу, если вы думаете, что все сломается и выведется ошибка Cannot read property 'name' of undefined

Причина: greet выполняется вне надлежащего контекста. И опять же есть есть множество решений этой проблемы.

  • Лично мне нравится
asyncGreet () {
this.someThingAsync()
.then(this.greet.bind(this))
}

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

  • Если вы понимаете, что greet никогда не должен вызываться за пределами контекста, то вы можете так же забиндить его в конструкторе класса:
class Foo {
constructor (name) {
this.name = name;
this.greet = then.greet.bind(this);
}
}
  • Вам так же стоит знать, что стрелки (=>) могут быть использованы для проброса контекста. Это так же будет работать:
asyncGreet () {
this.someThingAsync()
.then(() => {
this.greet()
})
}

Хотя я считаю, что последний пример менее элегантен в данном случае.

Я рад, что мы решили эту проблему.

Заключение

Мои поздравления, теперь вы умеете делать крутые вещи. Наверное. Иногда даже избегая глобальных поломок (хотя обычно без них не обходится). Cheers \o/

Дайте мне знать, если я не упомянул о чем-то важном!


Нашли ошибку? Воспользуйтесь функцией Private notes: выделяете текст с ошибкой, нажимаете на символ замка в появившемся бабле и оставляете свой комментарий. Спасибо!


Перевод статьи Who said javascript was easy ? Aurélien Hervé.

Оригинал перевода опубликован на сайте netology.ru

Show your support

Clapping shows how much you appreciated Workafrolic (±∞)’s story.