prototype

Introduction ------------- The prototype property is an object created by JavaScript for every Function() instance. Specifically, it links object instances created with the new keyword back to the constructor function that created them. This is done so that instances can share, or inherit, common methods and properties. Importantly, the sharing occurs during property lookup. Consider an example below. An array "myArray" is created from the Array() constructor, and then join() method is invoked. var myArray = new Array('foo', 'bar'); console.log(myArray.join()); // logs 'foo, bar' The join() method is not defined as a property of the myArray object instance, but somehow we have access to join() as if it were. This method is defined somewhere, but where? Well, it is defined as a property of the Array() constructor's prototype property. Since join() is not found within the array object instance, JavaScript looks up the prototype chain for a method called join(). prototype Property ------------------- The functions in JavaScript are objects and they contain methods and properties. Some of the methods that you are already familiar with are apply() and call() and some of the properties are length and constructor. Another property of the function objects is prototype. If you define a simple function foo() you can access its properties as you would do with any other object. function foo(a, b){return a * b;} foo.length foo.constructor Function() prototype is a property that gets created as soon as you define the function. Its initial value is an empty object. typeof foo.prototype "object" It's as if you added this property yourself like this. foo.prototype = {} You can augment(add) this empty object with properties and methods. They won't have any effect of the foo() function itself; they'll only be used when you use foo() as a constructor. Adding Methods and Properties Using the Prototype -------------------------------------------------- Augmenting (adding methods and properties to) the new object(created by using constructor function) is the way to add functionality to the object being created. Let's take a look at the constructor function Gadget() which uses this to add two properties and one method to the objects it creates. function Gadget(name, color) { this.name = name; this.color = color; this.whatAreYou = function(){ return 'I am a ' + this.color + ' ' + this.name; } } Adding methods and properties to the prototype property of the constructor function is another way to add functionality to the objects this constructor produces. Let's add two more properties, price and rating, and a getInfo() method. Since prototype contains an object, you can just keep adding to it like this: Gadget.prototype.price = 100; Gadget.prototype.rating = 3; Gadget.prototype.getInfo = function() { return 'Rating: ' + this.rating + ', price: ' + this.price; }; Instead of adding to the prototype object, another way to achieve the above result is to overwrite the prototype completely, replacing it with an object of your choice: Gadget.prototype = { price: 100, rating: 3, getInfo: function() { return 'Rating: ' + this.rating + ', price: ' + this.price; } }; Using the Prototype's Methods and Properties --------------------------------------------- All the methods and properties you have added to the prototype are directly available as soon as you create a new object using the constructor. If you create a newtoy object using the Gadget() constructor, you can access all the methods and properties already defined. var newtoy = new Gadget('webcam', 'black'); newtoy.name; "webcam" newtoy.color; "black" newtoy.whatAreYou(); "I am a black webcam" newtoy.price; 100 newtoy.rating; 3 newtoy.getInfo(); "Rating: 3, price: 100" It's important to note that the prototype is "live". Objects are passed by reference in JavaScript, and therefore the prototype is not copied with every new object instance. What does this mean in practice? It means that you can modify the prototype at any time and all objects (even those created before the modification) will inherit the changes. Let's continue the example, adding a new method to the prototype: Gadget.prototype.get = function(what) { return this[what]; }; Even though newtoy was created before the get() method was defined, newtoy will still have access to the new method: newtoy.get('price'); 100 newtoy.get('color'); "black" Own Properties versus prototype Properties ------------------------------------------- In the example above getInfo() used this internally to address the object. It could've also used Gadget.prototype to achieve the same result: Gadget.prototype.getInfo = function() { return 'Rating: ' + Gadget.prototype.rating + ', price: ' + Gadget.prototype.price; }; What's is the difference? To answer this question, let's examine how the prototype works in more detail. Let's again take our newtoy object: var newtoy = new Gadget('webcam', 'black'); When you try to access a property of newtoy, say newtoy.name the JavaScript engine will look through all of the properties of the object searching for one called name and, if it finds it, will return its value. newtoy.name "webcam" What if you try to access the rating property? The JavaScript engine will examine all of the properties of newtoy and will not find the one called rating. Then the script engine will identify the prototype of the constructor function used to create this object (same as if you do newtoy.constructor.prototype). If the property is found in the prototype, this property is used. newtoy.rating 3 This would be the same as if you accessed the prototype directly. Every object has a constructor property, which is a reference to the function that created the object, so in our case. newtoy.constructor Gadget(name, color) newtoy.constructor.prototype.rating 3 Now let's take this lookup one step further. Every object has a constructor. The prototype is an object, so it must have a constructor too. Which in turn has a prototype. In other words you can do. newtoy.constructor.prototype.constructor Gadget(name, color) newtoy.constructor.prototype.constructor.prototype Object price=100 rating=3 This might go on for a while, depending on how long the prototype chain is, but you eventually end up with the built-in Object() object, which is the highest-level parent. In practice, this means that if you try newtoy.toString() and newtoy doesn't have an own toString() method and its prototype doesn't either, in the end you'll get the Object's toString() newtoy.toString() "[object Object]" Enumerating Properties ------------------------- If you want to list all properties of an object, you can use a for-in loop. In Chapter 2, you saw how you could loop through all the elements of an array: var a = [1, 2, 3]; for (var i in a) { console.log(a[i]); } Arrays are objects, so you can expect that the for-in loop works for objects too: var o = {p1: 1, p2: 2}; for (var i in o) { console.log(i + '=' + o[i]); } This produces: p1=1 p2=2 There are some details to be aware of: Not all properties show up in a for-in loop. For example, the length (for arrays) and constructor properties will not show up. The properties that do show up are called enumerable. You can check which ones are enumerable with the help of the propertyIsEnumerable() method that every object provides. Prototypes that come through the prototype chain will also show up, provided they are enumerable. You can check if a property is an own property versus prototype's using the hasOwnProperty() method. propertyIsEnumerable() will return false for all of the prototype's properties, even those that are enumerable and will show up in the for-in loop. Let's see these methods in action. Take this simplified version of Gadget(): function Gadget(name, color) { this.name = name; this.color = color; this.someMethod = function(){return 1;} } Gadget.prototype.price = 100; Gadget.prototype.rating = 3; • • • Prototype [ 156 ] Creating a new object: var newtoy = new Gadget('webcam', 'black'); Now if you loop using a for-in, you see of the object's all properties, including those that come from the prototype: for (var prop in newtoy) { console.log(prop + ' = ' + newtoy[prop]); } The result also contains the object's methods (as methods are just properties that happen to be functions): name = webcam color = black someMethod = function () { return 1; } price = 100 rating = 3 If you want to distinguish between the object's own properties versus the prototype's properties, use hasOwnProperty(). Try first: newtoy.hasOwnProperty('name') true newtoy.hasOwnProperty('price') false Let's loop again, but showing only own properties: for (var prop in newtoy) { if (newtoy.hasOwnProperty(prop)) { console.log(prop + '=' + newtoy[prop]); } } The result: name=webcam color=black someMethod=function () { return 1; } Now let's try propertyIsEnumerable(). This method returns true for own properties that are not built-in: newtoy.propertyIsEnumerable('name') true Most built-in properties and methods are not enumerable: newtoy.propertyIsEnumerable('constructor') false Any properties coming down the prototype chain are not enumerable: newtoy.propertyIsEnumerable('price') false Note, however, that such properties are enumerable if you reach the object contained in the prototype and invoke its propertyIsEnumerable(). newtoy.constructor.prototype.propertyIsEnumerable('price') true isPrototypeOf ---------------- Every object gets the isPrototypeOf() method. This method tells you whether that specific object is used as a prototype of another object. Let's take a simple object monkey. var monkey = { hair: true, feeds: 'bananas', breathes: 'air' }; Now let's create a Human() constructor function and set its prototype property to point to monkey. function Human(name) { this.name = name; } Human.prototype = monkey; Now if you create a new Human object called george and ask: "Is monkey george's prototype?", you'll get true. var george = new Human('George'); monkey.isPrototypeOf(george) true The Secret __proto__ Link ------------------------- As you know already, the prototype property will be consulted when you try to access a property that does not exist in the current object. Let's again have an object called monkey and use it as a prototype when creating objects with the Human() constructor. var monkey = { feeds: 'bananas', breathes: 'air' }; function Human() {} Human.prototype = monkey; Now let's create a developer object and give it some properties: var developer = new Human(); developer.feeds = 'pizza'; developer.hacks = 'JavaScript'; Now let's consult some of the properties. hacks is a property of the developer object. developer.hacks "JavaScript" feeds could also be found in the object. developer.feeds "pizza" breathes doesn't exist as a property of the developer object, so the prototype is looked up, as if there is a secret link pointing to the prototype object. developer.breathes "air" Can you get from the developer object to the prototype object? Well, you could, using constructor as the middleman, so having something like developer.constructor.prototype should point to monkey. The problem is that this is not very reliable, because constructor is more for informational purposes and can easily be overwritten at any time. You can overwrite it with something that's not even an object and this will not affect the normal functioning of the prototype chain. Let's set the constructor property to some string: developer.constructor = 'junk' "junk" It seems like the prototype is now all messed up: typeof developer.constructor.prototype "undefined" ...but it isn't, because the developer still breathes "air": developer.breathes "air" This shows that the secret link to the prototype still exists. The secret link is exposed in Firefox as the __proto__ property (the word "proto" with two underscores before and two after). developer.__proto__ Object feeds=bananas breathes=air You can use this secret property for learning purposes but it's not a good idea to use it in your real scripts, because it does not exist in Internet Explorer, so your scripts won't be portable. For example, let's say you have created a number of objects with monkey as a prototype and now you want to change something in all objects. You can change monkey and all instances will inherit the change: monkey.test = 1 1 developer.test 1 __proto__ is not the same as prototype. __proto__ is a property of the instances, whereas prototype is a property of the constructor functions. typeof developer.__proto__ "object" typeof developer.prototype "undefined" Once again, you should use __proto__ only for learning or debugging purposes. Augmenting Built-in Objects The built-in objects such as the constructor functions Array, String, and even Object, and Function can be augmented through their prototypes, which means that you can, for example, add new methods to the Array prototype and in this way make them available to all arrays. Let's do this. In PHP there is a function called in_array() that tells you if a value exists in an array. In JavaScript there is no inArray() method, so let's implement it and add it to Array.prototype. Array.prototype.inArray = function(needle) { for (var i = 0, len = this.length; i < len; i++) { if (this[i] === needle) { return true; } } return false; } Now all arrays will have the new method. Let's test: var a = ['red', 'green', 'blue']; a.inArray('red'); true a.inArray('yellow'); false That was nice and easy! Let's do it again. Imagine your application often needs to reverse strings and you feel there should be a built-in reverse() method for string objects. After all, arrays have reverse(). You can easily add this reverse() method to the String prototype, borrowing Array.prototype.reverse() (there was a similar exercise at the end of Chapter 4). String.prototype.reverse = function() { return Array.prototype.reverse.apply(this.split('')).join(''); } This code uses split() to create an array from a string, then calls the reverse() method on this array, which produces a reversed array. The result array is turned back into a string using join(). Let's test the new method: "Stoyan".reverse(); "nayotS" Augmenting Built-in Objects—Discussion -------------------------------------- Augmenting built-in objects through the prototype is a very powerful technique and you can use it to shape JavaScript any way you like. Because of its power, you should always thoroughly consider your options before using this approach. Take the popular JavaScript library called Prototype. Its creator liked this approach so much that he even named the library after it. Using this library, you can work with JavaScript using methods very similar to the Ruby language. YUI (Yahoo! User Interface) library is another popular JavaScript library. Its creators are on the exact opposite side of the spectrum: they won't modify the built-in objects in any way. The reason is that once you know JavaScript, you're expecting it to work the same way, no matter which library you're using. Modifying core objects could only confuse the user of the library and create unexpected errors. The fact is that JavaScript changes and browsers come up with new versions that support more features. What you consider a missing feature today and decide to augment a prototype for, might be a built-in method tomorrow. In this case, your method is no longer needed. However, what if you have already written a lot of code that uses the method and your method is slightly different from the new built-in implementation? The very least you can do is check if the method exists before implementing it. Our last example should read something like: if (!String.prototype.reverse) { String.prototype.reverse = function() { return Array.prototype.reverse.apply(this.split('')).join(''); } } Best Practice If you decide to augment the prototype of built-in objects with a new property, do check for existence of the new property first. Some Prototype gotchas Here are two interesting behaviors to consider when dealing with prototypes: The prototype chain is live with the exception of when you completely replace the prototype object prototype.constructor is not reliable Creating a simple constructor function and two objects: function Dog(){this.tail = true;} var benji = new Dog(); var rusty = new Dog(); Even after you create the objects, you can still add properties to the prototype and the objects will have access to the new properties. Let's throw in the method say(): Dog.prototype.say = function(){return 'Woof!';} Both objects have access to the new method: benji.say(); "Woof!" rusty.say(); "Woof!" Up to this point if you consult your objects, asking which constructor function was used to create them, they'll report it correctly. benji.constructor; Dog() rusty.constructor; Dog() It is interesting to note that if you ask what is the constructor of the prototype object, you'll also get Dog(), which is not quite correct. The prototype is just a normal object created with Object(). It doesn't have any of the properties of an object constructed with Dog(). benji.constructor.prototype.constructor Dog() typeof benji.constructor.prototype.tail "undefined" Now let's completely overwrite the prototype object with a brand new object: Dog.prototype = {paws: 4, hair: true}; It turns out that our old objects do not get access to the new prototype's properties; they still keep the secret link pointing to the old prototype object: typeof benji.paws "undefined" benji.say() "Woof!" typeof benji.__proto__.say "function" typeof benji.__proto__.paws "undefined" Any new objects you create from now on will use the updated prototype: var lucy = new Dog(); lucy.say() TypeError: lucy.say is not a function lucy.paws 4 The secret __proto__ link points to the new prototype object: typeof lucy.__proto__.say "undefined" typeof lucy.__proto__.paws "number" Now the constructor property of the new objects no longer reports correctly. It should point to Dog(), but instead it points to Object(). lucy.constructor Object() benji.constructor Dog() The most confusing part is when you look up the prototype of the constructor: typeof lucy.constructor.prototype.paws "undefined" typeof benji.constructor.prototype.paws "number" The following would have fixed all of the unexpected behavior described above: Dog.prototype = {paws: 4, hair: true}; Dog.prototype.constructor = Dog; Best Practice When you overwrite the prototype, it is a good idea to reset the constructor property.
Follow Us
https://www.facebook.com/Rookie-Nerd-638990322793530 https://twitter.com/RookieNerdTutor https://plus.google.com/b/117136517396468545840 #
Contents