Strict mode

Режим strict (строгий режим), введенный в ECMAScript 5, позволяет использовать более строгий вариант JavaScript. Это не просто подмножество языка: в нем сознательно используется семантика, отличающаяся от обычно принятой. Не поддерживающие строгий режим браузеры будут по-другому выполнять код, написанный для строгого режима, поэтому не полагайтесь на строгий режим без тестирования поддержки используемых особенностей этого режима. Строгий и обычный режим могут сосуществовать одновременно, а скрипт может переключаться в строгий режим по мере надобности.

Строгий режим принёс ряд изменений в обычную семантику JavaScript. Во-первых, строгий режим заменяет исключениями некоторые ошибки, которые интерпретатор JavaScript ранее молча пропускал. Во-вторых, строгий режим исправляет ошибки, которые мешали движкам JavaScript выполнять оптимизацию -- в некоторых случаях код в строгом режиме может быть оптимизирован для более быстрого выполнения, чем код в обычном режиме. В-третьих, строгий режим запрещает использовать некоторые элементы синтаксиса, которые, вероятно, в следующих версиях ECMAScript получат особый смысл.

Если вы хотите изменить свой код так, чтобы он работал в строгой версии Javascript, посмотрите статью Переход к строгому режиму.

Активизация строгого режима

Строгий режим применяется ко всему скрипту или к отдельным функциям. Он не может быть применён к блокам операторов, заключенных в фигурные скобки -- попытка использовать его в подобном контексте будет проигнорирована. Код в eval,  в Function,   в аттрибутах обработчиков событий, в строках, переданных в setTimeout, и т.п. рассматривается как законченный скрипт, и активизация строгого режима в нём выполняется ожидаемым образом.

Строгий режим для скриптов

Чтобы активизировать строгий режим для всего скрипта, нужно поместить оператор "use strict";  или 'use strict'; перед всеми остальными операторами скрипта (выдержать приведенный синтаксис буквально).

// Синтаксис переключения в строгий режим всего скрипта
"use strict";
var v = "Привет!  Я скрипт в строгом режиме!";

В этой синтаксической конструкции кроется ловушка, в которую уже угодили даже самые известные сайты: нельзя бездумно объединять скрипты с разными режимами. Объединение скрипта в строгом режиме со скриптом в обычном выглядит как скрипт в строгом режиме! Справедливо и обратное: объединение обычного скрипта со строгим выглядит как нестрогий скрипт. Объединение только строгих или только обычных скриптов проходит без последствий, проблему вызывает совместное использование скриптов со строгим и обычным режимом. Поэтому рекомендуется включать строгий режим только на уровне функций (хотя бы в течение переходного периода).

Вы также можете использовать подход "обёртывания" всего содержимого скрипта в функцию, для которой включён строгий режим. Это уменьшит возможность возникновения проблем при объединении скриптов, но одновременно потребует явно экспортировать из контекста функции все глобальные переменные.

Строгий режим для функций

Аналогично, чтобы включить строгий режим для функции, поместите оператор "use strict"; (или 'use strict';) в тело функции перед любыми другими операторами.

function strict()
{
  // Function-level strict mode syntax
  'use strict';
  function nested() { return "And so am I!"; }
  return "Hi!  I'm a strict mode function!  " + nested();
}
function notStrict() { return "I'm not strict."; }

Изменения в строгом режиме

Строгий режим изменяет синтаксис и поведение среды исполнения. Изменения главным образом попадают в следующие категории: преобразование ошибок в исключения; изменения, упрощающие вычисление переменной в определённых случаях использования её имени; изменения, упрощающие eval и arguments; изменения, упрощающие написание "безопасного" JavaScript, и изменения, упреждающие дальнейшее развитие ECMAScript.

Преобразование ошибок в исключения

Строгий режим превращает некоторые прощавшиеся ранее ошибки в исключения. JavaScript был разработан с расчётом на низкий порог вхождения, и временами он придаёт заведомо ошибочным операциям семантику нормального кода. Иногда это помогает срочно решить проблему, а иногда это создаёт худшие проблемы в будущем. Строгий режим расценивает такие ошибки как ошибки времени выполнения, для того чтобы они могли быть обнаружены и исправлены в обязательном порядке.

Во-первых, строгий режим делает невозможным случайное создание глобальных переменных. В обычном JavaScript опечатка в имени переменной во время присваивания приводит к созданию нового свойства глобального объекта, и выполнение продолжается (хотя в современном JavaScript оно, вероятно, аварийно завершится в дальнейшем). Присваивания, которые могут случайно создать глобальную переменную, в строгом режиме выбрасывают исключение:

"use strict";
mistypedVaraible = 17; // throws a ReferenceError

Во-вторых, строгий режим заставляет присваивания, которые всё равно завершились бы неудачей, выбрасывать исключения. Например, NaN -- глобальная переменная, защищённая от записи. В обычном режиме присваивание NaN значения ничего не делает; разработчик не получает никакого сообщения об ошибке. В строгом режиме присваивание NaN значения выбрасывает исключение. Любое присваивание, которое в обычном режиме завершается неудачей (присваивание значения свойству, защищённому от записи; присваивание значения свойству, доступному только на чтение; присваивание нового свойства нерасширяемому объекту), в строгом режиме выбросит исключение:

"use strict";

// Assignment to a non-writable property
var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9; // throws a TypeError

// Assignment to a getter-only property
var obj2 = { get x() { return 17; } };
obj2.x = 5; // throws a TypeError

// Assignment to a new property on a non-extensible object
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // throws a TypeError

В-третьих, в строгом режиме попытки удалить неудаляемые свойства будут вызывать исключения (в то время как прежде такая попытка просто не имела бы эффекта):

"use strict";
delete Object.prototype; // throws a TypeError

В-четвёртых, строгий режим требует, чтобы все свойства, перечисленные в сериализованном объекте, встречались только один раз. В обычном коде имена свойств могут дублироваться, а значение свойства определяется последним объявлением. Но, в таком случае, дублирование -- просто почва для багов, если код редактируется с тем, чтобы поменять значение свойства как-то по-другому, кроме изменения последнего объявления. Дублирование имён свойств в строгом режиме является синтаксической ошибкой:

"use strict";
var o = { p: 1, p: 2 }; // !!! syntax error

В-пятых, строгий режим требует, чтобы имена аргументов в объявлении функций встречались только один раз. В обычном коде последний повторённый аргумент скрывает предыдущие аргументы с таким же именем. Эти предыдущие аргументы всё ещё доступны через arguments[i], так что они не полностью потеряны. Тем не менее, такое сокрытие несёт в себе мало смысла и, скорее всего, не имеет под собой цели (например, может скрывать опечатку), поэтому в строгом режиме дублирование имён аргументов является синтаксической ошибкой:

function sum(a, a, c) // !!! syntax error
{
  "use strict";
  return a + b + c; // wrong if this code ran
}

В-шестых, строгий режим запрещает синтаксис восьмеричной системы счисления. Восьмеричный синтаксис не является частью ECMAScript, но поддерживается во всех браузерах с помощью дописывания нуля спереди к восьмеричному числу: 0644 === 420 и "\045" === "%". Иногда начинающие разработчики думают, что ведущий ноль не имеет семантического значения, и используют его для выравнивания -- но это меняет значение числа! Восьмеричный синтаксис редко бывает полезен и может быть неправильно использован, поэтому строгий режим считает восьмеричные числа синтаксической ошибкой:

"use strict";
var sum = 015 + // !!! syntax error
          197 +
          142;

Упрощение вызовов переменных

Строгий режим упрощает отображение имён переменных на конкретные места их определения в коде. Многие оптимизации времени компиляции полагаются на возможность считать, что переменная X хранится в этом месте: это необходимо для полной оптимизации JavaScript кода. Иногда JavaScript делает невозможным такое простое отображение имени на определение переменной в коде до времени выполнения. Строгий режим убирает большинство случаев, когда такое происходит, так что компилятор может лучше оптимизировать код в строгом режиме.

Во-первых, строгий режим запрещает использование with. Проблема с with в том, во время выполнения любое имя внутри блока может отображаться как на свойство обрабатываемого объекта, так и на переменную в окружающем (или даже в глобальном) контексте -- невозможно знать об этом заранее. Строгий режим считает with синтаксической ошибкой, поэтому не остаётся шанса использовать имя переменной внутри with для ссылки на неизвестное место во время выполнения: 

"use strict";
var x = 17;
with (obj) // !!! syntax error
{
  // If this weren't strict mode, would this be var x, or
  // would it instead be obj.x?  It's impossible in general
  // to say without running the code, so the name can't be
  // optimized.
  x;
}

Простая альтернатива  with уже существует -- присваивание объекта переменной с коротким именем и затем доступ к нужному свойству как свойству этой переменной.

