Зачем в JavaScript нужен строгий режим?

Автор оригинала: John Au-Yeung
  • Перевод
Строгий режим (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-кода своих проектов?


RUVDS.com
1 481,48
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией

Похожие публикации

Комментарии 13

    +1
    Например, попытка выполнения следующего кода приведёт к выдаче исключения ReferenceError:
    'use strict';
    badVariable = 1;

    Только если эта переменная не объявлена в "родительских" областях видимости.

      +1
      Строгий режим запрещает назначать объекту свойства с одинаковыми именами


      Начиная с ECMAScript 2015, синтаксическая ошибка в коде
      'use strict';
      var o = { a: 1, a: 2 };
      не возникает. В MDN об этом упоминается. Только непонятно почему так сделали. Кто-нибудь знает?
        0

        Рискну предположить что для такого случая:


        const o = { ...a, a: 2 }; // where a = { a: 1 }

        чтобы в этом случае не выкидывало ошибку.

          0

          Нет, это же не из той оперы. А вообще, spread оператор обычно транспилируется (и рискну предположить, что роботает) через Object.assign, который уже не имеет никакого отношения к синтаксису.

            +1

            Отчего же? Консистентность языковых конструкций достаточно важна. Если используя spread можно задать два одинаковых ключа, а без него нельзя — то это нехорошо. Хотя версия с JSON вероятнее :-)

              0

              С одной стороны да, но с другой, было бы очень странно если бы spread оператор давал ошибку когда есть два одинаковых ключа даже если бы в литерале объекта это было бы ошибкой.

              +1
                0

                Согласен, но где-то сам видел, как какой-то транспилятор (вроде TypeScript) превратил spread в Object.assign.

            +1

            Скорее всего, для совместимости с JSON. В нем тоже присутствует такая же фича.

              0
              И это не очень-то хорошо. У нас недавно из-за этого была ошибка, отнявшая много времени.
                +1

                По поводу отлавливания ошибок – это лучше к Typescript.


                Там не только дубликаты статических ключей отлавливаются, но и динамических: демо

                  0
                  JSON — это не только Javascript. Наша ошибка была в PL.
            +2
            а можно ли включить для функции или скрипта «sloppy mode»? Например: в хроме для одноразовой задачки (нахождения оптимального решения) прямо в консоли разработчика понадобилось использовать eval внутри setTimeout и… хром не разрешает так делать

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое