JS 1.8.5: Object.*
Nowe sposoby na obiekty
w JavaScripcie 1.8.5 i ECMAScripcie 5
I kto to mówi?
- Marek Stępień - @marcoos, marcoos@marcoos.org
- współzałożyciel Aviary.pl (polski Firefox!)
- członek (Active Member) stowarzyszenia Mozilla Europe
- web developer w Interia.pl
- zwolennik otwartych standardów WWW
- bloger
Dla przypomnienia: typowy obiekt JS
var anObject = {
prop1: value1,
prop2: value2,
// ...
propN: valueN
};
Dla przypomnienia: łańcuch prototypów
Object.prototype
...
someOtherProto
someProto
...
anObject
Między Object.prototype
a naszym anObject
może istnieć dowolna liczba obiektów w łańcuchu prototypów, z których - licząc od dołu - pobierane będą własności nieobecne bezpośrednio w anObject
.
Dotychczasowe operacje na własnościach
- odczyt:
x = anObject.prop1
- zapis lub dodanie nowej:
anObject.prop2 = 8
- usunięcie:
delete anObject.prop3
- wywołanie jako metoda (jeśli
prop4
jest funkcją):anObject.prop4()
Ograniczenia
- każdy może zapisać, odczytać, usunąć, wywołać każdą własność każdego obiektu
- każdy może dodać dowolną własność do dowolnego istniejącego obiektu
- standard ECMA262 nie przewidywał możliwości tworzenia getterów/setterów
- podejście czysto prototypowe (tzn. bez
new
) utrudnione
Próby obchodzenia ograniczeń
- closures
- pseudogettery/pseudosettery
obj.setProp1()
,obj.getProp2()
- niestandardowe rozszerzenia niektórych silników (
__defineGetter__
,__defineSetter__
) anObj = Object.beget(protoObj)
D. Crockforda / niestd.anObj.__proto__
ES5 (oraz JS 1.8.5) rozwiązuje te problemy
Nowe metody obiektu Object
:
- defineProperty()
- defineProperties()
- getOwnPropertyDescriptor()
- create()
- seal()
- isSealed()
- freeze()
- isFrozen()
- preventExtensions()
- isExtensible()
- keys()
- getOwnPropertyNames()
Object.defineProperty()
- Od teraz można określić, czy własności mają być:
- zapisywalne (writable), czy tylko do odczytu
- wyliczalne (enumerable) - czy widać je w np.
for...in
,in
itp. - konfigurowalne (configurable) - czy można zmienić deskr. lub usunąć własność
- można też definiować gettery i settery (ale nie dla writable!)
Object.defineProperty()
Object.defineProperty(obj, prop, descriptor)
obj
- obiekt, do którego dodajemy własnośćprop
- nazwa własności (string)descriptor
- deskryptor własności
Deskryptor własności
Obiekt o następujących własnościach:
value
writable
configurable
enumerable
get
set
...able
są domyślnie false
; pozostałe: undefined
Deskryptor własności: trywialny przykład
// odpowiednik: obj = { x: 42; }
obj = {};
Object.defineProperty(obj, "x", {
writable: true, // będzie można ustawic wartość
enumerable: true, // będzie widać w for-in, in
configurable: true, // będzie można zmienić deskryptor
value: 42
});
Deskryptor własności: lepszy przykład :)
obj = { _x: 5 };
Object.defineProperty(obj, "x", {
enumerable: false, // nie będzie widać "x" w for-in, in
configurable: false, // nie będzie można zmienić deskryptora
get: function () { return this._x; },
set: function (newValue) {
if (newValue < 10) {
this._x = newValue;
} else {
this._x = -1;
}
}
}); // [demo (lokalnie)]
Deskryptor własności: lepszy przykład :)
Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor(obj, propName)
- pozwala pobrać deskryptor istniejącej własności.
Object.getOwnPropertyDescriptor([1,2], "1") // {"value":2,"writable":true, // "enumerable":true,"configurable":true}
Object.getOwnPropertyDescriptor([3,4,5], "length") // {"value":3,"writable":true, // "enumerable":false,"configurable":false}
Object.defineProperties()
- To samo, co
defineProperty()
, ale pozwala jednocześnie zdefiniować więcej własności. Object.defineProperties(obj, { prop1Name: prop1Descriptor, prop2Name: prop2Descriptor, // ... propKName: propKDescriptor });
Object.preventExtensions()
- uniemożliwia dodawanie nowych własności do obiektu - nikt nie będzie nam nic dopisywał do naszego obiektu
- istniejące własności można usuwać (
delete
), można zmieniać ich deskryptory, a także wartość var x = {a: 5, b: 7}; Object.preventExtensions(x); delete x.a; // zadziała x.b = 31337; // zadziała x.c = 42; // silent failure; w strict: TypeError
Object.isExtensible(anObj)
informuje o rozszerzalności obiektu
Object.seal()
- zapieczętowuje obiekt, tj. uniemożliwia dodawanie kolejnych i konfigurowanie/usuwanie istniejących własności
x = { a: 5, b: 7}; Object.seal(x); x.b = 31337; // zadziała delete x.a; // silent failure; w strict: TypeError x.c = 8; // silent failure; w strict: TypeError
Object.isSealed(anObj)
informuje, o zapieczętowaniu obiektu
Object.freeze()
- zamraża obiekt, uniemożliwiając jakiekolwiek modyfikacje
x = { a: 5, b: 7}; Object.freeze(x); x.c = 42; // silent failure; w strict: TypeError x.b = 31337; // silent failure; w strict: TypeError delete x.a; // silent failure; w strict: TypeError
Object.isFrozen(anObj)
informuje, o zamrożeniu obiektu
preventExtensions, seal, freeze...
- Dobra, zestawmy operacje na własnościach w tabelkę :)
metoda\operacje zmiana
wartości(prze-)konfi-
gurowaniedodawanie
nowych(brak) TAK TAK TAK preventExtensions()
TAK TAK NIE seal()
TAK NIE NIE freeze()
NIE NIE NIE
Object.create()
Object.beget()
D. Crockforda z dodatkowym argumentemanObj = Object.create(proto [, propertiesObject ]);
proto
- obiekt, który będzie prototypem nowego obiektupropertiesObject
- opcjonalny obiekt z deskryptorami własności (analogicznie jak przydefineProperties()
)
Object.create(): przykład trywialny
var anObj = Object.create(Object.prototype);
var anObj = new Object(); // równoważne
var anObj = {}; // równoważne- oczywiście w tym przypadku ostatnia wersja ma więcej sensu :)
Object.create(): lepszy przykład
- ← volumeUpButton
- ← volumeDownButton
Object.create(): lepszy przykład
var volumeButton = {
delta: 0, // o ile zmienia się głośność
onActivate: function () {
someAudioPlayer.volume += this.delta;
} // ...
};
var volumeUpButton = Object.create(volumeButton),
volumeDownButton = Object.create(volumeButton);
volumeUpButton.delta = 1;
volumeDownButton.delta = -1;
Object.keys()
- zwraca tablicę wyliczalnych nazw własności obiektu...
- bez dziedziczonych z prototypów
var obj = { a: 5, b: 7, c: 42 }, propNames = Object.keys(obj); alert(propNames.join(", ")); // "a, b, c"
Object.getOwnPropertyNames()
- zwraca tablicę wszystkich własności obiektu...
- bez dziedziczonych z prototypów
var arr = [1, 2, 3]; console.log(Object.getOwnPropertyNames(arr)); // wynik: ["length", "0", "1", "2"] console.log(Object.keys(arr)); // wynik: ["0", "1", "2"]
Gdzie działa?
- Firefox 4+
- Chrome 5+
- IE 9+
- Safari 5+
- (Opera obiecuje :))
Co robić?
- Na początku może to być trudne do ogarnięcia...
- ...ale jest na to jeden sposób:
Hack!
Dziękuję za uwagę
- Pytania? Walcie śmiało!
- bit.ly/meetjs || http://L10n.mozilla.org/~marcoos/slides/2011/meetjs-warszawa/
- J. Resig: ECMAScript 5 Objects and Properties
- N. Belmonte: ECMA Harmony and the Future of JavaScript
- ECMAScript.org
- Mozilla Developer Network
- Mozilla Hacks
- Ładniejsze rzeczy? Zobacz Web-o-Wonder!