10 ошибок в JavaScript, которые совершают почти все

Подборка ошибок в JavaScript и советов по ним для программистов, которые работают или только начали осваивать этот язык программирования.

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

Многие вещи, которые казались привычными в Ruby, Python, Java, C или даже PHP, в JS могут отсутствовать или работать иначе.

1. Точка с запятой нужна не всегда

Многие js-программисты слишком много внимания уделяют точкам с запятой в конце. На самом деле использовать их постоянно совсем не обязательно, а код без лишних символов выглядит чище.

var Thing = require('thing'),
    thing = new Thing(),
    stuff = thing.whatsit();

stuff.doStuff(function(err){
  if (err) console.log(err);
  else console.log('done');
})

vs

var Thing = require('thing')
var thing = new Thing()
var stuff = thing.whatsit()

stuff.doStuff(function(err){
  if (err) console.log(err)
  else console.log('done')
})

Использование большего количества var при объявлении переменных не замедляет работу программы, зато позволяет не использовать лишние символы в конце строки.

Перенос строки и точка с запятой играют одну и ту же роль в JavaScript. Точка с запятой нужна только для объединения разных частей кода в одной строке (при сжатии, например).

Рассмотрим следующий код:

function doStuff(thing) {
  var whatsit = new Whatsit(thing);
      stuff = new Stuff(whatsit);

  // что-то происходит

  return stuff
}

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

Правильней было бы опустить точки с запятой, а к объявлению stuff добавить var:

function doStuff(thing) {
  var whatsit = new Whatsit(thing)
  var stuff   = new Stuff(whatsit)

  // что-то происходит

  return stuff
}

2. Не возвращайте результат до завершения асинхронного обратного вызова

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

Рассмотрим типичный код контроллера на Express или Sails.JS:

var myFunction = function(req, res) {
  User.findOne({name: req.body.name}).exec(function(err, user){
    if (err) return res.json(400, err)
    user.update({description: req.body.description}).exec(function(err, user){
      if (err) return res.json(400, err)
    })
    return res.json(user)
  })
}

Размещение return res.json(user) за пределами колбека функции update приведет к тому, что view, которое будет передано AJAX-запросом, увидит только пользователь, не обновлявший страницу.

Перемещение return внутрь колбэка update() поможет устранить проблему:

var myFunction = function(req, res) {
  User.findOne({name: req.body.name}).exec(function(err, user){
    if (err) return res.json(400, err)
    user.update({description: req.body.description}).exec(function(err, user){
      if (err) return res.json(400, err)
      return res.json(user)
    })
  })
}

Похожая ошибка, которая часто встречается на клиентской стороне:

var thing = null
var thingId = $("#thing-id").val()
$.get("/thing/" + thingId, function(data){
  thing = data.thing
  console.log("got thing", thing)
}).fail(function(err){
  console.log("got error", err)
})
$(".thing-name").text(thing)

Здесь проблема в том, что $(".thing-name").text(thing) будет выполнена перед тем, как data будет получена с сервера. Таким образом, thing останется null.

Правильный вариант:

var thingId = $("#thing-id").val()
$.get("/thing/" + thingId, function(data){
  console.log("got thing", data.thing)
  $(".thing-name").text(data.thing)
}).fail(function(err){
  console.log("got error", err)
})

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

function thingyWhatsit(thingId, whatsitId) {
  var thingData = null
  var whatsitData = null
  $.get("/thing/" + thingId, function(data){
    thingData = data
  }).fail(function(err){
    console.log("got thing error", err)
  })
  $.get("/whatsit/" + whatsitId, function(data){
    whatsitData = data
  }).fail(function(err){
    console.log("got whatsit error", err)
  })
  return $.extend(true, {}, thingData, whatsitData)
}

var result = thingyWhatsit(tid, wid)

И thingData, и whatsitData будут возвращаться к вызову return, а функция всегда будет возвращать {}.

Есть соблазн сделать так:

function thingyWhatsit(thingId, whatsitId) {
  $.get("/thing/" + thingId, function(thingData){
    $.get("/whatsit/" + thingId, function(whatsitData){
      return $.extend(true, {}, thingData, whatsitData)
    }).fail(function(err){
      console.log("got inner error", err)
    })
  }).fail(function(err){
    console.log("got error", err)
  })
}

var result = thingyWhatsit(tid, wid)

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

Лучше использовать стороннюю библиотеку, например async. async позволяет оформить вызовы таким образом:

