2010-04-25 5 views
331

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

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'?

+0

Когда принятый ответ имеет меньше голосов, чем вопрос, может быть, принятый ответ неприемлем? @CMS заставил бы вас написать фабричную функцию, которая использует 'Object.create' внутри, чтобы получить те же одноступенчатые функции, что и' new UserA ('bob'); '. –

+1

Возможно, это самый * приемлемый ответ, учитывая, что у него больше всего голосов из 6 ответов. –

+2

См. Также [Понимание различий между Object.create() и новым SomeFunction()] (http://stackoverflow.com/q/4166616/1048572) – Bergi

ответ

225

Имея только один уровень наследования, ваш пример может не позволить вам увидеть реальные преимущества 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), что может быть действительно полезно.

+8

1. Спасибо за указатель на дифференциальное наследование. 2. Не означает ли это больше конструкторов? Мне нужно помнить, что каждый раз, когда я создаю пользователя, устанавливаю 'id' в MY_GLOBAL.nextId()? –

+3

Добро пожаловать @Graham, вы правы, больше нет конструкторов, необходимых для этого метода, хотя доступные в настоящее время версии Firefox 3.7apre5, новейшие сборки WebKit Nightly и бета-версия Chrome 5 [не так быстро] (http: //webreflection.blogspot.com/2010/03/new-constructor-vs-objectcreate.html) по сравнению с обычными старыми конструкторами, надеюсь, это изменится в ближайшем будущем. Для создания объекта вы можете создать функцию * factory * (например, 'function createUser (name) {...}', со всей необходимой логикой для создания ваших пользовательских объектов внутри с помощью объекта Object.create. – CMS

+5

Re: no больше конструкторов: обычно вы должны писать обычную функцию как «фабрику» для объектов. Внутренне она будет использовать «Object.create» для создания пустого объекта, а затем модифицировать его по необходимости, прежде чем возвращать его. не нужно помнить префикс 'new'. –

2

Вы должны сделать обычай Object.create() функции. Один, который касается проблем Крокфорда, а также вызывает вашу функцию init.

Это будет работать:

var userBPrototype = { 
    init: function(nameParam) { 
     this.name = nameParam; 
    }, 
    sayHello: function() { 
     console.log('Hello '+ this.name); 
    } 
}; 


function UserB(name) { 
    function F() {}; 
    F.prototype = userBPrototype; 
    var f = new F; 
    f.init(name); 
    return f; 
} 

var bob = UserB('bob'); 
bob.sayHello(); 

Здесь UserB, как Object.create, но с поправкой на наши нужды.

Если вы хотите, вы также можете позвонить:

var bob = new UserB('bob'); 
+1

Почему бы просто не использовать 'UserB'' var f = Object.create (userPrototype); f.init (имя); return f; '? –

+0

init можно назвать несколько раз. ничто не мешает этому. – oligofren

40

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

Для кросс-кода браузера одним из способов получить инициализацию объекта в то же время является настройка объекта Object.create от Crockford.Вот один способ: -

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() 
} 

