MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

Суворий режим

Переклад не закінчено. Будь ласка, допоможіть перекласти цю статтю з англійської.

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

Суворий режим вносить декілька змін до звичної семантики JavaScript. По-перше, суворий режим усуває деякі помилки, що проходять непоміченими, змінюючи їх на згенеровані помилки. По-друге, суворий режим виправляє помилки, через які рушіям (engines) 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 забороняє присвоєння властивостей примітивним (primitive) значенням. Без суворого режиму, присвоєння таких властивостей просто ігнорується (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 у суворому режимі не додає нові змінні у поточну властивість scope. У звичайному коді eval("var x;") створює змінну x у scope оточуючої функції або глобального об'єкту. Це означає, що, в цілому, у функції, що містить виклик 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;    // throws a TypeError
  restricted.arguments; // throws a 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 2017 Draft (ECMA-262)
The definition of 'Strict Mode Code' in that specification.
Draft Strict mode restriction and exceptions

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

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

 Зробили внесок у цю сторінку: DariaManko
 Востаннє оновлена: DariaManko,