function thingyWhatsit(thingId, whatsitId, callback) {
  async.parallel({
    thing: function(next) {
      $.get("/thing/" + thingId, function(data){
        next(null, data)
      }).fail(function(err){
        next(err, null)
      })
    },
    whatsit: (function(id) {
      $.get("/whatsit/" + whatsitId, function(data){
        next(null, data)
      }).fail(function(err){
        next(err, null)
      })
    }
  }, function(result) {
    if (result.err) {
      callback({errors: result.err}, null)
    } else {
      callback(null, $.extend(true, {}, result.thing, result.whatsit)
    }
  })
}

var result = thingyWhatsit(tid, wid)

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

3. Не забывайте, что = это не ==

Казалось бы, базовые операторы не должны вызывать проблем при использовании, однако невнимательность иногда приводит к глупым ошибкам.

var x = someFunction()

if (x = 'hello world') console.log("winning!", x)
else console.log("Dammit Chloe!", x)

Не имеет значения, что вернет someFunction() – вы всегда будете получать true, ведь в условии описана операция присваивания.

var x = someFunction()

if (x == 'hello world') console.log("winning!", x)
else console.log("ammit Chloe!," x)

Бывают и обратные ситуации:

var i == 10

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

function check(item, callback) {
  var item = new Item(),
      thing = new Thing(item),
      whatsit = New Wahtsit(thing),
      check_item = null;

for (var i = 0, l = items.length; i++) {
  check_item == items[i];
  setTimeout(function() {
    do_some_complex_stuff_with_checked_item();
    callback(check_item);
  }, 0);
}

Здесь строка check_item == items[i] будет просто расценена как false, значение никогда не будет присвоено check_item.

4. Не забывайте, что == это не ===, и что != не равно !==

Так же, как и в пункте 3, данные ошибки встречаются слишком часто. Помните, что оператор == сравнивает значения, а === дополнительно сравнивает типы.

5. Не используйте & там, где должно быть &&

Оператор & нужен для сравнения целых чисел, тогда как && сравнивает истинность операндов. Таким образом, 123 & false === 0 (потому что false становится 0), но 123 && false === false 9 & 5 === 1, а 9 && 5 === 5.

6. Есть разница между | и ||

Аналогично случаются ошибки, связанные с использованием операторов | и ||. Применение этих операторов аналогично описанному выше &, за исключением видов сравнения (здесь or вместо and).

7. Помните, что |= в JavaScript не то же, что ||= в Ruby

Эта ошибка встречается у Ruby-программистов.

x = some_thing_maybe_nil
# ...
x ||= 5

В Ruby, если бы x был nil, он бы равнялся 5. А если, к примеру, 9, то он так и остался бы с этим значением.

var x = some_thing_maybe_null()
// ...
x |= 5

В JavaScript данный код либо вернет ошибку, либо вернет 13, если x === 9.

8. Не путайте nil из Ruby c null в JavaScript (и наоборот)

Это одна из самых актуальных ошибок в JavaScript для Ruby-разработчиков. Ruby-программисты часто считают, что nil в Ruby будет означать то же, что и null в JavaScript. Однако понятия nil в JavaScript нет и невнимательность может привести к ошибке:

var x = nil

Здесь x будет содержать undefined, а не null.

9. Помните, что this не всегда то, чем кажется

function Thing(name) {
  this.name = name
}
Thing.prototype.delayedName = function(callback){
  setTimeout(function() {
    callback(this.name)
  }, 1000)
}
var thing = new Thing('whatsit')
thing.delayedName(function(name){
  console.log("clicked", name.toUpperCase())
})

Код выше только вызовет ошибку Uncaught TypeError: undefined is not a function.

Чтобы избежать этого, можно сделать так:

function Thing(name) {
  this.name = name
  var that = this
  this.delayedName = function(callback){
    setTimeout(function() {
      callback(that.name.toUpperCase())
    }, 1000)
  }
}
var thing = new Thing('whatsit')
thing.delayedName(function(name){
  console.log("clicked", name.toUpperCase())
})

Такой способ сработает, но приведет к утечке памяти: ссылка that не будет объявлена корректно.

Чтобы избежать подобных ошибок, нужно использовать замыкания, которые помогут обрабатывать области видимости должным образом.

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

В отличие от многих других языков, в JavaScript переменные существуют внутри блока кода.

Таким образом, этот код будет работать:

var i = 0
for (var ii = 0; ii < 9; ii++){
  i += 1
}
console.log("i = ", i)

Этот код тоже будет работать и не приведет к утечке памяти:

function delay(seconds, message) {
  setTimeout((function(startTime){
    var duration = (new Date() - startTime) / 1000.0
    console.log("delay of ", seconds, "for message", message, "actually took", duration, "seconds" )
  })(new Date()), seconds)
}

Однако так работать не будет:

function add(firstNumber, secondNumber) {
  var i = firstNumber + secondNumber
}

add(7, 666)
console.log("i =", i)

В этом случае i будет undefined.

Проблема здесь в том, что i объявлена внутри функции и относится к ее области видимости. Когда к переменной i обращаются за пределами ее области видимости, она всегда будет отличаться от ожидаемого результата. Ведь в этом случае, мы обращаемся, по сути, к другой переменной. Таким образом, если переменная с таким же именем будет объявлена в глобальной области видимости, мы получим ее значение.

Надеемся, после нашей статьи вы больше не совершите ни одной из перечисленных ошибок в JavaScript.

Больше советов по JavaScript:

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

eFusion
01 марта 2020

ТОП-15 книг по JavaScript: от новичка до профессионала

В этом посте мы собрали переведённые на русский язык книги по JavaScript – ...
admin
10 июня 2018

Лайфхак: в какой последовательности изучать JavaScript

Огромный инструментарий JS и тонны материалов по нему. С чего начать? Расск...