Использование "Object.create" вместо "new"
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'?
Ответы
Ответ 1
Только с одним уровнем наследования ваш пример может не позволить вам увидеть реальные преимущества 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
), что может быть действительно полезно.
Ответ 2
На самом деле нет никакого преимущества в использовании 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(...)
.
Ответ 3
Object.create еще не является стандартным для нескольких браузеров, например IE8, Opera v11.5, Konq 4.3 не имеет его. Вы можете использовать версию Object.create для Douglas Crockford для этих браузеров, но это не включает второй параметр "инициализирующий объект", используемый в ответе CMS.
Для кросс-кода браузера одним из способов получить инициализацию объекта в то же время является настройка Crockford 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()
}
Это поддерживает прототипное наследование Crockford, а также проверяет любой метод init в объекте, а затем запускает его с вашими параметрами (например, новый человек ( "Джон", "Смит" ). Затем ваш код будет: -
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 не является конструктором, но выглядит как обычный одноэлементный объект. Таким образом, с помощью этого метода вы можете создавать и инициализировать обычные обычные объекты.
Ответ 4
TL; DR:
new Computer()
будет вызывать функцию конструктора Computer(){}
один раз, а Object.create(Computer.prototype)
- нет.
Все преимущества основаны на этом пункте.
Замечание о производительности: конструктор, вызываемый как new Computer()
, сильно оптимизирован движком, поэтому он может быть даже быстрее, чем Object.create
.
Ответ 5
Вы можете сделать метод init
return 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');
Ответ 6
Другим возможным использованием 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). Он использует метод копирования-свойств (см. 1), поскольку он не копирует свойства объекта-объекта. Скорее он создает другой объект -destination-object с его прототипом, установленным на исходном объекте. Более того, когда свойства изменяются на объект dest, они создаются "на лету", маскируя свойства прототипа (src). Это представляет собой быстрый эффективный способ клонирования неизменяемых объектов.
Здесь следует указать, что это относится к исходным объектам, которые не должны быть изменены после создания (неизменяемы). Если исходный объект будет изменен после создания, все свойства маскирования не будут также изменены.
Загадайте здесь (http://jsfiddle.net/y5b5q/1/) (нужен браузер с поддержкой Object.create).
Ответ 7
Я думаю, что главное в этом вопросе - понять разницу между new
и Object.create
подходами Object.create
. Соответственно этому ответу и этому видео new
ключевое слово делает следующее:
-
Создает новый объект.
-
Связывает новый объект с конструкторской функцией (prototype
).
-
Делает this
переменную точкой для нового объекта.
-
Выполняет конструкторную функцию, используя новый объект, и неявный результат return this
;
-
Назначает имя функции конструктора новому 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();
to Guest.prototype = Object.create(SiteMember.prototype);
результатом будет:
[ 'James' ]
Ответ 8
Иногда вы не можете создать объект с NEW, но все еще можете вызвать метод CREATE.
Например: если вы хотите определить пользовательский элемент, он должен быть получен из HTMLElement.
proto = new HTMLElement //fail :(
proto = Object.create( HTMLElement.prototype ) //OK :)
document.registerElement( "custom-element", { prototype: proto } )
Ответ 9
Преимущество состоит в том, что Object.create
обычно меньше new
для большинства браузеров
В этом примере jsperf в браузере Chromium new
в 30 раз быстрее как Object.create(obj)
хотя оба довольно быстро. Это довольно странно, потому что новый делает больше вещей (например, вызывает конструктор), где Object.create должен просто создавать новый объект с переданным в объекте в качестве прототипа (секретная ссылка в Crockford-speak)
Возможно, браузеру не удалось сделать Object.create
более эффективным (возможно, они основывают его на new
под обложками... даже в собственном коде)
Ответ 10
Вы должны создать пользовательскую функцию 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');
Ответ 11
Хотя Дуглас Крокфорд раньше был ревностным сторонником Object.create(), и он в основном является причиной того, что эта конструкция фактически находится в javascript, у него больше нет такого мнения.
Он прекратил использовать Object.create, потому что он полностью прекратил использовать это ключевое слово, так как это вызывает слишком много проблем. Например, если вы не будете осторожны, он может легко указать на глобальный объект, который может иметь очень плохие последствия. И он утверждает, что без использования этого Object.create больше не имеет смысла.
Вы можете проверить это видео с 2014 года, где он говорит в Nordic.js:
https://www.youtube.com/watch?v=PSGEjv3Tqo0
Ответ 12
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;
В последнем случае необходимо установить свойство конструктора прототипа так, чтобы new Y
оператор new Y
вызывал правильный конструктор, в противном случае new Y
вызовет функцию X
Если программист хочет, чтобы new Y
вызывал X
, это было бы более правильно сделать в конструкторе Y с помощью X.call(this, u)
Ответ 13
Я предпочитаю закрытый подход.
Я все еще использую new
. Я не использую Object.create
. Я не использую this
.
Я все еще использую new
поскольку мне нравится декларативный характер этого.
Рассмотрим это для простого наследования.
window.Quad = (function() {
function Quad() {
const wheels = 4;
const drivingWheels = 2;
let motorSize = 0;
function setMotorSize(_) {
motorSize = _;
}
function getMotorSize() {
return motorSize;
}
function getWheelCount() {
return wheels;
}
function getDrivingWheelCount() {
return drivingWheels;
}
return Object.freeze({
getWheelCount,
getDrivingWheelCount,
getMotorSize,
setMotorSize
});
}
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
Обратная связь приветствуется.
Ответ 14
Резюме:
-
Object.create()
- это функция Javascript, которая принимает 2 аргумента и возвращает новый объект. - Первый аргумент - это объект, который будет прототипом вновь созданного объекта.
- Второй аргумент - это объект, который будет являться свойствами вновь созданного объекта.
Пример:
const proto = {
talk : () => console.log('hi')
}
const props = {
age: {
writable: true,
configurable: true,
value: 26
}
}
let Person = Object.create(proto, props)
console.log(Person.age);
Person.talk();