Строгий режим

Строгий режим (strict mode) у ECMAScript 5 — це спосіб використання обмеженого варіанта JavaScript. Строгий режим є не просто підмножиною JavaScript: це семантика, яка навмисно відрізняється від звичайного коду. Переглядачі, що не підтримують строгий режим, виконуватимуть його код у дещо інший спосіб, аніж ті переглядачі, що таку підтримку мають. Отже, перш ніж покладатися на строгий режим, перевірте наявність підтримки його окремих складників. Код у строгому режимі та код поза ним можуть співіснувати, тому скрипти можна переводити у строгий режим поступово.

Строгий режим вносить декілька змін до звичної семантики JavaScript. По-перше, деякі помилки, що в звичайному режимі лишаються непомітними, натомість у строгому режимі викидаються в явний спосіб. По-друге, строгий режим виправляє помилки, що ускладнюють або унеможливлюють виконання певних оптимізацій рушіями JavaScript: код у строгому режимі іноді можна змусити виконуватися швидше, ніж такий самий код у звичайному режимі. По-третє, строгий режим забороняє деякий синтаксис, який, скоріше за все, буде визначено у майбутніх версіях ECMAScript.

Дивіться "Перехід до строгого режиму" (transitioning to strict mode), якщо бажаєте змінити свій код так, щоб він працював із обмеженим варіантом JavaScript.

Іноді ви побачите посилання на стандартний, не строгий режим, як на “брудний режим”. Це неофіційний термін, але вам краще знати про нього, про всяк випадок.

Виклик строгого режиму

Строгий режим застосовується до цілих скриптів, або до індивідуальних функцій. Він не застосовується до блочних виразів, замкнених у фігурних дужках {}; спроба застосувати його в такому контексті не дасть результату. eval код, Function код, атрибути обробника подій, рядки, що передаються у WindowTimers.setTimeout(), і подібні до них є повноцінними скриптами, і виклик строгого режиму в них працює, як і очікується.

Строгий режим у скриптах

Щоб увімкнути строгий режим у цілому скрипті, запишіть точний вираз “use strict”; (або ‘use strict’;) перед будь-якими іншими виразами.

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

Цей синтаксис приховує пастку, в яку вже втрапив один великий сайт: неможливо наосліп поєднати не суперечливі скрипти. Розглянемо об'єднання скрипту у строгому режимі із скриптом у звичайному режимі: вся конкатенація виходить у строгому режимі! Зворотнє теж правда: скрипт у звичайному режимі, поєднаний із скриптом у строгому режимі, призводить до звичайного режиму. Об'єднання скриптів у строгому та звичайному режимах є проблематичним. Тому рекомендується включати строгий режим у кожній функції окремо (принаймні, на час переміщення).

Ви також можете застосувати підхід із загортанням всього вмісту скрипту у функцію та наданням цій зовнішній функції строгого режиму. Це позбавляє проблеми конкатенації, але це означає, що вам доведеться явно експортувати будь-які глобальні змінні поза область видимості функції.

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

Таким самим чином, щоб застосувати строгий режим у функції, напишіть точний вираз “use strict”; (або 'use strict';) у тілі функції перед будь-якими іншими виразами.

function strict() {
  // Синтаксис строгого режиму на рівні функції
  'use strict';
  function nested() { return "І я також!"; }
  return "Привіт!  Я функція у строгому режимі!  " + nested();
}
function notStrict() { return "А я ні."; }

Зміни у строгому режимі

Строгий режим змінює як синтаксис, так і поведінку під час виконання. Зміни загалом підпадають під такі категорії: зміни, що перетворюють похибки на помилки (такі, як помилки синтаксису під час виконання), зміни, що спрощують обчислення конкретної змінної для заданого використання імені, зміни, що спрощують eval та arguments, зміни, що спрощують написання “безпечного” коду JavaScript, а також зміни, що передбачають майбутню еволюцію ECMAScript.

