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

Дата: 29.04.2017 в 00:12, Категория: JavaScript
  • 1803
  • 16
Кто сказал, что JavaScript простой?

Сегодня я покажу несколько трюков и ловушек, о которых, возможно, должен знать каждый начинающий JavaScript-разработчик. Если вы опытный разработчик, то все равно не постесняйтесь прочитать эту статью (с понимающим взглядом).

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

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

К примеру, [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, и очевидно, что третий, является первым днем месяца, поэтому 1 - так как индексы иногда начинаются с 1.
  • Да, кстати new Date(2016, 1, 1) не добавят 2016 к 1900 году. Он просто выводит как 2016 год.

# 3. Replace does not replace

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

let s = "bob"
const replaced = s.replace('b', 'l')
replaced === "lob" // true. Заменяется только первый символ.
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] являются двумя отдельными массивами. Они просто содержат одни и те же значения, но имеют различные ссылки и не могут быть сопоставлены друг с другом, ни через простое равенство ==, ни через строгое ===, результат false.

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

# 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 ограничен областью видимости до ближайшего функционального блока, и пусть область видимости находится в ближайшем охватывающем блоке (оба являются глобальными, если находятся вне любого блока), который может быть меньше, чем функциональный блок." (источник)

  • Альтернативный способ, использование - bind (привязку):
Greeters.push(console.log.bind(null, i))

Есть много других решений. Это только мои лучшие 2 варианта :)

# 7. Говоря о привязке

Как вы думаете, что это даст?

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 = this.greet.bind(this)
  }
}

Вы также должны знать, что стрелочные функции (=>) могут использоваться для сохранения контекста. Это будет работать точно так же, как и способ выше:

asyncGreet () {
  this.someThingAsync()
  .then(() => {
    this.greet()
  })
}

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

Оригинальная статья: Who said javascript was easy? (Aurélien Hervé)

P.S.: Для практики, я решил перевести эту статью, так как она мне понравилась. Однако, из-за моего слабого английского, я часто использовал Google Translate. Если я где-то допустил ошибку, прошу извинить и указать на мою ошибку. Как я уже понял, писать статьи самому намного проще, нежели их переводить.