https://habr.com/ru/company/ruvds/blog/477284/- Блог компании RUVDS.com
- Разработка веб-сайтов
- JavaScript
Строгий режим (strict mode) — это важная часть современного JavaScript. Именно этот режим позволяет разработчикам пользоваться более ограниченным, чем стандартный, синтаксисом.
Семантика строгого режима отличается от традиционного нестрогого режима, который иногда называют «грязным» (sloppy mode). В таком режиме синтаксические правила языка не так строги, а когда происходят некоторые ошибки, система никак не оповещает о них пользователя. То есть — ошибки могут быть проигнорированы, а код, в котором они допущены, сможет выполняться дальше. Это способно привести к неожиданным результатам выполнения кода.
Строгий режим вносит в семантику JavaScript некоторые изменения. Он не даёт системе закрывать глаза на ошибки, выдавая соответствующие исключения. Это приводит к остановке выполнения программ.
Строгий режим, кроме того, помогает в написании программ, в которых нет недочётов, мешающих JS-движкам оптимизировать код. Далее, в этом режиме запрещено использование элементов синтаксиса, которые могут получить особый смысл в будущих версиях языка.
Особенности применения строгого режима
Строгий режим можно применять к отдельным функциям или к целому скрипту. Его нельзя применить только к отдельным инструкциям или к блокам кода, заключённым в фигурные скобки. Для того чтобы использовать строгий режим на уровне целого скрипта, в самое начало файла, до любых других команд, нужно поместить конструкцию
"use strict"
или
'use strict'
.
Если в проекте имеются некоторые скрипты, в которых не используется строгий режим, и другие, в которых этот режим используется, тогда может случиться так, что эти скрипты окажутся объединены.
Это приведёт к тому, что код, который не предназначен для выполнения в строгом режиме, окажется в таком состоянии, когда система попытается выполнить его в строгом режиме. Возможно и обратное — код, написанный для строгого режима, попадёт в нестрогий режим. Поэтому лучше всего не смешивать «строгие» и «нестрогие» скрипты.
Как уже было сказано, строгий режим можно применять к отдельным функциям. Для того чтобы это сделать — конструкцию
"use strict"
или
'use strict'
надо поместить в верхнюю часть тела функции, до любых других команд. Строгий режим при таком подходе применяется ко всему, что размещено в теле функции, включая вложенные функции.
Например:
const strictFunction = ()=>{
'use strict';
const nestedFunction = ()=>{
// эта функция тоже использует строгий режим
}
}
В JavaScript-модулях, которые появились в стандарте ES2015, строгий режим включён по умолчанию. Поэтому при работе с ними включать его явным образом не нужно.
Изменения, вводимые в работу JS-кода строгим режимом
Строгий режим влияет и на синтаксис кода, и на то, как код ведёт себя во время выполнения программы. Ошибки в коде преобразуются в исключения. То, что в нестрогом режиме тихо даёт сбой, в строгом вызывает сообщение об ошибке. Это похоже на то, как в нестрогом режиме система реагирует на синтаксические ошибки. В строгом режиме упрощается работа с переменными, жёстко регулируется использование функции
eval
и объекта
arguments
, упорядочивается работа с конструкциями, которые могут быть реализованы в будущих версиях языка.
▍Преобразование «тихих» ошибок в исключения
«Тихие» ошибки преобразуются в строгом режиме в исключения. В нестрогом режиме на такие ошибки система явным образом не реагирует. В строгом же режиме наличие таких ошибок приводит к неработоспособности кода.
Так, благодаря этому сложно совершить ошибку случайного объявления глобальной переменной, так как переменные и константы в строгом режиме нельзя объявлять без использования директив
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
— это глобальные сущности, значения которых нельзя перезаписывать, а свойство
foo
объекта
obj
не поддерживает перезапись. Свойство
foo
объекта
obj2
имеет лишь геттер. Объект
fixedObj
сделан нерасширяемым с помощью метода
Object.preventExtensions
.
К выдаче
TypeError
приведёт и попытка удаления неудаляемого свойства:
'use strict';
delete Array.prototype
Строгий режим запрещает назначать объекту свойства с одинаковыми именами. Как результат — попытка выполнения следующего кода приведёт к возникновению синтаксической ошибки:
'use strict';
let o = { a: 1, a: 2 };
Строгий режим требует, чтобы имена параметров функций были бы уникальными. В нестрогом режиме, если, например, два параметра функции имеют одно и то же имя
one
, тогда, при передаче функции аргументов, значением параметра станет то, что попало в аргумент, объявленный последним.
В строгом режиме запрещены параметры функций с одинаковыми именами. В результате попытка выполнения следующего кода приведёт к возникновению синтаксической ошибки:
'use strict';
const multiply = (x, x, y) => x*x*y;
В строгом режиме нельзя использовать восьмеричную запись чисел, предваряя число нулём. Этого нет в спецификации, но данная возможность поддерживается браузерами.
Такое положение дел путает разработчиков, заставляя их полагать, что 0, предшествующий числу, просто игнорируется, не имея особого смысла. В строгом режиме попытка воспользоваться числом, в начале которого стоит 0, приведёт к синтаксической ошибке.
Строгий режим, кроме того, запрещает использование конструкций, затрудняющих оптимизацию. Интерпретатору, перед выполнением оптимизации кода, нужно знать о том, что переменная хранится именно там, где, как считает интерпретатор, она хранится. В строгом режиме запрещается то, что мешает оптимизациям.
Один из примеров подобного запрета касается инструкции
with
. Если пользоваться данной инструкцией, то это мешает JS-интерпретатору узнать о том, к какой именно переменной или к какому именно свойству мы обращаемся, так как возможно такое, что сущность с одним и тем же именем имеется и снаружи, и внутри блока инструкции
with
.
Предположим, есть такой код:
let x = 1;
with (obj) {
x;
}
Интерпретатор не сможет узнать о том, ссылается ли переменная
x
, находящаяся внутри блока
with
, на внешнюю переменную
x
, или на свойство
obj.x
объекта
obj
.
В результате неясно — где именно в памяти будет расположено значение
x
. Для того чтобы избавиться от подобных неоднозначностей, в строгом режиме использование инструкции
with
запрещено. Посмотрим, что случится, если попытаться выполнить в строгом режиме следующий код:
'use strict';
let x = 1;
with (obj) {
x;
}
Результатом этой попытки будет синтаксическая ошибка.
Ещё в строгом режиме запрещено объявлять переменные в коде, переданном методу
eval
.
Например, в обычном режиме команда вида
eval('let x')
приведёт к объявлению переменной
x
. Это позволяет программистам скрывать объявления переменных в строках, что может привести к перезаписи определений тех же переменных, находящихся за пределами
eval
.
Для того чтобы это предотвратить, в строгом режиме запрещено объявлять переменные в коде, передаваемом в виде строки методу
eval
.
Строгий режим, кроме того, запрещает удаление обычных переменных. В результате попытка выполнить следующий код приведёт к синтаксической ошибке:
'use strict';
let x;
delete x;
▍Запрет некорректных синтаксических конструкций
В строгом режиме запрещено неправильное использование
eval
и
arguments
. Речь идёт о запрете всяческих манипуляций с ними. Например — это нечто вроде присваивания им новых значений, использование их имён в роли имён переменных, функций, параметров функций.
Вот примеры некорректного использования
eval
и
arguments
:
'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
и устанавливать новые значения
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
не приводит к записи значения 2 в
arguments[0]
.
▍Оптимизации производительности
В строгом режиме не поддерживается свойство
arguments.callee
. В обычном режиме оно возвращает имя функции-родителя той функции, свойство
callee
объекта
arguments
которой мы исследуем.
Поддержка этого свойства мешает оптимизациям, наподобие встраивания функций, так как использование
arguments.callee
требует доступности ссылки на невстроенную функцию при доступе к этому свойству. В строгом режиме использование
arguments.callee
приводит к появлению исключения
TypeError
.
В строгом режиме ключевое слово
this
не обязано всегда быть объектом. В обычных условиях, если
this
функции привязывается, с помощью
call
,
apply
или
bind
, к чему-то, что не является объектом, к значению примитивного типа вроде
undefined
,
null
,
number
или
boolean
, подобное значение должно быть объектом.
Если контекст
this
меняется на что-то, не являющееся объектом, его место занимает глобальный объект. Например —
window
. Это означает, что если вызвать функцию, установив её
this
в некое значение, не являющееся объектом, вместо этого значения в
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);
Все команды
console.log
выведут
true
, так как в строгом режиме значение
this
в функции не заменяется автоматически ссылкой на глобальный объект в том случае, если
this
устанавливается в значение, не являющееся объектом.
▍Изменения, имеющие отношение к безопасности
В строгом режиме нельзя делать общедоступными свойства функции
caller
и
arguments
. Дело в том, что
caller
, например, может дать доступ к функции, вызвавшей функцию, к свойству
caller
которой мы обращаемся.
В объекте
arguments
хранятся аргументы, переданные функции при её вызове. Например, если у нас имеется функция
fn
, это значит, что через
fn.caller
можно обратиться к функции, вызвавшей данную функцию, а с помощью
fn.arguments
можно увидеть аргументы, переданные
fn
при вызове.
Эти возможности представляют собой потенциальную угрозу безопасности. В результате в строгом режиме доступ к этим свойствам запрещён.
function secretFunction() {
'use strict';
secretFunction.caller;
secretFunction.arguments;
}
function restrictedRunner() {
return secretFunction();
}
restrictedRunner();
В предыдущем примере мы не можем, в строгом режиме, обратиться к
secretFunction.caller
и
secretFunction.arguments
. Дело в том, что эти свойства можно использовать для получения стека вызовов функции. Если попытаться запустить этот код — будет выдано исключение
TypeError
.
В строгом режиме для именования переменных или свойств объектов нельзя использовать идентификаторы, которые могут найти применение в будущих версиях JavaScript. Речь идёт, например, о следующих идентификаторах:
implements
,
interface
,
let
,
package
,
private
,
protected
,
public
,
static
и
yield
.
В ES2015 и в более поздних версиях стандарта эти идентификаторы стали зарезервированными словами. И их нельзя использовать для именования переменных или свойств в строгом режиме.
Итоги
Строгий режим — это стандарт, который существует уже многие годы. Он пользуется чрезвычайно широкой поддержкой браузеров. Проблемы с кодом, выполняемом в строгом режиме, могут возникать лишь у старых браузеров, таких, как Internet Explorer.
У современных браузеров не должно возникать сложностей со строгим режимом JavaScript. В результате можно сказать, что этот режим стоит использовать ради предотвращения «тихих» ошибок и ради повышения безопасности приложений. «Тихие» ошибки преобразуются в исключения, препятствующие выполнению программ, а в плане повышения безопасности можно, например, отметить механизмы строгого режима, ограничивающие
eval
и предотвращающие доступ к стеку вызовов функций. Кроме того, использование строгого режима облегчает оптимизацию кода JS-движками и заставляет программиста осторожно обращаться с зарезервированными словами, которые могут найти применение в будущих версиях JavaScript.
Уважаемые читатели! Пользуетесь ли вы строгим режимом при написании JS-кода своих проектов?
