- Inheritance, the
__proto__
Object.create
,Object.getPrototypeOf
- The
prototype
- Crossbrowser
Object.create(proto)
hasOwnProperty
- Looping with/without inherited properties
- Summary
In most languages, there are classes and objects. Classes inherit from other classes.
In JavaScript, the inheritance is prototype-based. That means that there are no classes. Instead, an object inherits from another object
Inheritance, the __proto__
When an object rabbit
inherits from another object animal
, in JavaScript that means that there is a special property rabbit.__proto__ = animal
.
When a rabbit
property is accessed, and the interpreter can’t find it in rabbit
, it follows the __proto__
link and searches in animal
.
The examples using __proto__
work only in Chrome/Firefox. That’s for sheer simplicity. Later we’ll go crossbrowser.
var animal = { eats: true } var rabbit = { jumps: true } rabbit.__proto__ = animal // inherit alert(rabbit.eats) // true
The eats
property is actually taken from animal
. Here’s the picture:
If the property is found in rabbit
, then __proto__
is not checked.
For example, when eats
is in the child object, parent is ignored:
var animal = { eats: true } var fedUpRabbit = { eats: false} fedUpRabbit.__proto__ = animal alert(fedUpRabbit.eats) // false
One could put a method into animal and it becomes available in rabbit
:
var animal = { eat: function() { alert( "I'm full" ) this.full = true } } var rabbit = { jump: function() { /* something */ } } rabbit.__proto__ = animal *!* rabbit.eat() */!*
The rabbit.eat()
is executed in two steps:
- First, the interpreter looks up
rabbit.eat
. There’s noeat
inrabbit
object, so it goes torabbit.__proto__
and finds it there. - The function runs with
this = rabbit
.The value of
this
is completely irrelevant to__proto__
. It is set exactly to the object before the dot (see more aboutthis
here).So,
this.full = true
stores the value in therabbit
object:
Look what we’ve got. An object calls parent method, but this
is set to the object itself. That’s the inheritance.
The object, referenced by __proto__
is called a prototype. So, animal
is a prototype of rabbit
.
When a property is read, like this.prop
, the interpreter looks it in the prototype.
When a property is assigned, like this.prop = value
, then there is no reason to search. The property is written directly into the object (here this
).
Same with delete obj.prop
. It only deletes the property on object itself, and leave it intact if it is in the prototype.
If you’ll be reading the specification, what we call __proto__
here is named [[Prototype]]
there. And yes, double square brackets are important, because there’s another property named prototype
.
Object.create
, Object.getPrototypeOf
The __proto__
is a non-standard property, provided by Firefox/Chrome. In other browsers the property still exists internally, but it is hidden.
All modern browsers except Opera (IE from 9) support two standard methods for working with prototypes:
- Object.create(proto[, props])
- Creates an empty object with given
__proto__
:var animal = { eats: true } rabbit = Object.create(animal) alert(rabbit.eats) // true
The code above creates empty
rabbit
with animal__proto__
:Once the rabbit is created, we can add properties to it.
var animal = { eats: true } rabbit = Object.create(animal) rabbit.jumps = true
There second argument
props
is optional and allows to set properties of the new object. Here we skip it, because we are concerned about the inheritance part.
- Object.getPrototypeOf(obj)
- Returns the value of
obj.__proto__
. The method is standard, so it works in browsers which don’t support__proto__
property:var animal = { eats: true } rabbit = Object.create(animal) alert( Object.getPrototypeOf(rabbit) === animal ) // true
So, most modern browsers allow to read the value of
__proto__
, but not to modify it.
The prototype
There is a good and crossbrowser way of setting __proto__
. It requires the use of constructor functions.
Remember, any function creates an object when called with new
. Here’s a simple Rabbit
constructor to start with:
function Rabbit(name) { this.name = name } var rabbit = new Rabbit('John') alert(rabbit.name) // John
A new
function call sets the __proto__
of the object to the value of its prototype
property.
Let’s see how it works. For example, let’s make new Rabbit
objects which inherits from animal
:
var animal = { eats: true } function Rabbit(name) { this.name = name } Rabbit.prototype = animal var rabbit = new Rabbit('John') alert( rabbit.eats ) // true, because rabbit.__proto__ == animal
The code Rabbit.prototype = animal
literally means the following:
”set __proto__ = animal
for all objects created by new Rabbit
”.
What is the result of the code? Why?
function Rabbit(name) { this.name = name } var john = new Rabbit('John') var animal = { eats: true } Rabbit.prototype = animal alert(john.eats)
The result is undefined
, because the prototype
property is set after the new Rabbit
.
As a consequence, john
doesn’t have animal
prototype, and hence there’s no eats
property.
Changing the prototype
has no effect on already-created objects.
Crossbrowser Object.create(proto)
The functionality of Object.create(proto)
is great, because it allows to inherit directly from the given object. It can be emulated by our own method which works in all browsers.
Here’s the function:
function inherit(proto) { function F() {} F.prototype = proto return new F }
The result of inherit(animal)
is identical to Object.create(animal)
: an new empty object, with object.__proto__ = animal
.
For example:
var animal = { eats: true } var rabbit = inherit(animal) alert(rabbit.eats) // true alert(rabbit.hasOwnProperty('eats')) // false, from prototype
Let’s go into details how it works. There are just three lines:
function inherit(proto) { function F() {} // (1) F.prototype = proto // (2) return new F() // (3) }
- A new function
F
is created. The function doesn’t set anything tothis
, sonew F
creates an empty object. F.prototype
is set to givenproto
- The result of
new F
is an empty object with__proto__
set to the value ofF.prototype
, which isproto
. - Bingo! We’ve got an empty object with given
proto
.
The function is widely used in libraries and frameworks.
Your function receives an object with options.
/* options contains menu settings: width, height etc */ function Menu(options) { // ... }
You want to “fix” certain options:
function Menu(options) { options.width = options.width || 300 // set default value // ... }
… But changing the argument may have bad consequences, because options
may be reused in external code.
One way to workaround is to clone options
by copying all properties into a new object, and modify it there. This implies certain overhead.
How to solve this problem effectively using inheritance?
P.S. Options can be modified/added, but never removed.
You can inherit from options
and modify/add options in the child object:
function inherit(proto) { function F() {} F.prototype = proto return new F } function Menu(options) { var opts = inherit(options) opts.width = opts.width || 300 // ... }
All changes will be reflected in the child object only. When Menu
finishes, the external code can continue with unmodified options
.
The P.S. about deletion is important here, because delete opts.width
doesn’t do anything if width
is in prototype.
hasOwnProperty
All objects have hasOwnProperty
method which allows to check if a property belongs to the object or its prototype.
For example:
function Rabbit(name) { this.name = name } Rabbit.prototype = { eats: true } var rabbit = new Rabbit('John') alert( rabbit.hasOwnProperty('eats') ) // false, in prototype alert( rabbit.hasOwnProperty('name') ) // true, in object
Looping with/without inherited properties
A for..in
loop outputs all properties from the object and its prototype:
function Rabbit(name) { this.name = name } Rabbit.prototype = { eats: true } var rabbit = new Rabbit('John') for(var p in rabbit) { alert (p + " = " + rabbit[p]) // outputs both "name" and "eats" }
To get a list of properties, which are not in prototype, filter them through hasOwnProperty
:
function Rabbit(name) { this.name = name } Rabbit.prototype = { eats: true } var rabbit = new Rabbit('John') for(var p in rabbit) { *!* if (!rabbit.hasOwnProperty(p)) continue // filter out "eats" */!* alert (p + " = " + rabbit[p]) // outputs only "name" }
Summary
The inheritance is implemented through a special property __proto__
(named [[Prototype]] in the specification).
- When a property is accessed, and the interpreter can’t find it in the object, it follows the
__proto__
link and searches it there. - The value of
this
for function properties is set to the object, not its prototype. - Assignment
obj.prop = val
and deletiondelete obj.prop
Managing __proto__
:
- Firefox/Chrome give direct access to
obj.__proto__
. Most recent browsers support read-only access withObject.getPrototypeOf(obj)
. - An empty object with given prototype can be created by
Object.create(proto)
in most modern browsers, or the following function in all browsers:function inherit(proto) { function F() {} F.prototype = proto return new F }
- A constructor function sets
__proto__
for objects it creates to the value of itsprototype
property.
Additional methods:
for..in
loop lists properties in the object and its prototype chain.obj.hasOwnProperty(prop)
returnstrue
only if theprop
belongs toobj
, not its prototype.