Strict Mode в ECMAScript. Полный справочник
По поводу строго режима существует множество информации. Но, к сожалению, очень мало статьей, покрывающих весь спектр особенностей строго режима. Поэтому, я решил составить свой справочник всех ограничений, исключений и отличий в исполнении "строгого" кода от "не строгого", в полном соответствии со спецификацией 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; `)
Однако, стоит учитывать, что строгий режим можно включить не в каждой функции. Если быть более конкретным, этот режим доступен только функциям с, так называемыми, простыми(IsSimpleParameterList) параметрами. Другими словами, не удасться включить строгий режим в следующих случаях
function foo(a = 1) { // в случае использования значений по умолчанию "use strict"; } function foo(a, ...rest) { // в случае использования rest-параметров "use strict"; } function foo({ a }) { // в случае использования деструкции "use strict"; } // Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
Так же, стоит учитывать, что код внутри строго режима будет распространять режим и на все вложенные синтаксические единицы.
"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
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 #<Object> 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
EN - https://t.me/frontend_almanac
RU - https://t.me/frontend_almanac_ru
English version: https://blog.frontend-almanac.com/strict-mode