ECMAScript Strict mode ve Firefoxu 4

JavaScript Firefox

Syntaxe JavaScriptu je přizpůsobena začátečníkům a JavaScript tak mnohé odpustí, což se nyní, kdy se JavaScript stává jedním z nejdůležitějších webových programovacích jazyků, ukazuje jako zásadní problém, který brání optimalizacím a přináší některé záludné chyby. Napraví to takzvaný Strict mode?

Seriál: Javascriptaření (9 dílů)

  1. Javascriptaření: hrajte si s funkcemi! 31.1.2011
  2. ECMAScript Strict mode ve Firefoxu 4 8.2.2011
  3. Javascriptaření: nejen jQuery živ je JavaScriptař 8.3.2011
  4. Javascriptaření: fyzika, grafika a společenská konverzace 23.3.2011
  5. JavaScriptaření: drátujeme, překládáme, spojujeme 31.3.2011
  6. Javascriptaření: ukažte mi, označte mě, opravte mě 13.4.2011
  7. Kontrola JavaScriptu s JSLint a JSHint 14.7.2011
  8. Základní vzory pro vytváření jmenných prostorů v JavaScriptu 10.10.2011
  9. Javascriptaření: překladače, pakovače 9.11.2011
qrcode

K článku není ukázkaK článku není k dispozici zdrojový kód

Článek vychází z anglického originálu ECMAScript 5 strict mode in Firefox 4, jehož autorem je Jeff Walden a který vyšel na stránkách Mozilla Hacks pod licencí CC-BY-SA. Pod stejnou licencí je k dispozici i tento překlad.

Javascriptový engine ve Firefoxu 4 byl vývojáři Mozilly výrazně vylepšen. Obětovali mnoho úsilí zvýšení jeho rychlosti, ale také pracovali na nových prvcích. Obzvláště se zaměřili na ECMAScript 5, poslední update standardu, na němž je postavený JavaScript.

Strict mode (striktní mód) je bezpochyby nejzajímavější novinkou v ECMAScriptu 5. Umožní se přepnout do omezené varianty JavaScriptu. Strict mode není jen podmnožinou JavaScriptu: úmyslně interpretuje kód odlišně. Prohlížeče nepodporující tento mód budou provádět kód psaný pro strict mode jinak, než prohlížeče, které ho podporují. Proto nelze spoléhat na funkčnost strict mode bez toho, abyste si ověřili, že potřebné vlastnosti strict mode prohlížeč skutečně podporuje správně.

Zdrojový kód napsaný pro strict mode se může vyskytovat souběžně vedle kódu pro normální (non-strict) mód, takže skripty mohou být do strict modu převáděné postupně. Strict mode naznačuje směr budoucího vývoje ECMAScriptu, kde bude ve strict modu zřejmě automaticky prováděn zdrojový kód s určitým <script type="...">.

Co vlastně strict mode dělá? Zaprvé zbavuje JavaScript některých zákeřných nástrah které nezpůsobovaly běhovou chybu, tím, že nyní vedou k chybě. Zadruhé opravuje omyly, které vedly k tomu, že optimalizace JavaScriptového kódu byla obtížná: zdrojový kód napsaný ve strict mode bude moci někdy běžet rychleji než ten stejný kód v normální módu. Strict mode v samotném Firefoxu 4 sice ještě není optimalizovaný, ale v budoucích verzích již bude. Zatřetí zakazuje používat syntaxi, která bude zřejmě využitá v budoucích verzích ECMAScriptu.

Spuštění strict modu

Strict mode může platit buď pro celý skript, nebo jen pro určité funkce. Nelze ho použít na bloky uzavřené složenými závorkami. Zdrojový kód předaný funkci eval, atributy event handlerů, řetězce předané funkci setTimeout a podobný kód je považován za skripty a provádění ve strict modu funguje podle očekávání.

Strict mode pro skripty

K provedení celého skriptu ve strict modu je potřeba jako první příkaz celého skriptu uvést "use strict" (nebo 'use strict').

// cely skript bude proveden ve strict modu
"use strict";
var v = "Hi!  I'm a strict mode script!";

Tato syntaxe v sobě skrývá past, do které se chytil nejeden web: není možné za sebe jen tak napojovat skripty. Vezměte si například spojení strict mode skriptu se skriptem ve normálním módu: celý spojený skript je považován za skript ve strict modu. To platí i naopak: normální plus strict mode skript vypadá celý jako non-strict. Se spojením dvou strict mode skriptů nejsou problémy, stejně jako skriptů ve normálním módu. Problémy způsobuje pouze zkřížení paprsků strict mode a normálního skriptu.

