Making inheritance make sense with Object.create()
JavaScript itself is conflicted about its prototypal nature. In a prototypal system, objects inherit from objects. JavaScript, however, lacks an operator that performs that operation. Instead it has a new operator...Prototypal Inheritance in JavaScript, Douglas Crockford
The goal is to create a new object with a given prototype link. The status quo goes something like this:
function Person(name) {
this.name = name;
}
Person.prototype = { species: 'human' };
var finn = new Person('Finn');
console.log(finn); // { name: 'Finn' }
console.log(finn.species); // human
var fiona = new Person('Fiona');
console.log(fiona); // { name: 'Fiona' }
console.log(fiona.species); // human
See that fancy new
operator? If you aren't already familiar with it, good luck understanding what's going on here. What does this
refer to? What does Person()
return? What is this prototype
property that's added to Person
and how is finn
linked to it?
The pattern for creating objects with the new
operator has always seemed really strange to me, like all the rules of JavaScript are suddenly suspended. A bunch of magic happens and poof—you've got yourself an object and somehow Person.prototype
is its prototype. Wait, what? No wonder people are confused about prototypal inheritance—none of it is ever explicitly coded!
This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript.Prototypal Inheritance in JavaScript, Douglas Crockford
The reality is that this paradigm sucks, and there's a better way to create objects with much less magic.
ECMAScript 5 (released in 2009) made Object.create()
available as an alternative to the new
syntax. You pass it any object (we'll call ours personProto
) and it returns a new object with personProto
as its prototype. The new
confusion is no longer necessary; we can rename Person()
to the more descriptive newPerson()
and lose the this
:
var personProto = { species: 'human' };
function newPerson(name) {
var person = Object.create(personProto);
person.name = name;
return person;
}
var finn = newPerson('Finn');
console.log(finn); // { name: 'Finn' }
console.log(finn.species); // human
var fiona = newPerson('Fiona');
console.log(fiona); // { name: 'Fiona' }
console.log(fiona.species); // human
Ah, suddenly it all makes sense! You define a function that when called creates an object with a specified prototype, assigns that object whatever properties you want, and finally returns it. It does the exact same thing as the first example, except without the hand-waving. This is essentially a native and better-looking implementation of Crockford's abstraction:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
It takes an old object as a parameter and returns an empty new object that inherits from the old one. If we attempt to obtain a member from the new object, and it lacks that key, then the old object will supply the member. Objects inherit from objects. What could be more object oriented than that?Prototypal Inheritance in JavaScript, Douglas Crockford
Browser compatibility is actually pretty great at this point. If you need to support legacy browsers, you can include an ES5 shim.
I don't know what this means for new
, if it is going obsolete or if there's some important use-case I'm overlooking. The least you can say is that Object.create()
is a good addition to the language, and a reaffirmation of JavaScript's simple, prototypal nature.