Co přináší nový ECMAScript 5?

Před cca měsícem a půl byl vydán finální pracovní návrh 5. edice specifikace ECMA-262, která definuje podobu JavaScriptu (nebo přesněji ECMAScriptu). Tento článek je prvním dílem dvoudílného miniseriálu, ve kterém se seznámíme s nejdůležitějšími změnami a novinkami, které nová verze specifikace přináší.

Úvod

Podoba jazyka JavaScript je standardizována mezinárodní standardizační organizací ECMA ve specifikaci ECMA-262 jako jazyk ECMAScript. Aktuální platná edice této specifikace nese číslo 3 a byla vydána v roce 1999 (dále ji budeme označovat „ES3“). JavaScript se ale od doby vydání této edice značně vyvíjel a také se stal z okrajového skriptovacího jazyka jedním z nejdůležitějších programovacích jazyků vůbec. Vyvstala tak potřeba specifikaci aktualizovat.

Historie příprav nové verze specifikace je poměrně zajímavá a můžete se s ní seznámit v jiném článku. Nám pro orientaci postačí pouze vědět, že původně vznikl poměrně odvážný návrh 4. edice ECMAScriptu, který do značné míry měnil charakter jazyka a podstatně ho zesložiťoval. Tento návrh byl posléze odmítnut a přednost dostala práce na inkrementálním vylepšení stávající specifikace, pracovně označovaném jako ECMAScript 3.1.

Práce na této verzi specifikace se nyní blíží ke konci a byl vydán její finální pracovní návrh (oficiálně označovaný jako „candidate specification“). Zároveň došlo k poměrně nelogické změně čísla edice z 3.1 na 5. Edice 4 bude úplně přeskočena a některé myšlenky z ní se snad dostanou do nějaké budoucí verze standardu (zatím pracovně označované „Harmony“). Finální verze 5. edice se očekává ke konci roku 2009.

V tomto článku se s 5. edicí ECMAScriptu (dále „ES5“) seznámíme a představíme si její nejdůležitější novinky.

Jak vypadá ECMAScript 5?

Jak autor článku během psaní zjistil, specifikace ES5 je oproti ES3 o poznání složitější. Přibylo v ní mnoho nových pojmů a interních algoritmů; několik algoritmů se také stalo podstatně složitějšími. Specifikace už není plně pochopitelná na první přečtení.

Za vše můžou především změny, které podstatně narušují některé dosavadní axiomy ECMAScriptu (především „objekt je hash tabulka mapující jména vlastností na jejich hodnoty“) a vnášejí tak do specifikace nutnost ošetřovat mnoho nových situací.

Největší změny ECMAScriptu 5 jsou:

  • gettery a settery
  • reflexe objektů
  • kontrola atributů vlastností objektů (pojem atribut vlastnosti je vysvětlen později)
  • možnost omezení práce s obekty
  • nové funkce pro práci s poli
  • podpora JSON
  • striktní režim

Právě na tyto změny se v dnešním a příštím článku podrobněji podíváme. Nutno ale předem zdůraznit, že různých menších změn je ve specifikaci mnohem víc a také že popis změn bude z prostorových důvodů poměrně stručný a tím pádem místy ne zcela přesný.

Při psaní jsem se snažil dohledat, zda jsou jednotlivé novinky ES5 už podporovány v  implementacích JavaScriptu v prohlížečích, a pokud ano, tento fakt uvést. Implementace JavaScriptu pro zjednodušení nenazývám jejich jmény, ale podle prohlížečů, v nichž jsou obsaženy (místo „ve SpiderMonkey 1.8.1“ tak v článku najdete „ve Firefoxu 3.5“). Myslím, že je to tak méně matoucí.

Gettery a settery

První novinkou ECMAScriptu 5 jsou gettery a settery. Tyto pojmy nejspíš znáte z jazyků jako C# – jde o funkce, které se zavolají při načtení nebo naopak zápisu hodnoty nějaké vlastnosti objektu namísto jejího prostého vyhledání v tabulce. Díky getterům a setterům můžeme implementovat virtuální vlastnosti, líné počítání hodnot a jejich kešování, akce navázané na změnu hodnoty vlastnosti apod. Uživatel přitom přistupuje k vlastnostem pomocí obvyklé syntaxe, volání getteru/setteru je pro něj transparentní.

