Co to jest „nowe” słowo kluczowe w JavaScript?

1744

Słowo newkluczowe w JavaScript może być dość mylące, gdy pojawia się po raz pierwszy, ponieważ ludzie myślą, że JavaScript nie jest zorientowanym obiektowo językiem programowania.

  • Co to jest?
  • Jakie problemy rozwiązuje?
  • Kiedy jest to właściwe, a kiedy nie?
Alon Gubkin
źródło
10
Również powiązany wątek - stackoverflow.com/questions/383402/…
Chetan Sastry
przeczytaj te przykłady pierwsze osoby, developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Martian2049,

Odpowiedzi:

2144

Robi 5 rzeczy:

  1. Tworzy nowy obiekt. Typ tego obiektu to po prostu obiekt .
  2. Ustawia wewnętrzną, niedostępną właściwość [[prototyp]] (tj. __Proto__ ) tego nowego obiektu na zewnętrzny, dostępny, obiekt prototypowy funkcji konstruktora (każdy obiekt funkcji ma automatycznie właściwość prototypu ).
  3. Sprawia, że thiszmienna wskazuje na nowo utworzony obiekt.
  4. Wykonuje funkcję konstruktora, używając nowo utworzonego obiektu, ilekroć thisjest wspomniane.
  5. Zwraca nowo utworzony obiekt, chyba że funkcja konstruktora zwróci nullodwołanie niebędące obiektem. W takim przypadku zwracane jest odwołanie do obiektu.

Uwaga: funkcja konstruktora odnosi się do funkcji po newsłowie kluczowym, jak w

new ConstructorFunction(arg1, arg2)

Po wykonaniu tej czynności, jeśli zażądana zostanie niezdefiniowana właściwość nowego obiektu, skrypt sprawdzi obiekt [[prototyp]] obiektu pod kątem właściwości. W ten sposób możesz uzyskać coś podobnego do tradycyjnego dziedziczenia klas w JavaScript.

Najtrudniejszą częścią tego jest punkt 2. Każdy obiekt (łącznie z funkcjami) ma tę wewnętrzną właściwość o nazwie [[prototyp]] . Można go ustawić tylko w czasie tworzenia obiektu, za pomocą nowego , za pomocą Object.create lub w oparciu o literał (funkcje domyślnie to Function.prototype, liczby to Number.prototype itp.). Można go odczytać tylko za pomocą Object.getPrototypeOf (someObject) . Nie ma innego sposobu na ustawienie lub odczytanie tej wartości.

Funkcje, oprócz właściwości hidden [[prototype]] , mają również właściwość o nazwie prototype , i to do niej można uzyskać dostęp i modyfikować, aby zapewnić odziedziczone właściwości i metody dla tworzonych obiektów.


Oto przykład:

ObjMaker = function() {this.a = 'first';};
// ObjMaker is just a function, there's nothing special about it that makes 
// it a constructor.

ObjMaker.prototype.b = 'second';
// like all functions, ObjMaker has an accessible prototype property that 
// we can alter. I just added a property called 'b' to it. Like 
// all objects, ObjMaker also has an inaccessible [[prototype]] property
// that we can't do anything with

obj1 = new ObjMaker();
// 3 things just happened.
// A new, empty object was created called obj1.  At first obj1 was the same
// as {}. The [[prototype]] property of obj1 was then set to the current
// object value of the ObjMaker.prototype (if ObjMaker.prototype is later
// assigned a new object value, obj1's [[prototype]] will not change, but you
// can alter the properties of ObjMaker.prototype to add to both the
// prototype and [[prototype]]). The ObjMaker function was executed, with
// obj1 in place of this... so obj1.a was set to 'first'.

obj1.a;
// returns 'first'
obj1.b;
// obj1 doesn't have a property called 'b', so JavaScript checks 
// its [[prototype]]. Its [[prototype]] is the same as ObjMaker.prototype
// ObjMaker.prototype has a property called 'b' with value 'second'
// returns 'second'

To jest jak dziedziczenie klas, ponieważ teraz new ObjMaker()wydaje się, że wszelkie obiekty, które tworzysz, odziedziczyły właściwość „b”.

