Ключевые слова JavaScript: полный список и ловушки

Содержание

О проверке кода. Примеры не запускались — в отличие от материалов по Node.js, где приводится фактический вывод. Говорим прямо, а не ставим бейдж «проверено» без проверки.

Тема выглядит справочной — список слов, который нужно один раз посмотреть. На деле в ней три ловушки, из-за которых код ведёт себя необъяснимо.

Полный список

Ключевые слова языка:

break      case       catch      class      const      continue
debugger   default    delete     do         else       export
extends    false      finally    for        function   if
import     in         instanceof new        null       return
super      switch     this       throw      true       try
typeof     var        void       while      with

Контекстные — ключевые не везде:

let   static   yield   await   async   get   set   of

Зарезервированы про запас — в языке не используются:

enum   implements   interface   package   private   protected   public

enum зарезервирован с ES3, то есть больше двадцати лет, и не использован до сих пор. Комитет держит слова занятыми на случай будущих возможностей — дешевле запретить сразу, чем сломать чужой код потом.

Три вида зарезервированных слов

Ключевые языка

Заняты грамматикой — имя переменной нельзя

  • class, for, function, return
  • const, new, this, typeof
  • const class = 1SyntaxError
  • Свойством объекта — можно (ES5+)

Контекстные

Ключевые только в своём окружении

  • await — внутри async и модуля
  • yield — внутри генератора
  • let, static — в строгом режиме и классе
  • Вне контекста — обычное имя

Про запас

Зарезервированы, но не используются

  • enum — занят с ES3, не задействован
  • implements, interface, package
  • private, protected, public
  • Держат на будущее

Ловушка первая: контекстные слова

Самое неочевидное. Одно и то же слово может быть ключевым или обычным именем — в зависимости от того, где стоит.

// в обычном скрипте это легально
function old() {
  var await = 5;      // ✅ await — просто имя
  console.log(await); // 5
}

// а здесь — синтаксическая ошибка
async function modern() {
  var await = 5;      // ❌ SyntaxError
}

То же с yield — он ключевой только внутри генератора, и с static — только внутри класса.

Зачем такая сложность? Ради обратной совместимости. Когда в ES2017 добавляли async/await, в мире уже был код с переменной await. Сделай комитет слово безусловно ключевым — этот код сломался бы при обновлении браузера. Контекстность позволила добавить возможность, ничего не поломав.

let — из той же истории: вне строгого режима var let = 1; всё ещё работает.

Ловушка вторая: свойства — можно

// ❌ имя переменной
const class = 'card';        // SyntaxError

// ✅ свойство объекта — легально
const el = { class: 'card', new: true, for: 'name' };
el.class;                    // 'card'
el['class'];                 // то же самое

С ES5 ключевые слова разрешены в качестве имён свойств. До этого писали obj['class'] через скобки, а obj.class было ошибкой — след этого до сих пор виден в старом коде.

Именно поэтому в DOM свойство называется className, а не class: API придумывали до ES5, когда el.class был синтаксической ошибкой. Историческая причина, которая пережила саму проблему — подробнее в разборе работы с классами.

По той же причине у метки формы htmlFor, а не for.

Ловушка третья: undefined — не ключевое слово

Самое странное в этом списке.

typeof null;        // "object"  ← знаменитая ошибка, которую не стали чинить
typeof undefined;   // "undefined"

null — ключевое слово, литерал. Его нельзя переопределить, null = 5 даст ошибку.

undefined — не ключевое слово. Это свойство глобального объекта, и в старом коде вне строгого режима его можно было переприсвоить:

// в древнем коде это работало и ломало всё вокруг
undefined = 5;
if (x === undefined) { ... }   // сравнение с пятёркой!

Отсюда взялся приём, который до сих пор встречается в минифицированных библиотеках:

if (x === void 0) { ... }   // void 0 ВСЕГДА undefined, переопределить нельзя

void — ключевое слово, поэтому void 0 надёжен. Сегодня в модулях и строгом режиме undefined защищён, и трюк не нужен — но в чужом коде вы его увидите.

Что нельзя называть, даже если можно

Формально легально, практически — беда:

const name = 'Аня';       // перекрывает window.name в браузере
const length = 10;        // внутри функции перекроет arguments.length
const top = 1;            // window.top — ссылка на верхнее окно

Это не ключевые слова, а свойства окружения. Ошибки не будет — будет необъяснимое поведение через полгода.

Отдельно: this и super не только ключевые, но и меняют смысл в зависимости от места. this в стрелочной функции берётся снаружи, в обычной — зависит от вызова.

Что было на этой странице в 2017 году

Первая версия вышла 20 июля 2017 года и давала список слов с пометкой, что их нельзя использовать как имена. Список верный, и большая его часть не изменилась.

Что изменилось:

  • Добавились контекстные async и await — их в 2017-м в списке не было, потому что ES2017 только вышел.
  • Не было объяснения самой механики контекстности. Слова подавались как плоский список запретов, а тонкость «await ключевой только внутри async» — именно та, из-за которой старый код не сломался.
  • Не было undefined против null. А это единственное место в теме, где непонимание приводит к реальным багам.

Список ключевых слов — та часть языка, которая почти не меняется: добавить слово безусловно ключевым означает сломать чужой код. Поэтому всё новое приходит контекстным, и, скорее всего, так будет и дальше.


Смежные темы: что такое JavaScript; ECMAScript 6 — откуда взялись let и const; массивы; уроки JavaScript.

Частые вопросы

Почему `class` нельзя использовать как имя переменной, а `name` можно?
class — ключевое слово языка, оно занято грамматикой. name — просто свойство глобального объекта в браузере; переменная с таким именем его перекроет, что легально, но может выйти боком.
`await` — ключевое слово или нет?
Контекстное. Внутри async-функции и на верхнем уровне модуля — ключевое; в обычной функции старого скрипта это просто имя. Поэтому старый код с переменной await не сломался при появлении async/await.
Правда, что `undefined` можно переопределить?
В старом коде и вне строгого режима — да, undefined был обычным глобальным свойством. Отсюда старый приём void 0 вместо него. Сегодня в модулях и строгом режиме это невозможно.
Зачем зарезервированы слова, которых в языке нет — `enum`, `implements`?
Про запас: комитет держит их занятыми на случай будущих возможностей. enum зарезервирован с ES3 и не использован до сих пор.
Можно ли назвать свойство объекта ключевым словом?
Да, с ES5: obj.class и { new: 1 } легальны. Ограничение осталось только на имена переменных, функций и параметров.