Native JavaScript objects store their methods in prototypes.
For example, When a new object is created, it doesn’t have anything. Then how would toString
work?
var obj = { } alert( obj.toString() )
That’s because obj = {}
is a short for obj = new Object
, where Object
is a native JavaScript constructor function.
..And the object, made by new Object
naturally receives object.__proto__
== Object.prototype, where Object.prototype
is a native object with Object.prototype.toString
property (and not only).
So, all properties of Object.prototype
become available in objects. That’s why all objects have toString
method.
Same story with Array
, Function
and other objects. Their methods are in Array.prototype
, Function.prototype
etc.
When an object property is accessed, the interpreter searches it:
- In the object itself,
- Then in it’s
__proto__
, - Then in it’s
__proto__.__proto__
etc.
The chain is followed until the property is found or the next __proto__ == null
.
The only object with __proto__ = null
is Object.prototype
. All chains eventually end at Object.prototype
or, in other words, all objects inherit from Object
.
Primitive values like 5
are implicitly converted to objects when a method is called, and the result is again a primitive.
Modifying native prototypes
Native prototypes can be modified. New methods can be introduced.
We could write a method to repeat a string many times (read [here](#new Array) about new Array
syntax):
String.prototype.repeat = function(times) { return new Array(times+1).join(this) } alert( "a".repeat(3) ) // aaa
Same way we could add a method Object.prototype.each(func)
to apply func
to every property.
Object.prototype.each = function(f) { for(var prop in this) { var value = this[prop] f.call(value, prop, value) // calls f(prop, value), this=value } } // Try it: (works wrong!) var obj = { name: 'John', age: 25 } obj.each(function(prop, val) { alert(prop) // name -> age -> (!) each })
As you can see from the comments, the code is wrong. It outputs extra each
property, because for..in
walks the prototype.
Fortunately, this functionality can be fixed if we add hasOwnProperty
check to for..in
.
Object.prototype.each = function(f) { for(var prop in this) { *!* if (Object.prototype.hasOwnProperty(prop)) continue // filter */!* var value = this[prop] f.call(value, prop, value) } } // Now correct var obj = { name: 'John', age: 25 } obj.each(function(prop, val) { alert(prop) // name -> age })
In this case it worked, now the code is correct. But generally we don’t want to put hasOwnProperty
in every loop…
New properties of Object.prototype appear in all for..in
loops. Don’t add them.
Built-in properties and methods are not listed in for..in
, because they have a special [[Enumerable]]
flag set to false
.
In browsers which support modern JavaScript (IE from version 9), this flag can be set for custom properties too.
For other objects, which are not iterated in for..in
loops (Strings
, Functions
etc), there are pro and contra against adding new methods:
- New methods allow to write shorter and cleaner code.
Compare"a".repeat(5)
andstr_repeat("a", 5)
. The first looks better for most people. - New properties may conflict. Imagine different people writing same named method in their libraries. Integration may be hard.
Prototypes are global for all your code, and modifying them is architecturally bad for same reason as introducing new global variables.
- But that’s in theory. In practice, there is a well-known library Prototype.JS which extends prototypes without terrible consequences
Modifying built-in prototypes is generally considered bad, but may be useful to implement modern ECMA-262 5th edition methods in older browsers.
For example, if there is no Object.create
, then add it:
if (!Object.create) { Object.create = function(proto) { function F() {} F.prototype = proto return new F } }
Inheriting from native objects
Native objects can be inherited. For example, Array.prototype
keeps all methods for new Array
instances.
If we want these methods be accessible in our myArray
, then myArray.__proto__ == Array.prototype
does the trick.
Let’s build a function which creates such objects:
// constructor function MyArray() { this.stringify = function() { return this.join(', ') } } // Make sure that (new MyArray) has the right __proto__ MyArray.prototype = Array.prototype // Test var myArr = new MyArray() myArr.push(1) myArr.push(2) alert( myArr.stringify() ) // 1, 2 alert( myArr.length ) // 2 in all except IE
That works great in all browsers except IE, which doesn’t autoupdate the length
property.
Method borrowing
If you want to use a piece of Array
functionality, it is not required to inherit it.
Instead, you can borrow a method, and then apply
it without inhereting:
var join = Array.prototype.join // same but shorter var join = [].join
.. And call it with a non-standard this
:
var obj = { 0: 'first', 1: 'second', length: 2 } alert( [].join.call(obj, ', ') ) // first, second
Method Array.prototype.join
is described in the specification ES-5, p.15.4.4.5. It doesn’t check for object type. All it does is a joining loop over properties with indicies 0..length-1
, so here it works fine.
Array
methods are often borrowed for manipulating array-like objects.
Summary
- Methods of native objects are stored in their prototypes.
- Native prototypes can be inherited or extended.
- Modifying top-level
Object.prototype
breaksfor..in
loops. Other prototypes are less dangerous to modify, but most developers don’t recommend it.
The notable exception is adding a support for modern methods for older browsers, like Object.create, Object.keys, Function.prototype.bind and others.