Это поддерживает Крокфорд прототипичного наследование, а также проверяет наличие какого-либо метод инициализации в объекте, а затем запускает его с параметром (ами), как, скажем, новый человек («Джон»,» 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(); 

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

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

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 не является конструктором, но выглядит как обычный одноэлементный объект. Таким образом, с помощью этого метода вы можете создавать и инициализировать обычные обычные объекты.

+4

Существует полипол для 'Object.create' в ES5 Shim https://github.com/kriskowal/es5-shim – ryanve

+0

Отличная статья! Я добавил [JSFiddle] (http://jsfiddle.net/AV7sV/), поэтому у вас есть рабочий пример для Object.build и Object.gen в одном файле. Кроме того, я добавил несколько точек с запятой, которые отсутствовали (доступно только в JSFiddle). – Matt

12

Вы могли бы сделать 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

Другим возможным использование Object.create является клонировать неизменные объекты в cheap and effective way.

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). Это выгодно для метода свойств копирования (см. 1), поскольку он не копирует свойства объекта-объекта. Скорее он создает другой объект -destination-object с его прототипом, установленным на исходном объекте. Более того, когда свойства изменяются на объекте dest, они создаются «на лету», маскируя свойства прототипа (src). Это представляет собой быстрый эффективный способ клонирования неизменяемых объектов.

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

Сценарий здесь (http://jsfiddle.net/y5b5q/1/) (необходим браузер с возможностью создания объектов Object.create).

+6

Вызов этого клона меня смущает (и, вероятно, многие другие). Для большинства людей метод «клонировать» подразумевает, что изменения оригинала не повлияют на клон. – kybernetikos

+0

Понял, что я ответил на ответ только для исходных объектов, считающихся неизменяемыми. – basos

+0

Повторное клонирование объектов таким образом быстро приведет вас к неприятностям, так как у вас будет объект с очень длинной цепочкой прототипов, которая сохранит все эти старые объекты в памяти. Иногда я использую 'Object.create' в тестировании, чтобы сделать mocks, которые переопределяют некоторые свойства * экземпляра * класса, сохраняя при этом остальных одинаковыми. Но я редко использую его. – Andy

42

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

Те, кто выступает за этот метод, в целом формулируют довольно двусмысленные преимущества: "scalability", или «more natural to JavaScript» и т. Д.

Однако я до сих пор не видим конкретный пример, который показывает, что Object.create имеет любые преимущества по сравнению с использованием new. Напротив, с ним есть известные проблемы. Sam Elsamman describes what happens when there are nested objects and Object.create(...) is used:

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 datum становится частью прототипа lion и bird и вызывает проблемы по мере совместного использования. При использовании нового прототипичного наследование явно:

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(...).

+25

Я не согласен. «Object.create» не применяет и не поощряет практику использования прототипов как любого типа «хранения значений данных по умолчанию», поскольку, как кажется, предлагается связанная статья. Надлежащей инициализацией данных является ответственность того, кто создает конкретный объект (например, завод или строитель в дизайне OO). * (Наследование данных, а не поведение возможно в JS, но не общий сценарий.) * – Kos

+41

До тех пор, пока вы понимаете, что аргумент Object.create должен быть прототипом, эта проблема не должна возникать. Очевидно, вы можете получить такое же плохое поведение при использовании ** нового **, если вы скажете Animal.prototype.traits = {}; Единственная причина, по которой вам ясно, что вы не должны этого делать, это то, что вы понимаете, как работают прототипы javascript. – plediii

+16

Боже мой! Так много downvotes для обеспечения правильного ответа :-) Точка Object.create не допускает механизм для аргументов конструктора, поэтому вы вынуждены расширять «данные». Теперь эти данные могут содержать вложенные объекты, что приводит к проблеме выше. С прототипным наследованием, с другой стороны, мы сталкиваемся с этой проблемой только в том случае, если мы должны явно выписать «Animal.prototype.traits = {}; '. Один из методов подразумевает другое явное. Не выбирайте тот, который приводит к проблемам. –

3

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

In this jsperf example, в Chromium, браузер new является 30 раз быстро, как Object.create(obj) хотя и довольно быстро. Это все довольно странно, потому что новое делает больше вещей (например, вызывает конструктор), где Object.create должен просто создавать новый объект с переданным в объекте в качестве прототипа (секретная ссылка в Crockford-speak)

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

+2

Этот jsperf, кажется, ушел. – UpTheCreek

18

TL; DR:

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

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

Хотя для некоторых причин оптимизации внутри двигателя Object.create может быть медленнее, что не является интуитивным.

4

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

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

proto = new HTMLElement //fail :(
proto = Object.create(HTMLElement.prototype) //OK :) 
document.registerElement("custom-element", { prototype: proto }) 
3

Я думаю, что главное в вопросе - это понять разницу между new и Object.create подходов. Соответственно к this answer и this videonew ключевого слова делает следующие вещи:

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

  2. Ссылки new object to constructor function (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' ] 
0

В то время как Дуглас Крокфорд раньше был ревностным сторонником Object.create(), и он в основном причина, почему эта конструкция на самом деле находится в JavaScript, он не дольше это мнение.

Он прекратил использование Object.create, потому что он прекратил использовать ключевое слово, так как это вызывает слишком много проблем. Например, если вы не будете осторожны, он может легко указать на глобальный объект, который может иметь очень плохие последствия. И он утверждает, что без использования этот Object.create больше не имеет смысла.

Вы можете проверить это видео с 2014 года, когда он говорит на Nordic.js:

https://www.youtube.com/watch?v=PSGEjv3Tqo0

enter image description here

0

Я предпочитаю закрывающий подход.

Я все еще использую new. Я не использую Object.create. Я не использую this.

Я все еще использую new, поскольку мне нравится декларативный характер этого.

Рассмотрите это для простого наследования.

window.Quad = (function() { 

    function Quad() { 

     const wheels = 4; 
     const drivingWheels = 2; 

     function getWheelCount() { 
      return wheels; 
     } 

     function getDrivingWheelCount() { 
      return drivingWheels; 
     } 
     return Object.freeze({ 
      getWheelCount, 
      getDrivingWheelCount 
     }); 
    } 

    return Object.freeze(Quad); 
})(); 

window.Car4wd = (function() { 

    function Car4wd() { 
     const quad = new Quad(); 

     const spareWheels = 1; 
     const extraDrivingWheels = 2; 

     function getSpareWheelCount() { 
      return spareWheels; 
     } 

     function getDrivingWheelCount() { 
      return quad.getDrivingWheelCount() + extraDrivingWheels; 
     } 

     return Object.freeze(Object.assign({}, quad, { 
      getSpareWheelCount, 
      getDrivingWheelCount 
     })); 
    } 

    return Object.freeze(Car4wd); 
})(); 

let myQuad = new Quad(); 
let myCar = new Car4wd(); 
console.log(myQuad.getWheelCount()); // 4 
console.log(myQuad.getDrivingWheelCount()); // 2 
console.log(myCar.getWheelCount()); // 4 
console.log(myCar.getDrivingWheelCount()); // 4 - The overridden method is called 
console.log(myCar.getSpareWheelCount()); // 1 

Отзыв приветствуется.