Jeśli chcesz coś w rodzaju podklasy, wykonaj następujące czynności:

SubObjMaker = function () {};
SubObjMaker.prototype = new ObjMaker(); // note: this pattern is deprecated!
// Because we used 'new', the [[prototype]] property of SubObjMaker.prototype
// is now set to the object value of ObjMaker.prototype.
// The modern way to do this is with Object.create(), which was added in ECMAScript 5:
// SubObjMaker.prototype = Object.create(ObjMaker.prototype);

SubObjMaker.prototype.c = 'third';  
obj2 = new SubObjMaker();
// [[prototype]] property of obj2 is now set to SubObjMaker.prototype
// Remember that the [[prototype]] property of SubObjMaker.prototype
// is ObjMaker.prototype. So now obj2 has a prototype chain!
// obj2 ---> SubObjMaker.prototype ---> ObjMaker.prototype

obj2.c;
// returns 'third', from SubObjMaker.prototype

obj2.b;
// returns 'second', from ObjMaker.prototype

obj2.a;
// returns 'first', from SubObjMaker.prototype, because SubObjMaker.prototype 
// was created with the ObjMaker function, which assigned a for us

Przeczytałem mnóstwo śmieci na ten temat, zanim w końcu znalazłem tę stronę , na której wyjaśniono to bardzo dobrze za pomocą ładnych schematów.

Daniel Howard
źródło
47
Chciałem tylko dodać: Istnieje sposób na uzyskanie dostępu do wewnętrznego [[prototypu]] przez __proto__. Jest to jednak niestandardowe i obsługiwane tylko przez stosunkowo nowe przeglądarki (i nie wszystkie). Nadchodzi znormalizowany sposób, mianowicie Object.getPrototypeOf (obj), ale jest to Ecmascript3.1 i sam jest obsługiwany tylko na nowych przeglądarkach - znowu. Zasadniczo zaleca się jednak, aby nie korzystać z tej właściwości, rzeczy szybko się komplikują.
Blub
9
Pytanie: co dzieje się inaczej, jeśli ObjMakerjest zdefiniowane jako funkcja zwracająca wartość?
Jim Blackler
13
@LonelyPixel newistnieje, więc nie musisz pisać metod fabrycznych do konstruowania / kopiowania funkcji / obiektów. Oznacza to: „Skopiuj to, czyniąc to tak, jak jego nadrzędna„ klasa ”; rób to skutecznie i poprawnie; i przechowuj informacje o spadku, które są dostępne tylko dla mnie, JS, wewnętrznie”. W tym celu modyfikuje niedostępne w inny sposób wnętrze prototypenowego obiektu, aby nieprzezroczysto hermetyzować odziedziczone elementy, naśladując klasyczne łańcuchy dziedziczenia OO (których nie można modyfikować w czasie wykonywania). Możesz to zasymulować bez new, ale dziedziczenie będzie modyfikowalne w czasie wykonywania. Dobrze? Zły? Zależy od Ciebie.
Inżynier
11
mały punkt do dodania: wywołanie do konstruktora, poprzedzone nowym słowem kluczowym, automatycznie zwraca utworzony obiekt; nie ma potrzeby jawnego zwracania go z poziomu konstruktora.
charlie roberts
7
Jest taka notatka Notice that this pattern is deprecated!. Jaki jest prawidłowy aktualny wzorzec, aby ustawić prototyp klasy?
Tom Pažourek
400

Załóżmy, że masz tę funkcję:

var Foo = function(){
  this.A = 1;
  this.B = 2;
};

Jeśli wywołasz to jako samodzielną funkcję, taką jak:

Foo();

Wykonanie tej funkcji doda dwie właściwości do windowobiektu ( Ai B). Dodaje to do, windowponieważ windowjest to obiekt, który wywołał funkcję podczas jej wykonywania, aw thisfunkcji jest obiekt, który wywołał funkcję. Przynajmniej w JavaScript.

Teraz nazwij to tak new:

var bar = new Foo();