Перетворення похибок на помилки

Строгий режим перетворює деякі раніше прийнятні похибки на помилки. JavaScript був розроблений таким чином, щоб бути простим для програмістів-початківців, й іноді позначає операції, які мали б бути помилками, як не помилкові. Іноді це вирішує невідкладну проблему, але іноді створює ще гірші проблеми в майбутньому. Строгий режим позначає ці похибки як помилки, що дозволяє виявляти їх та вчасно виправляти.

По-перше, строгий режим робить неможливим випадкове створення глобальних змінних. В звичайному режимі JavaScript неправильно написане ім'я змінної під час присвоювання створює нову властивість у глобальному об’єкті та продовжує “працювати” (хоча в майбутньому можливе не спрацювання: ймовірно, у новій версії JavaScript). Присвоювання, які могли випадково створити глобальні змінні, у строгому режимі генерують помилку:

"use strict";
                       // Припускаючи, що глобальна змінна mistypedVariable 
mistypedVariable = 17; // існує, ця строка згенерує помилку ReferenceError
                       // через неправильне написання змінної

По-друге, строгий режим змушує присвоювання, які б в іншому випадку непомітно не спрацювали, генерувати помилку. Наприклад, NaN є глобальною змінною, яку не можна редагувати. В нормальному коді присвоювання у NaN нічого не робить; розробник отримує неуспішний результат. У строгому режимі присвоювання у NaN викликає виняток. Будь-яке присвоювання, яке непомітно не спрацює у нормальному коді (присвоювання властивості, яку не можна редагувати, присвоювання властивості, яка має лише геттер, присвоювання нової властивості нерозширюваного об'єкту) згенерує виняток у строгому режимі:

"use strict";

// Присвоєння у властивість, яку не можна редагувати
var obj1 = {};
Object.defineProperty(obj1, "x", { value: 42, writable: false });
obj1.x = 9; // генерує помилку TypeError

// Присвоєння у властивість, яка має лише геттер
var obj2 = { get x() { return 17; } };
obj2.x = 5; // генерує помилку TypeError

// Присвоєння властивості нерозширюваного об'єкту
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // генерує помилку TypeError

По-третє, строгий режим генерує винятки при намаганні видалити властивість, яку не можна видаляти (раніше така спроба просто не мала б ніякого ефекту):

"use strict";
delete Object.prototype; // генерує помилку TypeError

По-четверте, строгий режим до версії Gecko 34 вимагає, щоб усі властивості у літералі об’єкту були унікальними. В звичайному коді можливо дублювати імена властивостей, при цьому останній запис встановлює значення властивості. Але, оскільки лише останній запис має значення, дублювання — це просто джерело помилок, якщо в коді змінюється значення властивості не в останньому запису. Дублювання імен властивостей є синтаксичною помилкою у строгому режимі:

Більше не працює у ECMAScript 2015 (bug 1041128).

"use strict";
var o = { p: 1, p: 2 }; // !!! синтаксична помилка

По-п’яте, строгий режим вимагає, щоб імена параметрів функцій були унікальними. В звичайному коді останній дубльований аргумент ховає попередні ідентично названі аргументи. Ці попередні аргументи лишаються доступними через arguments[i], тому вони не остаточно недоступні. Однак, таке приховання не має сенсу і, скоріше за все, є небажаним (воно може, наприклад, ховати помилку у написанні), тому у строгому режимі дублювання імен аргументів є синтаксичною помилкою:

function sum(a, a, c) { // !!! синтаксична помилка
  "use strict";
  return a + b + c; // неправильно, якщо виконати цей код
}

По-шосте, строгий режим у ECMAScript 5 забороняє вісімковий синтаксис. Вісімковий синтаксис не є частиною ECMAScript 5, але він підтримується у всіх веб-переглядачах додаванням нуля попереду вісімкового числа: 0644 === 420 and "\045" === "%". У ECMAScript 5 вісімкове число підтримується з допомогою додавання "0o", тобто 

var a = 0o10; // ES2015: вісімкове число

Програмісти-початківці вважають, що нуль попереду числа не має семантичного значення, тому вони використовують його для вирівнювання строки — але це змінює значення числа! Вісімковий синтаксис рідко використовується і може бути використаний помилково, тому строгий режим позначає вісімковий запис як синтаксичну помилку:

"use strict";
var sum = 015 + // !!! синтаксична помилка
          197 +
          142;

По-сьоме, строгий режим у ECMAScript 5 забороняє присвоєння властивостей примітивним значенням. Без строгого режиму, присвоєння таких властивостей просто ігнорується (no-op), однак, у строгому режимі генерується помилка TypeError.

(function() {
"use strict";

false.true = "";         // TypeError
(14).sailing = "home";     // TypeError
"with".you = "far away"; // TypeError

})();

Спрощення використання змінних

Строгий режим спрощує прив'язку імен змінних до певних визначень змінних у коді. Багато оптимізацій компілятора покладаються на спроможність сказати, що змінна Х зберігається у цій локації: це критично для повної оптимізації кода JavaScript. Іноді JavaScript робить базову прив'язку імені до визначення змінної в коді неможливим до запуску коду. Строгий режим усуває більшість випадків, в яких це відбувається, тому компілятор може краще оптимізувати код у строгому режимі.

По-перше, строгий режим забороняє with. Проблема з with полягає в тому, що будь-яке ім’я всередині блоку може вказувати або на властивість об’єкта, який йому передають, або на змінну з поточної (або навіть з глобальної) області видимості під час виконання: неможливо знати наперед. Строгий режим робить with синтаксичною помилкою і не дає можливості імені з with посилатися на невідому локацію під час виконання:

"use strict";
var x = 17;
with (obj) // !!! синтаксична помилка
{
  // Якби це не був строгий режим, була б це змінна x, або
  // це була б властивість obj.x?  Неможливо заздалегідь
  // сказати, не запустивши код, тому ім'я не можна
  // оптимізувати.
  x;
}

Проста альтернатива присвоювання об’єкта змінній з коротким ім’ям, з отриманням доступу до відповідної властивості через цю змінну, здатна замінити with.

По-друге, eval у строгому режимі не додає нові змінні у поточну область видимості. У звичайному коді eval("var x;") створює змінну x у області видимості поточної функції або глобального об'єкту. Це означає, що, в цілому, у функції, що містить виклик eval кожне ім’я, яке не посилається на аргумент або на локальну змінну, мусить бути прив'язане до певного оголошення під час виконання (тому що eval міг створити нову змінну, яка сховає зовнішню змінну). У строгому режимі eval створює змінні лише для коду, який обчислюється, тому eval не може впливати на те, посилається ім’я на зовнішню змінну, чи на якусь локальну змінну:

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

Відповідно, якщо функція eval викликається виразом у вигляді eval(...) у строгому режимі, код буде оцінюватися як код у строгому режимі. Код може явно увімкнути строгий режим, але в цьому нема необхідності.

function strict1(str) {
  "use strict";
  return eval(str); // str буде вважатися кодом у строгому режимі
}
function strict2(f, str) {
  "use strict";
  return f(str); // not eval(...): str матиме строгий режим тільки в тому
                 // випадку, якщо він його вмикає
}
function nonstrict(str) {
  return eval(str); // str матиме строгий режим тільки в тому випадку, 
                    // якщо він його вмикає
}

strict1("'Код у строгому режимі!'");
strict1("'use strict'; 'Код у строгому режимі!'");
strict2(eval, "'Код у звичайному режимі.'");
strict2(eval, "'use strict'; 'Код у строгому режимі!'");
nonstrict("'Код у звичайному режимі.'");
nonstrict("'use strict'; 'Код у строгому режимі!'");

Таким чином, імена у eval коді строгого режиму поводяться ідентично іменам у строгому режимі, які не оцінюються як результат eval.

По-третє, строгий режим забороняє видалення простих імен. delete name у строгому режимі є синтаксичною помилкою:

"use strict";

var x;
delete x; // !!! синтаксична помилка

eval("var y; delete y;"); // !!! синтаксична помилка

Спрощення 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);
console.assert(pair[0] === 42);
console.assert(pair[1] === 17);

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

