Strict Mode в ECMAScript. Полный справочник
- пятница, 9 февраля 2024 г. в 00:00:19
По поводу строго режима существует множество информации. Но, к сожалению, очень мало статьей, покрывающих весь спектр особенностей строго режима. Поэтому, я решил составить свой справочник всех ограничений, исключений и отличий в исполнении "строгого" кода от "не строгого", в полном соответствии со спецификацией 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 '#<Object>'
Аксессор { [[Set]]: undefined }
свойства объекта тоже приведет к ошибке
"use strict"
const object = {};
Object.defineProperty(object, "a", {
set: undefined,
});
object.a = "b";
// Uncaught TypeError: Cannot set property a of #<Object> 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
Если переменная имеет идентифактор, соответствующий какому-либо из этих двух ключевых слов, она не может осуществлять операции присваивания и унарного обновления
"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 переопределять нельзя
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, в строгом режиме не может объявлять переменные и функции в 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 и, соответственно, будет доступна только внутри этого окружения, вне зависимости от режима.
В нестрогом режиме, если ссылка на контекст пустая (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 к прямым ссылкам на переменную, агрумент функции и имя функции
"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 #<Object>
delete Object.prototype
// Uncaught TypeError: Cannot delete property 'prototype' of function Object() { [native code] }
В строгом режиме нельзя использовать выражение 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