Розуміння різниці між Object.create () та новим SomeFunction ()


392

Нещодавно я натрапив на Object.create()метод у JavaScript, і намагаюся вивести, чим він відрізняється від створення нового екземпляра об’єкта new SomeFunction()і коли ви хочете використовувати один за іншим.

Розглянемо наступний приклад:

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

Зауважте, що однакова поведінка спостерігається в обох випадках. Мені здається, що основні відмінності між цими двома сценаріями:

  • Об'єкт, що використовується Object.create()фактично, формує прототип нового об'єкта, тоді як new Function()з заявлених властивостей / функцій прототип не утворюється.
  • Ви не можете створити закриття з Object.create()синтаксисом, як це було б з функціональним синтаксисом. Це логічно, враховуючи область лексичного (проти блочного) типу JavaScript.

Чи наведені вище твердження правильні? А я чогось пропускаю? Коли б ви використовували одне над іншим?

EDIT: посилання на версію jsfiddle вищезгаданого зразка коду: http://jsfiddle.net/rZfYL/


Відповіді:


247

Об'єкт, що використовується в Object.create, фактично формує прототип нового об'єкта, де, як і в новій функції (), декларовані властивості / функції не утворюють прототипу.

Так, Object.createбудує об’єкт, який успадковує безпосередньо з того, що передається як його перший аргумент.

За допомогою конструкторських функцій новостворений об'єкт успадковує від прототипу конструктора, наприклад:

var o = new SomeConstructor();

У наведеному вище прикладі oуспадковується безпосередньо від SomeConstructor.prototype.

Тут є різниця: Object.createви можете створити об'єкт, який не успадковує нічого, Object.create(null);з іншого боку, якщо ви встановите SomeConstructor.prototype = null;новостворений об'єкт буде успадкований від Object.prototype.

Ви не можете створити закриття з синтаксисом Object.create так, як це було б з функціональним синтаксисом. Це логічно, враховуючи область лексичного (проти блочного) типу JavaScript.

Ну, ви можете створювати закриття, наприклад, використовуючи аргумент дескрипторів властивостей:

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

o.foo; // "foobar"

Зауважте, що я говорю про Object.createметод 5-го видання ECMAScript , а не про лайки Крокфорда.

Метод починає реально реалізовуватися в останніх браузерах, перевірте цю таблицю сумісності .


2
@CMS 2 питання. 1) Чи закінчується ланцюг області на Object.create (null) всесвітньою сферою (наприклад, "вікно" у браузері), чи він закінчується на собі? 2) Мені все ще не зрозуміло, чому було введено Object.create (наприклад, яка функція відсутня, щоб вказати цю адресу?) І чому можна використовувати її замість нової функції ();
Метт

9
@Matt, 1) ланцюг області дійсно не є спорідненою концепцією тут, ланцюг області пов'язаний з роздільною здатністю ідентифікатора , наприклад: як foo;це вирішено в поточному лексичному середовищі . 2) Щоб забезпечити простий спосіб реалізації спадщини, це дійсно потужна конструкція. IMO Я б використовував це, тому що він дійсно простий і легкий, але для виробничого коду нам потрібно почекати деякий час, поки ES5 не буде широко підтримуватися. Про відсутні функції, факт створення "первозданного" об'єкта Object.create(null);відсутній, це дійсно корисно реалізувати надійні об'єкти, схожі на хеш-таблицю ...
CMS

@CMS Спасибі Отже, просто створюючи об’єкт за допомогою "Object.create", ви отримуєте можливість вибору об'єкта, який повинен бути його прототипом.
Аншул

@CMS ОК, Object.create(null)значить, вам не доведеться використовувати hasOwnProperty()лайно при ітерації, тому що ніхто не успадковує ??? Мені це подобається - дякую. Звичайно, все ще робитимуть, hasOwnPropertyоскільки не всі користуватимуться, Object.create(null)тому я не впевнений, що це справжня користь ... Поки що я знайшов інші "переваги" Object.create()абсолютно непереконливих.
user949300

425

Дуже просто сказав new Xце Object.create(X.prototype)з додатково запустити constructorфункцію. (І надання constructorшансу returnфактичному об'єкту, який повинен бути результатом виразу замість this.)

Це воно. :)

Решта відповідей просто заплутані, бо, очевидно, ніхто з них не читає визначення new. ;)


23
+1 Простота та ясність! (Хоча Object.create (null) здається приємним варіантом - можливо, це слід згадати).
user949300

нехай це буде просто, це шлях
Білл

Це просто залишає питання про «засідці, тому функції мають прототип теж ? Який зв'язок між ними і об'єктними прототипами?»
Qwertie