Во-вторых, eval в строгом режиме не добавляет новых переменных в окружающий контекст. В обычном режиме eval("var x;") вводит переменную x в окружающую функцию или в глобальный контекст. Это означает, что в общем случае в функции, содержащей вызов eval любое имя, не ссылающееся на аргумент или локальную переменную должно быть отображено на определённое определение во время выполнения (потому что eval мог ввести новую переменную, которая перекрывала бы внешнюю переменную). В строгом режиме eval создаёт переменные только в контексте выполняемого кода, так что eval не может повлиять на то, ссылается ли имя на локальную или на внешнюю переменную:

var x = 17;
var evalX = eval("'use strict'; var x = 42; x");
assert(x === 17);
assert(evalX === 42);

Соответственно, если функция eval вызвана выражением типа eval(...) внутри кода в строгом режиме, передаваемый в неё код будет выполнен в строгом режиме. Передаваемый код может непосредственно включать строгий режим, но в этом нет необходимости.

function strict1(str)
{
  "use strict";
  return eval(str); // str will be treated as strict mode code
}
function strict2(f, str)
{
  "use strict";
  return f(str); // not eval(...): str is strict if and only if it invokes strict mode
}
function nonstrict(str)
{
  return eval(str); // str is strict if and only if it invokes strict mode
}
strict1("'Strict mode code!'");
strict1("'use strict'; 'Strict mode code!'");
strict2(eval, "'Non-strict code.'");
strict2(eval, "'use strict'; 'Strict mode code!'");
nonstrict("'Non-strict code.'");
nonstrict("'use strict'; 'Strict mode code!'");

Таким образом, имена в строгом коде, передаваемом в  eval,ведут себя так же, как имена в нестрогом коде, передаваемом в eval внутри строгого режима.

В-третьих, строгий режим запрещает удаление простых имён. delete name в строгом режиме является синтаксической ошибкой:

"use strict";
eval("var x; delete x;"); // !!! syntax error

Упрощение eval и arguments

Строгий режим делает arguments и eval не такими аномально магическими. Оба примешивают определённое количество магического поведения в обычном коде: eval добавляет или удаляет связки и меняет связывающие значения, а arguments из-за своих проиндексированных свойств, накладывающихся на именованные аргументы. Строгий режим делает большие шаги к рассмотрению eval и arguments как ключевых слов, хотя полноценные исправления будут сделаны только в будущей редакции ECMAScript.

Во-первых, имена eval и arguments не могут быть привязаны или получить значения с помощью синтаксиса языка. Все подобные попытки это сделать являются синтаксическими ошибками:

"use strict";
eval = 17;
arguments++;
++eval;
var obj = { set p(arguments) { } };
var eval;
try { } catch (arguments) { }
function x(eval) { }
function arguments() { }
var y = function eval() { };
var f = new Function("arguments", "'use strict'; return 17;");

Во-вторых, код  в строгом режиме не накладывает аргументы функции на свойства объектов arguments, созданных внутри него. В обычном коде внутри функции, первым аргументом которой является arg, присваивание arg значения также устанавливает значение arguments[0]и наоборот (кроме случаев, когда у функции нет аргументов, или arguments[0] удалён). Объекты arguments для функций в строгом режиме хранят исходные аргументы, с которыми функция была вызвана. arguments[i] не отслеживает изменения соответствующего именованного аргумента, и именованный аргумент не отслеживает значение соответствующего arguments[i].

function f(a)
{
  "use strict";
  a = 42;
  return [a, arguments[0]];
}
var pair = f(17);
assert(pair[0] === 42);
assert(pair[1] === 17);

В-третьих, arguments.callee больше не поддерживается.  В обычном коде  arguments.callee ссылается на вызывающую функцию. Это неудачный случай использования: можно просто дать вызывающей функции имя! Более того, arguments.callee значительно затрудняет такие оптимизации, как встраивание функций, потому что должна быть оставлена возможность предоставления ссылки на невстроенную функцию на случай, если arguments.callee используется. arguments.callee для функций в строгом режиме -- неудаляемое свойство, которое выбрасывает исключение при установке или запросе значения:

"use strict";
var f = function() { return arguments.callee; };
f(); // throws a TypeError

"Securing" JavaScript

Strict mode makes it easier to write "secure" JavaScript. Some websites now provide ways for users to write JavaScript which will be run by the website on behalf of other users. JavaScript in browsers can access the user's private information, so such JavaScript must be partially transformed before it is run, to censor access to forbidden functionality. JavaScript's flexibility makes it effectively impossible to do this without many runtime checks. Certain language functions are so pervasive that performing runtime checks has considerable performance cost. A few strict mode tweaks, plus requiring that user-submitted JavaScript be strict mode code and that it be invoked in a certain manner, substantially reduce the need for those runtime checks.