Strict mode pro funkce

Pro funkce se zapne strict mode obdobně jako pro celé skripty, tím, že se jako první příkaz funkce před všemi ostatním příkazy v jejím těle uvede "use strict" (nebo 'use strict').

function strict()
{
  // strict mode zapnutý pro funkci
  '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."; }

Změny, které strict mode přináší

Strict mode mění jak syntaxi, tak chování skriptu za běhu. Změny obecně spadají do těchto kategorií:

  • hlášení programátorských chyb a omylů jako errorů (syntaktických nebo běhových chyb)
  • zjednodušení způsobu, jak je jména v kódu mapují na konkrétní proměnné
  • zjednodušení eval a arguments
  • usnadnění psaní „bezpečného“ JavaScriptového kódu
  • očekávání a příprava na budoucí vývoj ECMAScriptu

Hlášení programátorských chyb a omylů jako errorů

Strict mode dělá errory z některých chyb a omylů, které dříve prošly a nebyly považovány za běhové chyby. JavaScript byl vytvořen tak, aby byl jednoduchý pro začínající programátory, a operace, které by měly být chybou, někdy chybu nezpůsobí. Někdy to nevadí, ale často to vede k mnohem větším problémům v budoucnosti. Strict mode považuje tyto chyby za errory, což vede k jejich odhalení a okamžité opravě.

Strict mode za prvé znemožňuje omylem vytvořit globální proměnnou. Překlep v názvu proměnné v přiřazení vytvoří v normálním JavaScriptu property na globálním objektu a všechno „projde“ (ačkoliv lze v budoucnosti očekávat chybu, např. v moderním JavaScriptu). Místo toho ve strict modu přiřazení, které by omylem vytvořilo globální proměnnou, vyhodí chybu.

"use strict";
mistypedVaraible = 17; // vyhodí ReferenceError

Za druhé způsobí, že přiřazení, které by jinak nefungovalo, ale prošlo, vyhodí výjimku. Například NaN je globální proměnná pouze pro čtení. V normálním JavaScriptu nemá přiřazení do NaN žádný efekt, ale vývojář se nedozví, že by došlo k jakémukoliv problému. Ve strict modu vyhodí přiřazení do NaN výjimku. Jakékoliv přiřazení, které v normálním kódu nefunguje, ale projde, vyhodí ve strict modu výjimku.

"use strict";
NaN = 42; // vyhodí TypeError
var obj = { get x() { return 17; } };
obj.x = 5; // vyhodí TypeError
var fixed = {};
Object.preventExtensions(fixed);
fixed.newProp = "ohai"; // vyhodí TypeError

Za třetí strict mode vyhodí výjimku, pokud se pokusíte smazat property, kterou nelze smazat, zatímco normálně by volání nic nezpůsobilo.

"use strict";
delete Object.prototype; // vyhodí TypeError

Za čtvrté strict mode požaduje, aby všechny properties objektu měly jedinečný název. Normální JavaScript dovoluje více properties se stejným názvem, přičemž hodnotu skutečně určuje poslední z nich. Protože ale pouze poslední z properties se stejným názvem skutečně „něco dělá“ a ostatní nemají smysl, je tato duplikace jen zdrojem chyb, pokud se změní hodnota jiné než poslední instance property se stejným názvem. Více properties se stejným názvem na jednom objektu způsobí ve strict modu chybu.

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

Za páté se ve strict modu požaduje, aby parametry jedné funkce měly různá jména. V normálním kódu skryje poslední parametr daného jména všechny předchozí stejně pojmenované parametry. K těmto skrytým parametrům se lze sice dostat pomocí arguments[i], takže nejsou zcela nedostupné, ale přesto nemá toto skrývání smysl a je obvykle nežádoucí (může být například způsobeno překlepem). Ve strict modu proto vede k syntaktické chybě.

function sum(a, a, c) // !!! syntax error
{
  "use strict";
  return a + b + c; // chyba, kdyby měl být tento kód proveden
}

Za šesté strict mode zakazuje zápis čísel v osmičkové soustavě. Tato syntaxe sice není součástí ECMAScriptu, ale je podporovaná ve všech prohlížečích, tak, že se před hodnotu v osmičkové soustavě napíše nula: 0644 === 420 a „45“ === „%“. Začínající vývojáři mnohdy očekávají, že počáteční nula nemá žádný význam, takže nuly na začátku používají k zarovnání – což ale změní hodnotu čísla! Zápis čísel v osmičkové soustavě je zřídkakdy užitečný a bývá chybně používán, proto použití číselné konstanty v osmičkové soustavě ve strict mode vede k syntaktické chybě.

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

Zjednodušení způsobu, jak je při užití jména proměnné určena konkrétní proměnná

Strict mode zjednodušuje způsob, jak je použití proměnné mapováno k její definici. Mnoho optimalizací prováděných kompilátorem spoléhá na to, že lze zjistit, že určitá proměnná je uložena na určitém místě. To je zásadní pro plnou optimalizaci JavaScriptového kódu. JavaScript někdy toto zjištění mapování jména proměnné na její definici v kódu neumožňuje jinak než až za běhu. Strict mode odstraňuje většinu takovýchto případů, takže kompilátor může lépe optimalizovat zdrojový kód ve strict modu.

Strict mode především zakazuje blok with. Blok with má ten problém, že jakékoliv jméno uvnitř tohoto bloku se může za běhu odkazovat buď na property předaného objektu, nebo na proměnnou z okolního kontextu, a nelze to rozhodnout předem. Strict mode považuje blok with za syntaktickou chybu, takže nebude moct dojít k tomu, aby název uvnitř bloku with odkazoval na neznámé místo.

"use strict";
var x = 17;
with (obj) // !!! syntax error
{
  // kdyby to nebyl strict mode, byla by to promenna x,
  // nebo obj.x? Obecne to nejde rozhodnout jinak
  // nez za běhu, coz zabranuje optimalizaci
  x;
}

Blok with lze jednoduše nahradit přiřazením objektu do proměnné a přístupem k property této proměnné.

Za druhé, ve strict modu eval nezpřístupní nové proměnné v okolním kontextu. V normálním JavaScriptovém kódu eval("var x;") zpřístupní proměnnou x v aktuální funkci nebo jako globální proměnnou. To obecně znamená, že ve funkci obsahující volání eval musí být každé jméno, které neodpovídá argumentu funkce nebo lokální proměnné, vyhodnoceno až za běhu (protože volání eval mohlo zpřístupnit novou proměnnou, která mohla skrýt proměnnou z okolního kontextu). Ve strict modu eval vytvoří proměnnou jen v rámci kódu, který zpracovává, takže volání eval ve funkci nemůže mít vliv na to, jestli jméno použité jinde ve stejné funkci odkazuje na lokální nebo vnější proměnnou.

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

S tím souvisí, že pokud je funkce eval volána ze strict mode kódu jako eval(...), tak kód uvnitř volání eval bude proveden také ve strict modu. Tento kód může explicitně zapnout strict mode, ale není to nutné.

unction strict1(str)
{
  "use strict";
  return eval(str); // str bude považován za kód ve strict modu
}
function strict2(f, str)
{
  "use strict";
  return f(str); // není použito eval(...): str bude proveden ve strict modu pokud ho explicitně zapne
}
function nonstrict(str)
{
  return eval(str); // str bude proveden ve strict modu pokud ho explicitně zapne
}
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!'");

Za třetí zakazuje strict mode mazání prostých proměnných v eval. Ty se v eval ve strict modu chovají naprosto stejně jako prosté proměnné ve strict modu, který není vyhodnocován pomocí eval: volání delete name ve strict modu je syntaktická chyba.

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

Zjednodušení eval a arguments

Strict mode ubírá konstruktům arguments a eval část jejich nadpřirozenosti. Oba se v normálním kódu chovají dost magicky: eval, když přidává či odebírá bindings a mění navázané hodnoty, a arguments tím, že indexované properties fungují jako alias k pojmenovaným parametrům. Strict mode udělal několik velkých kroků směrem k tomu, aby eval a arguments byly brány jako klíčová slova, ačkoliv konečné řešení bude muset počkat na některou budoucí verze ECMAScriptu.

Za prvé nemohou být jména eval a arguments svázána nebo přiřazena standardní syntaxí JavaScriptu. Všechna následující volání způsobí syntaktické chyby:

"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;");

