Zrozumienie różnicy między Object.create () a nową SomeFunction ()

391

Niedawno natknąłem się na tę Object.create()metodę w JavaScript i staram się wydedukować, jak różni się ona od tworzenia nowej instancji obiektu new SomeFunction()i kiedy chciałbyś użyć jednej nad drugą.

Rozważ następujący przykład:

var test = {
  val: 1,
  func: function() {
    return this.val;
  }
};
var testA = Object.create(test);

testA.val = 2;
console.log(test.func()); // 1
console.log(testA.func()); // 2

console.log('other test');
var otherTest = function() {
  this.val = 1;
  this.func = function() {
    return this.val;
  };
};

var otherTestA = new otherTest();
var otherTestB = new otherTest();
otherTestB.val = 2;
console.log(otherTestA.val); // 1 
console.log(otherTestB.val); // 2

console.log(otherTestA.func()); // 1
console.log(otherTestB.func()); // 2

Zauważ, że w obu przypadkach obserwuje się to samo zachowanie. Wydaje mi się, że podstawowe różnice między tymi dwoma scenariuszami to:

  • Obiekt użyty w Object.create()rzeczywistości tworzy prototyp nowego obiektu, podczas gdy new Function()z zadeklarowanych właściwości / funkcji nie tworzy prototypu.
  • Nie można tworzyć zamknięć za pomocą Object.create()składni, tak jak w przypadku składni funkcjonalnej. Jest to logiczne, biorąc pod uwagę zakres typu leksykalnego (vs blokowy) JavaScript.

Czy powyższe stwierdzenia są prawidłowe? Czy coś mi brakuje? Kiedy używałbyś jednego nad drugim?

EDYCJA: link do wersji jsfiddle powyższego przykładu kodu: http://jsfiddle.net/rZfYL/

Matt
źródło

Odpowiedzi:

247

Obiekt użyty w Object.create faktycznie tworzy prototyp nowego obiektu, przy czym tak jak w nowej funkcji Function () deklarowane właściwości / funkcje nie tworzą prototypu.

Tak, Object.createbuduje obiekt, który dziedziczy bezpośrednio z tego przekazanego jako pierwszy argument.

W przypadku funkcji konstruktora nowo utworzony obiekt dziedziczy po prototypie konstruktora, np .:

var o = new SomeConstructor();

W powyższym przykładzie odziedziczy bezpośrednio z SomeConstructor.prototype.

Różnica polega na tym, Object.createże możesz stworzyć obiekt, który niczego nie odziedziczy Object.create(null);, z drugiej strony, jeśli ustawisz, SomeConstructor.prototype = null;że nowo utworzony obiekt odziedziczy Object.prototype.

Nie można tworzyć zamknięć za pomocą składni Object.create, tak jak w przypadku składni funkcjonalnej. Jest to logiczne, biorąc pod uwagę zakres typu leksykalnego (vs blokowy) JavaScript.

Możesz tworzyć zamknięcia, np. Używając argumentu deskryptorów właściwości:

var o = Object.create({inherited: 1}, {
  foo: {
    get: (function () { // a closure
      var closured = 'foo';
      return function () {
        return closured+'bar';
      };
    })()
  }
});

o.foo; // "foobar"

Zauważ, że mówię o Object.createmetodzie ECMAScript 5. edycji , a nie podkładce Crockforda.

Metoda zaczyna być natywnie wdrażana w najnowszych przeglądarkach, sprawdź tę tabelę zgodności .