3
@Qwertie: У JS все є об'єктом. :) Вони скопіювали це з Java, який скопіював це з SmallTalk, який пройшов цілком до кінця. Це приємний випадок «виникнення», який в цілому полегшує життя.
Evi1M4chine

@ Evi1M4chine насправді в Java функції не є об'єктами (і з цього приводу не є також примітивами) ... і об'єкти не мають прототипів, тому порівняння здається непридатним. Те, що JS працює інакше, ніж інші популярні мови OO, є головним джерелом плутанини (і це не допомагає браузерам не надавати простий спосіб візуалізації мережі об’єктів, включаючи функції та прототипи). PS Я вважаю це посилання корисним: davidwalsh.name/javascript-objects-deconstruction
Qwertie

204

Ось кроки, які відбуваються всередині обох дзвінків:
(Підказка: різниця лише в кроці 3)


new Test():

  1. створити new Object()obj
  2. встановити obj.__proto__вTest.prototype
  3. return Test.call(obj) || obj; // normally obj is returned but constructors in JS can return a value

Object.create( Test.prototype )

  1. створити new Object()obj
  2. встановити obj.__proto__вTest.prototype
  3. return obj;

Тому в основному Object.createне виконується конструктор.


@ Так, використовуючи object.create ми шрифтом мають властивості функції, зазначеної у функції конструктора?

@sortednoun, доки властивості приватні та не вказані в прототипі, так, вони не будуть успадковані, і ви не будете мати їх у новому об'єкті (і, додав би, ви можете розраховувати на отримання можливих прототипних властивостей від батьків, саме тоді, коли батьківський конструктор був виконаний хоча б один раз).
Kamafeather

Як і у більшості функцій конструктора, методи визначаються у поверненому об'єкті, в newосновному всі функції дублюються, а Object.createне.
SparK

61

Дозвольте спробувати пояснити (докладніше про блог ):

  1. Коли ви пишете Carконструктор var Car = function(){}, це те , як речі всередині: У Діаграма прототипних ланцюгів при створенні об'єктів javascript нас є одна {prototype}прихована посилання на Function.prototypeякий не доступний і одна prototypeпосилання , Car.prototypeяка доступна і має фактичний constructorз Car. І Function.prototype, і Car.prototype мають приховані посилання на Object.prototype.
  2. Коли ми хочемо створити два еквівалентні об'єкти за допомогою newоператора та createметоду, ми повинні зробити це так: Honda = new Car();і Maruti = Object.create(Car.prototype). Діаграма прототипних ланцюгів для різних методів створення об'єктів Що відбувається?

    Honda = new Car();- Коли ви створюєте такий об'єкт, тоді {prototype}вказується на приховане властивість Car.prototype. Тож тут {prototype}об'єкт Honda завжди буде Car.prototype- ми не маємо жодної можливості змінити {prototype}властивість об'єкта. Що робити, якщо я хочу змінити прототип нашого новоствореного об’єкта?
    Maruti = Object.create(Car.prototype)- Коли ви створюєте такий об'єкт, у вас є додаткова можливість вибору {prototype}властивості об'єкта . Якщо ви хочете Car.prototype як {prototype}тоді, передайте його як параметр у функції. Якщо ви не хочете будь - яких {prototype}для вашого об'єкта , то ви можете передати nullтак: Maruti = Object.create(null).

Висновок - Використовуючи метод, Object.createви маєте свободу вибору {prototype}власності об'єкта . У new Car();вас немає такої свободи.

Кращий спосіб в OO JavaScript:

Припустимо, у нас є два об'єкти aі b.

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

Тепер, припустимо, aє деякі методи, до яких bтакож хочеться отримати доступ. Для цього нам потрібне успадкування об'єктів ( aмає бути прототипом bлише, якщо ми хочемо отримати доступ до цих методів). Якщо ми перевіримо прототипи, aа bпотім з’ясуємо, що вони поділяють прототип Object.prototype.

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

Проблема - ми хочемо об'єкт aяк прототип b, але тут ми створили об'єкт bз прототипом Object.prototype. Рішення - запроваджено ECMAScript 5 Object.create(), щоб легко досягти такої спадщини. Якщо ми створимо такий об'єкт b:

var b = Object.create(a);

тоді,

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

Отже, якщо ви робите об'єктно-орієнтований сценарій, то Object.create()це дуже корисно для спадкування.


Отже, це дещо схоже на створення об'єкта без виклику конструктора? Ми будемо насолоджуватися всіма перевагами класу. Клас obj instanceof також буде правдивим. Але ми не посилаємося на функцію Class через нову.
Praveen

