Строгий режим — это важная часть современного JavaScript. Он позволяет использовать ограниченный синтаксис JavaScript.
Семантика строгого режима отличается от старого “неаккуратного режима” JavaScript с его слабым синтаксисом и “замалчиваемыми” ошибками в коде — такие ошибки игнорируются, и код может запускаться с неожиданными результатами.
Строгий режим вносит несколько изменений в семантику JavaScript. Он заменяет исключениями “замалчиваемые” в обычном режиме ошибки, поэтому с этими ошибками код не запускается.
Он также исправляет ошибки, мешающие движкам JavaScript производить оптимизацию, и запрещает функции, которые могут быть определены в будущих версиях JavaScript.
Строгий режим применим как к отдельным функциям, так и к целому скрипту. Его нельзя применять только к операторам и другим блокам, заключенным в фигурные скобки. Чтобы скрипт использовал строгий режим, добавляем оператор "use strict"
или 'use strict'
в начало скрипта перед всеми остальными операторами.
Будьте внимательны: при объединении со скриптами, использующими строгий режим, скрипты, которые его не используют, начинают его использовать, и наоборот, поэтому объединять скрипты с разными режимами не стоит.
Чтобы применить строгий режим к функциям, нужно добавить оператор "use strict"
или 'use strict'
внутрь функции перед всеми остальными операторами. Он применяется ко всему, что находится внутри, включая вложенные функции.
Например:
const strictFunction = ()=>{
'use strict';
const nestedFunction = ()=>{
// эта функция также использует строгий режим
}
}
В модулях JavaScript, представленных в ES2015, строгий режим применяется автоматически, поэтому его не нужно включать операторами.
Изменения в строгом режиме
Строгий режим изменяет и синтаксис, и поведение кода во время выполнения. Наиболее важные изменения:
- преобразование ранее допустимых ошибок в ошибки синтаксиса или ошибки выполнения;
- изменения, упрощающие вычисление конкретных переменных;
- изменения, упрощающие функцию
eval
и объектarguments
; - изменения, которые будут применены в будущей спецификации ES.
Преобразование допустимых ошибок в недопустимые
В нестрогом режиме на некоторые допустимые ошибки JS никак не реагировал. Строгий режим ограничивает использование ошибочного синтаксиса и не позволяет коду запускаться с ошибками.
Он затрудняет создание глобальных переменных, не позволяя объявлять переменные с помощью var
, let
или const
. Например, код ниже выдаст ReferenceError
:
'use strict';
badVariable = 1;
Код не запустится в строгом режиме — глобальную переменную badVariable
можно создать только при выключенном строгом режиме, который нужен для предотвращения случайного создания глобальных переменных.
Теперь любой код с этой ранее допустимой ошибкой выдаст исключение. Это распространяется и на некорректный синтаксис, который игнорировался ранее.
Например, в строгом режиме мы не можем присваивать значения переменным только для чтения: arguments
, NaN
или eval
. Любое присвоение значений защищенным от записи глобальным переменным, свойствам только для геттеров и свойствам нерасширяемых объектов в строгом режиме выдаст исключение.
Ниже несколько примеров неудачного синтаксиса:
'use strict';
let undefined = 5;
let Infinity = 5;
let obj = {};
Object.defineProperty(obj, 'foo', { value: 1, writable: false });
obj.foo = 1
let obj2 = { get foo() { return 17; } };
obj2.foo = 2
let fixedObj = {};
Object.preventExtensions(fixedObj);
fixed.bar= 1;
Все примеры выше выдадут TypeError
. undefined
и Infinity
— глобальные переменные, защищенные от записи, obj
— защищенное от записи свойство.
Свойство foo
obj2
— единственное свойство геттера, и поэтому не может быть задано. fixedObj
был защищен от добавления новых свойств методом Object.preventExtensions
.
Кроме того, при попытке удалить неудаляемые свойства появится исключение TypeError
, например:
'use strict';
delete Array.prototype
В строгом режиме имена параметров функции должны быть уникальны. В обычном режиме, если у двух параметров одинаковое имя, определенное позже имя будет принято как значение параметра при передаче аргументов. Поэтому следующий пример выдаст синтаксическую ошибку:
const multiply = (x, x, y) => x*x*y;
В строгом режиме восьмеричная запись чисел также не разрешена. Она не является частью спецификации, но поддерживается в браузерах добавлением 0 к восьмеричным числам. Это сбивает разработчиков с толку, так как некоторые думают, что 0 перед числом ничего не значит. Как следствие, строгий режим не разрешает этот синтаксис и выдает ошибку.
Строгий режим предотвращает использование синтаксиса, усложняющего оптимизацию. Ему нужно знать, что переменная действительно хранится в том месте, где он думает, до того, как производить оптимизацию.
Один из примеров — это оператор with
. При его использовании интерпретатор JavaScript не знает, на какую переменную или свойство вы ссылаетесь, поскольку переменная с тем же именем может быть внутри или снаружи оператора with
.
Приведем пример:
let x = 1;
with (obj) {
x;
}
JavaScript не будет знать, x
внутри оператора with
ссылается на переменную x
или свойство obj
, obj.x
.
Следовательно, расположение x
в памяти неоднозначно. Таким образом, строгий режим запрещает использование оператора with
. При включенном строгом режиме пример ниже выдаст ошибку:
'use strict';
let x = 1;
with (obj) {
x;
}
Следующая вещь, запрещенная в строгом режиме — это объявление переменных внутри оператора eval
.
Например, без строгого режима eval('let x')
объявит переменную x
внутри кода. Это позволяет программистам прятать объявление переменных в строках, что может блокировать объявление той же переменной вне оператора eval
.
Чтобы предотвратить это, строгий режим не позволяет объявлять переменные в аргументе строки, который мы передаем внутрь оператора eval
.
Строгий режим также запрещает удаление простых имен переменных, поэтому код ниже выдаст синтаксическую ошибку:
'use strict';
let x;
delete x;
Запрет неверного синтаксиса
Неверный синтаксис метода eval
и объекта argument
не разрешен в строгом режиме.
Например, им нельзя задать новые значения или использовать их как имена переменных, функций или параметров функций.
Вот пример неверного использования eval
и argument
:
'use strict';
eval = 1;
arguments++;
arguments--;
++eval;
eval--;
let obj = { set p(arguments) { } };
let eval;
try { } catch (arguments) { }
try { } catch (eval) { }
function x(eval) { }
function arguments() { }
let y = function eval() { };
let eval = ()=>{ };
let f = new Function('arguments', "'use strict'; return 1;");
Строгий режим не разрешает создавать псевдоним для объекта arguments
и задавать с ним новые значения.
Без строгого режима, если первый параметр функции — a
, тогда установка a
также задает arguments[0]
. В строгом режиме у объекта arguments
всегда будет список аргументов, с которыми вызывается функция.
Например, если у нас есть:
const fn = function(a) {
'use strict';
a = 2;
return [a, arguments[0]];
}console.log(fn(1))
Тогда мы должны увидеть [2,1]
в журнале, потому что установка значения 2 в a
не задает его в arguments[0]
.
Оптимизация производительности
Кроме того, больше нет поддержки arguments.callee
. Без строгого режима arguments.callee
возвращает имя функции с arguments.callee
внутри.
Это мешает оптимизациям, например, встроенным функциям, потому что arguments.callee
требует, чтобы при его вызове была доступна ссылка на невстроенную функцию. Поэтому теперь в строгом режиме arguments.callee
вызывает TypeError
.
В строгом режиме значение this
не приводится к объекту. Если this
функции связан с call
, apply
или bind
с любыми необъектными типами, такими как примитивные типы undefined
, null
, number
, boolean
и так далее, они будут принудительно приведены к объекту.
Если контекст this
переключается в необъектный режим, его место занимает глобальный объект window
. Это означает, что глобальный объект открыт для функции, вызываемой this
, связанным с необъектным типом.
Например, если мы запустим код ниже:
'use strict';function fn() {
return this;
}
console.log(fn() === undefined);
console.log(fn.call(2) === 2);
console.log(fn.apply(null) === null);
console.log(fn.call(undefined) === undefined);
console.log(fn.bind(true)() === true);
Все журналы консоли будут иметь значение true
, так как this
внутри функции не преобразуется автоматически в глобальный объект window
, когда this
меняется на что-то, имеющее необъектный тип.
Исправления безопасности
В строгом режиме caller
и arguments
не публичные, так как caller
может отображать функцию, вызывающую другую функцию, к которой обращается это свойство caller
.
В arguments
есть аргументы, передаваемые при вызове функции. Например, если у нас есть функция fn
, тогда через fn.caller
мы можем увидеть аргументы, которые были переданы в fn
, когда был совершен ее вызов.
Это создает потенциальную дыру в безопасности, которая устраняется запретом доступа к этим двум свойствам функции.
function secretFunction() {
'use strict';
secretFunction.caller;
secretFunction.arguments;
}
function restrictedRunner() {
return secretFunction();
}
restrictedRunner();
В строгом режиме в примере выше мы не сможем получить доступ к secretFunction.caller
и secretFunction.arguments
, так как люди могут использовать их для получения стека функций. При выполнении код выдаст TypeError
.
Идентификаторы, которые станут ключевыми словами в будущих версиях JavaScript, нельзя использовать для именования переменных или свойств объектов. В ES2015 или позже следующие ключевые слова стали зарезервированными, и их нельзя использовать для определения идентификаторов в коде: implements
, interface
, let
, package
, private
, protected
, public
, static
и yield
.
Строгий режим был стандартом несколько лет. Обычно браузеры его поддерживают. Проблемы могут возникнуть только в старых браузерах, таких как Internet Explorer.
У других браузеров не должно быть проблем в работе со строгим режимом, поэтому его стоит использовать для предотвращения перечисленных выше ошибок.
Читайте также:
- Основы JavaScript: управление DOM элементами (часть 1)
- Как работает JavaScript
- Оператор Spread и деструктуризация в JavaScript: практическое руководство
Перевод статьи John Au-Yeung: Why Do We Need Strict Mode in JavaScript?