Memahami perbedaan antara Object.create () dan SomeFunction baru ()

392

Saya baru-baru ini menemukan Object.create()metode dalam JavaScript, dan saya mencoba untuk menyimpulkan bagaimana hal itu berbeda dari membuat contoh objek baru new SomeFunction(), dan ketika Anda ingin menggunakan satu di atas yang lain.

Perhatikan contoh berikut:

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

Perhatikan bahwa perilaku yang sama diamati dalam kedua kasus. Bagi saya, perbedaan utama antara kedua skenario ini adalah:

  • Objek yang digunakan Object.create()sebenarnya membentuk prototipe dari objek baru, sedangkan dalam new Function()dari properti / fungsi yang dinyatakan tidak membentuk prototipe.
  • Anda tidak dapat membuat penutupan dengan Object.create()sintaks seperti yang Anda lakukan dengan sintaks fungsional. Ini logis mengingat lingkup tipe leksikal (vs blok) dari JavaScript.

Apakah pernyataan di atas benar? Dan apakah saya kehilangan sesuatu? Kapan Anda akan menggunakan satu di atas yang lain?

EDIT: tautan ke versi jsfiddle dari contoh kode di atas: http://jsfiddle.net/rZfYL/

Mat
sumber

Jawaban:

246

Objek yang digunakan dalam Object.create sebenarnya membentuk prototipe dari objek baru, di mana seperti dalam Function () form yang baru menyatakan properti / fungsi tidak membentuk prototipe.

Ya, Object.createmembangun objek yang mewarisi langsung dari yang diteruskan sebagai argumen pertama.

Dengan fungsi konstruktor, objek yang baru dibuat mewarisi dari prototipe konstruktor, misalnya:

var o = new SomeConstructor();

Dalam contoh di atas, omewarisi langsung dari SomeConstructor.prototype.

Ada perbedaan di sini, dengan Object.createAnda dapat membuat objek yang tidak mewarisi dari apa pun Object.create(null);,, di sisi lain, jika Anda mengatur objek SomeConstructor.prototype = null;yang baru dibuat akan diwarisi dari Object.prototype.

Anda tidak dapat membuat penutupan dengan sintaks Object.create seperti yang Anda lakukan dengan sintaks fungsional. Ini logis mengingat lingkup tipe leksikal (vs blok) dari JavaScript.

Nah, Anda bisa membuat penutupan, misalnya menggunakan argumen deskriptor properti:

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

o.foo; // "foobar"

Perhatikan bahwa saya sedang berbicara tentang metode ECMAScript Edisi 5 Object.create, bukan shim Crockford.

Metode ini mulai diterapkan secara native di browser terbaru, periksa tabel kompatibilitas ini .

CMS
sumber
2
@CMS 2 pertanyaan. 1) Apakah rantai cakupan pada Object.create (null) masih berakhir di lingkup global (seperti 'jendela' di browser), atau apakah itu berakhir pada dirinya sendiri? 2) Masih belum jelas bagi saya mengapa Object.create diperkenalkan (mis., Fitur apa yang hilang yang ditujukan?) Dan mengapa orang akan menggunakannya alih-alih Function baru ();
Matt
9
@ Mat, 1) rantai lingkup tidak benar-benar konsep terkait di sini, rantai lingkup terkait dengan resolusi pengidentifikasi , misalnya: bagaimana foo;diselesaikan dalam lingkungan leksikal saat ini . 2) Untuk memberikan cara yang mudah untuk mengimplementasikan warisan, ini adalah konstruksi yang sangat kuat. IMO Saya akan menggunakannya karena sangat sederhana dan ringan, tetapi untuk kode produksi, kita masih perlu menunggu beberapa saat sampai ES5 didukung secara luas. Tentang fitur yang hilang, fakta membuat objek "asli", Object.create(null);hilang, sangat berguna untuk mengimplementasikan objek seperti tabel hash yang dapat diandalkan ...
CMS
@ CMS Terima kasih. Jadi sederhananya ketika Anda membuat objek dengan menggunakan 'Object.create', Anda mendapatkan kemampuan untuk memilih objek yang harus menjadi prototipe-nya.
Anshul
@ CMS OK, jadi Object.create(null)berarti Anda tidak perlu menggunakan hasOwnProperty()omong kosong saat iterasi karena tidak mewarisi ??? Saya suka itu - terima kasih. Tentu saja, semua orang masih akan melakukannya hasOwnPropertykarena tidak semua orang akan menggunakan Object.create(null)jadi saya tidak yakin itu manfaat nyata ... Sejauh ini saya telah menemukan "manfaat" lain yang Object.create()sama sekali tidak meyakinkan.
user949300
425