@Anshul Ви сказали, що a.isPrototypeOf(b);повернеться falseправильно, оскільки обидва Об'єкти різні і вказують на іншу пам'ять. Правильний спосіб зробити це з newоператором тут. - jsfiddle.net/167onunp .
Сагар Каріра

Чому б вам просто не встановити властивість прототипу b на a, а не робити це?
амністія

Стаття сподобалось і вашому блогу. Допомогли мені зрозуміти концепцію набагато краще. Дякую.
steady_daddy

1
У висновку все сказано.
kushalvm

44

Це:

var foo = new Foo();

і

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

досить схожі. Важлива відмінність полягає в тому, що він new Fooфактично запускає конструкторський код, тоді як Object.createне виконуватиме такий код, як

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

Зауважте, що якщо ви використовуєте двопараметричну версію, Object.create()тоді ви можете робити набагато більш потужні речі.


1
Чудове пояснення. Я можу додати, використовуючи Object.createв його найпростішій формі, як ця, ви можете опускати функції конструктора з вашого коду, користуючись перевагою успадкування прототипу.
Ріккі Бойс

23

Різниця полягає в так званому "псевдокласичному проти прототипічного успадкування". Пропозиція полягає у використанні у коді лише одного типу, а не змішування двох.

У псевдокласичному успадкуванні (з "новим" оператором) уявіть, що спочатку ви визначаєте псевдоклас, а потім створюєте об'єкти з цього класу. Наприклад, визначте псевдоклас "Person", а потім створіть "Alice" та "Bob" з "Person".

У прототипічному успадкуванні (використовуючи Object.create) ви безпосередньо створюєте конкретну особу "Аліса", а потім створюєте іншу особу "Боб", використовуючи "Алісу" як прототип. Тут немає «класу»; всі об’єкти.

Внутрішньо JavaScript використовує "наслідування за прототипом"; "псевдокласичний" спосіб - це лише якийсь цукор.

Дивіться це посилання для порівняння двох способів.


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

Підсумок:

1) з newключовим словом слід зазначити дві речі;

а) функція використовується як конструктор

б) function.prototypeоб’єкт передається у __proto__властивість ... або там, де __proto__він не підтримується, це друге місце, де новий об’єкт шукає, щоб знайти властивості

2) разом з Object.create(obj.prototype)вами будуєте об’єкт ( obj.prototype) і передаєте його до призначеного об'єкта.__proto__


15

Варіанти створення об'єктів.


Варіант 1 : ' new Object () ' -> Конструктор об'єктів без аргументів.

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

введіть тут опис зображення


Варіант 2 : ' новий Об'єкт (особа) ' -> Конструктор об'єкта з аргументом.

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 }

введіть тут опис зображення


Варіант 3.1 : ' Object.create (person) '. Використовуйте Object.create з простим об'єктом 'person'. 'Object.create (person)' створить (і поверне) новий порожній об’єкт і додасть властивість '__proto__' до того ж нового порожнього об’єкта. Ця властивість '__proto__' буде вказувати на об'єкт 'person'.

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!

введіть тут опис зображення


Варіант 3.2 : ' Object.create (Object.prototype) '. Використовуйте Object.create із вбудованим об'єктом -> 'Object.prototype'. 'Object.create (Object.prototype)' створить (і поверне) новий порожній об’єкт і додасть властивість '__proto__' до того ж нового порожнього об’єкта. Це властивість '__proto__' буде вказувати на об’єкт '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

введіть тут опис зображення


Варіант 4 : " новий 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!

введіть тут опис зображення


Приємний підсумок. Дякую. Це мені сьогодні допомогло !!
Anandaraja_Srinivasan

11

Внутрішньо Object.createце робить:

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

Синтаксис просто знімає ілюзію, що JavaScript використовує класичне спадкування.


25
Метод ECMAScript 5 Object.createробить значно більше, ви можете визначити властивості за допомогою дескрипторів властивостей, а також можете створити об'єкт, який не успадковує нічого ( Object.create(null);), цього типу прокладки слід уникати, оскільки ви не можете наслідувати це поведінка на ES3. Більше інформації
CMS

Погодьтеся з @CMS, але загалом, це проста поліфаза Object.create.
В. Ковпак

10

Відповідно до цієї відповіді та до цього відео new ключове слово робить наступні речі:

  1. Створює новий об’єкт.

  2. Пов'язує новий об’єкт з функцією конструктора ( prototype).

  3. Здійснює thisзмінну точку новому об'єкту.

  4. Виконує функцію конструктора за допомогою нового об’єкта та неявного виконання return this;

  5. Призначає ім'я функції конструктора для нового властивості об'єкта constructor.

Object.createвиконує тільки 1stі 2ndкроки !!!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.