Poznámka: Aby došlo ke správnému terminologickému zmatení, tak se v některých jazycích jako gettery/settery označují běžné metody, které zpřístupňují privátní vlastnosti objektů, aniž by se snažily syntakticky maskovat jako přímý přístup k vlastnosti. Pro nás bude ale platit význam uvedený v předchozím odstavci.

Příklady

Použití getterů a setterů v ES5 si vysvětlíme na příkladu. Mějme třídu Rectangle (obdélník), jejíž instance mají vlastnosti width (šířka), height (výška) a area (obsah). Hodnotu vlastnosti area nebudeme nikde ukládat, ale budeme ji počítat dynamicky z šířky a výšky pomocí getteru. Zajistíme tak, že bude vždy aktuální.

/* Konstruktor */
function Rectangle(width, height) {
  this.width = width;
  this.height = height;
}

Rectangle.prototype = {
  /* Definice getteru vlastnosti "area" */
  get area() { return this.width * this.height; }
}

/* Příklad použití */
var r = new Rectangle(10, 20);
alert(r.area); // spočítá obsah a vypíše "200" 

Pokud by se nám třeba z výkonnostních důvodů nelíbilo, že se hodnota vlastnosti počítá při každém přístupu k ní, můžeme třídu Rectangle implementovat jinak – vlastnost area bude úplně obyčejná, ale její hodnotu budeme automaticky měnit při změně hodnoty width nebo height pomocí setteru.

Velmi důležité je, že uživatel třídy změnu implementace vůbec nezaznamená! Při implementaci bez getterů/setterů bychom přitom museli měnit vlastnosti na metody a naopak (čehož by si uživatel určitě všiml), případně bychom museli přístup ke všem vlastnostem skrýt za metody (což by otravovalo jak uživatele, tak nás).

Nová implementace může vypadat třeba takto:

/* Konstruktor */
function Rectangle(width, height) {
  this._width = width;
  this._height = height;
  this._computeArea();
}

Rectangle.prototype = {
  /* Pomocná funkce na výpočet obsahu */
  _computeArea: function() { this.area = this._width * this._height; },

  /* Definice getterů a setterů */
  get width() { return this._width; },
  get height() { return this._height; },
  set width(width) { this._width = width; this._computeArea(); },
  set height(height) { this._height = height; this._computeArea(); },
}

/* Příklad použití */
var r = new Rectangle(10, 20);
alert(r.area); // vypíše "200"
r.width = 20;  // nastaví šířku a přepočítá obsah
alert(r.area); // vypíše "400" 

Další informace

Gettery a settery jsou jednou z oblastí specifikace ES5, která je již nějakou dobu implementována všemi běžnými prohlížeči – specifikace ES5 zde tedy jen kodifikuje existující chování. Výjimkou je IE, který sice gettery a settery od verze 8 podporuje, ale je třeba je definovat pomocí funkce Object.define­Property (k jejímu popisu se později dostaneme). Syntaxe uvedená v příkladech mu nic neříká.

Další informace o getterech a setterech můžete najít na příslušné stráncečeské mutaci vývojářského centra Mozilly.

Reflexe objektů

Reflexe znamená schopnost objektů sdělit světu různé informace o sobě. Detaily se obecně liší podle jazyka, ale prakticky vždy se jedná především o informace o typu objektu, datech, které obsahuje, a metodách, které je na něm možno zavolat.

Stávající situace

V ES3 možnost reflexe existuje jen ve velmi oslabené formě. Typ objektu je například možné zjistit jen velmi zhruba pomocí operátoru typeof. Prototyp objektu nejde zjistit vůbec, pouze je možné se pomocí funkce Object.prototy­pe.isPrototype­Of nebo operátoru instanceof dotázat na to, zda řetězec prototypů objektu obsahuje nějakou konkrétní hodnotu.