CMS
źródło
2
@CMS 2 pytania. 1) Czy łańcuch zasięgu w Object.create (null) nadal kończy się w zasięgu globalnym (takim jak „okno” w przeglądarce), czy też kończy się sam? 2) Nadal nie jest dla mnie jasne, dlaczego wprowadzono Object.create (np. Jakiej funkcji brakowało, że to rozwiązało?) I dlaczego należy jej używać zamiast nowej Function ();
Matt
9
@Matt, 1) łańcuch zasięgu nie jest tak naprawdę tutaj pojęciem pokrewnym, łańcuch zasięgu jest związany z rozpoznawaniem identyfikatora , np .: jak foo;jest rozwiązywany w obecnym środowisku leksykalnym . 2) Aby zapewnić łatwy sposób implementacji dziedziczenia, jest to naprawdę potężna konstrukcja. IMO Chciałbym go użyć, ponieważ jest naprawdę prosty i lekki, ale w przypadku kodu produkcyjnego nadal musimy poczekać, aż ES5 będzie szeroko obsługiwany. O brakujących cechach brakowało faktu utworzenia „nieskazitelnego” obiektu, Object.create(null);naprawdę przydatne jest zaimplementowanie niezawodnych obiektów podobnych do tablicy haszowej ...
CMS
@CMS Dzięki. Po prostu tworząc obiekt za pomocą „Object.create”, masz możliwość wybrania obiektu, który powinien być jego prototypem.
Anshul
@CMS OK, więc Object.create(null)oznacza, że ​​nie musisz używać hasOwnProperty()bzdur podczas iteracji, ponieważ nie dziedziczy ona żadnych ??? Podoba mi się - dzięki. Oczywiście, wszyscy nadal będą to robić, hasOwnPropertyponieważ nie wszyscy będą z tego korzystać, Object.create(null)więc nie jestem pewien, czy to prawdziwa korzyść ... Do tej pory znalazłem inne „zalety” Object.create()całkowicie nieprzekonujące.
user949300
424

Mówiąc najprościej, new Xpolega na Object.create(X.prototype)dodatkowym uruchomieniu constructorfunkcji. (I daje constructorszansę returnrzeczywistemu obiektowi, który powinien być wynikiem wyrażenia zamiast this.)

Otóż ​​to. :)

Reszta odpowiedzi jest po prostu myląca, ponieważ najwyraźniej nikt inny nie czyta definicji newżadnego z nich. ;)

Evi1M4chine
źródło
23
+1 Prostota i przejrzystość! (Chociaż Object.create (null) wydaje się dobrą opcją - może powinienem o tym wspomnieć).
user949300
postaraj się, żeby to było proste
Bill
To po prostu pozostawia pytanie: „czekaj, więc funkcje mają również prototypy ? Jaki jest związek między nimi a prototypami obiektów ?”
Qwertie
3
@Qwertie: W JS wszystko jest przedmiotem. :) Skopiowali to z Javy, która skopiowała ją z SmallTalk, który poszedł z nią do końca. To dobry przypadek „pojawienia się”, który ogólnie ułatwia życie.
Evi1M4chine
@ Evi1M4chine faktycznie w Javie, funkcje nie są obiektami (i żadne z nich nie są prymitywami) ... a obiekty nie mają prototypów, więc porównanie wydaje się nieodpowiednie. Fakt, że JS działa inaczej niż inne popularne języki OO, jest głównym źródłem nieporozumień (i nie pomaga to, że przeglądarki nie zapewniają łatwego sposobu wizualizacji sieci obiektów, w tym funkcji i prototypów). PS Uważam, że ten link jest pomocny: davidwalsh.name/javascript-objects-deconstruction
Qwertie
204

Oto kroki, które mają miejsce wewnętrznie dla obu połączeń:
(Wskazówka: jedyną różnicą jest krok 3)


new Test():

  1. utwórz new Object()obj
  2. ustawiony obj.__proto__naTest.prototype
  3. return Test.call(obj) || obj; // normally obj is returned but constructors in JS can return a value

Object.create( Test.prototype )

  1. utwórz new Object()obj
  2. ustawiony obj.__proto__naTest.prototype
  3. return obj;

W zasadzie Object.createnie uruchamia konstruktora.

Ray Hulha
źródło
@Ray, więc używając object.create mamy czcionkę, która ma właściwości funkcji wymienione w funkcji konstruktora?
@sortednoun, o ile właściwości są prywatne i nie są określone w prototypie, tak, nie zostaną one odziedziczone i nie będziesz mieć ich w nowym obiekcie (i, dodałbym, możesz oczekiwać, że w końcu otrzymasz prototypowe właściwości od nadrzędnego, tylko gdy nadrzędny konstruktor został wykonany co najmniej raz).
Kamafeather
Podobnie jak w przypadku większości funkcji konstruktora, metody są definiowane w zwracanym obiekcie, w newzasadzie wszystkie funkcje są zduplikowane, a Object.createnie.
SparK
61

