The Power of Prototypes

A prototype is an object whose properties are shared by all objects that have that prototype. An object’s prototype can usually be accessed using the aptly named prototype property, though there are exceptions.[34]

However, you can’t just go and write A.prototype = B. Instead, you need to use the new keyword, which takes a function and creates an object that “inherits” that function’s prototype. When a function is used this way, it’s referred to as a constructor. Here’s a quick example:

 Boy = -> ​# by convention, constructor names are capitalized
 Boy::sing = ->
  console.log ​"It ain't easy being a boy named Sue"
 sue = ​new​ Boy()
 sue.sing()

Here, Boy::sing is shorthand for Boy.prototype.sing. The :: symbol is to prototype as @ is to this.

The output looks like this:

 It ain't easy being a boy named Sue

Cool, right? Let’s learn more about what constructors can do.

Making Objects with Constructors

When we write new <constructor>, several things happen: a new object is created, that object is given the prototype from the constructor, and the constructor is executed (in the new object’s context). So let’s say we want to define a “gift” type, where every new gift stores the name passed to the constructor and announces the existing number of gifts:

 Gift = (@name) ->
  Gift.count++
  @day = Gift.count
  @announce()
 
 Gift.count = 0
 Gift::announce = ->
  console.log ​"On day ​​#{​@day​}​​ of Christmas I received ​​#{​@name​}​​"
 
 gift1 = ​new​ Gift(​'a partridge in a pear tree'​)
 gift2 = ​new​ Gift(​'two turtle doves'​)

The syntax (@name) -> is a handy shorthand for (name) -> @name = name. Here’s the output:

 On day 1 of Christmas I received a partridge in a pear tree
 On day 2 of Christmas I received two turtle doves

Each time the Gift constructor runs, it does four things: assigns the given name to @name (using the argument shorthand), increments the count property on the Gift constructor, copies that value to @day, and runs the @announce function inherited from the prototype.

Notice that all of the functions on the new object run in the context of the object. Prototypes are the reason why the this keyword, as odd as it may seem, is essential to the JavaScript language. Prototypes allow a single function to be shared across many objects, and this allows shared functions to access the state of different objects depending on how they’re called.

When it comes to parentheses, constructors have some syntactic quirks to watch out for:

  1. When you invoke a constructor with no arguments, you can omit the parentheses. So new Date and new Date() are equivalent.

  2. Parentheses do, however, matter when you invoke a constructor that’s attached to an object: new x.Y() creates a new instance of x.Y, while new X().y instantiates X and returns its y property.

  3. When you omit the parentheses, they implicitly go at the end: new x.Y is equivalent to new x.Y().

CoffeeScript gets a lot of guff from some corners for its implicit parentheses, but these rules come from pure JavaScript.

Prototype Precedence

When an object inherits properties from a prototype, changes to the prototype will change the inherited properties as well:

 Raven = ->
 Raven::quoth = -> console.log ​'Nevermore'
 raven1 = ​new​ Raven
 raven1.quoth() ​# Nevermore
 
 Raven::quoth = -> console.log ​"I'm hungry"
 raven1.quoth() ​# I'm hungry

Properties attached directly to objects take precedence over prototype properties. So we can remove that ambiguity by writing this:

 raven2 = ​new​ Raven
 raven2.quoth = -> console.log ​"I'm my own kind of raven"
 raven1.quoth() ​# I'm hungry
 raven2.quoth() ​# I'm my own kind of raven

To check whether a property is attached to an object directly or inherited from a prototype, use the hasOwnProperty function:

 console.log raven1.hasOwnProperty(​'quoth'​) ​# false
 console.log raven2.hasOwnProperty(​'quoth'​) ​# true

I should mention one interesting syntactical implication. The statement obj.a = obj.a is not always a no-op:

 raven3 = ​new​ Raven
 console.log raven3.hasOwnProperty(​'quoth'​) ​# false
 raven3.quoth = raven3.quoth
 console.log raven3.hasOwnProperty(​'quoth'​) ​# true

All of this prototype manipulation is well and good, but it’s a bit messy, isn’t it? Shouldn’t there be a clearer way of distinguishing constructor properties (such as Gift.count) from prototype properties (such as Gift::announce)? And of distinguishing constructors from other functions? That’s exactly what CoffeeScript’s class keyword allows us to do.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.188.244