Sangat sederhana dikatakan, new Xadalah Object.create(X.prototype)dengan tambahan menjalankan constructorfungsi. (Dan memberi constructorkesempatan returnpada objek aktual yang seharusnya menjadi hasil dari ekspresi alih-alih this.)

Itu dia. :)

Jawaban sisanya hanya membingungkan, karena ternyata tidak ada orang lain yang membaca definisi newkeduanya. ;)

Evi1M4chine
sumber
23
+1 Kesederhanaan dan kejelasan! (Meskipun Object.create (null) tampaknya pilihan yang bagus - mungkin harus menyebutkan itu).
user949300
tetap sederhana itulah cara untuk pergi
Bill
Itu hanya meninggalkan pertanyaan "tunggu, jadi fungsi memiliki prototipe juga ? Apa hubungan antara itu dan prototipe objek ?"
Qwertie
3
@ Qwertie: Di JS, semuanya adalah objek. :) Mereka menyalinnya dari Jawa, yang menyalinnya dari SmallTalk, yang pergi sampai akhir dengan itu. Ini adalah kasus “kemunculan” yang menyenangkan, membuat hidup lebih mudah secara umum.
Evi1M4chine
@ Evi1M4chine sebenarnya di Jawa, fungsi bukan objek (dan juga tidak primitif, dalam hal ini) ... dan objek tidak memiliki prototipe, jadi perbandingannya tampaknya tidak pas. Fakta bahwa JS bekerja secara berbeda dari bahasa OO populer lainnya adalah sumber utama kebingungan (dan itu tidak membantu browser tidak menyediakan cara mudah untuk memvisualisasikan jaringan objek termasuk fungsi dan prototipe). PS Saya menemukan tautan ini bermanfaat: davidwalsh.name/javascript-objects-deconstruction
Qwertie
204

Berikut adalah langkah-langkah yang terjadi secara internal untuk kedua panggilan:
(Petunjuk: satu-satunya perbedaan dalam langkah 3)


new Test():

  1. buat new Object()obj
  2. diatur obj.__proto__keTest.prototype
  3. return Test.call(obj) || obj; // normally obj is returned but constructors in JS can return a value

Object.create( Test.prototype )

  1. buat new Object()obj
  2. diatur obj.__proto__keTest.prototype
  3. return obj;

Jadi pada dasarnya Object.createtidak menjalankan konstruktor.

Ray Hulha
sumber
@Ray jadi menggunakan object.create font kita memiliki sifat fungsi yang disebutkan dalam fungsi konstruktor?
@snounnoun selama properti bersifat pribadi dan tidak ditentukan pada prototipe, ya, mereka tidak akan diwarisi dan Anda tidak akan memilikinya di objek baru (dan, saya akan menambahkan, Anda dapat mengharapkan untuk mendapatkan properti prototipe akhirnya dari induk, tepat ketika konstruktor induk telah dieksekusi setidaknya sekali).
Kamafeather
Seperti kebanyakan fungsi konstruktor metode didefinisikan dalam objek yang dikembalikan, newpada dasarnya semua fungsi digandakan, sementara Object.createtidak.
SparK
61

