Strict Mode в ECMAScript. Полный справочник

ad023d01ed4754bf122f7ab2f7aca7b8

По поводу строго режима существует множество информации. Но, к сожалению, очень мало статьей, покрывающих весь спектр особенностей строго режима. Поэтому, я решил составить свой справочник всех ограничений, исключений и отличий в исполнении «строгого» кода от «не строгого», в полном соответствии со спецификацией ECMA-262.

Что такое строгий режим

В спецификации ECMA-262 существует два понятия: «нестрогий режим» (non-strict mode) и «строгий режим» (strict mode). Каждая синтаксическая единица ECMAScript всегда исполняется с использованием синтаксиса и семантики того или другого режима. В отличие от нестрого, строгий режим исполнения накладывает ряд синтаксических ограничений, и, даже может исполняться иначе.

Какой код исполняется в строгом режиме, а какой в нестрогом

Для начала, стоит сказать, что, будучи примененным к данной синтаксической единице, отменить строгий режим в процессе исполнения для неё уже нельзя. Спецификацией не предусмотрено никаких директив для этого.

В строгом режим работает весь модульный код Module code и все части классового кода ClassDeclaration. Нет никакого способа отключить строгий режим в таком коде.

Весь остальной код, по умолчанию работает в нестрогом режиме, но его можно активировать посредством директивы «use strict» в Directive Prologue (строка, идущая до каких-либо других деклараций).

Ниже приведены возможные варианты включения строго режима в разных типах кода.

"use strict"
// Global code теперь работает в строгом режиме

eval(`"use strict"
// Eval code теперь работает в строгом режиме
`);

function foo() {
  "use strict"
  // Function code теперь работает в строгом режиме, а сама функция теперь называется строгой
}

new Function('a', 'b', `
"use strict";

// При использовании конструктора функции, строгий режим можно включить
// в последнем аргументе конструктра, являющимся телом функции
return a + b;
`)

Так же, стоит учитывать, что код внутри строго режима будет распространять режим и на все вложенные синтаксические единицы.

"use strict"

eval(`
// Eval code работает в строгом режиме
`);

function foo() {
  // как и Function code
  
  function bar() {
    // и вложенные функции тоже
  }
}

Зарезервированные слова

Зарезервированными (reserved words) считаются слова, которые нельзя использовать в качестве идентификатора. Не стоит путать зарезервированные слова с ключевыми (keywords). Многие из ключевых слов являются зарезервированными, но не все. Также, есть слова, которые являются зарезервированными только в конкретном контексте (например,  await является зарезервированным только внутри асинхронной функции или модуля).

Конкретно в strict mode зарезервированными считаются следующие слова:

implements,  interface,  let,  package,  private,  protected,  public,  static,  yield

Это значит, что их нельзя использовать в качестве индентификаторов

"use strict"

let implements = "";

// Uncaught SyntaxError: Unexpected strict mode reserved word

Восьмеричный литерал

В строгом режиме его использование запрещено. На всякий случай напомню, восьмеричный литерал начинается с символа 0 (можно несколько) и далее цифры  0, 1, 2, 3, 4, 5, 6, 7

"use strict"
01

// Uncaught SyntaxError: Octal literals are not allowed in strict mode.

Объявление переменных и свойства объекта

В нестрогом режиме, подобная конструкция приводит к тому, что в глобальном контексте объявляется переменная, которой присваивается значение

a = "a"

// Window {
//   ...
//   a: "a"
// }

В строгом режиме такой код запрещен

"use strict"

a = "a";

// Uncaught ReferenceError: a is not defined

Также, в строгом режиме учитывается атрибут { [[Writable]]: false } свойств объекта

"use strict"

const object = {};

Object.defineProperty(object, "a", {
    value: "a",
    writable: false,
});

object.a = "b";

// Uncaught TypeError: Cannot assign to read only property 'a' of object '#'

Аксессор { [[Set]]: undefined } свойства объекта тоже приведет к ошибке

"use strict"

const object = {};

Object.defineProperty(object, "a", {
    set: undefined,
});

object.a = "b";

// Uncaught TypeError: Cannot set property a of # which has only a getter

Аналогично, нельзя и добавлять новые свойства нерасширяемому объекту

"use strict"

const object = {};
Object.preventExtensions(object);

// Нерасширяемыми, также, считаются запечатанные объекты
// Object.seal(object);
//
// и замороженные
// Object.freeze(object)

object.a = "a";

// Uncaught TypeError: Cannot add property a, object is not extensible

eval и arguments

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

"use strict"

// Каждая строчка ниже приведет к ошибке
// Uncaught SyntaxError: Unexpected eval or arguments in strict mode

let eval = 1;

let argumnets = 1;

eval++;

arguments--;

--eval;

++arguments;

Так же, нельзя использовать использовать эти слова в качестве идентификатора функции

