Использование "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 создать пользователя "Боб"?

14 ответов

Решение

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

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

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

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

Для кросс-браузерного кода один из способов инициализации объекта в это время заключается в настройке Crockford'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()
}

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

TL;DR:

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

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

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

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

Другое возможное использование 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). Это быстрый эффективный способ клонирования неизменяемых объектов.

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

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

Я думаю, что главный вопрос, о котором идет речь, - это понять разницу между 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();
Но нам не нужно выполнять метод родительского конструктора, нам нужен только метод make getName быть доступным в Гость. Следовательно, мы должны использовать Object.create,
Если заменить Guest.prototype = new SiteMember();
в Guest.prototype = Object.create(SiteMember.prototype); результат будет:

[ 'James' ]

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

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

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

Резюме:

  • 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();

Практическое применение:

  1. Основным преимуществом создания объекта таким способом является то, что прототип может быть явно определен. При использовании литерала объекта или new Ключевое слово у вас нет никакого контроля над этим (однако, вы можете перезаписать их, конечно).
  2. Если мы хотим иметь прототип new Ключевое слово вызывает функцию конструктора. С Object.create() нет необходимости вызывать или даже объявлять функцию конструктора.
  3. Это может быть полезным инструментом, когда вы хотите создавать объекты очень динамично. Мы можем создать функцию фабрики объектов, которая создает объекты с разными прототипами в зависимости от полученных аргументов.

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

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

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

new Оператор

  • Это используется для создания объекта из функции-конструктора
  • В new ключевые слова также выполняет функцию конструктора
function Car() {
  console.log(this) // this points to myCar
  this.name = "Honda";
}

var myCar = new Car()
console.log(myCar) // Car {name: "Honda", constructor: Object}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // true
console.log(myCar.constructor) // function Car() {}
console.log(myCar.constructor === Car) // true
console.log(typeof myCar) // object

Object.create

  • Вы также можете использовать Object.create создать новый объект
  • Но он не выполняет функцию конструктора
  • Object.create используется для создания объекта из другого объекта
const Car = {
  name: "Honda"
}

var myCar = Object.create(Car)
console.log(myCar) // Object {}
console.log(myCar.name) // Honda
console.log(myCar instanceof Car) // ERROR
console.log(myCar.constructor) // Anonymous function object
console.log(myCar.constructor === Car) // false
console.log(typeof myCar) // object

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 вызовет функцию X, Если программист хочет new Y звонить Xбыло бы более правильно сделать в конструкторе Y с X.call(this, u)

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

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

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

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

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

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

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');

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

Я все еще использую 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

Обратная связь приветствуется.

Другие вопросы по тегам