Biarkan saya mencoba menjelaskan (lebih banyak di Blog ):

  1. Ketika Anda menulis Carkonstruktor var Car = function(){}, ini adalah bagaimana hal-hal yang internal: Diagram rantai prototipal saat membuat objek javascript Kami memiliki satu {prototype}link yang tersembunyi untuk Function.prototypeyang tidak dapat diakses dan satu prototypelink Car.prototypeyang dapat diakses dan memiliki aktual constructordari Car. Function.prototype dan Car.prototype memiliki tautan tersembunyi Object.prototype.
  2. Ketika kita ingin membuat dua objek yang setara dengan menggunakan newoperator dan createmetode maka kita harus melakukannya seperti ini: Honda = new Car();dan Maruti = Object.create(Car.prototype). Diagram rantai prototipe untuk metode pembuatan objek yang berbeda Apa yang terjadi?

    Honda = new Car();- Ketika Anda membuat objek seperti ini, maka {prototype}properti tersembunyi diarahkan ke Car.prototype. Jadi di sini, {prototype}objek Honda akan selalu Car.prototype- kita tidak punya pilihan untuk mengubah {prototype}properti objek. Bagaimana jika saya ingin mengubah prototipe dari objek kami yang baru dibuat?
    Maruti = Object.create(Car.prototype)- Ketika Anda membuat objek seperti ini, Anda memiliki opsi tambahan untuk memilih {prototype}properti objek Anda . Jika Anda ingin Car.prototype sebagai yang {prototype}lalu kirimkan sebagai parameter dalam fungsi. Jika Anda tidak ingin ada {prototype}untuk objek Anda maka Anda dapat melewati nullseperti ini: Maruti = Object.create(null).

Kesimpulan - Dengan menggunakan metode ini Object.createAnda memiliki kebebasan untuk memilih {prototype}properti objek Anda . Di new Car();, Anda tidak memiliki kebebasan itu.

Cara yang disukai dalam OO JavaScript:

Misalkan kita memiliki dua objek adan b.

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

Sekarang, misalkan amemiliki beberapa metode yang bjuga ingin diakses. Untuk itu, kita memerlukan pewarisan objek ( aharus menjadi prototipe bhanya jika kita ingin akses ke metode tersebut). Jika kami memeriksa prototipe adan bkemudian kami akan menemukan bahwa mereka berbagi prototipe Object.prototype.

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

Masalah - kami ingin objek asebagai prototipe b, tetapi di sini kami membuat objek bdengan prototipe Object.prototype. Solusi - ECMAScript 5 diperkenalkan Object.create(), untuk mencapai warisan dengan mudah. Jika kita membuat objek bseperti ini:

var b = Object.create(a);

kemudian,

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

Jadi, jika Anda melakukan scripting berorientasi objek maka Object.create()sangat berguna untuk warisan.

Anshul
sumber
Jadi, ini agak mirip dengan pembuatan objek tanpa permohonan konstruktor? Kami akan menikmati semua manfaat dari kelas ini. Instance dari Kelas juga akan benar. Tetapi kami tidak menjalankan fungsi Kelas melalui yang baru.
Praveen
@Anshul Anda mengatakan a.isPrototypeOf(b);akan mengembalikan falseyang benar, karena kedua Objek berbeda dan menunjuk ke memori yang berbeda. Cara yang benar untuk melakukan ini dengan newoperator ada di sini. - jsfiddle.net/167ununp .
Sagar Karira
Mengapa Anda tidak mengatur properti prototipe b ke a, alih-alih melakukan ini?
Amnestic
Menyukai artikel di blog Anda juga. Membantu saya memahami konsep dengan lebih baik. Terima kasih.
stable_daddy
1
Kesimpulannya mengatakan itu semua.
kushalvm
44

Ini:

var foo = new Foo();

dan

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

sangat mirip. Satu perbedaan penting adalah bahwa new Foosebenarnya menjalankan kode konstruktor, sedangkan Object.createtidak akan menjalankan kode seperti

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

Perhatikan bahwa jika Anda menggunakan versi dua-parameter Object.create()maka Anda dapat melakukan banyak hal yang lebih kuat.

Leopd
sumber
1
Penjelasan yang bagus. Mungkin saya tambahkan, menggunakan Object.createdalam bentuk yang paling sederhana seperti ini memungkinkan Anda untuk menghilangkan fungsi konstruktor dari kode Anda sambil mengambil keuntungan dari warisan prototipe.
Ricky Boyce
23

Perbedaannya adalah apa yang disebut "warisan pseudoklasik vs purwarupa". Sarannya adalah untuk menggunakan hanya satu jenis dalam kode Anda, bukan mencampur keduanya.