Za druhé strict mode nepodporuje chování indexovaných properties objektu arguments jako aliasů parametrů, pokud tento arguments objekt vznikl ve strict modu. V normálním JavaScriptu ve funkci, jejímž prvním parametrem je arg, způsobí přiřazení do arg i změnu arguments[0], a naopak (alespoň pokud byly předány nějaké argumenty a arguments[0] nebyl smazán). Ve strict mode funkcích místo toho arguments drží původní hodnotu argumentů, které byly předány funkci při jejím volání. Hodnota arguments[i] není jakkoliv navázaná na odpovídající pojmenovaný argument, ani naopak.

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

Za třetí, arguments.callee už není podporován. V normálním JavaScriptu se arguments.callee odkazuje na volající funkci. Užitečnost je dost slabá: vrátit vnější volající funkci… Arguments.callee navíc dost zásadně znemožňuje optimalizace jako inline funkce a podobné, protože pro případ volání arguments.callee musí být možné získat odkaz zpět na původní funkci. Proto je ve funkcích prováděných ve strict modu arguments.callee pouze nemazatelnou property, která vyhodí výjimku, pokud je do ní zapisováno nebo je z ní čteno.

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

„Bezpečný“ JavaScript

Strict mode zjednodušuje psaní „bezpečného“ JavaScriptového kódu. Některé webové servery v současné době umožňují uživatelům zadávat JavaScriptový kód, který bude tímto serverem spouštěn jinými uživateli. Protože JavaScript v prohlížečích může přistupovat k soukromým údajům uživatele, takový kód musí být před spuštěním částečně předzpracován, aby z něj byly odstraněny nepovolené operace. Flexibilita JavaScriptu v podstatě znemožňuje provést tuto „cenzuru“ JavaScriptového kódu bez mnoha běhových kontrol. Některé vlastnosti JavaScriptu mají navíc tak široký dopad, že provádění běhových kontrol má znatelný vliv na rychlost provádění. Několik vychytávek strict modu, společně s tím, že uživateli zadávaný JavaScript bude ve strict mode a bude spouštěn určitým způsobem, podstatně snižují potřebu těchto běhových kontrol.

