JavaScript - obiekty
W JS niemal wszystko zachowuje się jak obiekt, oprócz null
(mimo że typeof null === 'object'
z powodów historycznych),undefined
i typów prostych (number, string, boolean, etc. - gdzie tak na prawdę są to obiekty stworzone za pomocą odpowiednich konstruktorów np. Number
).
Poniżej ES5 niektóre właściwości obiektu muszą być pisane jako string, jeśli nie to parser zwraca SyntaxError, np. { 'delete' : 'text' }
.
- Wprowadzenie do programowania obiektowego w języku JavaScript
Mozilla Developers Network
Programowanie obiektowe - definicje
Enkapsulacja (hermetyczność)
Niektóre elementy danej klasy powinny być zabezpieczone przez bezpośrednie wywołanie z innych obszarów programu, aby zachować kontrolę modyfikacji danych. Ta zasada głównie tyczy się pól (zmiennych). Przykładem obrazującym tę zasadę może być klasa Biblioteka, której książki powinny być dostępne jedynie poprzez specjalne metody (symulujące osobę obsługującą czytelnika), a nie bezpośrednio (jak gdyby czytelnik mógł wejść do biblioteki, wziąć samemu książkę i wyjść).
Dziedziczenie
Definicje pewnych bytów mogą być uszczegóławiane poprzez zdefiniowanie innych bytów dziedziczących z tych bardziej ogólnych. Dzięki temu, klasy podrzędne nie muszą na nowo definiować pewnych metod, a jedynie mogą korzystać z tych, które zostały wprowadzone dla klasy nadrzędnej.
Polimorfizm
Obiekty mogą być przechowywane jako instancje tej samej, ogólnej klasy, ale jednocześnie być instancją różnych, szczegółowych klas, dzięki czemu diagramy stanów i klas zyskują nowe właściwości bądź zachowują się charakterystycznie dla klasy podrzędnej.
Abstrakcyjność
Właściwość, dzięki której byty w programie mogą się komunikować i działać bez wiedzy o szczegółach działania (implementacji) innych bytów. W językach programowania jest to zasada realizowana głównie przez klasy i metody abstrakcyjne, a także interfejsy.
Szybki przykład obiektowości JS
// wersja 1 - obiekt Car
var myCar, myNewCar, myNextCar;
myCar = {}; // myCar = new Object();
myCar.make = 'Ford';
myCar.model = 'Mustang';
myCar.year = '1969';
// wersja 2 - obiekt Car
myNewCar = {
make: 'Honda',
model: 'Civic',
year: 1999
};
// wersja 3 - konstruktor car
function car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
myNextCar = new car('Volvo', 'S70', 1998);
// metody obiektu
function displayCar() {
var result = 'A Beautiful ' + this.year + ' ' + this.make + ' ' + this.model;
pretty_print(result);
}
function car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
this.displayCar = displayCar;
}
myNextCar = new car('Volvo', 'S70', 1998);
myNextCar.speed = 240;
// prototypy - tworzenie hierarchii dziedziczenia
function car(make, model, year) {
this.make = make; this.model = model;
this.year = year || new Date().getFullYear();
}
function truck(make, model, capacity, year) {
this.base = car;
this.base(make, model, year);
this.capacity = capacity;
}
truck.prototype = new car;
Sprawdzanie typów, instancji klas i prototypów funkcji
typeof [variable]
instanceof [object]
function.prototype
classInstance.constructor.name
function Foo() {}
var foo = new Foo();
typeof Foo; // == "function"
typeof foo; // == "object"
foo instanceof Foo; // == true
foo.constructor.name; // == "Foo"
Foo.name; // == "Foo"
Foo.prototype.isPrototypeOf(foo); // == true
Foo.prototype.bar = function (x) {return x+x;};
foo.bar(21); // == 42
Własne obiekty
Przykład JSON
Przykład literału obiektowego.
{
"firstName" : "Robert",
"lastName" : "Kowalski"
}
Przykład obiektu
Poniższy przykład prezentuje obiekt myObj
z metodą myFunc()
.
var myObj = {
myString: "Hello world!",
myFunc: function(){
return this.myString;
}
};
myObj.myFunc(); // = "Hello world!"
Alternatywnie:
var myObj = new Object();
myObj.myString = "Hello world!";
myObj.myFunc = function(){
return this.myString;
};
myObj.myFunc(); // = "Hello world!"
Przykład modułu
Poniżej znajduje się wzorzec modułu w JavaScript, z którego najczęściej korzystam.
var myApp = (function(space) {
var newPrivate = 2;
space.init = function () {
$(document.ready() {
// ..
});
};
return space;
}(myApp || {}));
// ..
myApp.init();
Do space.init
warto dodać dwa kontenery na metody i eventy. Poniższy kod pochodzi z developer.mozilla.org.
// Utwórz pojemnik MyApp.commonMethod na typowe metody i właściwości
commonMethod = {
regExForName: "",
// zdefiniuj wyrażenie regularne do walidacji nazwiska
regExForPhone: "",
// zdefiniuj wyrażenie regularne do walidacji numeru telefonu
validateName: function(name){
// Zrób coś z nazwiskiem. Możesz użyć zmiennej regExForName
// użycie "this.regExForName"
},
validatePhoneNo: function(phoneNo){
// zrób coś z numerem telefonu
}
}
// Obiekt razem z deklaracją metod
event = {
addListener: function(el, type, fn) {
// jakiś kod
},
removeListener: function(el, type, fn) {
// jakiś kod
},
getEvent: function(e) {
// jakiś kod
}
// Można dodać kolejne metody i właściwości
}
// Składnia do użycia metody AddListener:
MyApp.event.addListener("yourel", "type", callback);
Przykład konstruktora
Obiekty tworzy się również poprzez wywołanie konstruktorów za pomocą new
. Tak na prawdę każde wywołanie funkcji, które jest poprzedone słowem kluczowym new
, zachowuje się jak konstruktor.
Wewnątrz konstruktora - wywoływanej fukcji - wartość this
wskazuje na nowo utworzony obiekt Object
. Prototyp prototype
nowo utworzonego obiektu będzie wskazywał na prototyp prototype
obiektu funkcji, która została wywołana jako konstruktor.
var MyConstructor = function(){
this.myNumber = 5;
}
myNewObj = new MyConstructor(); // = {myNumber: 5}
myNewObj.myNumber; // = 5
W odróżnieniu od fabryki:
- nie ma tworzonego obiektu bezpośrednio w funkcji
- właściwości i metody są skojarzone bezpośrednio z obiektem
this
- nie ma wywołania
return
Fabryki
Często zaleca się nie korzystać z operatora new
, ponieważ zapominanie o jego stosowaniu może prowadzić do błędów.
W celu stworzenia nowego obiektu, powinno się używać fabryki i konstruować nowy obiekt wewnątrz tej fabryki.
Poniższa fabryka Foo()
zwraca nowu obiekt obj
.
function Foo() {
var obj = {};
obj.value = 'blub';
var private = 2;
obj.someMethod = function(value) {
this.value = value;
}
obj.getPrivate = function() {
return private;
}
return obj;
}
Metoda ta posiada jednak pewne wady.
- Zużywa więcej pamięci, ponieważ tworzony obiekt nie współdzieli metod poprzez prototyp.
- Aby móc dziedziczyć fabryka musi skopiować wszystkie metody z dziedziczonego obiektu lub przypisać ten obiekt, z którego się dziedziczy, jako prototyp do nowo utworzonego obiektu.
- Porzucenie łańcucha prototypów tylko ze względu na opuszczone słowo kluczowe
new
jest sprzeczne z duchem języka.
Przykład przypisywania prototypu
JavaScript nie posiada klasycznego modelu dziedziczenia. Zamiast tego dziedziczenie jest realizowane poprzez prototypy. W JavaScripcie, jeśli metoda lub pole/właściwość nie może zostać znalezione w aktualnie przetwarzanym obiekcie, to następuje przejście do prototypu i szukanie tych wartości tam. Podczas dostępu do właściwości obiektu JavaScript przejdzie w górę łańcucha prototypów, dopóki nie znajdzie właściwości bez nazwy. Nie ma tutaj żadnego kopiowania, obiekt przechowuje referencję do prototypu, tak więc zmiana prototypu powoduje zmianę również w podstawowym obiekcie.
Prototyp za pomocą __proto__
Jest to metoda nieustandaryzowana.
var myObj = {
myString: "Hello world!"
};
var myPrototype = {
meaningOfLife: 42,
myFunc: function(){
return this.myString.toLowerCase()
}
};
myObj.__proto__ = myPrototype;
myObj.meaningOfLife; // = 42
// This works for functions, too.
myObj.myFunc(); // = "hello world!"
[ES5] Prototyp za pomocą Object.create()
Metoda Object.create()
jest dostępna od ES5.
var myObj = {
myString: "Hello world!"
};
var myPrototype = {
meaningOfLife: 42,
myFunc: function(){
return this.myString.toLowerCase()
}
};
var myObj = Object.create(myPrototype);
myObj.meaningOfLife; // = 42
// This works for functions, too.
myObj.myFunc(); // = "hello world!"
Istnieje jednak polyfill dla tej metody.
if (Object.create === undefined){ // don't overwrite it if it exists
Object.create = function(proto){
// make a temporary constructor with the right prototype
var Constructor = function(){};
Constructor.prototype = proto;
// then use it to create a new, appropriately-prototyped object
return new Constructor();
}
}
Prototyp za pomocą konstruktora
Metoda ta działa powszechnie. Konstruktory mają pole/właściwość nazywaną prototype
, nie jest to jednak prototyp konstruktora, a prototyp obiektu utworzonego dzięki danemu konstruktorowi.
MyConstructor.prototype = {
myNumber: 5,
getMyNumber: function(){
return this.myNumber;
}
};
var myNewObj2 = new MyConstructor();
myNewObj2.getMyNumber(); // = 5
myNewObj2.myNumber = 6
myNewObj2.getMyNumber(); // = 6
Rozszerzanie prototypu
Do prototypu można dodawać własne metody oraz pola i właściwości. Przykładowo możemy rozszerzyć prototyp wbudowanego obiektu String()
o funkcję zwracającą pierwszy znak ciągu. Nie należy jednak z tej opcji korzystać, chyba że chcemy stworzyć polyfill dla starszych wersji JS.
String.prototype.firstCharacter = function(){
return this.charAt(0);
}
"abc".firstCharacter(); // = "a"
Pozostałe metody tworzenia konstruktorów i prototypów
- Combination Constructor/Prototype Pattern
- Dynamic Prototype Pattern
- Parasitic Constructor Pattern
- Durable Constructor Pattern
Metody dziedziczenia
- Combination Inheritance
- Prototypal Inheritance
- Parasitic Inheritance
- Parasitic Combination Inheritance
Właściwości obiektu
Wyróżniamy dwa typy właściwości obiektów:
- data properties - z atrybutami
configurable, enumerable, writable, value
- accessor properties - z atrybutami
configurable, enumerable, get, set
Metoda Object.defineProperties()
Metoda ta służy do deklarowania właściwości, w szczególności tych, które mają mieć niestandardowe ustawienia atrybutów.
Poniżej przykładowy kod, w którym ustalamy własne metody set
i get
. Notacja zapisu dla _year
oznacza, że nie chcemy, aby zmienna była dostępna poza obiektem (ma być hermetyczna).
Kiedyś do ustalania getterów i setterów były wykorzystywane właściwości __defineGetter__()
oraz __defineSetter()
.
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
},
edition: {
value: 1
},
year: {
get: function() {
return this._year;
},
set: function(newValue) {
if(newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
}
});
Metoda Object.getOwnPropertyDescriptor()
Metoda ta służy do uzyskiwania deskryptora właściwości. Działa tylko na instancji, więc jeśli chcemy mieć dostęp do właściwości prototypu musimy ją wywołać bezpośrednio na nim.
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value); //2004
alert(descriptor.configurable); //false
alert(type of descriptor.get); //undefined
[ES5] Metoda Object.keys()
Zwraca wszystkie klucze właściwości obiektu. Jest to ulepszona wersja metody getOwnPropertyNames()
, która to wypisywała również właściwość constructor
.
[ES5] Definiowanie konstruktora obiektu
Poniższy kod zadziała tylko w ES5.
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
Funkcje obiektów
Metoda hasOwnProperty()
Jedyną metodą służącą do sprawdzenia istnienia jakiejś właściwości w konkretnym obiekcie jest metoda hasOwnProperty
. Zaleca się korzystać z hasOwnProperty
w każdej pętli for in
. Pozwoli to uniknąć błędów pochodzących z rozszerzonych natywnych prototypów.
[ES5] Metoda Object.getPrototypeOf()
Jest alternatywą dla metody isPrototypeOf()
.
Metody call()
i apply()
Tymczasowe przesyłanie obiektów do funkcji.
// We can also specify a context for a function to execute in when we invoke it
// using `call` or `apply`.
var anotherFunc = function(s){
return this.myString + s;
}
anotherFunc.call(myObj, " And Hello Moon!"); // = "Hello World! And Hello Moon!"
// The `apply` function is nearly identical, but takes an array for an argument
// list.
anotherFunc.apply(myObj, [" And Hello Sun!"]); // = "Hello World! And Hello Sun!"
Jest to przydatne w przypadku metody hasOwnProperty()
, która sprawdza, czy właściwość istnieje w danym obiekcie (łącznie z prototypami), a która może zostać nadpisana w obiekcie, dlatego konieczne jest wywołanie jej przez metodę call()
.
var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};
foo.hasOwnProperty('bar'); // zawsze zwraca false
// Została użyta metoda innego obiektu i wywołana z kontekstem
// `this` ustawionym na foo
({}).hasOwnProperty.call(foo, 'bar'); // true
Metoda bind()
Stały odpowiednik call()
i apply()
.
// But, `call` and `apply` are only temporary. When we want it to stick, we can
// use `bind`.
var anotherFunc = function (text) {
return console.log("Hello World" + text);
}
var boundFunc = anotherFunc.bind(myObj);
boundFunc(" And Hello Saturn!"); // = "Hello World! And Hello Saturn!"
Wbudowane obiekty
Obiekt Image()
Przed zastąpieniem rysunku widocznego na stronie należy skorzystać z metody wstępnego pobierania grafiki.
var preloadImage =
['images/roll.png',
'images/flower.png',
'images/cat.jpg'];
var imgs = [];
for(var i = 0; i < preloadImages.length; i++) {
imgs[i] = new Image();
imgs[i].src = preloadImages[i];
}