Строгий режим (Strict mode) современного JavaScript не позволяет использовать старый "неаккуратный" режим JavaScript с игнорированием некоторых ошибок в коде, вследствие чего код может запускаться с неожиданными результатами.
Strict mode изменяет как допустимый синтаксис кода, так и поведение кода во время выполнения, что влияет на появление в коде ошибок синтаксиса или ошибок выполнения, которые ранее пропускались движком в обычном режиме.
Строгий режим заменяет исключениями игнорируемые в обычном режиме ошибки, поэтому с этими ошибками код не запускается.
Строгий режим применим как к отдельным функциям, так и к целому скрипту, однако его нельзя применять к операторам и другим блокам, заключенным в фигурные скобки.
Включение строгого режима производится размещением специальной директивы "use strict" (или 'use strict'):
- на первой строке скрипта - режим включается для всего скрипта;
- в начале функции (после открывающей фигурной скобки) - режим включается для кода функции, включая вложенные функции.
Строгий режим в функции:
1 2 3 4 5 6 |
(function () { "use strict"; // ... код функции ... })(); |
Строгий режим в стрелочной функции:
1 2 3 4 5 6 |
const mainFunction = () => { 'use strict'; const nestedFunction = () => { // эта функция тоже подчинена строгому режиму }; }; |
Особенности использования строгого режима (Strict mode) в JavaScript:
- При объединении со скриптами, использующими строгий режим, скрипты, которые его не используют, начинают его использовать, и наоборот: объединение обычного скрипта со строгим выглядит как нестрогий скрипт.
- Отменить строгий режим невозможно (нет никакой отменяющей директивы, которая возвращала бы движок к старому поведению).
- В структурах современного JavaScript ("классах" и "модулях") строгий режим включается автоматически, поэтому в них добавлять директиву "use strict" не нужно.
Появление в коде JavaScript ошибок синтаксиса или ошибок выполнения при включении строгого режима
В обычном (нестрогом) режиме на некоторые допустимые ошибки JS никак не реагирует. Строгий режим ограничивает использование ошибочного синтаксиса и не позволяет коду запускаться с ошибками в случаях:
- создания глобальных переменных;
- удаления защищенных свойств объекта;
- использования неуникальных имен параметров функции;
- использования восьмеричной записи чисел;
- использование синтаксиса, усложняющего оптимизацию (например, при использовании оператора with);
- объявлении переменных внутри оператора eval;
- попыток удаления простых имен переменных.
Так, строгий режим предотвращает создание глобальных переменных, например, код ниже вызовет ReferenceError:
1 2 3 4 5 6 |
'use strict'; globalVariable = 1; // ReferenceError: assignment to undeclared variable globalVariable (Firefox) // ReferenceError: globalVariable is not defined (Chrome) // ReferenceError: Variable undefined in strict mode (Edge) |
Таким образом, в строгом режиме указанный выше код не запустится: глобальную переменную globalVariable можно создать только при выключенном строгом режиме, который предотвращает случайное создание глобальных переменных.
Кроме того, в строгом режиме нельзя присваивать значения:
- переменным только для чтения (например, arguments, NaN или eval);
- защищенным от записи глобальным переменным (undefined и Infinity);
- защищенным от записи свойствам объектов (свойствам только для геттеров) и свойствам нерасширяемых объектов.
Примеры отсюда
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
"use strict"; let undefined = 5; // SyntaxError: redeclaration of non-configurable global property undefined let Infinity = 5; // SyntaxError: redeclaration of non-configurable global property Infinity // Защищенное от записи свойство объекта let obj = {}; Object.defineProperty(obj, "foo", { value: 1, writable: false }); obj.foo = 1; // TypeError: "foo" is read-only // Свойство геттера let obj2 = { get foo() { return 17; }, }; obj2.foo = 2; // TypeError: setting getter-only property "foo" // Нерасширяемый объект (защищен от добавления новых свойств методом Object.preventExtensions) let fixedObj = {}; Object.preventExtensions(fixedObj); fixedObj.bar = 1; //TypeError: can't define property "bar": Object is not extensible |
При попытке удалить защищенные свойства объекта появится исключение TypeError, например:
1 2 |
'use strict'; delete Array.prototype // TypeError: property "prototype" is non-configurable and can't be deleted |
В строгом режиме имена параметров функции должны быть уникальны. В обычном же режиме, если у двух параметров одинаковое имя, определенное позже имя будет принято как значение параметра при передаче аргументов. Поэтому следующий пример выдаст синтаксическую ошибку:
1 |
const multiply = (x, x, y) => x * x * y; // SyntaxError: duplicate argument names not allowed in this context |
В строгом режиме восьмеричная запись чисел не разрешена.
Она не является частью спецификации, но поддерживается в браузерах добавлением 0 к восьмеричным числам. Это сбивает разработчиков с толку, так как некоторые думают, что 0 перед числом ничего не значит.
1 2 3 4 5 |
"use strict"; var sum = 015 + // SyntaxError: Octal literals are not allowed in strict mode. 197 + 142; |
Строгий режим предотвращает использование синтаксиса, усложняющего оптимизацию. Один из примеров — это оператор with, при использовании которого интерпретатор JavaScript не знает, на какую переменную или свойство вы ссылаетесь, поскольку переменная с тем же именем может быть внутри или снаружи оператора with.
Использование оператора with не рекомендуется.
Например:
1 2 3 4 |
let x = 1; with (obj) { // SyntaxError: strict mode code may not contain 'with' statements x; } |
JavaScript не сможет определить x внутри оператора with ссылается на переменную x или свойство obj.x. Следовательно, расположение x в памяти неоднозначно. Поэтому строгий режим запрещает использование оператора with.
Следующая вещь, имеющая особенности в строгом режиме — это объявление переменных внутри оператора eval.
Использование оператора eval не рекомендуется, он считается устаревшим.
Например, без строгого режима eval('let x') объявит переменную x внутри кода. Это позволяет прятать объявление переменных в строках, что может блокировать объявление той же переменной вне оператора eval. Чтобы предотвратить это, строгий режим не позволяет объявлять переменные в аргументе строки, который мы передаем внутрь оператора eval.
Строгий режим также запрещает удаление простых имен переменных, поэтому код ниже выдаст синтаксическую ошибку:
1 2 3 |
'use strict'; let x; delete x; // SyntaxError: applying the 'delete' operator to an unqualified name is deprecated |
Неверный синтаксис метода eval и объекта argument не разрешен в строгом режиме.
Например, им нельзя задать новые значения или использовать их как имена переменных, функций или параметров функций. Вот пример неверного использования eval и argument (отсюда):
1 2 3 4 |
eval = 1; // SyntaxError: 'eval' can't be defined or assigned to in strict mode code let eval; // SyntaxError: 'eval' can't be defined or assigned to in strict mode code ++eval; // SyntaxError: 'eval' can't be defined or assigned to in strict mode code eval--; // SyntaxError: 'eval' can't be defined or assigned to in strict mode code |
1 2 |
try { } catch (eval) {} // SyntaxError: 'eval' can't be defined or assigned to in strict mode code |
1 |
function x(eval) {} // SyntaxError: 'eval' can't be defined or assigned to in strict mode code |
1 |
let y = function eval() {}; // SyntaxError: 'eval' can't be defined or assigned to in strict mode code |
1 |
let eval = () => {}; // SyntaxError: 'eval' can't be defined or assigned to in strict mode code |
1 2 |
arguments++; // SyntaxError: 'arguments' can't be defined or assigned to in strict mode code arguments--; // SyntaxError: 'arguments' can't be defined or assigned to in strict mode code |
1 |
let obj = { set p(arguments) {} }; // SyntaxError: 'arguments' can't be defined or assigned to in strict mode code |
1 2 |
try { } catch (arguments) {} // SyntaxError: 'arguments' can't be defined or assigned to in strict mode code |
1 |
function arguments() {} // SyntaxError: 'arguments' can't be defined or assigned to in strict mode code |
1 |
let f = new Function("arguments", "'use strict'; return 1;"); // SyntaxError: 'arguments' can't be defined or assigned to in strict mode code |
Строгий режим не разрешает создавать псевдоним для объекта arguments и задавать с ним новые значения.
Рассмотрим пример:
1 2 3 4 5 6 |
const fn = function (a) { // "use strict"; a = 2; return [a, arguments[0]]; }; console.log(fn(1)); |
Без строгого режима, если параметр функции — a, тогда присвоение внутри функции переменной a некоторого значения также присваивает значение arguments[0]. В строгом режиме параметр a и переменная a внутри функции различаются (у объекта arguments всегда будет список аргументов, с которыми вызывается функция).
Например:
- в обычном (нестрогом) режиме:
1 2 3 4 5 |
const fn = function (a) { a = 2; return [a, arguments[0]]; }; console.log(fn(1)); // Array [ 2, 2 ] |
- в строгом режиме:
1 2 3 4 5 6 |
const fn = function (a) { "use strict"; a = 2; return [a, arguments[0]]; }; console.log(fn(1)); // Array [ 2, 1 ] |
Оптимизация производительности JavaScript в строгом режиме
Подробнее о arguments.callee
В обычном режиме arguments.callee возвращает имя функции с arguments.callee внутри. Например:
1 2 3 4 |
var f = function test() { alert(typeof test); // undefined alert(typeof arguments.callee); // function }; |
Это мешает оптимизациям, например, встроенным функциям, потому что arguments.callee требует, чтобы при его вызове была доступна ссылка на невстроенную функцию. Поэтому теперь в строгом режиме arguments.callee вызывает TypeError:
1 2 3 4 |
var f = function test() { alert(typeof test); // undefined alert(typeof arguments.callee); // TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at test }; |
В обычной функции this всегда представляет собой объект:
- либо это непосредственно объект, в случае вызова с this представляющим объект-значение;
- либо значение, упакованное в объект, в случае вызова с this типа Boolean, string, или number;
- либо глобальный объект, если тип this это undefined или null.
При этом автоматическая упаковка снижает производительность. Кроме того, когда обычная функция находится в глобальной области видимости, ключевое слово this, использованное в ней, будет привязано к объекту window, что делает видимым глобальный объект и является угрозой безопасности, потому что глобальный объект предоставляет доступ к функциональности, которая должна быть ограничена в среде "безопасного" JavaScript.
Для точного определения конкретного this необходимо использовать call, apply, или bind.
Однако если функция выполняется в строгом режиме, то в this будет записано undefined, так как в этом режиме запрещены привязки по умолчанию.
1 2 3 4 5 |
function test() { "use strict"; return this; } console.log(test()); // undefined (а не объект Window) |
Сравните:
1 2 3 4 |
function test() { return this; } console.log(test()); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …} |
Таким образом значение, передаваемое в функцию как this, в строгом режиме не приводится к объекту (не "упаковывается"), т.е. для функции в строгом режиме точно определённый this не упаковывается в объект, а если не определён точно, this является undefined.
Если this функции связан call, apply или bind с любыми необъектными типами, такими как примитивные типы undefined, null, number, boolean и так далее, они будут принудительно приведены к объекту:
1 2 3 4 5 6 7 8 9 |
"use strict"; function fun() { return this; } console.assert(fun() === undefined); console.assert(fun.call(2) === 2); console.assert(fun.apply(null) === null); console.assert(fun.call(undefined) === undefined); console.assert(fun.bind(true)() === true); |
Все журналы консоли будут иметь значение true, так как this внутри функции в строгом режиме не преобразуется автоматически в глобальный объект window.
Исправления безопасности в режиме strict mode
В строгом режиме caller и arguments представляют собой неудаляемые свойства, которые приведут к вызову исключения при попытке их чтения или записи:
1 2 3 4 5 6 7 8 9 10 |
function restricted() { "use strict"; restricted.caller; // выдаст TypeError restricted.arguments; // выдаст TypeError } function privilegedInvoker() { return restricted(); } privilegedInvoker(); |
Потенциальная дыра в безопасности устраняется запретом доступа к этим двум свойствам функции. В строгом режиме в примере выше мы не сможем получить доступ к restricted.caller и restricted.arguments и использовать их для получения стека функций. При выполнении код выдаст TypeError.