forked from arcturo/library
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path03_classes.html
253 lines (168 loc) · 10.1 KB
/
03_classes.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>The Little Book on CoffeeScript - Classes</title>
<link rel="stylesheet" href="site/site.css" type="text/css" charset="utf-8">
<link rel="stylesheet" href="site/highlight.css" type="text/css" charset="utf-8">
<script src="site/jquery.js" type="text/javascript" charset="utf-8"></script>
<script src="site/highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="site/coffee-script.js" type="text/javascript" charset="utf-8"></script>
<script src="site/preview.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
hljs.initHighlightingOnLoad();
</script>
</head>
<body>
<div id="container">
<header>
<h1><a href="index.html">The Little Book on CoffeeScript</a></h1>
</header>
<div id="content">
<div class="back"><a href="index.html">« Back to all chapters</a></div>
<h1>Classes</h1>
<p>Classes in JavaScript seem to have the kind of effect that cloves of garlic have to Dracula for some purists; although, let's be honest, if you're that way inclined, you're unlikely to be reading a book on CoffeeScript. However, it turns out that classes are just as damn useful in JavaScript as they are in other languages and CoffeeScript provides a great abstraction.</p>
<p>Behind the scenes, CoffeeScript is using JavaScript's native prototype to create classes; adding a bit of syntactic sugar for static property inheritance and context persistence. As a developer all that's exposed to you is the <code>class</code> keyword.</p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
</code></pre>
<p>In the example above, <code>Animal</code> is the name of the class, and also the name of the resultant variable that you can use to create instances. Behind the scenes CoffeeScript is using constructor functions, which means you can instantiate classes using the <code>new</code> operator.</p>
<p><span class="csscript"></span></p>
<pre><code>animal = new Animal
</code></pre>
<p>Defining constructors (functions that get invoked upon instantiation) is simple, just use a function named <code>constructor</code>. This is akin to using Ruby's <code>initialize</code> or Python's <code>__init__</code>.</p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
constructor: (name) ->
@name = name
</code></pre>
<p>In fact, CoffeeScript provides a shorthand for the common pattern of setting instance properties. By prefixing argument's with <code>@</code>, CoffeeScript will automatically set the arguments as instance properties in the constructor. Indeed, this shorthand will also work for normal functions outside classes. The example below is equivalent to the last example, where we set the instance properties manually.</p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
constructor: (@name) ->
</code></pre>
<p>As you'd expect, any arguments passed on instantiation are proxied to the constructor function.</p>
<p><span class="csscript"></span></p>
<pre><code>animal = new Animal("Parrot")
alert "Animal is a #{animal.name}"
</code></pre>
<h2>Instance properties</h2>
<p>Adding additional instance properties to a class is very straightforward, it's exactly the syntax as adding properties onto an object. Just make sure properties are indented correctly inside the class body.</p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
price: 5
sell: (customer) ->
animal = new Animal
animal.sell(new Customer)
</code></pre>
<p>Context changes are rife within JavaScript, and earlier in the Syntax chapter we talked about how CoffeeScript can lock the value of <code>this</code> to a particular context using a fat arrow function: <code>=></code>. This ensures that whatever context a function is called under, it'll always execute inside the context it was created in. CoffeeScript has extended support for fat arrows to classes, so by using a fat arrow for an instance method you'll ensure that it's invoked in the correct context, and that <code>this</code> is always equal to the current instance.</p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
price: 5
sell: =>
alert "Give me #{@price} shillings!"
animal = new Animal
$("#sell").click(animal.sell)
</code></pre>
<p>As demonstrated in the example above, this is especially useful in event callbacks. Normally the <code>sell()</code> function would be invoked in the context of the <code>#sell</code> element. However, by using fat arrows for <code>sell()</code>, we're ensuring the correct context is being maintained, and that <code>this.price</code> equals <code>5</code>.</p>
<h2>Static properties</h2>
<p>How about defining class (i.e. static) properties? Well, it turns out that inside a class definition, <code>this</code> refers to the class object. In other words you can set class properties by setting them directly on <code>this</code>.</p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
this.find = (name) ->
Animal.find("Parrot")
</code></pre>
<p>In fact, as you may remember, CoffeeScript aliases <code>this</code> to <code>@</code>, which lets you write static properties even more succinctly:</p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
@find: (name) ->
Animal.find("Parrot")
</code></pre>
<h2>Inheritance & Super</h2>
<p>It wouldn't be a proper class implementation without some form of inheritance, and CoffeeScript doesn't disappoint. You can inherit from another class by using the <code>extends</code> keyword. In the example below, <code>Parrot</code> extends from <code>Animal</code>, inheriting all of its instance properties, such as <code>alive()</code></p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
constructor: (@name) ->
alive: ->
false
class Parrot extends Animal
constructor: ->
super("Parrot")
dead: ->
not @alive()
</code></pre>
<p>You'll notice that in the example above, we're using the <code>super()</code> keyword. Behind the scenes, this is translated into a function call on the class' parent prototype, invoked in the current context. In this case, it'll be <code>Parrot.__super__.constructor.call(this, "Parrot");</code>. In practice, this will have exactly the same effect as invoking <code>super</code> in Ruby or Python, invoking the overridden inherited function.</p>
<p>Unless you override the <code>constructor</code>, by default CoffeeScript will invoke the parent's constructor when instances are created.</p>
<p>CoffeeScript uses prototypal inheritance to automatically inherit all of a class's instance properties. This ensures that classes are dynamic; even if you add properties to a parent class after a child has been created, the property will still be propagated to all of its inherited children.</p>
<p><span class="csscript"></span></p>
<pre><code>class Animal
constructor: (@name) ->
class Parrot extends Animal
Animal::rip = true
parrot = new Parrot("Macaw")
alert("This parrot is no more") if parrot.rip
</code></pre>
<p>It's worth pointing out though that static properties are copied to subclasses, rather than inherited using prototype as instance properties are. This is due to implementation details with JavaScript's prototypal architecture, and is a difficult problem to work around.</p>
<h2>Mixins</h2>
<p><a href="http://en.wikipedia.org/wiki/Mixin">Mixins</a> are not something supported natively by CoffeeScript, for the good reason that they can be trivially implemented yourself. For example, here's two functions, <code>extend()</code> and <code>include()</code> that'll add class and instance properties respectively to a class.</p>
<p><span class="csscript"></span></p>
<pre><code>extend = (obj, mixin) ->
obj[name] = method for name, method of mixin
obj
include = (klass, mixin) ->
extend klass.prototype, mixin
# Usage
include Parrot,
isDeceased: true
(new Parrot).isDeceased
</code></pre>
<p>Mixins are a great pattern for sharing common logic between modules when inheritance is not suited. The advantage of mixins, is that you can include multiple ones, compared to inheritance where only one class can be inherited from.</p>
<h2>Extending classes</h2>
<p>Mixins are pretty neat, but they're not very object orientated. Instead, let's integrate mixins into CoffeeScript's classes. We're going to define a class called <code>Module</code> that we can inherit from for mixin support. <code>Module</code> will have two static functions, <code>@extend()</code> and <code>@include()</code> which we can use for extending the class with static and instance properties respectively.</p>
<p><span class="csscript"></span></p>
<pre><code>moduleKeywords = ['extended', 'included']
class Module
@extend: (obj) ->
for key, value of obj when key not in moduleKeywords
@[key] = value
obj.extended?.apply(@)
this
@include: (obj) ->
for key, value of obj when key not in moduleKeywords
# Assign properties to the prototype
@::[key] = value
obj.included?.apply(@)
this
</code></pre>
<p>The little dance around the <code>moduleKeywords</code> variable is to ensure we have callback support when mixins extend a class. Let's take a look at our <code>Module</code> class in action:</p>
<p><span class="csscript"></span></p>
<pre><code>classProperties =
find: (id) ->
create: (attrs) ->
instanceProperties =
save: ->
class User extends Module
@extend classProperties
@include instanceProperties
# Usage:
user = User.find(1)
user = new User
user.save()
</code></pre>
<p>As you can see, we've added some static properties, <code>find()</code> and <code>create()</code> to the <code>User</code> class, as well as some instance properties, <code>save()</code>.
Since we've got callbacks whenever modules are extended, we can shortcut the process of applying both static and instance properties:</p>
<p><span class="csscript"></span></p>
<pre><code>ORM =
find: (id) ->
create: (attrs) ->
extended: ->
@include
save: ->
class User extends Module
@extend ORM
</code></pre>
<p>Super simple and elegant!</p>
</div>
</div>
</body>
</html>