Dalam warisan pseudoklasik (dengan operator "baru"), bayangkan bahwa Anda pertama-tama mendefinisikan kelas pseudo, dan kemudian membuat objek dari kelas itu. Misalnya, tentukan "Orang" kelas pseudo, lalu buat "Alice" dan "Bob" dari "Orang".

Dalam warisan prototypal (menggunakan Object.create), Anda secara langsung membuat orang tertentu "Alice", dan kemudian membuat orang lain "Bob" menggunakan "Alice" sebagai prototipe. Tidak ada "kelas" di sini; semua benda.

Secara internal, JavaScript menggunakan "pewarisan prototypal"; cara "pseudoklasik" hanyalah gula.

Lihat tautan ini untuk perbandingan dua cara.

pengguna1931858
sumber
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

Ringkasan:

1) dengan newkata kunci ada dua hal yang perlu diperhatikan;

a) fungsi digunakan sebagai konstruktor

b) function.prototypeobjek diteruskan ke __proto__properti ... atau di mana __proto__tidak didukung, itu adalah tempat kedua di mana objek baru terlihat untuk menemukan properti

2) dengan Object.create(obj.prototype)Anda membangun objek ( obj.prototype) dan meneruskannya ke objek yang dimaksud .. dengan perbedaan bahwa sekarang objek baru __proto__juga menunjuk ke obj.prototype (harap ref oleh xj9 untuk itu)

pengguna3124360
sumber
15

Varian pembuatan objek.


Varian 1 : ' Objek baru () ' -> Konstruktor objek tanpa argumen.

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

masukkan deskripsi gambar di sini


Varian 2 : ' Objek baru (orang) ' -> Konstruktor objek dengan argumen.

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 }

masukkan deskripsi gambar di sini


Varian 3.1 : ' Object.create (person) '. Gunakan Object.create dengan objek 'orang' sederhana. 'Object.create (person)' akan membuat (dan mengembalikan) objek kosong baru dan menambahkan properti '__proto__' ke objek kosong baru yang sama. Properti ini '__proto__' akan menunjuk ke objek 'orang'.

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!

masukkan deskripsi gambar di sini


Varian 3.2 : ' Object.create (Object.prototype) '. Gunakan Object.create dengan objek bawaan -> 'Object.prototype'. 'Object.create (Object.prototype)' akan membuat (dan mengembalikan) objek kosong baru dan menambahkan properti '__proto__' ke objek kosong baru yang sama. Properti ini '__proto__' akan menunjuk ke objek '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

masukkan deskripsi gambar di sini


Varian 4 : 'Beberapa Fungsi baru () '

// '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!

masukkan deskripsi gambar di sini

Ted
sumber
Ringkasan yang bagus. Terima kasih. Itu membantu saya hari ini !!
Anandaraja_Srinivasan
11

Secara internal Object.createmelakukan ini:

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

Sintaks hanya menghilangkan ilusi bahwa JavaScript menggunakan Warisan Klasik.

xj9
sumber
25
Metode ECMAScript 5 Object.create, melakukan lebih dari itu, Anda dapat mendefinisikan properti berdasarkan deskriptor properti dan Anda dapat membuat objek yang tidak mewarisi dari apa pun ( Object.create(null);), jenis shim ini harus dihindari karena Anda tidak dapat benar-benar meniru itu perilaku pada ES3. Info lebih lanjut
CMS
Setuju dengan @CMS tetapi secara umum, ini adalah polyfill sederhana untuk Object.create.
V. Kovpak
10

Sesuai dengan jawaban ini dan kata kunci video ini new melakukan hal-hal berikut:

  1. Menciptakan objek baru.

  2. Menautkan objek baru ke fungsi konstruktor ( prototype).

  3. Membuat thistitik variabel ke objek baru.

  4. Menjalankan fungsi konstruktor menggunakan objek baru dan melakukan implisit return this;

  5. Menetapkan nama fungsi konstruktor ke properti objek baru constructor.

Object.createhanya melakukan 1stdan 2ndmelangkah !!!

V. Kovpak
sumber