Poznámka: Ve Firefoxu, Safari a Chrome je ke zjištění prototypu objektu možno použít speciální vlastnost __proto__, která obsahuje odkaz na něj. Tato vlastnost ale nemá oporu ve specifikaci ES3.

Vlastnosti obsažené v objektu se v ES3 dají zjistit pomocí příkazu for…in  – ten ale neprochází úplně všechny vlastnosti a navíc zahrnuje i vlastnosti v prototypech. Naštěstí existuje funkce Object.prototy­pe.hasOwnProper­ty, která umožňuje zjistit, zda je daná vlastnost definována přímo v objektu samotném. Ke zjištění, zda bude vlastnost zahrnutá při průchodu cyklem for..in, je pro změnu možné použít funkci Object.prototy­pe.propertyIsE­numerable.

Pokud je funkce definována na prototypu konstruktoru nějakého objektu (jako například Object.prototy­pe.hasOwnProper­ty), znamená to, že je možné ji volat přímo na instancích daného objektu (například volání {a: 42}.hasOwnPro­perty(„a“) vrátí true).

Pokud je funkce definována přímo na samotném konstruktoru objektu (jako například Object.getPro­totypeOf níže), na instancích ji volat nelze. Můžete si ji představit jako statickou metodu v klasických jazycích.

Rozšíření reflexe v ECMAScriptu 5

ES5 možnosti reflexe rozšiřuje prostřednictvím několika nových funkcí:

  • Object.getPro­totypeOf  – vrací prototyp objektu předaného v parametru
  • Object.keys – vrací pole s názvy vlastností objektu předaného v parametru; vynechává přitom vlastnosti, které by vynechal příkaz for…in
  • Object.getOwnPro­pertyNames – vrací pole s názvy vlastností objektu předaného v parametru a to včetně těch, které by vynechal příkaz for…in

Funkce Object.keysObject.getOw­nPropertyNames pracují vždy jen s vlastnosti objektů definovanými přímo v nich, nedívají se tedy do prototypů.

Funkce Object.getPro­totypeOf bude k dispozici ve Firefoxu 3.5, funkce Object.keys funguje už dnes v Safari.

Detekce polí

Jedním z problémů slabé reflexe v ES3 je také nemožnost spolehlivě zjistit, zda je nějaký objekt pole. Pokud totiž objekt pochází z jiného skriptového kontextu (např. z jiného rámu stránky), má nastavenou jinou hodnotu prototypu než pole v aktuálním kontextu. Tím pádem není možné hodnoty přímo srovnat ani použít operátor instanceof. Vzhledem k tomu, že operátor typeof na polích vrací generické „object“, není čeho se při detekci chytit.

Dnes se detekce typicky řeší hackem pomocí metody toString (viz například kód v jQuery). V ES5 bude ale možno použít vestavěnou funkci Array.isArray, která pole spolehlivě pozná.

Vytváření objektů s explicitním prototypem

Zajímavou možností ES5, která souvisí s reflexí, je možnost vytvořit objekt s explicitně uvedeným prototypem. To v ES3 nejde a prototyp se implicitně nastavuje na hodnotu vlastnosti prototype konstruktoru objektu (tj. funkce, na níž aplikujeme operátor new). ES5 přichází s funkcí Object.create, která vytvoří nový objekt, jehož prototyp je specifikován v parametru.

Kontrola atributů vlastností objektů

Každá vlastnost objektu má v ECMAScriptu své atributy, které určují některé její charakteristiky:

  • zda je možné měnit hodnotu vlastnosti
  • zda je možné vlastnost z objektu smazat (operátorem delete)
  • zda je vlastnost viditelná při procházení příkazem for…in

Vlastnosti obyčejných objektů je například možné měnit, mazat i procházet, ale třeba s délkou řetězce („abcd“.length) není možné udělat ani jedno z toho.

Doteď nebylo v ECMAScriptu možné s atributy vlastností nijak přímo manipulovat – třeba vytvořit vlastnost určenou jen ke čtení. Takové věci mohl dělat jen runtime jazyka. ES5 to napravuje a přidává několik funkcí, které atributy umožňují zjistit a změnit:

  • Object.getOwnPro­pertyDescriptor  – vrátí deskriptor dané vlastnosti objektu
  • Object.define­Property  – definuje novou vlastnost objektu s atributy specifikovanými předaným deskriptorem
  • Object.define­Properties – hromadně definuje nové vlastnosti objektu s atributy specifikovanými předaným polem deskriptorů

