Использование «Object.create» вместо «new» - PullRequest
355 голосов
/ 25 апреля 2010

Javascript 1.9.3 / ECMAScript 5 представляет Object.create, который Дуглас Крокфорд среди других защищал в течение длительного времени. Как заменить new в приведенном ниже коде на Object.create?

var UserA = function(nameParam) {
    this.id = MY_GLOBAL.nextId();
    this.name = nameParam;
}
UserA.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}
var bob = new UserA('bob');
bob.sayHello();

(Предположим, MY_GLOBAL.nextId существует).

Лучшее, что я могу придумать, это:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB);
bob.init('Bob');
bob.sayHello();

Кажется, что нет никакого преимущества, поэтому я думаю, что я не получаю его. Я, наверное, слишком неоклассичен. Как мне использовать Object.create для создания пользователя 'bob'?

Ответы [ 14 ]

239 голосов
/ 26 апреля 2010

С одним уровнем наследования ваш пример может не дать вам увидеть реальные преимущества Object.create.

Этот метод позволяет вам легко реализовать дифференциальное наследование , где объекты могут напрямую наследоваться от других объектов.

В вашем примере userB я не думаю, что ваш метод init должен быть открытым или даже существовать, если вы снова вызовете этот метод вв существующем экземпляре объекта свойства id и name изменятся.

Object.create позволяет инициализировать свойства объекта, используя его второй аргумент, например:

var userB = {
  sayHello: function() {
    console.log('Hello '+ this.name);
  }
};

var bob = Object.create(userB, {
  'id' : {
    value: MY_GLOBAL.nextId(),
    enumerable:true // writable:false, configurable(deletable):false by default
  },
  'name': {
    value: 'Bob',
    enumerable: true
  }
});

Как вы можетевидите, свойства могут быть инициализированы на втором аргументе Object.create, с литералом объекта, использующим синтаксис, аналогичный используемому в Object.defineProperties и Object.defineProperty методах.

Позволяет установить свойствоатрибуты (enumerable, writable или configurable), которые могут быть действительно полезны.

50 голосов
/ 03 октября 2012

Нет никакого преимущества в использовании Object.create(...) над new object.

Сторонники этого метода обычно заявляют о довольно неоднозначных преимуществах: "масштабируемость" или " более естественный для JavaScript " и т. Д.

Однако мне еще предстоит увидеть конкретный пример, который показывает, что Object.create обладает любыми преимуществами по сравнению с использованием new. Наоборот, есть известные проблемы с этим. Сэм Эльсамман описывает, что происходит, когда есть вложенные объекты и Object.create(...) используется :

var Animal = {
    traits: {},
}
var lion = Object.create(Animal);
lion.traits.legs = 4;
var bird = Object.create(Animal);
bird.traits.legs = 2;
alert(lion.traits.legs) // shows 2!!!

Это происходит потому, что Object.create(...) поддерживает практику, в которой данные используются для создания новых объектов; здесь элемент данных Animal становится частью прототипа lion и bird и вызывает проблемы при его совместном использовании. При использовании new наследование прототипа является явным:

function Animal() {
    this.traits = {};
}

function Lion() { }
Lion.prototype = new Animal();
function Bird() { }
Bird.prototype = new Animal();

var lion = new Lion();
lion.traits.legs = 4;
var bird = new Bird();
bird.traits.legs = 2;
alert(lion.traits.legs) // now shows 4

Что касается необязательных атрибутов свойств, которые передаются в Object.create(...), их можно добавить с помощью Object.defineProperties(...).

41 голосов
/ 04 июля 2011

Object.create еще не является стандартным для нескольких браузеров, например, IE8, Opera v11.5, Konq 4.3 не имеют его. Вы можете использовать версию Object.create Дугласа Крокфорда для этих браузеров, но она не включает второй параметр 'объект инициализации', используемый в ответе CMS.

Для кроссбраузерного кода одним из способов инициализации объекта в это время является настройка объекта Crocford's Object.create. Вот один из методов: -

Object.build = function(o) {
   var initArgs = Array.prototype.slice.call(arguments,1)
   function F() {
      if((typeof o.init === 'function') && initArgs.length) {
         o.init.apply(this,initArgs)
      }
   }
   F.prototype = o
   return new F()
}

Это поддерживает наследование прототипа Крокфорда, а также проверяет любой метод init в объекте, а затем запускает его с вашими параметрами, например, скажем new man ('John', 'Smith'). Ваш код становится: -

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.build(userB, 'Bob');  // Different from your code
bob.sayHello();