Pozwól, że wyjaśnię (więcej na blogu ):

  1. Kiedy piszesz Carkonstruktor var Car = function(){}, dzieje się to wewnętrznie: Schemat łańcuchów prototypowych podczas tworzenia obiektów javascript mamy jeden {prototype}ukryty link, do Function.prototypektórego nie jest dostępny i jeden prototypelink, do Car.prototypektórego jest dostępny i który jest faktycznie constructordostępny Car. Zarówno Function.prototype, jak i Car.prototype mają ukryte linki do Object.prototype.
  2. Kiedy chcemy stworzyć dwa równoważne obiekty za pomocą newoperatora i createmetody, musimy to zrobić w następujący sposób: Honda = new Car();i Maruti = Object.create(Car.prototype). Schemat łańcuchów prototypowych dla różnych metod tworzenia obiektów Co się dzieje?

    Honda = new Car();- Podczas tworzenia takiego obiektu {prototype}wskazywana jest właściwość ukryta Car.prototype. Tak więc tutaj {prototype}obiekt Hondy zawsze będzie Car.prototype- nie mamy żadnej możliwości zmiany {prototype}właściwości obiektu. Co jeśli chcę zmienić prototyp naszego nowo utworzonego obiektu?
    Maruti = Object.create(Car.prototype)- Podczas tworzenia takiego obiektu masz dodatkową opcję wyboru {prototype}właściwości obiektu. Jeśli chcesz Car.prototype jako, a {prototype}następnie przekaż go jako parametr w funkcji. Jeśli nie chcesz, każdy {prototype}dla obiektu potem można przekazać nulltak: Maruti = Object.create(null).

Wniosek - korzystając z metody Object.createmasz swobodę wyboru {prototype}właściwości obiektu . W new Car();, nie ma tej swobody.

Preferowany sposób w OO JavaScript:

Załóżmy, że mamy dwa przedmioty ai b.

var a = new Object();
var b = new Object();

Załóżmy teraz, że ama kilka metod, które brównież chcą uzyskać dostęp. W tym celu wymagamy dziedziczenia obiektów ( apowinien to być prototyp btylko wtedy, gdy chcemy uzyskać dostęp do tych metod). Jeśli sprawdzimy prototypy, aa bnastępnie dowiemy się, że mają one wspólny prototyp Object.prototype.

Object.prototype.isPrototypeOf(b); //true
a.isPrototypeOf(b); //false (the problem comes into the picture here).

Problem - chcemy obiekt ajako prototyp b, ale tutaj stworzyliśmy obiekt bz prototypem Object.prototype. Rozwiązanie - wprowadzono ECMAScript 5 Object.create(), aby łatwo osiągnąć takie dziedzictwo. Jeśli tworzymy btaki obiekt :

var b = Object.create(a);

następnie,

a.isPrototypeOf(b);// true (problem solved, you included object a in the prototype chain of object b.)

Tak więc, jeśli wykonujesz skrypty obiektowe, Object.create()jest to bardzo przydatne do dziedziczenia.

Anszul
źródło
Więc jest to trochę podobne do tworzenia obiektów bez wywoływania konstruktora? Będziemy cieszyć się wszystkimi zaletami klasy. Klasa instanceof obiektu również będzie prawdziwa. Ale nie wywołujemy funkcji klasy przez nowy.
Praveen
@Anshul Powiedziałeś, że a.isPrototypeOf(b);powróci, falseco jest słuszne, ponieważ oba Obiekty są różne i wskazują na inną pamięć. Prawidłowy sposób zrobienia tego z newoperatorem znajduje się tutaj. - jsfiddle.net/167onunp .
Sagar Karira,
Dlaczego zamiast tego nie ustawić właściwości prototypowej b na a, zamiast tego?
Amnestyczny
Podobał mi się również artykuł na blogu. Pomógł mi lepiej zrozumieć tę koncepcję. Dziękuję Ci.
steady_daddy
1
Wniosek mówi wszystko.
kushalvm
44

To:

var foo = new Foo();

i