Po dodaniu newdo wywołania funkcji dzieje się, że nowy obiekt jest tworzony (tylko var bar = new Object()) i że thiswewnątrz funkcji wskazuje na nowo Objectutworzony właśnie obiekt, a nie na obiekt, który wywołał funkcję. Tak więc barjest teraz obiektem o właściwościach Ai B. Każda funkcja może być konstruktorem, to po prostu nie zawsze ma sens.

JulianR
źródło
7
Zależy od kontekstu wykonania. W moim przypadku (skrypty Qt) jest to po prostu obiekt globalny.
Maxym
2
czy spowoduje to większe zużycie pamięci?
Jürgen Paul
2
ponieważ okno jest obiektem, który wywołał funkcję - musi być: ponieważ okno jest obiektem zawierającym funkcję.
Dávid Horváth,
1
@Taurus W przeglądarce internetowej funkcja nie-metodyczna będzie metodą windowniejawną. Nawet w zamknięciu, nawet jeśli anonimowy. Jednak w tym przykładzie jest to proste wywołanie metody w oknie: Foo();=> [default context].Foo();=> window.Foo();. W tym wyrażeniu windowjest kontekst (nie tylko dzwoniący , co nie ma znaczenia).
Dávid Horváth,
1
@Taurus Zasadniczo tak. Jednak w ECMA 6 i 7 rzeczy są bardziej złożone (patrz lambdy, klasy itp.).
Dávid Horváth,
164

Oprócz odpowiedzi Daniela Howarda, oto, co newrobi (a przynajmniej wydaje się, że robi):

function New(func) {
    var res = {};
    if (func.prototype !== null) {
        res.__proto__ = func.prototype;
    }
    var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
    if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
        return ret;
    }
    return res;
}

Podczas

var obj = New(A, 1, 2);

jest równa

var obj = new A(1, 2);
bazylia
źródło
73
Odkryłem, że javascript jest łatwiejszy do zrozumienia niż angielski: v
damphat
Doskonała odpowiedź. Mam jedno maleńkie pytanie: jak to może być możliwe, func.prototypeaby być null? Czy mógłbyś rozwinąć tę kwestię?
Tom Pažourek
6
@tomp możesz zastąpić właściwość prototypu, po prostu pisząc A.prototype = null;W takim przypadku new A()powstanie obiekt, to znaczy wewnętrzny prototyp wskazuje na Objectobiekt: jsfiddle.net/Mk42Z
basilikum
2
Sprawdzanie typu może być niepoprawne, ponieważ obiekt hosta może wytwarzać coś innego niż „obiekt” lub „funkcja”. Wolę, aby sprawdzić, czy coś jest przedmiotem Object(ret) === ret.
Oriol,
2
@Oriol dziękuję za komentarz. To prawda, co mówisz, a każdy faktyczny test powinien być wykonany w bardziej niezawodny sposób. Myślę jednak o tej pojęciowej odpowiedzi, typeoftest po prostu ułatwia zrozumienie, co dzieje się za kulisami.
basilikum
109

Dla początkujących, aby lepiej to zrozumieć

wypróbuj następujący kod w konsoli przeglądarki.

function Foo() { 
    return this; 
}

var a = Foo();       //returns window object
var b = new Foo();   //returns empty object of foo

a instanceof Window;  // true
a instanceof Foo;     // false

b instanceof Window;  // false
b instanceof Foo;     // true

Teraz możesz przeczytać odpowiedź wiki społeczności :)

Anulal S
źródło
4
Dobra odpowiedź. Również - pominięcie return this;daje taką samą wydajność.
Nelu
37

więc prawdopodobnie nie jest to tworzenie instancji obiektu

Służy do tego właśnie. Konstruktor funkcji definiuje się w następujący sposób:

function Person(name) {
    this.name = name;
}

var john = new Person('John');

Jednak dodatkową zaletą ECMAScript jest to, że możesz rozszerzyć tę .prototypewłaściwość, abyśmy mogli zrobić coś takiego ...

Person.prototype.getName = function() { return this.name; }

Wszystkie obiekty utworzone z tego konstruktora będą teraz miały getNamełańcuch prototypowy, do którego mają dostęp.

