prototype i __proto__ w JavaScript odnoszą się do tego samego mechanizmu dziedziczenia prototypowego, ale działają na różnych poziomach: prototype należy do funkcji (konstruktorów) i opisuje „wzorzec” dla przyszłych obiektów, a __proto__ należy do konkretnych obiektów i wskazuje na ich prototyp (faktyczny element łańcucha dziedziczenia).
Poniżej znajdziesz praktyczne wyjaśnienie – od „klasycznych” konstruktorów po współczesną składnię class – z myślą o twórcach aplikacji webowych.
1. Dlaczego w ogóle prototypy są ważne?
JavaScript opiera się na dziedziczeniu prototypowym – nawet składnia class jest tylko cukrem składniowym nad prototypami.
W skrócie, w modelu wyszukiwania właściwości silnik JS najpierw sprawdza obiekt, a jeśli nie znajdzie pasującej nazwy, przechodzi po łańcuchu prototypów aż do null. To właśnie ten łańcuch decyduje, skąd „biorą się” metody i właściwości niewystępujące bezpośrednio na obiekcie.
prototype i __proto__ to dwa różne interfejsy pracy z tym samym mechanizmem.
2. Wewnętrzna właściwość [[Prototype]] – punkt wyjścia
Kluczowe fakty o [[Prototype]]:
- to wewnętrzna, ukryta właściwość obiektu opisana w specyfikacji,
- nie można jej odczytać ani ustawić bezpośrednio nazwą
[[Prototype]]w kodzie, - jest to „link” do obiektu, z którego obecny obiekt dziedziczy właściwości i metody.
W praktyce dostęp do [[Prototype]] uzyskujemy za pomocą akcesorów historycznych lub nowoczesnego API. Współcześnie stosuj Object.getPrototypeOf(obj), Object.setPrototypeOf(obj, proto) oraz Object.create(proto) zamiast bezpośredniego użycia __proto__.
prototype służy do zdefiniowania tego, co później stanie się wartością [[Prototype]] nowo utworzonych obiektów.
3. prototype – własność funkcji (konstruktorów)
Do kogo należy prototype?
prototype to własność funkcji (zwłaszcza konstruktorów wywoływanych przez new), której używamy, gdy traktujemy funkcję jako „projekt” przyszłych instancji.
Przykład:
function Person(name) { this.name = name; }
Person.prototype.sayHello = function () {
return `Cześć, jestem ${this.name}`;
};
W tym układzie Person.prototype to zwykły obiekt, do którego dopisujemy metody i właściwości, a wszystkie instancje utworzone przez new Person(...) będą z niego dziedziczyć.
Co dzieje się przy new?
Wywołanie konstruktora inicjuje typowy, czterostopniowy proces:
- Tworzony jest nowy obiekt:
{}. - Jego wewnętrzna właściwość
[[Prototype]]jest ustawiana naPerson.prototype. - Funkcja
Personjest wywoływana zthiswskazującym na ten nowy obiekt. - Jeśli konstruktor nie zwróci innego obiektu, wynik to ten nowy obiekt.
Efekt możesz sprawdzić w konsoli:
const jan = new Person('Jan');
jan.__proto__ === Person.prototype; // true
prototype funkcji konstruktora staje się prototypem ([[Prototype]]) tworzonych przez nią instancji.
Do czego używamy prototype?
Najczęstsze zastosowania to:
- dodawanie metod współdzielonych przez wszystkie instancje (np.
sayHello,toString), - realizacja dziedziczenia (łączenie prototypów różnych konstruktorów),
- unikanie duplikowania tych samych metod w każdej instancji.
Przykład dodawania kolejnej metody:
Person.prototype.getInitials = function () {
return this.name
.split(' ')
.map((s) => s + '.')
.join('');
};
Wszystkie istniejące i przyszłe instancje Person natychmiast „widzą” tę metodę dzięki łańcuchowi prototypów.
4. __proto__ – własność obiektów
Czym jest __proto__?
__proto__ to historyczny akcesor (getter/setter) logicznie dostępny na obiektach, który zapewnia dostęp do ich [[Prototype]]. Pozwala sprawdzić, z jakiego prototypu obiekt dziedziczy, a nawet ten prototyp zmienić (co nie jest zalecane w nowym kodzie).
Porównanie dwóch sposobów odczytu prototypu instancji:
const jan = new Person('Jan');
console.log(jan.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(jan) === Person.prototype); // true
Obie linie zwracają true, ale druga używa nowszego, zalecanego sposobu.
Z punktu widzenia specyfikacji każdy obiekt ma wewnętrzne [[Prototype]], a __proto__ (tam, gdzie istnieje) po prostu pozwala się do niego dostać.
Przykład ręcznego ustawienia prototypu (dawny sposób):
const animal = { type: 'animal' };
const dog = { name: 'Reksio' };
dog.__proto__ = animal; // ustawiamy prototyp dog na animal
console.log(dog.type); // "animal" – szukane w prototypie
Nowoczesny odpowiednik:
const animal = { type: 'animal' };
const dog = Object.create(animal); // [[Prototype]] = animal
dog.name = 'Reksio';
console.log(dog.type); // "animal"
5. Najważniejsze różnice: prototype vs __proto__
Poniższa tabela syntetyzuje różnice w praktycznym użyciu:
| Cecha | prototype |
__proto__ |
|---|---|---|
| Należy do | funkcji (konstruktorów) | obiektów (instancji) |
| Rola | definiuje prototyp przyszłych instancji | wskazuje aktualny prototyp danego obiektu |
| Moment użycia | przy projektowaniu „klasy” / konstruktora | przy analizie lub manipulacji konkretnej instancji |
| Typ | zwykły obiekt | akcesor do wewnętrznego [[Prototype]] |
Jak działa z new |
new ustawia [[Prototype]] na fn.prototype |
instancja po new ma obj.__proto__ === fn.prototype |
| Nowoczesna alternatywa | składnia class { ... } |
Object.getPrototypeOf, Object.setPrototypeOf |
| Status | normalna, oficjalna część języka | historyczna (legacy), mniej zalecana w nowym kodzie |
6. __proto__ jest przestarzałe (legacy) – i dlaczego to ważne
__proto__ nie jest gwarantowane na wszystkich obiektach i implementacjach, dlatego jego użycie bywa nieprzewidywalne i niespójne.
Zamiast __proto__ stosuj standardowe metody pracy z prototypem:
- Object.getPrototypeOf(obj) – odczyt wewnętrznego
[[Prototype]]obiektu; - Object.setPrototypeOf(obj, proto) – ustawienie
[[Prototype]]istniejącego obiektu; - Object.create(proto) – tworzenie nowego obiektu z zadanym prototypem.
W nowym kodzie preferuj API Object.*, a __proto__ traktuj głównie jako kontekst do zrozumienia starszych materiałów lub logów narzędzi developerskich.
7. Praktyczne przykłady dla webdevelopera
„Klasyczny” konstruktor + prototype
Załóżmy, że budujesz prostą aplikację i chcesz mieć obiekt Button z metodami współdzielonymi między instancjami:
function Button(label) {
this.label = label;
}
Button.prototype.render = function () {
const btn = document.createElement('button');
btn.textContent = this.label;
return btn;
};
Button.prototype.disable = function (element) {
element.setAttribute('disabled', 'true');
};
Użycie:
const saveBtn = new Button('Zapisz');
const saveElement = saveBtn.render();
document.body.appendChild(saveElement);
// Metoda disable pochodzi z prototypu
saveBtn.disable(saveElement);
Tutaj warto zapamiętać trzy rzeczy:
Button.prototypeprzechowuje metodyrenderidisable,- każda instancja
Button(saveBtn,cancelBtnitd.) dziedziczy je przez swój[[Prototype]], - instancja nie ma właściwości
prototype, ale ma__proto__wskazujące naButton.prototype.
Sprawdzenie prototypu instancji
Poniższe wywołania pokazują różnicę między prototype a __proto__ na instancji:
console.log(saveBtn.prototype); // undefined – instancja nie ma "prototype"
console.log(saveBtn.__proto__); // obiekt prototypu (Button.prototype)
console.log(Object.getPrototypeOf(saveBtn) === Button.prototype); // true
prototype działa na funkcjach/konstruktorach, a nie na instancjach.
Tworzenie obiektu z określonym prototypem – API Object.create
Zamiast bezpośredniego ustawiania __proto__ lepiej skorzystać z fabrycznej funkcji Object.create:
const base = { role: 'admin' };
const user = Object.create(base); // [[Prototype]] = base
user.name = 'Anna';
console.log(user.role); // "admin"
Zyskujesz:
- czytelność,
- zgodność ze standardem,
- przewidywalne zachowanie w różnych środowiskach.
8. Łańcuch prototypów w praktyce
Poniższy przykład pokazuje dziedziczenie między konstruktorami:
function Animal() {}
Animal.prototype.eat = function () {
return 'jem';
};
function Dog(name) {
this.name = name;
}
Dog.prototype = Object.create(Animal.prototype); // dziedziczenie
Dog.prototype.bark = function () {
return 'hau';
};
const reksio = new Dog('Reksio');
Łańcuch prototypów przyjmuje postać:
reksio --> Dog.prototype --> Animal.prototype --> Object.prototype --> null
Wynik wyszukiwania metod wygląda następująco:
reksio.bark(); // znalezione na Dog.prototype
reksio.eat(); // nie ma na reksio ani na Dog.prototype -> szukamy dalej -> Animal.prototype
reksio.toString(); // brak na reksio/Dog/Animal -> Object.prototype
prototype konstruktorów tworzy strukturę klasopodobną, a Object.getPrototypeOf pozwala prześledzić tę strukturę od poziomu instancji.
9. Częste pułapki związane z prototype i __proto__
Nadpisywanie vs modyfikowanie prototype
Różnica między pełnym nadpisaniem obiektu prototype a dopisywaniem do istniejącego obiektu jest istotna:
// Nadpisanie całego obiektu prototype
Dog.prototype = {
bark() {
return 'hau';
}
};
kontra:
// Dodanie metody do istniejącego prototype
Dog.prototype.bark = function () {
return 'hau';
};
W pierwszym przypadku tracisz m.in. domyślne pole constructor wskazujące na Dog, a instancje utworzone przed zmianą zachowają stary prototyp, podczas gdy nowe otrzymają nowy.
Dlatego często stosuje się:
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // ręczne przywrócenie konstruktora
Mylenie prototype i __proto__ na instancjach
Oto częsty błąd i właściwe sprawdzenie:
const dog = new Dog('Reksio');
dog.prototype; // undefined – to NIE jest konstruktor
dog.__proto__; // obiekt będący prototypem, równa się Dog.prototype
Zasada jest prosta: jeśli chcesz zobaczyć, co dziedziczy instancja, użyj Object.getPrototypeOf; jeśli chcesz zdefiniować zachowanie instancji, modyfikuj Constructor.prototype.
Używanie __proto__ w nowym kodzie
Manipulowanie prototypem istniejących obiektów w trakcie działania bywa kosztowne wydajnościowo i nie wszystkie obiekty muszą mieć __proto__.
Zalecenie:
- do odczytu prototypu:
Object.getPrototypeOf(obj), - do ustawiania prototypu nowego obiektu:
Object.create(proto), - zmianę prototypu istniejącego obiektu (
Object.setPrototypeOf) traktować raczej jako operację wyjątkową.
10. prototype i __proto__ a składnia class
Od czasu wprowadzenia składni class możesz definiować metody „klasowe” w czytelny sposób, ale pod spodem nadal działa ten sam mechanizm prototypów.
Przykład klasy:
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
return `Cześć, jestem ${this.name}`;
}
}
Sprawdzenie powiązania instancji z prototypem:
const jan = new Person('Jan');
console.log(Object.getPrototypeOf(jan) === Person.prototype); // true
W praktyce:
- zamiast ręcznie manipulować
prototypei__proto__, - używasz
class,extendsisuper, - ale zrozumienie różnicy między
prototypei__proto__ułatwia debugowanie i analizę starszego lub transpile’owanego kodu.
11. Krótkie „ściągi” w formie mentalnych modeli
prototype (na funkcji): „to prototyp obiektu, który powstanie, gdy ktoś wywoła new TąFunkcją()” – używaj przy projektowaniu typu/„klasy”.
__proto__ (na obiekcie): „z tego prototypu pochodzę – tu szukaj moich brakujących właściwości” – używaj raczej Object.getPrototypeOf do diagnozy instancji.
Z praktycznego punktu widzenia w nowoczesnym kodzie webowym warto pamiętać o następujących priorytetach:
- do implementowania logiki:
classlubprototype, - do introspekcji i tworzenia obiektów:
Object.getPrototypeOfiObject.create, - do
__proto__odnoś się głównie przy czytaniu starszych tutoriali, bibliotek lub logów w konsoli.