var foo = Object.create(Foo.prototype);

są dość podobne. Jedną ważną różnicą jest to, że new Foofaktycznie uruchamia kod konstruktora, podczas gdy Object.createnie wykonuje kodu takiego jak

function Foo() {
    alert("This constructor does not run with Object.create");
}

Zauważ, że jeśli używasz dwuparametrowej wersji Object.create(), możesz robić znacznie potężniejsze rzeczy.

Leopd
źródło
1
Świetne wyjaśnienie. Mogę dodać, użycie Object.createw najprostszej formie, takiej jak ta, pozwala pominąć funkcje konstruktora w kodzie, korzystając z dziedziczenia prototypów.
Ricky Boyce,
23

Różnica polega na tak zwanym „dziedziczeniu pseudoklasycznym a prototypowym”. Sugeruje się użycie tylko jednego typu w kodzie, a nie mieszanie dwóch.

W dziedziczeniu pseudoklasycznym (z operatorem „new”) wyobraź sobie, że najpierw definiujesz pseudoklasę, a następnie tworzysz obiekty z tej klasy. Na przykład zdefiniuj pseudoklasę „Osoba”, a następnie utwórz „Alice” i „Bob” z „Osoby”.

W dziedziczeniu prototypowym (przy użyciu Object.create) bezpośrednio tworzysz określoną osobę „Alice”, a następnie tworzysz inną osobę „Bob”, używając „Alice” jako prototypu. Nie ma tu „klasy”; wszystkie są przedmiotami.

Wewnętrznie JavaScript używa „dziedziczenia prototypowego”; „pseudoklasyczny” sposób to tylko trochę cukru.

Zobacz ten link, aby porównać dwa sposoby.

użytkownik1931858
źródło
21
function Test(){
    this.prop1 = 'prop1';
    this.prop2 = 'prop2';
    this.func1 = function(){
        return this.prop1 + this.prop2;
    }
};

Test.prototype.protoProp1 = 'protoProp1';
Test.prototype.protoProp2 = 'protoProp2';
var newKeywordTest = new Test();
var objectCreateTest = Object.create(Test.prototype);

/* Object.create   */
console.log(objectCreateTest.prop1); // undefined
console.log(objectCreateTest.protoProp1); // protoProp1 
console.log(objectCreateTest.__proto__.protoProp1); // protoProp1

/* new    */
console.log(newKeywordTest.prop1); // prop1
console.log(newKeywordTest.__proto__.protoProp1); // protoProp1

Podsumowanie:

1) ze newsłowem kluczowym należy zwrócić uwagę na dwie rzeczy;

a) funkcja jest używana jako konstruktor

b) function.prototypeobiekt jest przekazywany do __proto__właściwości ... lub gdzie __proto__nie jest obsługiwany, jest to drugie miejsce, w którym nowy obiekt szuka właściwości

2) wraz z Object.create(obj.prototype)tobą konstruujesz obiekt ( obj.prototype) i przekazujesz go do zamierzonego obiektu. Z tą różnicą, że teraz nowy obiekt __proto__wskazuje również na obj.prototype (w tym celu proszę odesłać xj9)

użytkownik3124360
źródło
15

Warianty tworzenia obiektów.


Wariant 1 : „ new Object () ” -> Konstruktor obiektów bez argumentów.

var p1 = new Object(); // 'new Object()' create and return empty object -> {}

var p2 = new Object(); // 'new Object()' create and return empty object -> {}

console.log(p1); // empty object -> {}

console.log(p2); // empty object -> {}

// p1 and p2 are pointers to different objects
console.log(p1 === p2); // false

console.log(p1.prototype); // undefined

// empty object which is in fact Object.prototype
console.log(p1.__proto__); // {}

// empty object to which p1.__proto__ points
console.log(Object.prototype); // {}

console.log(p1.__proto__ === Object.prototype); // true

// null, which is in fact Object.prototype.__proto__
console.log(p1.__proto__.__proto__); // null

console.log(Object.prototype.__proto__); // null

wprowadź opis zdjęcia tutaj


Wariant 2 : „ nowy obiekt (osoba) ” -> Konstruktor obiektów z argumentem.

