Přejít k navigační liště

Zdroják » JavaScript » Co přináší nový ECMAScript 5?

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?

Komentáře

Subscribe
Upozornit na
guest
19 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
karf

Velmi důležité je, že uživatel třídy změnu implementace vůbec nezaznamená

Přimlouval bych se za to, vůbec nepoužívat slovo třída v souvislosti s JavaScriptem. Jinak fajn článek, těším se na pokračování.

karf

Asi bych se držel označení konstruktor a prototyp konstruktoru. Možná to zní krkolomně, ale přpadá mi to méně matoucí – je to jen můj názor. Ono existuje dost JS frameworků, které si zavádějí všelijaké vlastní implementace klasické "třídové" dědičnosti, člověk si pak při čtení různých článků musí v mysli pojem "třída" přepínat na různé významy.

pas

Proto je obrovská škoda, že ES4 byl zavržen – tam je třída tak, jak ji chápe každý normální programátor. Vůbec nechápu, čeho se báli. Výrobci browserů by prostě postupně doplnili implementaci ES4 jako druhého dostupného jazyka (Mozilla už ji měla k dispozici od Adobe) a vývojář by měl pořád na výběr, ve kterém z těch dvou jazyků bude vyvíjet. Přechod by mohl být pozvolný, ne žádná revoluce.

pas

Já nevím, co je na tom naivního. Stejně jako Silverlight je možné programovat v různých jazycích, i browsery by umožňovaly něco takového. Spíše mi připadá naivní snaha shodnout se na jednom jediném jazyku, jde to trochu proti ekonomickým teoriím. Zkrátka Mozilla by přišla s ES3 a ES4 (už to měla skoro připravené), jiné browsery by přišly s jinými jazyky. Jistě, tím pádem by některé aplikace fungovaly jen v některých browserech, ale to je úplně odlišná situace od toho chaosu, který tu panoval, když každý browser si interpretoval standardy po svém. Toto by fungovalo jako konkurecne, která by nutila ke zlepšování jak browsery, tak i jednotlivé jazyky. Bez konkurence celý obor zakrní nebo se vývoj zpomalí. A na tom vydělá jedině Silverlight a Flash.

pas

Reakce o kousek níže pod názorem pana Hassmana.

Martin Hassman

Prohlížeče se tam, kde jen můžou, vyhýbají nějakému větvení a la nová metoda/stará metoda a tam, kde k tomu z historických důvodů muselo dojít, se ještě po mnoha letech jedná o přítěž.

Rovněž si myslím, že je to naivní. Protože s tím asi nebudete souhlasit, doporučuji podrobněji nastudovat historii prohlížečů a webových technologií a případných problémů. Jedině asi tak pochopíte.

pas

Právě že historii prohlížečů už asi 15 let sleduju, a vedle toho i historii RIA technologií, přišel jsem s tímto nápadem. Nyní ho ještě upřesním a poopravím. Skriptovací jazyk by mohl být zcela oddělen od samotných browserů, vyvíjený klidně i třetími stranami a dle potřeby doinstalovávaný jako plugin. Pak by nastala zdravá konkurence mezi jazyky, která by uživatele téměř nijak neotravovala (stejně jako ho neotravuje, když si jednou za uherský rok nainstaluje Flash a Silverlight), vývojáři by měli na výběr to, co jim nejlépe vyhovuje, a celý obor by šel efektivně kupředu, než když se tak obrovské odvětví jako je web musí shodnout na jednom jazyku a dopadá to tak, že to je jazyk nedokonalý, protože se každý bojí revoluce.

Podotýkám, že rozhodně nezpochybňuju, že HTML a DOM standardizované být musí.

Martin Hassman

Obavam se, ze tento krok by webovou platformu pravdepodobne zabil.

pas

Ale proč? Neříkám, že mám vše do důsledku domyšlené, a tak bych rád o tom diskutoval. Další rozvinutí toho nápadu: Zpracování JavaScriptu se posunuje a bude posunovat směrem od interpretovaného skriptovacího jazyka k JIT kompilaci, je to tak nebo se pletu? Co potom brání tomu, aby vývojář dodal BUĎ zdrojový JavaScript NEBO předkompilovaný bytecode, který si vytvořil v libovolném zdrojovém jazyku? Stačilo by standardizovat za prvé HTML/DOM a za druhé bytecode, který s ním manipuluje. Je nějaký důvod z hlediska ideálů webové platformy nutné standardizovat ty zdrojové jazyky? Není možné se inspirovat Silverlightem? Znamenalo by to snad nějaký vendor-lock? A opakuji – jako "konzervativci" byste se měli obávat, že tato jazyková rigidnost odláká lidi směrem k progresivnějším RIA technologiím.

Mirek.Charvat

Nesleduju vývoj v oblasti JS tak do detailu, takže mi možná něco podstatného uniká a plácnu pitomost, ale myšlenka JS pluginu vypadá velmi logicky. Po technické stránce je toto řešení naprosto čisté …

Ale z ekonomického hlediska?

… Za Flash Playerem stojí Adobe a má ekonomický zájem na prodeji nástrojů pro tvorbu Flash aplikací – mimo jiné … za Silverlightem je Microsoft … kdo se podobným způsobem postaví za "JS-player" a proč?

pas

Tak připomenu poslední iteraci mého nápadu – web platforma standardizuje pouze VM pro zpracování bytecodu, nikoliv konkrétní jazyk (a z důvodu zpětné kompatibility obsahuje i kompilátor starého dobrého JavaScriptu do bytecodu). Kdo tedy má zájem na výrobě nástrojů pro produkci onoho bytecodu? Za prvé open source komunity nadšenců, kteří se snaží prosadit svůj jazyk jako nejlepší na světě (viz třeba Ruby). Za druhé zavedené firmy, které se snaží prodat své vývojářské nástroje navázané na existující jazyky (Visual Studio, Flash Builder). Zkuste mi vysvětlit, proč je tato architektura špatná a čemu by neprospěla?

Mirek.Charvat

No, ta architektura se mi nezdá špatná … ale to je houby platný … tady je důležité, proč to tedy někdo právě takto neudělá (nebo už neudělal)?

Neberte tu otázku jako protiargument. Ta otázka prostě visí v luftě, ať si o tom my dva, nebo kdokoli jiný myslí co chce :o)

pas

Proč to někdo neudělá? No já nevím, třeba to ještě nikoho nenapadlo. :) Můžeme o tom leda někde veřejně diskutovat, a tak se to třeba někdy donese i k někomu kompetentnějšímu. Tedy pokud mi tady někdo nějak rozumně nevysvětlí, že to je špatný nápad.

Ale mě to je v podstatě jedno, mě živí a zajímají mě především RIA technologie, takže tady vlastně napovídám milovníkům tradičních webových technologií, jak jim pomoct konkurovat a přežít. I když si pan Hassman nejspíš myslí, že mi jde o to je pohřbít. ;-)

alblaho

No, asi do toho zas tak nevidíte. JIT kompilace se nedělá do bytecode, ale do nativního kódu, protože jedině ten může běžet přímo na železe.

To co popisujete je opravdu .Net. Různé jazyky se kompilují do jednoho bytecode a ten se pak kompiluje (JIT) do nativního. Samozřejmě že by to tak fungovat mohlo, je to lety prověřený princip. Problém je, že je to nahony vzdálené současné situaci v prohlížečích. JS prohlížeče umí „od přírody“, Java, Silverlight, Flash jsou ty pluginy.

_

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.

Na objekt prototypu sa dá odkazovať cez objekt.construc­tor.prototype čo funguje úplne rovnako ako neštandardná vlastnosť objekt.__proto__

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.