Java Script

Строгий режим — это важная часть современного 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. 

У других браузеров не должно быть проблем в работе со строгим режимом, поэтому его стоит использовать для предотвращения перечисленных выше ошибок.

Читайте также:


Перевод статьи John Au-Yeung: Why Do We Need Strict Mode in JavaScript?