const person = {
    name: 'no name',
    lastName: 'no lastName',
    age: -1
}

// 'new Object(person)' return 'person', which is pointer to the object ->
//  -> { name: 'no name', lastName: 'no lastName', age: -1 }
var p1 = new Object(person);

// 'new Object(person)' return 'person', which is pointer to the object ->
//  -> { name: 'no name', lastName: 'no lastName', age: -1 }
var p2 = new Object(person);

// person, p1 and p2 are pointers to the same object
console.log(p1 === p2); // true
console.log(p1 === person); // true
console.log(p2 === person); // true

p1.name = 'John'; // change 'name' by 'p1'
p2.lastName = 'Doe'; // change 'lastName' by 'p2'
person.age = 25; // change 'age' by 'person'

// when print 'p1', 'p2' and 'person', it's the same result,
// because the object they points is the same
console.log(p1); // { name: 'John', lastName: 'Doe', age: 25 }
console.log(p2); // { name: 'John', lastName: 'Doe', age: 25 }
console.log(person); // { name: 'John', lastName: 'Doe', age: 25 }

wprowadź opis zdjęcia tutaj


Wariant 3.1 : „ Object.create (person) ”. Użyj Object.create z prostym obiektem „person”. „Object.create (osoba)” utworzy (i zwróci) nowy pusty obiekt i doda właściwość „__proto__” do tego samego nowego pustego obiektu. Ta właściwość „__proto__” będzie wskazywać na obiekt „osoba”.

const person = {
        name: 'no name',
        lastName: 'no lastName',
        age: -1,
        getInfo: function getName() {
           return `${this.name} ${this.lastName}, ${this.age}!`;
    }
}

var p1 = Object.create(person);

var p2 = Object.create(person);

// 'p1.__proto__' and 'p2.__proto__' points to
// the same object -> 'person'
// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(p1.__proto__);
console.log(p2.__proto__);
console.log(p1.__proto__ === p2.__proto__); // true

console.log(person.__proto__); // {}(which is the Object.prototype)

// 'person', 'p1' and 'p2' are different
console.log(p1 === person); // false
console.log(p1 === p2); // false
console.log(p2 === person); // false

// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(person);

console.log(p1); // empty object - {}

console.log(p2); // empty object - {}

// add properties to object 'p1'
// (properties with the same names like in object 'person')
p1.name = 'John';
p1.lastName = 'Doe';
p1.age = 25;

// add properties to object 'p2'
// (properties with the same names like in object 'person')
p2.name = 'Tom';
p2.lastName = 'Harrison';
p2.age = 38;

// { name: 'no name', lastName: 'no lastName', age: -1, getInfo: [Function: getName] }
console.log(person);

// { name: 'John', lastName: 'Doe', age: 25 }
console.log(p1);

// { name: 'Tom', lastName: 'Harrison', age: 38 }
console.log(p2);

// use by '__proto__'(link from 'p1' to 'person'),
// person's function 'getInfo'
console.log(p1.getInfo()); // John Doe, 25!

// use by '__proto__'(link from 'p2' to 'person'),
// person's function 'getInfo'
console.log(p2.getInfo()); // Tom Harrison, 38!

wprowadź opis zdjęcia tutaj


Wariant 3.2 : „ Object.create (Object.prototype) ”. Użyj Object.create z wbudowanym obiektem -> 'Object.prototype'. „Object.create (Object.prototype)” utworzy (i zwróci) nowy pusty obiekt i doda właściwość „__proto__” do tego samego nowego pustego obiektu. Ta właściwość „__proto__” będzie wskazywać na obiekt „Object.prototype”.

// 'Object.create(Object.prototype)' :
// 1. create and return empty object -> {}.
// 2. add to 'p1' property '__proto__', which is link to 'Object.prototype'
var p1 = Object.create(Object.prototype);

// 'Object.create(Object.prototype)' :
// 1. create and return empty object -> {}.
// 2. add to 'p2' property '__proto__', which is link to 'Object.prototype'
var p2 = Object.create(Object.prototype);

console.log(p1); // {}

console.log(p2); // {}

console.log(p1 === p2); // false

console.log(p1.prototype); // undefined