Таким образом, bob наследует метод sayHello и теперь имеет собственные свойства id = 1 и name = 'Bob'. Эти свойства доступны как для записи, так и для перечисления. Это также намного более простой способ инициализации, чем для ECMA Object.create, особенно если вас не интересуют доступные для записи, перечисления и настраиваемые атрибуты.

Для инициализации без метода init можно использовать следующий мод Крокфорда: -

Object.gen = function(o) {
   var makeArgs = arguments 
   function F() {
      var prop, i=1, arg, val
      for(prop in o) {
         if(!o.hasOwnProperty(prop)) continue
         val = o[prop]
         arg = makeArgs[i++]
         if(typeof arg === 'undefined') break
         this[prop] = arg
      }
   }
   F.prototype = o
   return new F()
}

Это заполняет собственные свойства userB, в порядке их определения, используя параметры Object.gen слева направо после параметра userB. Он использует цикл for (prop in o), поэтому, согласно стандартам ECMA, порядок перечисления свойств не может быть гарантирован так же, как порядок определения свойства. Тем не менее, несколько примеров кода, протестированных в (4) основных браузерах, показывают, что они одинаковы при условии использования фильтра hasOwnProperty, а иногда даже если и нет.

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}};  // For example

var userB = {
   name: null,
   id: null,
   sayHello: function() {
      console.log('Hello '+ this.name);
   }
}

var bob = Object.gen(userB, 'Bob', MY_GLOBAL.nextId());

Несколько проще, чем Object.build, так как userB не нуждается в методе init. Также userB не является конструктором, а выглядит как обычный одноэлементный объект. Поэтому с помощью этого метода вы можете создавать и инициализировать обычные простые объекты.

21 голосов
/ 12 сентября 2014

TL; DR:

new Computer() вызовет функцию конструктора Computer(){} один раз, а Object.create(Computer.prototype) - нет.

Все преимущества основаны на этом пункте.

Замечание о производительности: конструктор, вызывающий как new Computer(), сильно оптимизирован двигателем, поэтому он может быть даже быстрее, чем Object.create.

13 голосов
/ 03 августа 2012

Вы можете заставить метод init вернуть this, а затем объединить вызовы, как показано ниже:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
        return this;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};

var bob = Object.create(userB).init('Bob');
7 голосов
/ 06 сентября 2012

Другое возможное использование Object.create - клонирование неизменяемых объектов дешевым и эффективным способом .

var anObj = {
    a: "test",
    b: "jest"
};

var bObj = Object.create(anObj);

bObj.b = "gone"; // replace an existing (by masking prototype)
bObj.c = "brand"; // add a new to demonstrate it is actually a new obj

// now bObj is {a: test, b: gone, c: brand}

Примечания : Приведенный выше фрагмент кода создает клон исходного объекта (он же не ссылка, как в cObj = aObj). Он имеет преимущество перед методом copy-properties (см. 1 ) в том смысле, что он не копирует свойства элемента объекта. Скорее он создает другой объект -destination- с его прототипом, установленным на исходном объекте. Более того, когда свойства изменяются на объекте dest, они создаются «на лету», маскируя свойства прототипа (src). Это быстрый и эффективный способ клонирования неизменяемых объектов.

Предостережение заключается в том, что это относится к исходным объектам, которые не следует изменять после создания (неизменяемые). Если исходный объект изменяется после создания, все немаскированные свойства клона также будут изменены.