Za prvé, hodnota předaná funkci jako this ve strict modu není zabalena do objektu. Ve funkci v normálním JavaScriptu je this vždy objektem: přesně tím, který byl funkci předán, pokud to byla proměnná objektového typu; hodnotou zabalenou do objektu, pokud byl funkci předán Boolean, řetězec nebo číslo; anebo globálním objektem, pokud byla funkci předána hodnota undefined nebo null. (Konkrétní hodnotu parametru this lze předat pomocí call, apply nebo bind.) Automatické zabalení do objektu má dopad na výkon, ale hlavně – zpřístupnění globálního objektu v prohlížečích je bezpečnostní riziko, protože globální objekt poskytuje přístup k různým funkcím, který prostředí pro „bezpečný“ JavaScript nesmí dovolovat. Ve funkci ve strict modu jsou proto tyto hodnoty, předané jako this, zachované beze změny:

"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);

Mimochodem, vestavěné metody už také nezaobalují this do objektu, pokud je null, nebo undefined. (Tato změna nesouvisí se strict modem, ale její příčinou je stejná snaha: nezpřístupňovat globální objekt.) Dříve mělo předání null nebo undefined do vestavěné metody jako je Array.prototype.sort() tentýž efekt, jako kdyby byl předán globální objekt. Nyní ale obě tyto hodnoty předané jako this způsobí vyhození TypeError. Logické proměnné, čísla a řetězce jsou těmito metodami stále baleny do objektů: změnilo se chování pouze v těch případech, kdy by pracovaly nad globálním objektem.

Za druhé, ve strict modu už není možné „procházet“ zásobník volání metod pomocí všeobecně dostupných rozšíření k ECMAScriptu. V normálním JavaScriptu s těmito rozšířeními odkazuje během volání funkce fun fun.caller na funkci, která jako bezprostředně poslední zavolala fun, a fun.arguments je objekt arguments pro volání fun. Obě tato rozšíření jsou problematická pro bezpečnost JavaScriptu, protože dovolují „zabezpečenému“ kódu přistupovat k „privilegovaným“ funkcím a jejich potenciálně nezabezpečeným argumentům. Pokud fun běží ve strict modu, jsou fun.caller a fun.arguments nesmazatelné properties, které vyhodí výjimku, pokud je do nich zapisováno nebo je z nich čteno.

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

Za třetí, arguments u funkcí ve strict modu neposkytují přístup k proměnným použitým při volání funkce. V některých starších implementacích ECMAScriptu byl arguments.caller objekt, jehož properties byly odkazy na tyto proměnné, použité při volání. To je bezpečnostní riziko, protože porušuje možnost skrýt „privilegované“ objekty pomocí funkční abstrakce. Zároveň i brání většině optimalizací. Z těchto důvodů už arguments.caller není implementován žádným ze současných prohlížečů. I přesto ve strict modu kvůli zpětné kompatibilitě arguments.caller existuje jako nesmazatelná property, která vyhodí výjimku při čtení i zápisu.