"use strict";
var f = function() { return arguments.callee; };
f(); // генерує помилку TypeError

"Убезпечення" JavaScript

Строгий режим спрощує написання “безпечного” коду в JavaScript. Деякі веб-сайти зараз надають користувачам можливість писати код JavaScript, який буде виконуватися на цій сторінці від імені інших користувачів. JavaScript у веб-переглядачах може мати доступ до приватної інформації користувачів, тому такий код має бути частково перетворений перед виконанням, щоб контролювати доступ до забороненої функціональності. Гнучкість JavaScript робить це практично неможливим без купи перевірок під час виконання. Певні функції мови настільки розповсюджені, що перевірки під час виконання призводять до суттєвих втрат у продуктивності. Кілька вивертів строгого режиму, плюс вимога, щоб код JavaScript, наданий користувачем, був у строгому режимі і щоб він запускався певним чином, суттєво зменшують необхідність в таких перевірках.

По-перше, значення, що передається у функцію як this, в строгому режимі не зобов'язане бути об'єктом (т. зв. “обгорнуте” значення). У звичайній функції this завжди є об'єктом: це або переданий об'єкт, якщо функція викликається із this, що є об'єктом; обгорнуте значення, якщо функція викликається із логічним (Boolean), строковим (string) або числовим (number) this; або глобальний об'єкт, якщо функція викликається із this, що дорівнює undefined або null . (Використовуйте call, apply, або bind щоб вказати конкретний this.) Автоматичне обгортання не лише призводить до втрат продуктивності. Викриття глобального об'єкту у веб-переглядачах є небезпечним, тому що глобальний об'єкт надає доступ до функціональності, яку “безпечний” JavaScript код має обмежувати. Таким чином, для функції у строгому режимі заданий this не обгортається у об'єкт, а якщо він не заданий, this буде мати значення undefined:

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

