Using Object.create
is certainly a valid approach, but the example itself seems to me a bit misleading regarding the inner workings of Object.create
. The blog post does a really good job of summarizing the different ways to create objects in javascript, but I don't think the example for Object.create
gives a good idea of how it works, which is more similar to the new/constructor
approach than it may seem.
Object.create
allows to create an object based on a prototype
, but without a constructor
. This means that the prototype chain
of the created object isn't dependent on a constructor
, (which is why it may be simpler to follow, this prototype linked via the constructor is not very straightforward or easy to follow). But Object.create
still creates a prototype chain
, in the same way new
does.
So in your example, when you define name
in human
for example here:
var human = {
name: '',
And then when you create jane
:
var jane = Object.create(female, {
name: {value: 'Jane'}
You're not really assigning a value to the name property
you defined in human
. You're in fact adding a property to jane. But human.name
still is a property in prototype chain
of jane
. It works because javascript will follow the prototype chain to find the first matching property, but human.name
is still somehow linked to jane
.
See here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
Same thing happens if you use a constructor:
var Person = function(gender, name) {
this.name = name;
}
var newPerson = new Person();
And same goes for sayPlanet
function.
It's a valid approach, but can lead to strange behaviors. For example, you could decide to modifiy sayPlanet
for all humans by assigning it this way:
human.sayPlanet = function(){console.log('new sayPlanet')}
This would then work for all humans
, except the ones for which you have given a sayPlanet
property of their own. Which in your case may be the expected result. But still, you have to see if sayPlanet
really should be a property of human.
With gender
, it's applied in human
, and in male
and female
. So changing human.gender
wouldn't work on any one. But it's still a property of human
, which is a bit confusing when you want to work with these objects. You basically have a property that is defined, that is writable, but that when changed has no effect at all. It's mainly an indication as to which property you need to add to your humans instances or somewhere in the prototype chain. Again, it seems to be used a lot, but when explained with this kind of examples, it somehow gives the impression that Object.create
just combines properties, but it's not what it does.
In the end, you need to chose if you want to work with prototypes
or not. If not, then Functional inheritance is probably the best way. Then each object is different and has its own set of properties, that you can initialize, and you don't have to worry about prototypes
.
If you want to use prototypes
, then you can use new/constructor
or Object.create
approach. But Object.create
will create a prototype chain the same way new
does, it just gets rid of the constructors.
A small example of how Object.create
and new
share some behaviors:
var human = {
name: '',
gender: '',
planetOfBirth: 'Earth',
sayGender: function () {
console.log(this.name + ' says my gender is ' + this.gender);
},
sayPlanet: function () {
console.log(this.name + ' was born on ' + this.planetOfBirth);
}
};
var male = Object.create(human, {
gender: {value: 'Male'}
});
var female = Object.create(human, {
gender: {value: 'Female'}
});
var david = Object.create(male, {
name: {value: 'David'},
planetOfBirth: {value: 'Mars', configurable: true}
});
var jane = Object.create(female, {
name: {value: 'Jane'},
sayPlanet: {value: function(){
console.log("something different");
}, writable: true, enumerable: true, configurable: true
}
});
var Male = function(name){ // in this case the real constructor is female or male. Name is the only property that 'needs' to be initialized
this.name = name;
this.planetOfBirth = 'Jupiter';
}
Male.prototype = Object.create(male);
var john = new Male('John')
david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars
jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth
john.sayGender(); // John says my gender is Female
john.sayPlanet(); // John was born on Earth
delete david.planetOfBirth; //just to show how human properties will still be in the chain even if you delete them
delete john.name; // It's true also if you use new.
delete jane.sayPlanet;
console.log('\n','----after deleting properties----');
david.sayPlanet();
jane.sayPlanet();
john.sayGender();
human.planetOfBirth = 'Venus'; // This will apply to all humans, even the ones already created
console.log('\n','----after assigning planetOfBirth on human----');
david.sayPlanet();
jane.sayPlanet();
john.sayPlanet(); // John still has planetOfBirth as its own property, since it wasn't deleted
delete john.planetOfBirth;
console.log('\n','----after deleting john planetOfBirth----');
john.sayPlanet(); // But it's still there
And in fact (just to be more confusing), some people combine Object.create
with new/constructor
or with Functional inheritance. Something like this for example:
https://john-dugan.com/object-oriented-javascript-pattern-comparison/#oloo-pattern
Applied to your example it would give something like this:
var human = {
planetOfBirth: 'Earth',
sayGender: function () {
console.log(this.name + ' says my gender is ' + this.gender);
},
sayPlanet: function () {
console.log(this.name + ' was born on ' + this.planetOfBirth);
},
init: function(name){
this.name = name;
}
};
var male = Object.create(human, {
gender: {value: 'Male'} // This is part of male/female prototype and can't be written, which seems logical
});
var female = Object.create(human, {
gender: {value: 'Female'}
});
var david = Object.create(male).init('David');
david.planetOfBirth = 'Mars';
var jane = Object.create(female).init('Jane')
jane.sayPlanet = function(){console.log('something different')};
var john = Object.create(male).init('John');
john.planetOfBirth = 'Jupiter';
david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars
jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth
john.sayGender(); // John says my gender is Female
john.sayPlanet(); // John was born on Earth
delete david.planetOfBirth; // Overridden properties will still exists after delete, but not the others.
delete john.name;
delete jane.sayPlanet;
console.log('\n','----after deleting properties----');
david.sayPlanet();
jane.sayPlanet();
john.sayPlanet();
human.planetOfBirth = 'Venus'; // This will apply to all humans, even the ones already created.
// But not for humans woth overridden planetOfBirth.
console.log('\n','----after assigning planetOfBirth on human----');
david.sayPlanet();
jane.sayPlanet();
john.sayPlanet(); // John's name is now undefinded
delete john.planetOfBirth;
console.log('\n','----after deleting john planetOfBirth----');
john.sayPlanet(); //
Not necessarily better, but it works as well, and in my opinion has certain advantages.
In any case, as others said, there doesn't seem to be a standard or default way to do this.