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.