6

Я думаю, что разница в моей голове, но я просто хочу быть уверен.В Javascript разница между «Object.create» и «new»

На странице Дуглас Крокфорд Prototypal Inheritance in JavaScript, он говорит

В прототипных системе, объекты наследуют от объектов. JavaScript, , однако, отсутствует оператор, который выполняет эту операцию. Вместо этого у него есть новый оператор, так что новый f() создает новый объект, который наследует от f.prototype.

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

Вот тест:

var Person = function(name, age) { 
     this.name = name; 
     this.age = age; 
} 
Person.prototype.toString = function(){return this.name + ', ' + this.age}; 

// The old way... 
var jim = new Person("Jim",13); 
for (n in jim) { 
    if (jim.hasOwnProperty(n)) { 
     console.log(n); 
    } 
} 
// This will output 'name' and 'age'. 

// The pure way... 
var tim = Object.create(new Person("Tim",14)); 
for (n in tim) { 
    if (tim.hasOwnProperty(n)) { 
     console.log(n); 
    } 
} 
// This will output nothing because all the members belong to the prototype. 
// If I remove the hasOwnProperty check then 'name' and 'age' will be output. 

ли я понимаю правильно, что разница только становится очевидным при тестировании членов на самом объекте?

+3

см. Http://stackoverflow.com/questions/4166616/understanding-the-difference-between-object-create-and-new-somefunction-in-j –

+0

Я видел этот вопрос вчера, я думаю, но ответ мне не был ясен. Теперь, когда я выполнил свои тесты и набрал свой вопрос, это понятно! – Jules

ответ

3

Ваши предположения верны, но есть еще одна картина, о которой Дуглас мало о чем говорит - прототип можно использовать и для свойств. Ваш человек класс мог быть написан как:

var Person = function(name, age) { 
    this.name = name; 
    this.age = age; 
} 
Person.prototype.name = null; //default value if you don't init in ctor 
Person.prototype.age = null; 
Person.prototype.gender = "male"; 
Person.prototype.toString = function(){return this.name + ', ' + this.age;}; 

В этом случае итерация свойств экземпляра этого класса, как вы в вашем примере, не будет генерировать никакого вывода для свойства «гендер».

EDIT 1: Присвоение имени и возраста в конструкторе делать делают свойства видимым hasOwnProperty (спасибо @ Matt за напоминание мне об этом). Непризнанное гендерное свойство не будет видно, пока кто-то не установит его в экземпляре.

EDIT 2: Для дальнейшего добавить к этому, я представляю альтернативный тип наследования - тот, который я лично использовал для очень крупных проектов:

var inherits = function(childCtor, parentCtor) { 
    function tempCtor() {}; 
    tempCtor.prototype = parentCtor.prototype; 
    childCtor.superclass = parentCtor.prototype; 
    childCtor.prototype = new tempCtor(); 
    childCtor.prototype.constructor = childCtor; 
}; 

var Person = function(name){ 
    this.name = name; 
} 
Person.prototype.name = ""; 
Person.prototype.toString = function(){ 
    return "My name is " + this.name; 
} 

var OldPerson = function(name, age){ 
    OldPerson.superclass.constructor.call(this); 
    this.age = age 
}; 
inherits(OldPerson, Person); 
OldPerson.prototype.age = 0; 
OldPerson.prototype.toString = function(){ 
    var oldString = OldPerson.superclass.toString.call(this); 
    return oldString + " and my age is " + this.age; 
} 

Это довольно распространенный шаблон с небольшим поворотом - родительский класс присоединяется к дочернему элементу через свойство «суперкласс», позволяющее вам получить доступ к методам/свойствам, переопределенным дочерним элементом. Технически вы можете заменить OldPerson.superclass на Person, однако это не идеально. Если вы когда-либо изменили OldPerson на наследование от другого класса, кроме Person, вам также придется обновлять все ссылки на Person.

EDIT 3: Просто довести этот полный круг, вот версия функции «наследуется», который использует Object.create и функции точно так же, как я уже описал:

var inherits = function(childCtor, parentCtor) { 
    childCtor.prototype = Object.create(parentCtor.prototype); 
    childCtor.superclass = parentCtor.prototype; 
}; 
3

EDIT : Этот ответ был первоначально ответом на ответ @ jordancpaul, который он с тех пор исправил.Я оставлю часть моего ответа, которая поможет объяснить важную разницу между свойствами прототипа и свойствами экземпляра:

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

Person.prototype.favoriteColors = []; //Do not do this!

Теперь, если вы создаете новый экземпляр Person, используя либо Object.create или new, он не работает, как и следовало ожидать ...

var jim = new Person("Jim",13); 
jim.favoriteColors.push('red'); 
var tim = new Person("Tim",14); 
tim.favoriteColors.push('blue'); 

console.log(tim.favoriteColors); //outputs an array containing red AND blue! 

Это Безразлично» t означает, что вы никогда не сможете объявить свойства прототипа, но если вы это сделаете, вы и каждый разработчик, который работает над вашим кодом, должны знать об этой ловушке. В таком случае, как это, если вы предпочитаете объявляя свойства на прототипе по какой-либо причине, вы можете сделать:

Person.prototype.favoriteColors = null

и инициализировать его в пустой массив в конструкторе:

var Person = function(name, age) { 
    ... 
    this.favoriteColors = []; 
} 

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

Безопасный способ только объявить методы прототипа и всегда объявлять свойства в конструкторе.

Во всяком случае, речь шла о Object.create ...

Первый аргумент, передаваемый Object.create устанавливается в качестве прототипа нового экземпляра. Лучше использовать:

var person = { 
    initialize: function(name, age) { 
     this.name = name; 
     this.age = age; 
     return this; 
    }, 

    toString: function() { 
     return this.name + ', ' + this.age; 
    } 
}; 

var tim = Object.create(person).initialize("Tim",14); 

Теперь результат будет таким же, как в первом примере.

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

Лично у меня смешанные чувства по поводу подхода Object.create; это очень приятно для наследования из-за второго параметра, который вы можете использовать для добавления дополнительных свойств в существующий прототип, но он также более подробный и делает так, что проверки экземпляра больше не работают (альтернативой в этом примере будет проверка person.isPrototypeOf(tim)) ,

Основная причина, почему я говорю Object.create многословен из-за второго параметра, но есть некоторые полезные библиотеки там, что адрес, который:

https://github.com/Gozala/selfish

https://github.com/Raynos/pd

(и другие)

Надеюсь, это было более познавательно, чем запутывание!

+1

Я просто написал * очень * простую библиотеку, чтобы помочь с наследованием, которая следует за традиционным методом прототипального наследования, но при расширении «классов» использует Object.create: https://github.com/mbrowne/simpleoo. js –

+0

Yup, падшая жертва 'Class.prototype.property = []' pitfall сама. Я обновил свой исходный пост немного подробнее - возможно, вы уже видели этот шаблон. – jordancpaul

Смежные вопросы