"use strict";
function fun(a, b)
{
  "use strict";
  var v = 12;
  return arguments.caller; // vyhodí TypeError
}
fun(1, 2); // nezpřístupňuje v (or a or b)

Dláždění cesty k budoucím verzím ECMAScriptu

Budoucí verze ECMAScriptu zřejmě přinesou novou syntaxi, a strict mode v ECMAScriptu 5 obsahuje určitá omezení pro usnadnění tohoto přechodu. Bude jednodušší udělat změny, pokud základy pro tyto změny jsou ve strict modu zakázány.

Za prvé se ve strict modu několik identifikátorů stalo klíčovými slovy. Jsou to implements, interface, let, package, private, protected, public, static, a yield. Ve strict modu tedy nelze použít tyto identifikátory jako jména proměnných nebo parametrů. A pozor na jednu věc specifickou pro Mozillu: pokud máte kód v JavaScriptu verze 1.7 nebo vyšší (psaný pro chrome, nebo jste použili správný <script type="">) a je to kód ve strict modu, pak let a yield budou mít funkci, kterou mají od té doby, co jsou součástí JavaScriptu. Ale v případě strict mode JavaScriptu uvozeného pomocí <script src=""> nebo <script>...</script> nebudete moci tyto identifikátry vůbec použít.

Dále strict mode zakazuje deklaraci funkcí jinde než na nejvyšší úrovni skriptu nebo funkce. Normálně prohlížeče povolují deklarace funkce „kdekoliv“. Nejde o součást ECMAScriptu 5. Jedná se o rozšíření, jehož význam není mezi různými prohlížeči sjednocený. V budoucích verzích ECMAScriptu snad bude specifikována nová sémantika deklarací funkcí na jiných než nejvyšších úrovních skriptů a funkcí. Zákaz takovýchto deklarací funkcí ve strict modu připraví půdu pro specifikaci v některé budoucí verzi ECMAScriptu.

"use strict";
if (true)
{
  function f() { } // !!! syntakticka chyba
  f();
}
for (var i = 0; i &lt; 5; i++)
{
  function f2() { } // !!! syntakticka chyba
  f2();
}
function baz() // koser
{
  function eit() { } // taky koser
}

Tento zákaz není vlastně skutečnou součástí strict modu, protože takovéto deklarace funkcí jsou rozšířením ECMAScriptu. Ale jde o doporučení ECMAScript Committee a prohlížeče se ho budou držet.

Podpora strict mode v prohlížečích

Firefox 4 je prvním prohlížečem, který uceleně implemntuje strict mode. Nitro engine, který lze nalézt v mnoha WebKit prohlížečích, na něj neztrácí (se svou téměr kompletní podporou strict modu) mnoho. Chrome také začal s jeho implementací. Internet Explorer a Opera zatím nezačaly s implementací strict modu – je jen na vás, abyste poslali tvůrcům těchto prohlížečů požadavky na jeho podporu.

Prohlížeče nepodporují strict mode spolehlivě, takže není dobře se na něj slepě spoléhat. Strict mode mění význam kódu. Spoléhání na tyto změny povede k chybám v prohlížečích, které strict mode nepodporují. Buďte s používáním strict modu opatrní a pomocí testů a běhových kontrol se ujistěte, že podstatné vlastnosti strict modu jsou v prohlížeči naimplementované.

Pro otestování strict modu si můžete stáhnout nightly build Firefoxu a můžete se pustit do hraní. Také mějte na paměti jeho omezení při psaní nového kódu a úpravách existujícího. (Nejjistější bude ale zřejmě počkat s produkčním používáním strict modu až na dobu, kdy bude podporován ve stabilních verzích prohlížečů.)

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 16

Přehled komentářů

Frrr Dobra kniha
balki Re: Dobra kniha
nonym Java
MyOwnClone Re: Java
pravdokop Kroky správným směrem
fari Re: Kroky správným směrem
Michal Augustýn Re: Kroky správným směrem
blizzboz Re: Kroky správným směrem
Michal Augustýn Re: Kroky správným směrem
blizzboz Re: Kroky správným směrem
Michal Augustýn Re: Kroky správným směrem
juraj Re: Kroky správným směrem
Pavel Re: Kroky správným směrem
juraj preventExtensions
snehuliak Re: preventExtensions
snehuliak strict je ako prechod od Basicu ku Pascalu
Zdroj: https://www.zdrojak.cz/?p=3425