Mogę się wypowiedzieć nt. JavaScriptu z racji mojej znajomości tego języka.
W JavaScripcie, new F() to coś innego niż F(). Oba są wywołaniami funkcji, ale operator new, oprócz wywoływania funkcji, robi coś ekstra: ustawia kontekst (this) wewnątrz F, pilnuje, by F zwróciło obiekt i umożliwia utworzenie obiektu dziedziczącego po prototypie.
JavaScript to język dynamiczny. Nie da się statycznie wywnioskować, że pisząc F() chcemy wywołać konstruktor -- może po prostu chcemy wywołać funkcję F? Wiele funkcji pisze się tak, że można je wywołać zarówno z new, jak i bez new i efekt będzie ten sam. Od tej reguły są jednak wyjątki. Nawet wśród funkcji wbudowanych. Np. funkcja Date(). Wywołana z new zwraca obiekt. Bez new -- ciąg znaków.
To powiedziawszy, operator new można uznać za jeden z błędów projektowych JavaScriptu. new zostało dodane do języka po to, by upodobnić się do języków z dziedziczeniem klasycznym, w szczególności do Javy. To błąd, bo JavaScript nie posiada dziedziczenia klasycznego. Nie udostępnia programiście takiego pojęcia jak "klasa". Specyfikacja języka definiuje co prawda ukrytą, roboczą własność [[Class]], ale nazywa jej wartość "klasyfikacją obiektu", a nie klasą.
JavaScript, zamiast dziedziczenia klasycznego, ma dziedziczenie prototypowe. Jest ono prostsze i -- można się kłócić -- bardziej eleganckie, a na pewno minimalistyczne. Nie ma tu podziału na "obiekty" (instancje) i jakieś ezoteryczne "klasy". Nie. W JavaScripcie są tylko obiekty. Obiekty dziedziczą po obiektach-prototypach. I tyle.
Gdzie tu miejsce dla new?
No... właśnie specjalnie go nie ma.
Teoretycznie (jak zaleca np. Douglas Crockford), gdy tworzymy obiekt, powinniśmy powiedzieć: daj mi pusty obiekt myObj dziedziczący po obiekcie myProto (jak "prototype"). W ECMAScript 5 wprowadzono funkcję Object.create, która pozwala nam zrobić właśnie to (pomijam w tym poście pozostałe funkcje Object.create):
Kopiuj
var myObj = Object.create(myProto);
I dzięki tej małej funkcji, operator new robi nam się absolutnie niepotrzebny. Wywołanie Object.create możemy ukryć w naszych "konstruktorach", czy też "metodach wytwórczych" (że pozwolę je sobie tak nazwać):
Kopiuj
var programmerPrototype = {
// definicje funkcji i pól składowych wspólnych dla wszystkich programistów
writeCode: function() {
// ...
}
};
function createProgrammer(nameArg, languageArg) {
var that = Object.create(programmerPrototype);
// różnice pomiędzy programistami
that.name = nameArg;
that.language = languageArg;
return that;
}
// tworzenie instancji
var uncleBob = createProgrammer("Robert", "Java");
uncleBob.writeCode();
Powyższy wzorzec jest prosty, ale bardzo klasyczny -- prototyp jest stały i wygląda z grubsza jak definicja klasy. Można go równie dobrze napisać, nie używając Object.create i używając operatora new. Poniżej ekwiwalent przykładu z Object.create:
Kopiuj
Programmer.prototype = {
// definicje funkcji i pól składowych wspólnych dla wszystkich programistów
writeCode: function() {
// ...
}
};
function Programmer(nameArg, languageArg) {
// różnice pomiędzy programistami
this.name = nameArg;
this.language = languageArg;
}
// tworzenie instancji
var uncleBob = new Programmer("Robert", "Java");
uncleBob.writeCode();
Czyli: tu new nie jest takie złe, bo każdy programista ma prototyp, który jest dokładnie tym samym obiektem.
W bardziej skomplikowanym kodzie dość często zdarza mi się jednak stosować dynamicznie tworzone prototypy lub nawet ich łańcuchy. Chcę móc dynamicznie dobierać rodziny obiektów, każda z własnym prototypem.
Tu już notacja z new oraz Konstruktor.prototype jest cholernie niewygodna i pokraczna. Muszę użyć czegoś w rodzaju Object.create. Ponieważ nie wszystkie przeglądarki mają Object.create, muszę sobie taką funkcję napisać...
Kopiuj
function createWithPrototype(proto) {
function Dummy() {
}
Dummy.prototype = proto;
return new Dummy();
}
// wywołanie:
var myObj = createWithPrototype(myProto);
Tutaj widać, że Object.create i new są w JavaScripcie ekwiwalentne (a tak naprawdę Object.create jest potężniejsze, bo jako drugi parametr może przyjąć tzw. deskryptor obiektu -- ale mniejsza o to). Czyli: new jest w nowoczesnym JavaScripcie zbędne. Crockford zdaje się sugerować, by i w starym JavaScripcie użyć new tylko raz: pisząc createWithPrototype() ;).
Normalnie, gdy ktoś napisał funkcję z myślą o tym, by była używana z new, a my o new zapomnimy (silnik JS o tym nie ostrzeże, bo new nie zawsze jest obowiązkowe!), to stanie się tragedia.
Załóżmy, że pominęliśmy new wywołując zdefiniowany wyżej konstruktor Programmer:
Kopiuj
var bob = Programmer("Kmieciu", "Delphi");
Wewnątrz funkcji Programmer doczepiamy jakieś własności do obiektuthis. Jeśli funkcja jest wywołana z new, this wskazuje w niej na nowo utworzony obiekt (czyli programistę). Jeśli new pominiemy, to mamy zwykłe wywołanie funkcji globalnej. I this wskazuje na obiekt globalny, którym w przeglądarce jest window. Więc nasz konstruktor (wywołany jak zwykła funkcja globalna), zamiast pól obiektu bob, tworzy pola obiektu window: window.name i window.language, czyli zmienne globalne name i language. Tragedia.
Gdybyśmy korzystali z Object.create, bylibyśmy bezpieczni.