JavaScriptMiddleExperience
Какие ошибки делают разработчики, переходящие на JavaScript с другого языка или стека?
Переходя на JS с Java/Python/C#, разработчики чаще всего ошибаются: сериализуют параллельные запросы через await в цикле, теряют this в callback, путают CJS с ESM и игнорируют unhandled Promise rejections.
Перенос синхронного мышления в асинхронный код
Разработчики с Java/C#/PHP фоном пишут псевдосинхронный код, не понимая event loop:
// Типичная ошибка: блокирующий цикл
const results = [];
for (const id of ids) {
const item = await fetchItem(id); // запросы идут последовательно!
results.push(item);
}
// Правильно: параллельные запросы
const results = await Promise.all(ids.map(id => fetchItem(id)));
// Или с ограничением конкурентности (p-limit):
import pLimit from 'p-limit';
const limit = pLimit(5); // не более 5 параллельных запросов
const results = await Promise.all(
ids.map(id => limit(() => fetchItem(id)))
);
Игнорирование прототипной типизации (приходя из Java/C++)
В Java класс — жёсткий контракт. В JavaScript объект можно изменить в любой момент, а instanceof ненадёжен при передаче объектов между realms (iframe, vm.runInNewContext):
// Ловушка: проверка типа через constructor name
function processArray(arr) {
if (arr instanceof Array) { // ломается для Array из другого iframe!
return arr.map(x => x * 2);
}
}
// Правильно: Array.isArray() или Object.prototype.toString
if (Array.isArray(arr)) { /* ... */ }
if (Object.prototype.toString.call(arr) === '[object Array]') { /* ... */ }
Мутация объектов как в Python/Ruby
Python-разработчики привыкают к явному deep copy. В JS передача объектов по ссылке вызывает трудноуловимые баги:
// Баг: мутация входного аргумента
function addDefaults(config) {
config.timeout = config.timeout || 5000; // мутирует оригинал!
return config;
}
// Правильно: иммутабельный паттерн
function addDefaults(config) {
return { timeout: 5000, ...config }; // spread создаёт shallow copy
}
// Для deep copy:
const deep = structuredClone(original); // Node.js 17+, доступно нативно
this binding — камень преткновения для Java/C# разработчиков
class Timer {
constructor() {
this.count = 0;
}
start() {
// Ошибка: this теряется в callback
setInterval(function() {
this.count++; // this === undefined в strict mode!
}, 1000);
// Правильно: стрелочная функция сохраняет lexical this
setInterval(() => {
this.count++; // this === Timer instance
}, 1000);
}
}
Непонимание module system (CJS vs ESM)
Разработчики с Python/Java фона путаются в двух системах модулей Node.js:
// CommonJS (require) — синхронный, динамический
const fs = require('fs'); // работает внутри if-блоков
// ESM (import) — статический, асинхронный, tree-shakeable
import { readFile } from 'fs/promises';
// Проблема: смешивание CJS и ESM без правильного package.json
// package.json: { "type": "module" } — весь проект ESM
// package.json: { "type": "commonjs" } — весь проект CJS
// .mjs / .cjs расширения для явного указания формата
Обработка ошибок без учёта async контекста
Java-разработчики ставят один try/catch на весь блок. В async JS непойманный rejection крашит процесс:
// Проблема: promise без .catch
async function riskyOperation() {
await someAsyncCall(); // если бросает — unhandledRejection!
}
riskyOperation(); // нет await, нет catch — тихая потеря ошибки
// Правильно:
try {
await riskyOperation();
} catch (err) {
logger.error({ err }, 'Operation failed');
}
// Глобальная страховка:
process.on('unhandledRejection', (reason) => {
logger.fatal({ reason }, 'Unhandled rejection');
process.exit(1);
});
Подводные камни
- Использование == вместо === из-за привычки к языкам без coercion — 0 == false, null == undefined, "" == 0 все возвращают true.
- Полагаться на порядок ключей объекта (не гарантирован спецификацией для нецелочисленных ключей до ES2015).
- Не учитывать hoisting: var поднимается в начало функции, let/const — нет. Разработчики с C#/Java фона удивляются использованию var до объявления.
- Игнорировать разницу между null и undefined — в TypeScript это разные типы, JSON.stringify() убирает undefined из объектов, что меняет поведение API.
- Копировать паттерн «один файл — один класс» из Java в JS без необходимости — в JS модуль может экспортировать несколько функций, и это идиоматично.
- Не использовать деструктуризацию, optional chaining (?.) и nullish coalescing (??) — код становится многословным и хрупким.
What hurts your answer
- Перечислять ошибки без объяснения причин
- Не отличать beginner mistakes от production failure modes
- Не предлагать процесс, который предотвращает повторение ошибок
What they're listening for
- Знает типичные ошибки при работе с JavaScript
- Понимает причины ошибок
- Предлагает практики prevention и early detection