Fiddle here (http://jsfiddle.net/y5b5q/1/) (необходим браузер с поддержкой Object.create).

6 голосов
/ 27 июля 2017

Думаю, главный вопрос - понять разницу между new и Object.create подходами. В соответствии с этим ответом и этим видео * ключевое слово new выполняет следующие действия:

  1. Создает новый объект.

  2. Связывает новый объект с функцией конструктора (prototype).

  3. Создает this переменную точку для нового объекта.

  4. Выполняет функцию конструктора, используя новый объект и неявное выполнение return this;

  5. Назначает имя функции конструктора для свойства нового объекта constructor.

Object.create выполняет только 1st и 2nd шагов !!!

В приведенном примере кода это не имеет большого значения, но в следующем примере:

var onlineUsers = [];
function SiteMember(name) {
    this.name = name;
    onlineUsers.push(name);
}
SiteMember.prototype.getName = function() {
    return this.name;
}
function Guest(name) {
    SiteMember.call(this, name);
}
Guest.prototype = new SiteMember();

var g = new Guest('James');
console.log(onlineUsers);

Как побочный эффект будет:

[ undefined, 'James' ]

из-за Guest.prototype = new SiteMember();
Но нам не нужно выполнять метод родительского конструктора, нам нужно только сделать метод getName доступным в гостевой системе. Следовательно, мы должны использовать Object.create.
Если заменить Guest.prototype = new SiteMember();
до Guest.prototype = Object.create(SiteMember.prototype); результат будет:

[ 'James' ]
5 голосов
/ 04 августа 2015

Иногда вы не можете создать объект с NEW, но все еще можете вызвать метод CREATE.

Например: если вы хотите определить пользовательский элемент, он должен быть производным от HTMLElement.

proto = new HTMLElement  //fail :(
proto = Object.create( HTMLElement.prototype )  //OK :)
document.registerElement( "custom-element", { prototype: proto } )
3 голосов
/ 13 июня 2014

Преимущество заключается в том, что Object.create обычно медленнее, чем new в большинстве браузеров

В этом примере jsperf в Chromium браузер new в 30 раз быстрее , чем Object.create(obj), хотя оба довольно быстрые. Все это довольно странно, потому что new делает больше вещей (например, вызывает конструктор), где Object.create должен просто создавать новый Object с переданным объектом в качестве прототипа (секретная ссылка на языке Крокфорда)

Возможно, браузеры не стали делать Object.create более эффективным (возможно, они основывают его на new под прикрытием ... даже в собственном коде)

2 голосов
/ 09 декабря 2018

new и Object.create служат различным целям. new предназначен для создания нового экземпляра типа объекта. Object.create предназначен для простого создания нового объекта и установки его прототипа. Почему это полезно? Для реализации наследования без доступа к свойству __proto__. Прототип экземпляра объекта, именуемый [[Prototype]], является внутренним свойством виртуальной машины и не предназначен для прямого доступа. Единственная причина, по которой на самом деле можно получить прямой доступ к [[Prototype]] как свойству __proto__, заключается в том, что он всегда был стандартом де-факто реализации ECMAScript для каждой крупной виртуальной машины, и на этом этапе его удаление нарушило бы много существующий код.

В ответ на приведенный выше ответ 7ochem, объекты никогда не должны иметь свой прототип, равный результату оператора new, не только потому, что нет смысла вызывать один и тот же конструктор прототипа несколько раз, но и потому, что два экземпляра объекта один и тот же класс может закончить с другим поведением, если его прототип изменяется после создания. Оба примера являются просто плохим кодом в результате неправильного понимания и нарушения предполагаемого поведения цепочки наследования прототипа.

Вместо доступа к __proto__ прототип экземпляра должен записываться при создании с помощью Object.create или позже с помощью Object.setPrototypeOf и считываться с помощью Object.getPrototypeOf или Object.isPrototypeOf.

Кроме того, как указывает документация Mozilla для Object.setPrototypeOf , плохая идея модифицировать прототип объекта после его создания по соображениям производительности, в дополнение к тому факту, что изменение Прототип объекта после его создания может вызвать неопределенное поведение, если данный фрагмент кода, который обращается к нему, может быть выполнен до ИЛИ после изменения прототипа, если только этот код не очень тщательно проверяет текущий прототип или не обращается к какому-либо свойству, отличающемуся между два.

Учитывая

const X = function (v) { this.v = v }; X.prototype.whatAmI = 'X'; X.prototype.getWhatIAm = () => this.whatAmI; X.prototype.getV = () => this.v;

следующий псевдокод VM эквивалентен выражению const x0 = new X(1);:

const x0 = {}; x0.[[Prototype]] = X.prototype; X.prototype.constructor.call(x0, 1);

Обратите внимание, что хотя конструктор может возвращать любое значение, оператор new всегда игнорирует возвращаемое значение и возвращает ссылку на вновь созданный объект.

А следующий псевдокод эквивалентен утверждению const x1 = Object.create(X.prototype);:

const x0 = {}; x0.[[Prototype]] = X.prototype;

Как видите, единственное различие между ними состоит в том, что Object.create не выполняет конструктор, который может фактически вернуть любое значение, а просто возвращает ссылку на новый объект this, если не указано иное.

Теперь, если мы хотим создать подкласс Y со следующим определением:

const Y = function(u) { this.u = u; } Y.prototype.whatAmI = 'Y'; Y.prototype.getU = () => this.u;

Тогда мы можем сделать так, чтобы он наследовал от X, написав __proto__:

Y.prototype.__proto__ = X.prototype;

Хотя то же самое можно выполнить, даже не записав в __proto__:

Y.prototype = Object.create(X.prototype); Y.prototype.constructor = Y;

В последнем случае необходимо установить свойство constructor прототипа так, чтобы правильный оператор вызывался оператором new Y, в противном случае new Y вызовет функцию X. Если программист хочет, чтобы new Y вызывал X, это было бы более правильно сделать в конструкторе Y с X.call(this, u)

...