meder omuraliev
źródło
6
konstruktory funkcji są używane jak klasy, nie ma classsłowa kluczowego, ale można zrobić prawie to samo.
meder omuraliev
Istnieje słowo kluczowe klasy - klasa jest zarezerwowana do użytku w przyszłości
Greg,
11
Nawiasem mówiąc, dlatego używasz .className, a nie .class, aby ustawić klasę CSS
Greg,
23
Powinien być wielkimi literami Osoba zgodnie z konwencją.
eomeroff
27

JavaScript jest zorientowanym obiektowo językiem programowania i jest używany dokładnie do tworzenia instancji. Jest oparty na prototypach, a nie klasach, ale to nie znaczy, że nie jest zorientowany obiektowo.

Michał
źródło
6
Lubię powiedzieć, że JavaScript wydaje się być bardziej obiektowy niż wszystkie języki oparte na klasach. W JavaScript wszystko, co piszesz, natychmiast staje się obiektem, ale w językach opartych na klasach najpierw piszesz deklaracje, a dopiero później tworzysz określone instancje (obiekty) klas. Prototyp JavaScript wydaje się niejasno przypominać wszystkie te rzeczy VTABLE dla języków opartych na klasach.
JustAMartin
14

JavaScript jest dynamicznym językiem programowania, który obsługuje paradygmat programowania obiektowego i służy do tworzenia nowych instancji obiektów.

Klasy nie są konieczne dla obiektów - JavaScript jest językiem opartym na prototypach .

Greg
źródło
12

Istnieją już bardzo świetne odpowiedzi, ale zamieszczam nową, aby podkreślić moją obserwację w przypadku III poniżej, co dzieje się, gdy masz wyraźną instrukcję return w funkcji, którą newwymyślasz. Spójrz na poniższe przypadki:

Przypadek I :

var Foo = function(){
  this.A = 1; 
  this.B = 2;
};
console.log(Foo()); //prints undefined
console.log(window.A); //prints 1

Powyżej jest zwykły przypadek wywołania wskazanej funkcji anonimowej Foo. Gdy wywołujesz tę funkcję, ona zwraca undefined. Ponieważ nie ma wyraźnej instrukcji return, interpreter JavaScript na siłę wstawia return undefined;instrukcję na końcu funkcji. Tutaj okno jest obiektem wywołania (kontekstowym this), który otrzymuje nowe Ai Bwłaściwości.

Przypadek II :

var Foo = function(){
  this.A = 1;
  this.B = 2;
};
var bar = new Foo();
console.log(bar()); //illegal isn't pointing to a function but an object
console.log(bar.A); //prints 1

Tutaj interpreter JavaScript, widząc newsłowo kluczowe, tworzy nowy obiekt, który działa jako obiekt wywołania (kontekstowy this) wskazywanej przez anonimową funkcję Foo. W takim przypadku Ai Bstają się właściwościami nowo utworzonego obiektu (zamiast obiektu okna). Ponieważ nie masz żadnej wyraźnej instrukcji return, interpreter JavaScript zdecydowanie wstawia instrukcję return, aby zwrócić nowy obiekt utworzony z powodu użycia newsłowa kluczowego.

Przypadek III :

var Foo = function(){
  this.A = 1;
  this.B = 2;
  return {C:20,D:30}; 
};
var bar = new Foo();
console.log(bar.C);//prints 20
console.log(bar.A); //prints undefined. bar is not pointing to the object which got created due to new keyword.

Tutaj znowu interpreter JavaScript, widząc newsłowo kluczowe, tworzy nowy obiekt, który działa jak obiekt wywołania (kontekstowy this) wskazanej przez anonimową funkcję Foo. Ponownie Ai Bstań ​​się właściwościami nowo utworzonego obiektu. Ale tym razem masz wyraźną instrukcję return, więc interpreter JavaScript nie zrobi nic własnego.

W przypadku III należy zauważyć, że obiekt tworzony z powodu newsłowa kluczowego został utracony z radaru. barwskazuje na zupełnie inny obiekt, który nie jest tym, który interpreter JavaScript utworzył z powodu newsłowa kluczowego.