console.log(p2.prototype); // undefined

console.log(p1.__proto__ === Object.prototype); // true

console.log(p2.__proto__ === Object.prototype); // true

wprowadź opis zdjęcia tutaj


Wariant 4 : „ new SomeFunction ()

// 'this' in constructor-function 'Person'
// represents a new instace,
// that will be created by 'new Person(...)'
// and returned implicitly
function Person(name, lastName, age) {

    this.name = name;
    this.lastName = lastName;
    this.age = age;

    //-----------------------------------------------------------------
    // !--- only for demonstration ---
    // if add function 'getInfo' into
    // constructor-function 'Person',
    // then all instances will have a copy of the function 'getInfo'!
    //
    // this.getInfo: function getInfo() {
    //  return this.name + " " + this.lastName + ", " + this.age + "!";
    // }
    //-----------------------------------------------------------------
}

// 'Person.prototype' is an empty object
// (before add function 'getInfo')
console.log(Person.prototype); // Person {}

// With 'getInfo' added to 'Person.prototype',
// instances by their properties '__proto__',
// will have access to the function 'getInfo'.
// With this approach, instances not need
// a copy of the function 'getInfo' for every instance.
Person.prototype.getInfo = function getInfo() {
    return this.name + " " + this.lastName + ", " + this.age + "!";
}

// after function 'getInfo' is added to 'Person.prototype'
console.log(Person.prototype); // Person { getInfo: [Function: getInfo] }

// create instance 'p1'
var p1 = new Person('John', 'Doe', 25);

// create instance 'p2'
var p2 = new Person('Tom', 'Harrison', 38);

// Person { name: 'John', lastName: 'Doe', age: 25 }
console.log(p1);

// Person { name: 'Tom', lastName: 'Harrison', age: 38 }
console.log(p2);

// 'p1.__proto__' points to 'Person.prototype'
console.log(p1.__proto__); // Person { getInfo: [Function: getInfo] }

// 'p2.__proto__' points to 'Person.prototype'
console.log(p2.__proto__); // Person { getInfo: [Function: getInfo] }

console.log(p1.__proto__ === p2.__proto__); // true

// 'p1' and 'p2' points to different objects(instaces of 'Person')
console.log(p1 === p2); // false

// 'p1' by its property '__proto__' reaches 'Person.prototype.getInfo' 
// and use 'getInfo' with 'p1'-instance's data
console.log(p1.getInfo()); // John Doe, 25!

// 'p2' by its property '__proto__' reaches 'Person.prototype.getInfo' 
// and use 'getInfo' with 'p2'-instance's data
console.log(p2.getInfo()); // Tom Harrison, 38!

wprowadź opis zdjęcia tutaj

Przetrząsać
źródło
Ładne podsumowanie. Dzięki. To pomogło mi dzisiaj !!
Anandaraja_Srinivasan,
11

Wewnętrznie Object.createrobi to:

Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
};

Składnia po prostu usuwa złudzenie, że JavaScript używa klasycznego dziedziczenia.

xj9
źródło
25
Metoda ECMAScript 5 Object.createrobi o wiele więcej, możesz zdefiniować właściwości za pomocą deskryptorów właściwości i możesz stworzyć obiekt, który niczego nie odziedziczy ( Object.create(null);), tego typu podkładek należy unikać, ponieważ tak naprawdę nie można tego naśladować zachowanie na ES3. Więcej informacji
CMS
Zgadzam się z @CMS, ale ogólnie rzecz biorąc, jest to prosty polifill dla Object.create.
V. Kovpak
10

Zgodnie z tą odpowiedzią i tym new słowem kluczowym wideo wykonuje następujące czynności:

  1. Tworzy nowy obiekt.

  2. Łączy nowy obiekt z funkcją konstruktora ( prototype).

  3. Sprawia, że thiszmienna wskazuje nowy obiekt.

  4. Wykonuje funkcję konstruktora przy użyciu nowego obiektu i niejawnie wykonuje return this;

  5. Przypisuje nazwę funkcji konstruktora do właściwości nowego obiektu constructor.

Object.createwykonuje tylko 1sti 2ndkroki !!!

V. Kovpak
źródło