ECMAScript 5
Autor: Piotr Karpiuk o czwartek 15. Marzec 2012
Zaakceptowana oficjalnie w 1999 wersja 3 ECMAScript jest pewną bazą wszystkich implementacji języka JavaScript w dzisiejszych przeglądarkach. Tym niemniej, język się rozwija i dziś w najnowszych przeglądarkach jest już zaimplementowana obsługa ECMAScript 5, co dokładniej pokazuje tabelka (na skutek różnych perturbacji wersja 4 została zarzucona). Ba, mówi się już o wersji 6 (nazwa kodowa Harmony), której specyfikacja ma być gotowa na koniec 2013 roku, a niektóre przeglądarki (np. Google Chrome 19) już eksperymentalnie zaimplementowały niektóre zawarte tam nowinki – ale o tym innym razem. Przedmiotem dzisiejszego posta jest ECMAScript 5.
ECMAScript 5.1 Spec
Wersja 5 nie zawiera żadnych rewolucyjnych zmian, zwłaszcza składniowych. Jest zestawem modyfikacji poprawiających bezpieczeństwo, jak również długo oczekiwanych usprawnień. O ile nie włączymy trybu ścisłego (o czym za chwilę), jest również zgodna wstecz.
Strict mode
Tryb ścisły (ang. strict mode) usuwa z języka pewne funkcje i wymusza określony styl kodowania, co czyni program prostszym i bardziej odpornym na błędy. Naruszenie dyscypliny powoduje wyjątek. Tryb ścisły włączamy umieszczając na początku zakresu zmiennych (na poziomie funkcji lub globalnym) łańcuch "strict mode"
– starsze implementacje języka po prostu go zignorują. Planuje się, że w przyszłości tryb ścisły będzie jedynym dostępnym trybem.
W trybie ścisłym wyjątek spowoduje np.:
- użycie zmiennej bez jej wcześniejszej deklaracji,
- modyfikacja atrybutów obiektu niezgodnie z określoną polityką – patrz punkt „Object & Property System” w dalszej części,
- usuwanie zmiennej,
- definiowanie w jednym obiekcie dwóch własności o tej samej nazwie, lub w sygnaturze funkcji dwóch parametrów o tej samej nazwie:
var x = { foo: true, foo: false }; // błąd function f(foo, foo) { ... }; // błąd
- korzystanie wewnątrz funkcji z
arguments.caller
iarguments.callee
, - użycie konstrukcji
with
.
JSON
W ECMAScript 5 pojawiła się wreszcie natywna implementacja funkcji konwertującej struktury danych na JSON (JSON.stringify(value[, replacer[, space]])
) i odwrotnie (JSON.parse(text[, reviver])
).
var obj = JSON.parse('{"name":"Jan"}'); var str = JSON.stringify({ name: "Jan", last: "Kowalski" }, null, 2);
W przypadku obu funkcji można podać drugi parametr który pozwala w locie modyfikować proces konwersji – szczegóły w dokumentacji, natomiast w funkcji stringify()
można też podać trzeci parametr, który określa wcięcia używane przy formatowaniu JSONa (brak tego parametru oznacza brak formatowania).
Nowe metody w klasach Function
, Array
, Date
Function.prototype.bind(thisArg, arg1, arg2, ...)
– opakowuje wskazaną funkcję w inną, ustalając kontekst tej pierwszej nathisArg
; działanie jest takie samo jak w przypadkubind()
z biblioteki Prototype,String.prototype.trim()
– zwraca łańcuch z usuniętymi spacjami na początku i końcu łańcucha,Array.isArray(arr)
– czy argumentarr
jest tablicąarray.indexOf(what [, fromIndex])
,array.lastIndexOf(what [, fromIndex])
- Rozszerzenia funkcyjne dla tablic (w każdym przypadku
callback
oznacza funkcję postacifunction(val, index, array) { ... }
):array.every(callback [, context])
– leniwa, zwracatrue
wtw. gdy callback dla każdego elementu tablicy zwróci prawdęarray.some(callback [, context])
– leniwa, zwracatrue
gdy callback dla jednego chociaż elementu tablicy zwróci prawdęarray.forEach(callback [, context])
– wykonuje callback dla każdego elementu tablicyarray.map(callback [, context])
– zwraca tablicę wyników wykonania callbacka na każdym elemencie tablicyarray.filter(callback [, context])
– tablica tych elementów tablicy, dla których callback zwrócił prawdę
- Rozszerzenia funkcyjne dla tablic (w każdym przypadku
callback
oznacza funkcję postacifunction(accumulator, curValue, index, array) { ... }
):array.reduce(callback, initialValue)
– aplikuje callback dla akumulatora i kolejnych elementów tablicy zwracając ostateczną postać akumulatoraarray.reduceRight(callback, initialValue)
– j.w., ale od prawej do lewej
Date.now()
zwraca liczbę ms od 1 stycznia 1970 do chwili obecnejDate.prototype.toISOString()
zwraca łańcuch postaci"2012-04-03T22:59:33.610Z"
, taki łańcuch można też podać w kontruktorze typuDate
Object & Property System
Przywykliśmy traktować obiekt JavaScriptu jak słownik, w którym kluczami są łańcuchy a wartościami cokolwiek. Jest to dość prymitywne podejście do obiektowości, które utrudnia wdrożenie dyscypliny koniecznej do tworzenia większych programów. O ile wcześniej własność obiektu miała jedynie nazwę i wartość, to teraz każda własność ma nazwę i deskryptor (ang. property descriptor) z następującymi atrybutami:
- value, writable – wartość własności i flaga określająca czy można zmieniać tą wartość,
- enumerable – flaga, która mówi czy własność pojawi się podczas iterowania obiektu za pomocą
for(var key in obj) { ... }
, - get, set – funkcje kontrolujące dostęp do wartości własności (wołane przy próbie odpowiednio odczytu lub zapisu wartości); Uwaga: własność może mieć zdefiniowane albo atrybuty
value
/writable
(jest to tzw. data descriptor), alboget
/set
(accessor descriptor), - configurable – flaga, której wartość
false
oznacza, że własności nie można usunąć, a deskryptor własności nie może być modyfikowany (z wyjątkiem atrybutuwritable
, który może być przestawiony nafalse
).
Domyślne wartości atrybutów deskryptora to false
(dla flag) i undefined
w pozostałych przypadkach.
Metoda Object.defineProperty(obj, prop, descriptor)
tworzy nową własność lub modyfikuje istniejącą:
var o = {}; Object.defineProperty(o, "a", {value : 37, writable : true, enumerable : true, configurable : true}); var bValue; Object.defineProperty(o, "b", {get : function(){ return bValue; }, set : function(newValue){ bValue = newValue; }, enumerable : true, configurable : true}); o.b = 38;
Object.defineProperties(obj, props)
jest skrótem zastępującym wielokrotne wywołania poprzedniej metody. Deskryptor wskazanej własności obiektu (nie dziedziczonej z prototypu) można otrzymać wywołując metodę Object.getOwnPropertyDescriptor(obj, propName)
. Object.keys(obj)
zwraca tablicę wyliczalnych własności nie odziedziczonych z prototypu, a Object.getOwnPropertyNames(obj)
listę wszystkich własności nie odziedziczonych z prototypu.
Object.create(proto[, propertiesObject])
tworzy nowy obiekt z podanym prototypem i własnościami określonymi tak jak w przypadku defineProperties()
.
Metoda Object.preventExtensions(obj)
nieodwracalnie blokuje możliwość dodawania własności do wskazanego obiektu (nie mówi nic o usuwaniu), a Object.isExtensible(obj)
zwraca stan flagi. Mocniejsze działanie ma metoda Object.seal(obj)
, która dodatkowo ustawia atrybut configurable
wszystkich własności obiektu na false
(co, przypomnijmy, zabrania usuwać własności), a Object.isSealed(obj)
zwraca stan flagi.
Najmocniejsze działanie ma Object.freeze(obj)
, która dodatkowo blokuje nawet możliwość zmiany atrybutu writable
deskryptorów własności obiektu, czyniąc obiekt autentycznie niemodyfikowalnym (ang. immutable). Jak się można domyśleć, Object.isFrozen(obj)
zwraca stan flagi.
Inne zmiany
- wsparcie dla Unicode 3.0,
- w literale łańcuchowym dozwolony jest znak końca wiersza poprzedzony backslashem (czytaj: można tworzyć wielowierszowe literały łańcuchowe).