First, the value passed as this to a function in strict mode isn't boxed into an object. For a normal function, this is always an object: the provided object if called with an object-valued this; the value, boxed, if called with a Boolean, string, or number this; or the global object if called with an undefined or null this. (Use call, apply, or bind to specify a particular this.) Automatic boxing is a performance cost, but exposing the global object in browsers is a security hazard, because the global object provides access to functionality "secure" JavaScript environments must restrict. Thus for a strict mode function, the specified this is used unchanged:

"use strict";
function fun() { return this; }
assert(fun() === undefined);
assert(fun.call(2) === 2);
assert(fun.apply(null) === null);
assert(fun.call(undefined) === undefined);
assert(fun.bind(true)() === true);

Second, in strict mode it's no longer possible to "walk" the JavaScript stack via commonly-implemented extensions to ECMAScript. In normal code with these extensions, when a function fun is in the middle of being called, fun.caller is the function that most recently called fun, and fun.arguments is the arguments for that invocation of fun. Both extensions are problematic for "secure" JavaScript, because they allow "secured" code to access "privileged" functions and their (potentially unsecured) arguments. If fun is in strict mode, both fun.caller and fun.arguments are non-deletable properties which throw when set or retrieved:

function restricted()
{
  "use strict";
  restricted.caller;    // throws a TypeError
  restricted.arguments; // throws a TypeError
}
function privilegedInvoker()
{
  return restricted();
}
privilegedInvoker();

Third, arguments for strict mode functions no longer provide access to the corresponding function call's variables. In some old ECMAScript implementations arguments.caller was an object whose properties aliased variables in that function. This is a security hazard because it breaks the ability to hide privileged values via function abstraction; it also precludes most optimizations. For these reasons no recent browsers implement it. Yet because of its historical functionality, arguments.caller for a strict mode function is also a non-deletable property which throws when set or retrieved:

"use strict";
function fun(a, b)
{
  "use strict";
  var v = 12;
  return arguments.caller; // throws a TypeError
}
fun(1, 2); // doesn't expose v (or a or b)

Paving the way for future ECMAScript versions

Future ECMAScript versions will likely introduce new syntax, and strict mode in ECMAScript 5 applies some restrictions to ease the transition. It will be easier to make some changes if the foundations of those changes are prohibited in strict mode.

First, in strict mode a short list of identifiers become reserved keywords. These words are implements, interface, let, package, private, protected, public, static, and yield. In strict mode, then, you can't name or use variables or arguments with these names.

function package(protected) // !!!
{
  "use strict";
  var implements; // !!!

  interface: // !!!
  while (true)
  {
    break interface; // !!!
  }

  function private() { } // !!!
}
function fun(static) { 'use strict'; } // !!!

Two Mozilla-specific caveats: First, if your code is JavaScript 1.7 or greater (your chrome code, or you've used the right <script type="">) and is strict mode code, let and yield have the functionality they've had since those keywords were first introduced. But strict mode code on the web, loaded with <script src=""> or <script>...</script>, won't be able to use let/yield as identifiers.  Second, while ES5 unconditionally reserves the words class, enum, export, extends, import, and super, before Firefox 5 Mozilla reserved them only in strict mode.

Second, strict mode prohibits function statements not at the top level of a script or function. In normal code in browsers, function statements are permitted "everywhere". This is not part of ES5 (or even ES3)! It's an extension with incompatible semantics in different browsers. Future ECMAScript editions will hopefully specify new semantics for function statements not at the top level of a script or function. Prohibiting such function statements in strict mode "clears the deck" for specification in a future ECMAScript release:

"use strict";
if (true)
{
  function f() { } // !!! syntax error
  f();
}
for (var i = 0; i < 5; i++)
{
  function f2() { } // !!! syntax error
  f2();
}
function baz() // kosher
{
  function eit() { } // also kosher
}

This prohibition isn't strict mode proper, because such function statements are an extension of basic ES5. But it is the recommendation of the ECMAScript committee, and browsers will implement it.

Strict mode in browsers

Browsers don't reliably implement strict mode yet, so don't blindly depend on it. Strict mode changes semantics. Relying on those changes will cause mistakes and errors in browsers which don't implement strict mode. Exercise caution in using strict mode, and back up reliance on strict mode with feature tests that check whether relevant parts of strict mode are implemented. Finally, make sure to test your code in browsers that do and don't support strict mode.  If you test only in browsers that don't support strict mode, you're very likely to have problems in browsers that do, and vice versa.

See Also

Метки документа и участники

Обновлялась последний раз: fscholz,
Скрыть боковую панель