Подводные камни JavaScript

JavaScript и Python во многом похожи. Ниже рассмотрим особенности синтаксиса JavaScript которые часто приводят к ошибкам и непониманию.

Доступ по ключу

Для наглядности рассмотрим пример

var joke = {
  "text": "жизнь — она как зебра",
}

// теперь тот же код запишем короче, в стиле JS
var joke = {
  text: "жизнь — она как зебра", // кавычки для ключей можно опустить
}

// если мы хотим создать объект с заранее неизвестным ключом
var key = getKey()
var joke = {
  [key]: "жизнь — она как зебра",
}

Как теперь получить доступ к свойствам объекта joke:

console.log(joke['text'])

// тот же код, но в стиле JS
console.log(joke.text)

// если ключ заранее не известен, то квадратные кавычки снова полезны
var key = 'text'
console.log(joke[key])

Если обратиться к объекту по несуществующему ключу, то вместо исключения мы получим значение undefined

joke.address // undefined

Проверка на пустое значение

Одна из лучших находок Гвидо — это удобное преобразование данных к boolean типу. Конструкция if value: предсказуемо работает даже если value окажется пустым dict или list. В Javascript не все так гладко. Рассмотрим несколько примеров:

// все как у людей:
Boolean(null) // false
Boolean(undefined) // false
Boolean(0) // false
Boolean('') // false

Boolean(1) // true

// а теперь, внимание!
Boolean({}) // true
Boolean([]) // true

Так получается из-за кривости особенностей объектной модели в Javascript. Если хочешь в этом разобраться — читай про прототипное наследование.

Что из всего этого следует. Если уверен что в переменной не лежит нечто похожее на Object, то можно использовать короткую запись:

// длинная запись
if (typeof joke.author === "undefined" || joke.author.length === 0){
  console.log('Author is unknown')
}

// короткая запись
if (!joke.author){
  console.log('Author is unknown')
}

Передача аргументов в функцию

Интерпретатор JavaScript не сверяет количество переданных аргументов с объявлением функции. Пример:

function alert_user(user_name, next_action){
  // ... здесь живет логика функции
}

// обычные вызов функции, все как и ожидалось
alert_user(user_name='Jack', next_action=exit_action)

// здесь творится что-то неладное, но интерпретатор промолчит
alert_user(user_name=undefined, next_action=undefined)

// то же самое, но в короткой записи. функция получит два значения undefined
alert_user()

Передача this в метод класса

Начнем с того, что в Javascript любая функция может стать классом (точнее, конструктором объектов) если её вызвать с ключевым словом new. Пример:

function get_joke(){
  // ... здесь живет логика функции
  return {
    'text': "жизнь — она как зебра"
  }
}
var joke = get_joke()

теперь запишем то же самое с помощью new

function get_joke(){
  // ... здесь живет логика функции
  // внутри get_joke мы получаем объект this
  this.text = "жизнь — она как зебра"
}
var joke = new get_joke()

если мы хотим добавить метод объекту, то сделать это можно так:

function get_joke(){
  // ... здесь живет логика функции
  // внутрь get_joke теперь передан объект this - свежесозданный объект
  this.text = "жизнь — она как зебра"
  this.fetch_rating = function(){
    // ... здесь живет логика метода и доступен this
  }
}
var joke = new get_joke()
joke.fetch_rating()

На этом магия не закачивается. Внимание, следим за руками:

joke.fetch_rating() // все идет как надо

var fetching_method = joke.fetch_rating
fetching_method() // упс, все ломается

Во втором варианте записи теряется привязка метода к объекту joke — переменная this — и тело функции fetching_method не может до него добраться. Приходится шаманить:

var fetching_method = joke.fetch_rating
// снова привязываем функцию fetch_rating к объекту joke
var binded_fetching_method = fetching_method.bind(joke)

binded_fetching_method() // ура! работает

Этот прием используется когда хотим передать метод объекта в качестве callback функции.

В более свежей версии Javascript добавили синтаксического сахара:

class Joke{
  constructor(){
    this.text = "жизнь — она как зебра"
  }
  fetch_rating(){
    // ... здесь живет логика метода и доступен this
  }
}

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

Добавка для сильных духом

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