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.