Cytując Davida Flanagana z JavaScripit: The Definitive Guide (wydanie 6), rozdz. 4, strona # 62:

Kiedy ocenia się wyrażenie tworzenia obiektu, JavaScript najpierw tworzy nowy pusty obiekt, tak jak ten utworzony przez inicjator obiektu {}. Następnie wywołuje określoną funkcję z określonymi argumentami, przekazując nowy obiekt jako wartość tego słowa kluczowego. Funkcja może następnie użyć tego do zainicjowania właściwości nowo utworzonego obiektu. Funkcje napisane do użycia jako konstruktory nie zwracają wartości, a wartością wyrażenia tworzenia obiektu jest nowo utworzony i zainicjowany obiekt. Jeśli konstruktor zwróci wartość obiektu, wartość ta staje się wartością wyrażenia tworzenia obiektu, a nowo utworzony obiekt jest odrzucany.

--- Informacje dodatkowe ---

Funkcje używane we fragmencie kodu powyższych przypadków mają specjalne nazwy w świecie JS, jak poniżej:

Przypadek I i II - funkcja konstruktora

Przypadek III - funkcja fabryczna. Funkcje fabryczne nie powinny być używane ze newsłowem kluczowym, które zrobiłem, aby wyjaśnić tę koncepcję w bieżącym wątku.

O różnicy między nimi możesz przeczytać w tym wątku.

RBT
źródło
twój przypadek 3 jest obserwacją gr8
appu
5

czasami kod jest łatwiejszy niż słowa:

var func1 = function (x) { this.x = x; }                    // used with 'new' only
var func2 = function (x) { var z={}; z.x = x; return z; }   // used both ways
func1.prototype.y = 11;
func2.prototype.y = 12;

A1 = new func1(1);      // has A1.x  AND  A1.y
A2 =     func1(1);      // undefined ('this' refers to 'window')
B1 = new func2(2);      // has B1.x  ONLY
B2 =     func2(2);      // has B2.x  ONLY

dla mnie, dopóki nie prototypuję, używam stylu func2, ponieważ daje mi to nieco większą elastyczność wewnątrz i na zewnątrz funkcji.

rsbkk
źródło
3
B1 = new func2(2); <- Dlaczego tego nie będzie B1.y ?
sunny_dev
@sunny_dev Nie jestem ekspertem od JS, ale prawdopodobnie dlatego, że func2 zwraca bezpośrednio wartość (obiekt z), zamiast pracować / zwracać wartości wewnętrzne (this)
Eagle
5

Słowo newkluczowe zmienia kontekst, w którym funkcja jest uruchamiana, i zwraca wskaźnik do tego kontekstu.

Kiedy nie używasz newsłowa kluczowego, kontekstem, w którym Vehicle()działa funkcja, jest ten sam kontekst, z którego wywołujesz Vehiclefunkcję. Słowo thiskluczowe będzie odnosić się do tego samego kontekstu. Gdy używasz new Vehicle(), tworzony jest nowy kontekst, więc słowo kluczowe thiswewnątrz funkcji odnosi się do nowego kontekstu. W zamian dostajesz nowo utworzony kontekst.

Juzer Ali
źródło
To bardzo wnikliwa odpowiedź pod względem zakresu. Dodatek Gr8 do odpowiedzi.
appu
3

Słowo newkluczowe służy do tworzenia nowych instancji obiektów. I tak, javascript to dynamiczny język programowania, który obsługuje paradygmat programowania obiektowego. Konwencja dotycząca nazewnictwa obiektów polega na tym, aby zawsze używać wielkiej litery w przypadku obiektów, które powinny być tworzone przez nowe słowo kluczowe.

obj = new Element();
erenon
źródło
2

Podsumowanie:

Słowo newkluczowe jest używane w javascript do utworzenia obiektu z funkcji konstruktora. Słowo newkluczowe musi zostać umieszczone przed wywołaniem funkcji konstruktora i będzie wykonywać następujące czynności:

  1. Tworzy nowy obiekt
  2. Ustawia prototyp tego obiektu na właściwość prototypową funkcji konstruktora
  3. Wiąże thissłowo kluczowe z nowo utworzonym obiektem i wykonuje funkcję konstruktora
  4. Zwraca nowo utworzony obiekt

