Понимание создания объекта-прототипа с помощью "Object.create()" вместо ключевого слова "новое"

Я пришел к коду, содержащему эти строки

var data = function() {
    function Metadata() { /*some initialization here*/ }

    Metadata.prototype = Object.create(Backend.prototype);
    Metadata.prototype.constructor = Metadata;

    return Metadata;
}

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

var d = new data()

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

Metadata.prototype = Object.create(Backend.prototype);
Metadata.prototype.constructor = Metadata;

Что они делают? Нужны ли они? И в чем разница между Object.create и new?

Ответ 1

JavaScript - это язык, основанный на прототипе. Это означает, что он не использует ключевое слово класса, как на других языках. Вместо этого JavaScript использует функции как классы. В вашем примере переменная данных может быть ассимилирована классу:

var Data = function() { ... }

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

var data = new Data()

Так как ECMA Script 6, мы можем использовать метод создания экземпляров Object.create(), который создает непосвященный объект с указанным объектом и свойствами прототипа. Он принимает аргумент объекта, который должен быть прототипом вновь созданного объекта. (Он также скопирует конструктор)

Итак, следующие строки - это способ сделать метаданные, расширяющие объект Backend, и сохранить свой собственный конструктор:

// Metadata extends Backend
Metadata.prototype = Object.create(Backend.prototype);
Metadata.prototype.constructor = Metadata;

Но этот код не совсем эквивалентен Metadata.prototype = new Backend();. См. Этот пример:

//Base class
var Backend = function(){ this.publicProperty='SomeValue'; }

//Extension class 1
var Metadata1 = function() { }
Metadata1.prototype = Object.create(Backend.prototype);
Metadata1.prototype.constructor = Metadata1;

//Extension class 2
var Metadata2 = function() { }
Metadata2.prototype = new Backend();


/*
 *  Then the results are different (see code snippet at the end of this post)
 */
//result1 = 'undefined'
var data1 = new Metadata1();
var result1 = data1.publicProperty;

//result2 = 'SomeValue'
var data2 = new Metadata2();
var result2 = data2.publicProperty;

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

Еще одно отличие состоит в том, что при Object.create вы можете создать объект, который не наследует ничего (Object.create(null)).
Если вы сделаете Metadata.prototype = null, новый созданный объект наследует от Object.prototype


Примечание. В более раннем браузере (IE8 и ниже) вы можете использовать этот эквивалентный код для Object.create:

Object.create = function(o){
    function F(){}
    F.prototype=o;
    return new F();
}

Вот фрагмент рабочего кода, который показывает различия между двумя подходами

//Base class
var Backend = function(){ this.publicProperty='SomeValue'; }

//Extension class 1
var Metadata1 = function() { }
Metadata1.prototype = Object.create(Backend.prototype);
Metadata1.prototype.constructor = Metadata1;

//Extension class 2
var Metadata2 = function() { }
Metadata2.prototype = new Backend();


//result: 'undefined'
var data1 = new Metadata1();
$("#result1").text("result1: " +  (typeof data1.publicProperty=='undefined' ? 'undefined' : data1.publicProperty));

//result: 'SomeValue'
var data2 = new Metadata2();
$("#result2").text("result2: " +  (typeof data2.publicProperty=='undefined' ? 'undefined' : data2.publicProperty));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result1"></div>
<div id="result2"></div>

Ответ 2

Эти две строки являются прототипом способа расширения Metadata Backend.

Классы в JavaScript определяются как функции. Если вы определяете prototype для функции, вы можете использовать ключевое слово new (или Object.create) для создания экземпляра класса. Функции/свойства, которые находятся на прототипе, будут в новом экземпляре создаваемого вами класса.

В приведенном выше коде Metadata.prototype устанавливается экземпляр Backend, поэтому Metadata.prototype наследует вызов методов/свойств на Backend.prototype.

Вы можете найти более Наследование и цепочка прототипов.

Ответ 3

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

И эти две строки используются для выражения наследования и для того, чтобы сделать Metadata extends Backend в строке:

Metadata.prototype = Object.create(Backend.prototype);

Мы определяем прототип объекта Metadata, который должен быть прототипом вновь созданного объекта.

В этой строке:

Metadata.prototype.constructor = Metadata;

Определим конструктор Metadata, который будет использоваться для создания новых экземпляров Metadata.

И наследование можно протестировать следующим образом:

var meta = new Metadata(); 

console.log('Is meta an instance of Metadata? ' + (meta instanceof Metadata)); // true
console.log('Is meta an instance of Backend? ' + (meta instanceof Backend)); // true

И вы можете найти больше об этом с другим примером в Object.create() documentaion здесь.

Ответ 4

Object.create - это метод ES6, он создает объект с данным объектом в качестве прототипа.

Metadata.prototype = Object.create(Backend.prototype);

Таким образом, указанная выше строка означает Metadata наследует все свойства и методы из Backend. Это как-то похоже на следующую строку перед ES6:

Metadata.prototype = new Backend();

Однако Metadata также наследует свойство constructor из Backend:

var metaData = new Metadata();
metaData.constructor; // function Backend()

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

Metadata.prototype.constructor = Metadata;

var metaData = new Metadata();
metaData.constructor; // function Metadata()

Обратите внимание, что это не путается с оператором instanceof:

metaData instanceof Metadata // true
metaData instanceof Backend  // true