-
Notifications
You must be signed in to change notification settings - Fork 65
Expand file tree
/
Copy path04_idioms.html
More file actions
286 lines (188 loc) · 12.3 KB
/
04_idioms.html
File metadata and controls
286 lines (188 loc) · 12.3 KB
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>The Little Book on CoffeeScript - Idioms</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>Common CoffeeScript idioms</h1>
<p>Every language has a set of idioms and practices, and CoffeeScript is no exception. This chapter will explore those conventions, and show you some JavaScript to CoffeeScript comparisons so you can get a practical sense of the language.</p>
<h2>Each</h2>
<p>In JavaScript to iterate over every item in an array, we could either use the newly added <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach"><code>forEach()</code></a> function, or an old C style <code>for</code> loop. If you're planning to use some of JavaScript's latest features introduced in ECMAScript 5, I advise you also include a <a href="https://github.com/kriskowal/es5-shim">shim</a> in the page to emulate support in older browsers.</p>
<pre><code>for (var i=0; i < array.length; i++)
myFunction(array[i]);
array.forEach(function(item, i){
myFunction(item)
});
</code></pre>
<p>Although the <code>forEach()</code> syntax is much more succinct and readable, it suffers from the drawback that the callback function will be invoked every iteration of the array, and is therefore much slower than the equivalent <code>for</code> loop. Let's see how it looks in CoffeeScript.</p>
<p><span class="csscript"></span></p>
<pre><code>myFunction(item) for item in array
</code></pre>
<p>It's a readable and concise syntax, I'm sure you'll agree, and what's great is that it compiles to a <code>for</code> loop behind the scenes. In other words CoffeeScript's syntax offers the same expressiveness as <code>forEach()</code>, but without the speed and shimming caveats.</p>
<h2>Map</h2>
<p>As with <code>forEach()</code>, ES5 also includes a native map function that has a much more succinct syntax than the classic <code>for</code> loop, namely <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map"><code>map()</code></a>. Unfortunately it suffers from much the same caveats that <code>forEach()</code> does, its speed is greatly reduced due to the function calls.</p>
<pre><code>var result = []
for (var i=0; i < array.length; i++)
result.push(array[i].name)
var result = array.map(function(item, i){
return item.name;
});
</code></pre>
<p>As we covered in the syntax chapter, CoffeeScript's comprehensions can be used to get the same behavior as <code>map()</code>. Notice we're surrounding the comprehension with parens, which is <strong>absolutely critical</strong> in ensuring the comprehension returns what you'd expect, the mapped array.</p>
<p><span class="csscript"></span></p>
<pre><code>result = (item.name for item in array)
</code></pre>
<h2>Select</h2>
<p>Again, ES5 has a utility function <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/filter"><code>filter()</code></a> for reducing arrays:</p>
<pre><code>var result = []
for (var i=0; i < array.length; i++)
if (array[i].name == "test")
result.push(array[i])
result = array.filter(function(item, i){
return item.name == "test"
});
</code></pre>
<p>CoffeeScript's basic syntax uses the <code>when</code> keyword to filter items with a comparison. Behind the scenes a <code>for</code> loop is generated. The whole execution is performed in an anonymous function to ward against scope leakage and variable conflict.</p>
<p><span class="csscript"></span></p>
<pre><code>result = (item for item in array when item.name is "test")
</code></pre>
<p>Don't forgot to include the parens, as otherwise <code>result</code> will be the last item in the array.
CoffeeScript's comprehensions are so flexible that they allow you to do powerful selections as in the following example:</p>
<p><span class="csscript"></span></p>
<pre><code>passed = []
failed = []
(if score > 60 then passed else failed).push score for score in [49, 58, 76, 82, 88, 90]
# Or
passed = (score for score in scores when score > 60)
</code></pre>
<p>If comprehensions get too long, you can split them onto multiple lines.</p>
<p><span class="csscript"></span></p>
<pre><code>passed = []
failed = []
for score in [49, 58, 76, 82, 88, 90]
(if score > 60 then passed else failed).push score
</code></pre>
<h2>Includes</h2>
<p>Checking to see if a value is inside an array is typically done with <code>indexOf()</code>, which rather mind-bogglingly still requires a shim, as Internet Explorer hasn't implemented it.</p>
<pre><code>var included = (array.indexOf("test") != -1)
</code></pre>
<p>CoffeeScript has a neat alternative to this which Pythonists may recognize, namely <code>in</code>.</p>
<p><span class="csscript"></span></p>
<pre><code>included = "test" in array
</code></pre>
<p>Behind the scenes, CoffeeScript is using <code>Array.prototype.indexOf()</code>, and shimming if necessary, to detect if the value is inside the array. Unfortunately this means the same <code>in</code> syntax won't work for strings. We need to revert back to using <code>indexOf()</code> and testing if the result is negative:</p>
<p><span class="csscript"></span></p>
<pre><code>included = "a long test string".indexOf("test") isnt -1
</code></pre>
<p>Or even better, hijack the bitwise operator so we don't have to do a <code>-1</code> comparison.</p>
<p><span class="csscript"></span></p>
<pre><code>string = "a long test string"
included = !!~ string.indexOf "test"
</code></pre>
<h2>Property iteration</h2>
<p>To iterate over a bunch of properties in JavaScript, you'd use the <code>in</code> operator, for example:</p>
<pre><code>var object = {one: 1, two: 2}
for(var key in object) alert(key + " = " + object[key])
</code></pre>
<p>However, as you've seen in the previous section, CoffeeScript has already reserved <code>in</code> for use with arrays. Instead, the operator has been renamed <code>of</code>, and can be used like thus:</p>
<p><span class="csscript"></span></p>
<pre><code>object = {one: 1, two: 2}
alert("#{key} = #{value}") for key, value of object
</code></pre>
<p>As you can see, you can specify variables for both the property name, and its value; rather convenient.</p>
<h2>Min/Max</h2>
<p>This technique is not specific to CoffeeScript, but I thought it useful to demonstrate anyway. <code>Math.max</code> and <code>Math.min</code> take multiple arguments, so you can easily use <code>...</code> to pass an array to them, retrieving the maximum and minimum values in the array.</p>
<p><span class="csscript"></span></p>
<pre><code>Math.max [14, 35, -7, 46, 98]... # 98
Math.min [14, 35, -7, 46, 98]... # -7
</code></pre>
<p>It's worth noting that this trick will fail with really large arrays as browsers have a limitation on the amount of arguments you can pass to functions.</p>
<h2>Multiple arguments</h2>
<p>In the <code>Math.max</code> example above, we're using <code>...</code> to de-structure the array and passing it as multiple arguments to <code>max</code>. Behind the scenes, CoffeeScript is converting the function call to use <code>apply()</code>, ensuring the array is passed as multiple arguments to <code>max</code>. We can use this feature in other ways too, such as proxying function calls:</p>
<p><span class="csscript"></span></p>
<pre><code>Log =
log: ->
console?.log(arguments...)
</code></pre>
<p>Or you can alter the arguments before they're passed onwards:</p>
<p><span class="csscript"></span></p>
<pre><code>Log =
logPrefix: "(App)"
log: (args...) ->
args.unshift(@logPrefix) if @logPrefix
console?.log(args...)
</code></pre>
<p>Bear in mind though, that CoffeeScript will automatically set the function invocation context to the object the function is being invoked on. In the example above, that would be <code>console</code>. If you want to set the context specifically, then you'll need to call <code>apply()</code> manually.</p>
<h2>And/or</h2>
<p>CoffeeScript style guides indicates that <code>or</code> is preferred over <code>||</code>, and <code>and</code> is preferred over <code>&&</code>. I can see why, as the former is somewhat more readable. Nevertheless, the two styles have identical results.</p>
<p>This preference over more English style code also applies to using <code>is</code> over <code>==</code> and <code>isnt</code> over <code>!=</code>.</p>
<p><span class="csscript"></span></p>
<pre><code>string = "migrating coconuts"
string == string # true
string is string # true
</code></pre>
<p>One extremely nice addition to CoffeeScript is the 'or equals', which is a pattern Rubyists may recognize as <code>||=</code>:</p>
<p><span class="csscript"></span></p>
<pre><code>hash or= {}
</code></pre>
<p>If hash evaluates to <code>false</code>, then it's set to an empty object. It's important to note here that this expression also recognizes <code>0</code>, <code>""</code> and <code>null</code> as false. If that isn't your intention, you'll need to use CoffeeScript's existential operator, which only gets activated if <code>hash</code> is <code>undefined</code> or <code>null</code>:</p>
<p><span class="csscript"></span></p>
<pre><code>hash ?= {}
</code></pre>
<h2>Destructuring assignments</h2>
<p>Destructuring assignments can be used with any depth of array and object nesting, to help pull out deeply nested properties.</p>
<p><span class="csscript"></span></p>
<pre><code>someObject = { a: 'value for a', b: 'value for b' }
{ a, b } = someObject
console.log "a is '#{a}', b is '#{b}'"
</code></pre>
<p>This is especially useful in Node applications when requiring modules:</p>
<p><span class="csscript"></span></p>
<pre><code>{join, resolve} = require('path')
join('/Users', 'Alex')
</code></pre>
<h2>External libraries</h2>
<p>Using external libraries is exactly the same as calling functions on CoffeeScript libraries; since at the end of the day everything is compiled down to JavaScript. Using CoffeeScript with <a href="http://jquery.com">jQuery</a> is especially elegant, due to the amount of callbacks in jQuery's API.</p>
<p><span class="csscript"></span></p>
<pre><code># Use local alias
$ = jQuery
$ ->
# DOMContentLoaded
$(".el").click ->
alert("Clicked!")
</code></pre>
<p>Since all of CoffeeScript's output is wrapped in an anonymous function, we can set a local <code>$</code> alias for <code>jQuery</code>. This will make sure that even if jQuery's no conflict mode is enabled and the <code>$</code> re-defined, our script will still function as intended.</p>
<h2>Private variables</h2>
<p>The <code>do</code> keyword in CoffeeScript lets us execute functions immediately, a great way of encapsulating scope & protecting variables. In the example below, we're defining a variable <code>classToType</code> in the context of an anonymous function which is immediately called by <code>do</code>. That anonymous function returns a second anonymous function, which will be ultimate value of <code>type</code>. Since <code>classToType</code> is defined in a context that no reference is kept to, it can't be accessed outside that scope.</p>
<p><span class="csscript"></span></p>
<pre><code># Execute function immediately
type = do ->
classToType = {}
for name in "Boolean Number String Function Array Date RegExp Undefined Null".split(" ")
classToType["[object " + name + "]"] = name.toLowerCase()
# Return a function
(obj) ->
strType = Object::toString.call(obj)
classToType[strType] or "object"
</code></pre>
<p>In other words, <code>classToType</code> is completely private, and can never again be referenced outside the executing anonymous function. This pattern is a great way of encapsulating scope and hiding variables.</p>
</div>
</div>
</body>
</html>