Це означає, окрім іншого, що у веб-переглядачах більше неможливо посилатися на об'єкт window через this всередині функції строгого режиму.

По-друге, у строгому режимі більше неможливо “пройтися” через стек JavaScript за допомогою часто вживаних розширень ECMAScript. В звичайному коді із цими розширеннями, коли функція fun знаходиться у процесі виклику, fun.caller – це функція, яка останньою викликала fun, а fun.arguments– це аргументи для цього виклику fun. Обидва розширення є проблематичними для “безпечного” коду JavaScript, тому що вони дозволяють “безпечному” коду мати доступ до “привілейованих” функцій та їхніх (потенційно небезпечних) аргументів. Якщо fun знаходиться у строгому режимі, fun.caller та fun.arguments є властивостями, які не можна видаляти, і генерують помилку під час присвоєння або виклику:

function restricted()
{
  "use strict";
  restricted.caller;    // викидає TypeError
  restricted.arguments; // викидає TypeError
}
function privilegedInvoker()
{
  return restricted();
}
privilegedInvoker();

По-третє, arguments у строгому режимі функції більше не надають доступу до відповідних змінних викликаної функції. В деяких старих версіях ECMAScript arguments.caller був об'єктом, чиї властивості були псевдонімами змінних у цій функції. Це небезпечно, тому що порушує можливість приховувати привілейовані значення через абстракцію функції; це також перешкоджає більшості оптимізацій. З цих причин сучасні переглядачі не мають цієї можливості. Тим не менш, через свою історичну функціональність, arguments.caller у функції строгого режиму також є властивістю, яку не можна видаляти, і генерує помилку під час присвоєння або виклику:

"use strict";
function fun(a, b)
{
  "use strict";
  var v = 12;
  return arguments.caller; // генерує помилку TypeError
}
fun(1, 2); // не викриває v (або a, або b)

Прокладання шляху для майбутніх версій ECMAScript

Майбутні версії ECMAScript, скоріше за все, запропонують новий синтаксис, а строгий режим у ECMAScript 5 застосовує деякі обмеження, щоб спростити перехід. Буде простіше внести деякі зміни, якщо основи цих змін заборонені у строгому режимі.

По-перше, у строгому режимі деякі ідентифікатори стають зарезервованими ключовими словами. Ці слова: implements, interface, let, package, private, protected, public, static, та yield. Тоді у строгому режимі ви не зможете називати ними або використовувати змінні або аргументи з такими іменами.

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

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

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

Два застереження для Mozilla: по-перше, якщо ваш код використовує JavaScript версії 1.7 або вище (наприклад, у коді хрома, або при використанні <script type="">) та використовує строгий режим, let та yield матимуть функціональність, яку вони мали з тих пір, як ці ключові слова були вперше введені. Але код у строгому режимі у мережі, завантажений з допомогою <script src=""> або <script>...</script>, не зможуть використовувати let/yield як ідентифікатори. По-друге, в той час, як ES5 безумовно резервує слова class, enum, export, extends, import, та super, раніше Firefox 5 Mozilla резервував їх лише у строгому режимі.

По-друге, строгий режим забороняє функціональні вирази не на верхньому рівні скрипта чи функції. В звичайному коді в переглядачах функціональні вирази дозволені “будь-де”. Це не є частиноюES5 (або навітьES3)! Це розширення з несумісною семантикою у різних переглядачах. Є надія, що в майбутніх версіях ECMAScript буде введено нову семантику для функціональних виразів не на верхньому рівні скрипта або функції. Заборона таких функціональних виразів у строгому режимі "розчищає поле" для специфікації майбутнього випуску ECMAScript:

"use strict";
if (true) {
  function f() { } // !!! синтаксична помилка
  f();
}

for (var i = 0; i < 5; i++) {
  function f2() { } // !!! синтаксична помилка
  f2();
}

function baz() { // кошерно
  function eit() { } // теж кошерно
}

Ця заборона не є правильною для строгого режиму, тому що такі функціональні вирази є розширенням базового ES5. Але це рекомендація комітету ECMAScript, і бразуери будуть її впроваджувати.

Строгий режим у веб-переглядачах

У більшості переглядачів в наш час реалізовано строгий режим. Однак, не покладайтеся на нього наосліп, оскільки досі існує велика кількість версій переглядачів, які лише частково підтримують строгий режим або взагалі його не підтримують (наприклад, Internet Explorer нижче 10-ї версії!). Строгий режим змінює семантику. Покладання на строгий режим призведе до численних помилок у переглядачах, в яких не реалізовано строгий режим. Використовуйте строгий режим з обережністю, та, опираючись на строгий режим, підстраховуйтесь тестуванням функціональності, яке перевірить, чи реалізовані відповідні частини строгого режиму. Нарешті, переконайтеся, що протестували свій код у переглядачах, які підтримують та не підтримують строгий режим. Якщо ви тестуєте лише у тих переглядачах, які не підтримують строгий режим, ви, ймовірно, будете мати проблеми у переглядачах, які його підтримують, та навпаки.

Специфікації

Специфікація Статус Коментар
ECMAScript 5.1 (ECMA-262)
The definition of 'Strict Mode Code' in that specification.
Standard Базове визначення. Дивіться також: Strict mode restriction and exceptions
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Strict Mode Code' in that specification.
Standard Strict mode restriction and exceptions
ECMAScript Latest Draft (ECMA-262)
The definition of 'Strict Mode Code' in that specification.
Draft Strict mode restriction and exceptions

Дивіться також