"use strict"

function eval() {}
function arguments() {}

// Uncaught SyntaxError: Unexpected eval or arguments in strict mode

и использовать их в качестве параметра блока catch

"use strict"

try {}
catch (arguments) {}

// Uncaught SyntaxError: Unexpected eval or arguments in strict mode

arguments

В строгой функции (когда строгий режим включен внутри самой фукнции) объект arguments переопределять нельзя

function foo() {
    "use strict"
    arguments = [];
}

foo(1);

// Uncaught SyntaxError: Unexpected eval or arguments in strict mode

В ранних версиях JavaScript небыло никакой другой возможности получить ссылку из функции на саму себя, кроме как через arguments.callee. Сейчас такой проблемы нет и можно спокойно использовать идентификатор функции внутрии неё самой, а использовать arguments.callee в строгих функциях — запрещено.

function foo() {
    "use strict"
    console.log(arguments.callee);
}

foo();

// Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

Вместо этого стоит использовать идентификатор самой функции

function foo() {
    "use strict"
    console.log(foo);
}

foo();

// ƒ foo() {
//    "use strict"
//    console.log(foo);
// }

Кроме этого, важный неочевидный момент — в строгой функции, элементы arguments не имеют связанности с прямыми аргументами

function foo(a) {
    "use strict"
    arguments[0] = 2;
    
    console.log(a, arguments[0])
}

foo(1);

// 1 2

Здесь, переопределив элемент arguments, сам аргумент a остался неизменным.

В нестрогом режиме подобный код поведет себя иначе

function foo(a) {
    arguments[0] = 2;
    
    console.log(a, arguments[0])
}

foo(1);

// 2 2

Здесь, переопределение элемента arguments приведет и к переопределению прямого аргумента.

Помимо этого, у строгой функции нельзя каким-либо образом изменить или расширить ссылку arguments и нестандартизированную ссылку caller, даже если глобальное окружение, само по себе, в строгом режиме не находится

function foo() {
    "use strict"
}

foo.caller = null
foo.arguments = null

foo();

// Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

eval

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

eval(`"use strict"
     var a = 1`);

console.log(a);

// Uncaught ReferenceError: a is not defined

В нестрогом режиме переменная объявится в глобальном окружении

eval("var a = 1");

console.log(a);

// 1

На всякий случай уточню, речь идет именно о VariableEnvironment (переменная объявляется с помощью var). В случае с let,  const и class, переменная сразу объявляется в LexicalEnvironment и, соответственно, будет доступна только внутри этого окружения, вне зависимости от режима.

this

В нестрогом режиме, если ссылка на контекст пустая (undefined или null), она автоматически меняется на ссылку глобального контекста

function foo() {
    console.log(this);
}

foo();

// Window { ... }

В строгом же режиме, ссылка меняться не будет и останется пустой

"use strict"

function foo() {
    console.log(this);
}

foo();

// undefined

То же справедливо и при использовании Function.prototype.apply и Function.prototype.call

"use strict"

function foo() {
    console.log(this);
}

foo.apply(null);

// null

delete

В строгом режиме нельзя применять оператор delete к прямым ссылкам на переменную, агрумент функции и имя функции

"use strict"

var a = 1;
delete a;

function foo(arg) {
    delete arg;
}

delete foo;

// Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.

Вообще, этот оператор стоит применять только к свойствам объекта, например так

"use strict"

const object = {
    a: 1,
};

delete object.a;

// <- true

Однако, если свойство объекта имеет атрибут { [[Configurable]]: false } или каким либо другим образом защищено от удаления, в строгом режиме удалить его также не получится

"use strict"

const object = {};

Object.defineProperty(object, "a", { configurable: false });

delete object.a;

// Uncaught TypeError: Cannot delete property 'a' of #

delete Object.prototype

// Uncaught TypeError: Cannot delete property 'prototype' of function Object() { [native code] }

with

В строгом режиме нельзя использовать выражение with. Любые попытки его применить вызовут синтаксическую ошибку

"use strict"

const object = {};

with (object) {}

// Uncaught SyntaxError: Strict mode code may not include a with statement

Дубликаты параметров функций

В строгой функции, попытка дублирования названий параметров приведет к синтаксической ошибке

function foo(a, a) {
    "use strict"
}

foo();

// Uncaught SyntaxError: Duplicate parameter name not allowed in this context

Аналогично и при использовании конструктора функции

const foo = new Function('a', 'a', `
"use strict";
`);

foo();

// Uncaught SyntaxError: Duplicate parameter name not allowed in this context

В то время как в нестрогой функции дублирование возможно

function foo(a, a) {
    console.log(a);
}

foo(1, 2);

// 2

Эту и другие мои статьи, так же, читайте в моем канале:

RU:  https://t.me/frontend_almanac_ru
EN:  https://t.me/frontend_almanac

© Habrahabr.ru