A look at Prototypal Inheritance in JavaScript and how we can implement it.
Here we have a JavaScript object, describing a single tweet…
12345678
vartweet={"created_at":"Sun, 05 Aug 2012 10:30:12 +0000","from_user":"NME","id":231185161608376321,"id_str":"231185161608376321","profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/1731598749\/twitter3_normal.jpg","text":"Arctic Monkeys set to score a Top 20 hit with their cover of The Beatles' 'Come Together' http:\/\/t.co\/1GvDZXoc"};
…from which we can extract information:
123456789
tweet.text;// Using dot notation.// Returns Artic Monkeys set to score...// Or...tweet['text'];// Using bracket notation.// Also returns Artic Monkeys set to score...
We can derive new information from the tweet, such as the tweet’s full HTTP address. Here’s a short function for the job:
1234567891011
// function 'where' takes a tweet object and returns a URL string,// constructed from the properties of the passed tweet.varwhere=function(tweet){return"https://twitter.com/"+tweet.from_user+"/status/"+tweet.id_str;};// Invoking the functionwhere(tweet);// Returns https://twitter.com/NME/status/231185161608376321
As it stands, we need to pass our object to the function where, but what if we could ask the object directly, like so:
1
tweet.where();
Not a problem. We just need to make the function a property of the object:
Notice above I’m using JavaScript’s this keyword for accessing a_tweets properties.
Working with Multiple Objects
What if we have an array of tweets and we want each object to respond to where?
123456789
// Loop through array of tweets, assigning copy of the// 'where' function to each tweet object.for(varx,length=tweets.length;x<length;x++){tweets[x].where=function(){return"https://twitter.com/"+this.user_name+"/status/"+this.tweet_id;}}tweets[0].where();
Assigning a copy of the ‘where’ function to every tweet isn’t a very efficient use of memory or code. What if we could have one instance of the where function and have all the tweet objects access that instance. This is where Prototypal Inheritance steps forward.
Prototypal Inheritance
Any JavaScript object can have a pointer to an object called its prototype. Multiple objects can all have the same prototype. Crucially, objects inherit properties from their prototypes:
1234567891011121314151617
// Here's an empty object created using Object constructor's 'create' method:varemptyObject=Object.create(null);// When creating new objects with this method, you can set the object's prototype // by passing the aspiring prototype object:// Elixir is a regular JavaScript object, created using object literal notation. It will act as our prototype.varelixir={'immortal':true};// Create a new object passing elixirvarmortalObject=Object.create(elixir);mortalObject.immortal;// Returns true!
mortalObject is inheriting immortality from its prototype object (the elixir object).
Other objects can drink from the elixir of life too:
12345678910111213141516
varordinaryObject=Object.create(elixir);ordinaryObject.immortal;// Returns true// What if the elixir runs dry?elixir.immortal=false;// Now all objects inheriting from elixir have lost their immortality:ordinaryObject.immortal;// Returns falsemortalObject.immortal;// Returns false
With that very brief tour of JavaScript prototypes, we can return to our tweets example. Using prototypal inheritance, we’ll endow the tweets with the ‘where’ method:
cortex will be the prototype object our tweet objects will inherit from:
12345678910111213141516171819202122
// Helper function takes a regular tweet objectfunctiongenerateSmartTweet(tweet){varobj=Object.create(cortex)obj.id=tweet.id_str;obj.text=tweet.text;obj.username=tweet.from_user;returnobj;}// Container for our smart tweetsvarsmart_tweets=[];// Iterate over our array of tweets passing each tweet to generateSmartTweet,// pushing the returned 'smart' tweet to our smart tweets array.for(varx,varlength=tweets.length;x<length;x++){smart_tweets.push(generateSmartTweet(tweets[x]));}smart_tweets[0].where();// Returns https://twitter.com/NME/status/231185161608376321
All tweets whose prototype object is the cortex will immediately have access to any new methods we assign to the cortex. Perhaps we’d like to know how long ago in words the tweet was tweeted? I won’t re-invent the wheel for this method - below is some code borrowed from John Resig (of jQuery fame):
// I'll pop this function into another, more generic object (`Helper`). // It has general appeal, and could be used by other code.varHelper={};Helper.timeAgoInWords=function(time){vardate=newDate((time||"").replace(/-/g,"/").replace(/[TZ]/g," ")),diff=(((newDate()).getTime()-date.getTime())/1000),day_diff=Math.floor(diff/86400);if(isNaN(day_diff)||day_diff<0||day_diff>=31)return;returnday_diff==0&&(diff<60&&"just now"||diff<120&&"1 minute ago"||diff<3600&&Math.floor(diff/60)+" minutes ago"||diff<7200&&"1 hour ago"||diff<86400&&Math.floor(diff/3600)+" hours ago")||day_diff==1&&"Yesterday"||day_diff<7&&day_diff+" days ago"||day_diff<31&&Math.ceil(day_diff/7)+" weeks ago";}}};// Back to the cortex...varcortex={where:function(){return"https://twitter.com/"+this.user_name+"/status/"+this.tweet_id;},when:function(){returnHelper.timeAgoInWords(this.created_at);}};
It would also be handy if we could have the tweet text ‘linkified’ for us:
And with that, we have an example (with some practical value) of how JavaScript’s prototypal inheritance allows objects to easily inherit from other, more general objects.