Przykład:

function Dog (age) {
  this.age = age;
}

const doggie = new Dog(12);

console.log(doggie);
console.log(doggie.__proto__ === Dog.prototype) // true

Co dokładnie się dzieje:

  1. const doggie mówi: Potrzebujemy pamięci do deklarowania zmiennej.
  2. Operator przypisania =mówi: Zainicjujemy tę zmienną wyrażeniem po=
  3. Wyrażenie to new Dog(12). Silnik JS widzi nowe słowo kluczowe, tworzy nowy obiekt i ustawia prototyp na Dog.prototype
  4. Funkcja konstruktora jest wykonywana z thiswartością ustawioną na nowy obiekt. Na tym etapie jest przypisywany wiek do nowo utworzonego obiektu pieska.
  5. Nowo utworzony obiekt jest zwracany i przypisywany do zmiennej pieska.
Willem van der Veen
źródło
1

Słowo newkluczowe tworzy instancje obiektów za pomocą funkcji jako konstruktora. Na przykład:

var Foo = function() {};
Foo.prototype.bar = 'bar';

var foo = new Foo();
foo instanceof Foo; // true

Instancje dziedziczą prototypepo funkcji konstruktora. Biorąc pod uwagę powyższy przykład ...

foo.bar; // 'bar'
brak powiek
źródło
2
Nowe słowo kluczowe zasadniczo kojarzy już funkcję jako konstruktor; nic nie musisz zwracać. Możesz po prostu zrobić: function foo (x) {this.bar = x; } var obj = new foo (10); alert (obj.bar);
reko_t
Nie musisz zwracać obiektów z funkcji konstruktora, chyba że specjalnie tego chcesz. Na przykład, jeśli musisz zwracać określoną instancję obiektu zamiast tworzyć nowy obiekt za każdym razem (z dowolnego powodu). W twoim przykładzie jest to jednak całkowicie niepotrzebne.
Chetan Sastry
To był przykład. Państwo może zwrócić obiekt. W tym scenariuszu jest wiele wzorców, podałem jeden jako „na przykład”, stąd moje słowa „na przykład”.
powiek
1

Cóż, JavaScript na si może znacznie różnić się w zależności od platformy, ponieważ jest to zawsze implementacja oryginalnej specyfikacji EcmaScript.

W każdym razie, niezależnie od implementacji, wszystkie implementacje JavaScript zgodne z prawą specyfikacji EcmaScript dadzą ci język zorientowany obiektowo. Zgodnie ze standardem ES:

ECMAScript to zorientowany obiektowo język programowania do wykonywania obliczeń i manipulowania obiektami obliczeniowymi w środowisku hosta.

Teraz, kiedy ustaliliśmy, że JavaScript jest implementacją EcmaScript, a zatem jest językiem obiektowym. Definicja newoperacji w dowolnym języku obiektowym mówi, że takie słowo kluczowe służy do tworzenia instancji obiektu z klasy określonego typu (w tym typów anonimowych, w przypadkach takich jak C #).

W EcmaScript nie używamy klas, jak można przeczytać w specyfikacji:

ECMAScript nie korzysta z klas takich jak C ++, Smalltalk lub Java. Zamiast tego obiekty mogą być tworzone na różne sposoby, w tym za pomocą notacji literalnej lub konstruktorów, które tworzą obiekty, a następnie wykonują kod, który inicjuje całość lub część z nich, przypisując wartości początkowe do ich właściwości. Każdy konstruktor jest funkcją, która ma właściwość o nazwie - prototyp ‖, która służy do implementacji dziedziczenia opartego na prototypach i wspólnych właściwości. Obiekty są tworzone przy
użyciu konstruktorów w nowych wyrażeniach; na przykład nowa data (2009,11) tworzy nowy obiekt daty. Wywołanie konstruktora bez użycia nowego ma konsekwencje zależne od konstruktora. Na przykład Date () tworzy ciąg znaków reprezentujący bieżącą datę i godzinę zamiast obiektu.

João Pinho
źródło