Понимание разницы между 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.

Являются ли приведенные выше утверждения правильными? И я что-то упустил? Когда бы вы использовали один поверх другого?

РЕДАКТИРОВАТЬ: ссылка на jsfiddle версию приведенного выше примера кода: http://jsfiddle.net/rZfYL/


Ответы:


246

Объект, используемый в 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методе ECMAScript 5-го издания , а не об обтекателе Крокфорда.

Этот метод начинает реализовываться в последних браузерах, проверьте эту таблицу совместимости .


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()объект
  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()объект
  2. установить obj.__proto__наTest.prototype
  3. return obj;

Так что в основном Object.createне выполняет конструктор.


@Ray, поэтому при использовании 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 через new.
Правин

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

Почему бы вам просто не установить свойство prototype для b вместо a?
Amnestic

Понравилась статья в вашем блоге тоже. Помог мне понять концепцию намного лучше. Спасибо.
stable_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

Разница заключается в так называемом «псевдоклассическом и прототипном наследовании». Рекомендуется использовать только один тип в вашем коде, не смешивая их.

В псевдоклассическом наследовании (с оператором «new») представьте, что вы сначала определяете псевдокласс, а затем создаете объекты из этого класса. Например, определите псевдокласс «Персона», а затем создайте «Алиса» и «Боб» из «Персона».

В наследовании прототипа (с использованием 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__также указывает на obj.prototype (пожалуйста, ссылки на xj9 для этого)


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) создаст (и вернет) новый пустой объект и добавит свойство __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, но в целом, это просто polyfill для 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.