Deskriptor vlastnosti je objekt, který popisuje její atributy a také je přes něj možné získat či nastavit getter a setter vlastnosti. Vypadá například takto:

{
  writable: false,     // nejde měnit hodnotu vlastnosti
  configurable: false, // vlastnost nejde smazat (operátorem delete)
  enumerable: true,    // vlastnost je viditelná při procházení příkazem for...in
} 

První dvě funkce pro práci s atributy vlastností a jejich deskriptory dnes jako jediný prohlížeč podporuje IE 8.

Možnost omezení práce s objekty

ES5 zavádí několik nových funkcí, které umožňují omezit uživatele v zacházení s vybranými objekty.

Pokud chceme zakázat změnu hodnoty všech vlastností objektu, můžeme ho zmrazit funkcí Object.freeze. Funkcí Object.isFrozen se pak můžeme dotázat, zda je objekt zmrazen či nikoliv.

Podobně lze objekt zapečetit, tj. zakázat možnost smazání jeho vlastností. Provede se to funkcí Object.seal. Zda je objekt zapečetěný, nám prozradí doplňující funkce Object.isSealed.

Kromě změny atributů jednotlivých vlastností je také možné deklarovat, že do objektu nejdou přidávat nové vlastnosti. Zařídí to funkce Object.preven­tExtensions. Zda je přidávání nových vlastností objektu povoleno či zakázáno, můžeme zjistit pomocí funkce Object.isExten­sible.

Zmražování a zapečeťování objektů jsou spolu s možností zákazu rozšíření důležité především pro různé knihovny, komponenty a mash-upy. Ty – pokud využijí všechny tři možnosti – se tak můžou spolehnout, že pokud předají externímu kódu referenci na nějaký svůj vnitřní objekt, nebude s ním moci provést nic „ošklivého“. To je důležité především z bezpečnostního hlediska, ale může to ušetřit i mnohé dlouhé večery při ladění. V neposlední řadě mohou informace o uzamčení či zapečetění objektu pomoci interpretu při různých optimalizacích.

V tuto chvíli omezení práce s objekty neimplementuje žádný z prohlížečů.

Co nás čeká příště

Ve druhém dílu našeho miniseriálu se podíváme na nové funkce pro práci s poli, podporu JSON, striktní režim a několik dalších menších novinek nové verze ECMAScriptu.

Těšíte se na novinky ECMAScriptu 5?

Autor je vývojář se zájmem o programovací jazyky, webové aplikace a problémy programování jako takového. Vystudoval informatiku na MFF UK a během studií zde i trochu učil. Aktuálně pracuje v SUSE.

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

Komentáře: 19

Přehled komentářů

karf RE: Co přináší nový ECMAScript 5?
David Majda RE: Co přináší nový ECMAScript 5?
karf RE: Co přináší nový ECMAScript 5?
pas RE: Co přináší nový ECMAScript 5?
David Majda RE: Co přináší nový ECMAScript 5?
pas RE: Co přináší nový ECMAScript 5?
David Majda RE: Co přináší nový ECMAScript 5?
pas RE: Co přináší nový ECMAScript 5?
Martin Hassman RE: Co přináší nový ECMAScript 5?
pas RE: Co přináší nový ECMAScript 5?
Martin Hassman RE: Co přináší nový ECMAScript 5?
pas RE: Co přináší nový ECMAScript 5?
Mirek.Charvat RE: Co přináší nový ECMAScript 5?
pas RE: Co přináší nový ECMAScript 5?
Mirek.Charvat RE: Co přináší nový ECMAScript 5?
pas RE: Co přináší nový ECMAScript 5?
alblaho Re: RE: Co přináší nový ECMAScript 5?
David Majda Upřesnění
_ Re: Co přináší nový ECMAScript 5?
Zdroj: https://www.zdrojak.cz/?p=3012