文章目录
  1. 1. 基础语法
    1. 1.1. PROPERTY ACCESS
    2. 1.2. Types, existential, and combining operators
    3. 1.3. Statements as expressions
    4. 1.4. Method
    5. 1.5. length, join, slice, and concat
    6. 1.6. In & Range & Comprehensions
  2. 2. Functions
    1. 2.1. do
  3. 3. object
  4. 4. class
  5. 5. compose
    1. 5.1. Style
  6. 6. DSL
    1. 6.1. html
    2. 6.2. css
  7. 7. 兼容
  8. 8. Module
  9. 9. Cake

基础语法

> coffee –c –w status.coffee Watch

1
2
<script src='http://coffeescript.org/extras/coffee-script.js'></script>
<script type='text/coffeescript' src='status.coffee'></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
houseRoast = null

hasMilk = (style) ->
  switch style
    when "latte", "cappuccino"
      yes
    else
      no

makeCoffee = (requestedStyle) ->
  style = requestedStyle || 'Espresso'
  if houseRoast?
    "#{houseRoast} #{style}"
  else
    style

barista = (style) ->
  time = (new Date()).getHours()
  if hasMilk(style) and type > 12 then "NO!"
  else
    coffee = makeCoffee style
    "Enjoy your #{coffee}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
chuckNorris = 'Chuck Norris'
weak = 'weak'
chuckNorris is weak   # false
chuckNorris isnt weak # true

5 isnt 6 # true
5 is not 6 # false

# Unlike in JavaScript, the equality and inequality operators in CoffeeScript aren’t type
# coercive. They’re equivalent to === and !== in JavaScript:

'' == false # false
1 == '1' #true

4 + '3' # '43'
'apples''oranges' # NaN
'3'*3 # 9

not (3%2) # false
not (4%2) # true

PROPERTY ACCESS

1
2
3
movie = title: 'Way of the Dragon', star: 'Bruce Lee'
myPropertyName = 'title'
movie[myPropertyName] # 'Way of the Dragon'

Types, existential, and combining operators

1
2
3
4
5
6
7
8
9
10
11
reference = null
reference == null # true
typeof null  # 'object'

pegasus? # false
roundSquare? # false
pegasus = 'Horse with wings'
pegasus? # true

typeof roundSquare
# 'undefined'

Statements as expressions

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
connectJackNumber = (number) ->
  "Connecting jack #{number}"
  
receiver = 'Betty'

switch receiver
  when 'Betty'
    connectJackNumber
    4
  when 'Sandra'
    connectJackNumber
    22
  when 'Toby'
    connectJackNumber
    9
  else
    'I am sorry, your call cannot be connected'


style = 'latte'
milk = switch style
  when "latte", "cappuccino"
    yes
  else
    no

## Try Catch
flyAway = (animal) ->
  if animal is 'pig'
    throw 'Pigs cannot fly'
  else
    'Fly away!'
    
peter = 'pig'
try
  flyAway peter
catch error
  error
finally
  'Clean up!'

# inline blocks
year = 1983
if year is 1983 then hair = 'perm'

lastDigit = 4
daySuffix = switch lastDigit
  when 1 then 'st'
  when 2 then 'nd'
  when 3 then 'rd'
  else 'th'

hair = 'permed' if year is 1983

Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'haystack'.search 'needle' # -1
'haystack'.search 'hay' # 0
'haystack'.search 'stack' # 3


milkDrinks = 'latté,mocha,cappuccino,flat white,eiskaffee'
hakMilk = (style) ->
  milkDrinks.search(style) isnt -1

hasMilk 'mocha' # true
hasMilk 'espresso romano' # false

'haystack'.replace 'hay', 'needle' # 'needlestack'
milkDrinks.replace 'latté', 'latte'

'Cappuccino'.toLowerCase() # 'cappuccino'
'I am shouting!'.toUpperCase() # 'I AM SHOUTING!'


'Banana,Banana'.split /,/ # [ 'Banana', 'Banana' ]
'latte,mocha,cappuccino,flat white,eiskaffee'.split /,/ # [ 'latte', 'mocha', 'cappuccino', 'flat white', 'eiskaffee' ]

length, join, slice, and concat

1
2
3
4
5
6
7
8
9
10
11
12
13
fence = ['fence pail', 'fence pail']
fence.length # 2

fence[999] = 'fence pail'
fence.length # 1000

['double', 'barreled'].join '-' # 'double-barreled'
['good', 'bad', 'ugly'].slice 0, 2 # ['good', 'bad']

[0,1,2,3,4,5].slice 0,1 # [0]
[0,1,2,3,4,5].slice 3,5 # [3,4]

['mythril', 'energon'].concat ['nitron', 'durasteel', 'unobtanium'] # [ 'mythril', 'energon', 'nitron', 'durasteel', 'unobtanium' ]

In & Range & Comprehensions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'to be' in ['to be', 'not to be'] # true
living = 'the present'
living in ['the past', 'the present'] # true

[1..10] # [ 1,2,3,4,5,6,7,8,9,10 ]
[5..1]  # [ 5,4,3,2,1 ]

number for number in [9,0,2,1,0] # [9,0,2,1,0]
number + 1 for number in [9,0,2,1,0] # [10,1,3,2,1]
letter for letter in ['x','y','z'] # [x,y,z]

mix = (ingredient) -> "Mixing #{ingredient}"
for ingredient in ingredients when ingredient.search('flour') < 0
  mix ingredient

person for person in ['Kingpin', 'Galactus', 'Thanos', 'Doomsday'] by 2 # ['Kingpin', 'Thanos']

Functions

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
count = (text, delimiter) ->
  return 0 unless text
  words = text.split(delimiter || /,/)
  words.length


partyMessage = -> console.log "It's party time!"
setTimeout partyMessage, 1000

interval = setInterval partyMessage, 1000
clearInterval interval

multiple = (initial, numbers) ->
  total = initial or 1
  for number in numbers
    total = total * number
  total

sum = (numbers) ->
  total = 0
  for number in numbers
    total  = total + number
  total

## Accumulate
accumulate = (initial, numbers, accumulator) ->
  total = initial or 0
  for number in numbers
    total = accumulator total, number
  total

sum = (acc, current) -> acc + current
accumulate(0, [5, 5, 5], sum)

logArgument = (logMe='default') -> console.log logMe

do

1
2
do ->
  name = 'Ren'

object

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
futurama =
  characters:[
    'Fry'
    'Leela'
    'Bender'
    'The Professor'
    'Scruffy'
  ]
quotes:[
  'Good news everyone'
  'Bite my shiny metal'
]

phonebook =
  numbers:
    hannibal: '555-5551'
    darth: '555-5552'
    hal900: 'disconnected'
    freddy: '555-5554'
    'T-800': '555-5555'
  list: ->
    "#{name}: #{number}" for name, number of @numbers
  add: (name, number) ->
    if not (name of @numbers)
      @numbers[name] = number
  get: (name) ->
    if name of @numbers
      "#{name}: #{@numbers[name]}"
    else
      "#{name} not found"

process.stdin.setEncoding 'utf8'
stdin = process.openStdin()
stdin.on 'data', (chunk) ->
  args = chunk.split(' ')
  command = args[0].trim()
  name = args[1].trim() if args[1]
  number = args[2].trim() if args[2]
  switch
    when 'add'
      res = phonebook.add(name, number) if name and number
    when 'get'
      console.log phonebook.get(name) if name

score for name, score of {bob: 152, john: 139, tracy: 209}

# 不选原型中
for own page, count of views
  sum = sum + count

views[key] ?= 0


# fat arrow => bind the value of this 
someElement.onClick = ->
  setTimeout =>
    this.innerHTML 'Got clicked', 1000

cassetteCopy = {}
for own property, value of cassette
   cassetteCopy[property] = value

class

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
class Views
  constructor: ->
    @pages = {}
  increment: (key) ->
    @pages[key] ?= 0
    @pages[key] = @pages[key] + 1
  total: () ->
    sum = 0
    for own url, count of @pages
      sum = sum + count
    sum

class Camera extneds Product
  megapixiels: ->
    @info.megapixiels || 'Unknow'

class Product
  instance = []  # 私有变量
  # 静态方法
  @find = (query) ->
    (product for product in instances when product.name is query)
  
  constructor: (name) ->
    instances = instances.concat [@]
    @name = name

class Product
  constructor: (name, cost) ->
    @name = name
    @cost = cost
  price: ->
    @cost

class Camera extends Product
  markup = 2
  price: ->
    super()*markup

Example::justAdded = -> "just added!"

## is equivalent to
Example.prototype.justAdded = -> "just added!"

Array::join = -> "Array::join was redefined"

include = (klass, module) ->
  for key, value of module
    klass::[key] = value

compose

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
compose = (f, g) -> (x) -> f g x
taxForProducts = compose tax, netProfit
loyaltyDiscountForUser = compose loyaltyDiscount, userSpend


# before
before = (decoration) ->
  (base) ->
    (params) ->
      decoration params
      base params

after = (decoration) ->
  (base) ->
    (params...) ->
      result = base params...
      decoration params...
      result

around = (decoration) ->
  (base) ->
    (params...) ->
     callback = -> base params...
     decoration ([callback].concat params)...

beforeAsync = (decoration) ->
  (base) ->
    (params..., callback) ->
      result = undefined
      applyBase = =>
        result = base.apply @, (params.concat callback)
      decoration.apply @, (params.concat applyBase)
      result

user?.contact?.phone?.home

Style

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
highlight = (names...) ->
  for name in names
    color find(name), 'yellow'

teams = ['wolverines', 'sabertooths', 'mongooses']
highlight teams...

[a,b] = [b,a]

competitors = [
  name: 'wildcats'
  points: 5
  ,
  name: 'bobcats'
  points: 3
]

makeCompetition = ({max, sort}) ->
  {max, sort}

[first, middle..., last] = competitors

render = (user) ->
  """
  Home phone for #{user?.name?.first}: #{user?.contact?.phone?.home}
  """

contact = user?.contact?.phone?.home || 'Not provided'

class DuckRace
  constructor: (@ducks) ->
  go: ->
    duck.walk() for duck in @ducks

evens = numbers.filter (item) -> item%2 == 0

taxes = paid.map (item) -> item*0.1

# 链式调用
using = (object, fn) -> fn.apply object
using turtle, ->
  @forward 2
  @rotate 90
  @forward 4

x y z 4 # x(y(z(4)))

DSL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Email
  SMTP_PORT = 25
  constructor: (option) ->
    ['from', 'to', 'subject', 'body'].forEach (key) =>
      @["_#{key}"] = option?[key]
      @[key] = (newValue) ->
        @["_#{key}"] = newValue
        @

scruffysEmail
  .to('agtron@coffeescriptinaction.com')
  .from('scruffy@coffeescriptinaction.com')
  .subject('Hi Agtron!')
  .body '''
    This is a test email.
  '''
  
scruffysEmail.send()

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
loggedIn = -> true
doctype 5
html ->
  body ->
    ul class: 'info', ->
      li -> 'Logged in' if loggedIn()

doctype = (variant) ->
  switch variant
    when 5
      "<!DOCTYPE html>"

markup = (wrapper) ->
  (attributes..., descendents) ->
    attributesMarkup = if attributes.length is 1
      " " + ("#{name}='#{value}'" for name, value of attributes[0]).join ' '
    else
      ''
    "<#{wrapper}#{attributesMarkup}>#{descendents() || ''}</#{wrapper}>"

html = markup 'html'
body = markup 'body'
ul = markup 'ul'
li = markup 'li'

css

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
emphasis = ->
  fontWeight: 'bold'

css
  'ul':
    emphasis()
  '.x':
    fontSize: '2em'

css = (raw) ->
  hyphenate = (property) ->
    dashThenUpperAsLower = (match, pre, upper) ->
      "#{pre}-#{upper.toLowerCase}"
    property.replace /([a-z])(A-Z)/g, dashThenUpperAsLower

    output = (
      for selector, rules of raw
        rules = (for ruleName, ruleValue of rules
          "#{hyphenate ruleName}: #{ruleValue};"
        ).join '\n'
        """
        #{selector} {
          #{rules}
        }
        """
    ).join '\n'
1
2
3
coffee = require 'coffee-script'

coffee.eval '2 + 4'

兼容

1
2
3
4
5
6
7
8
9
10
11
12
13
document.querySelector ?= (selector) ->
  if /^#/.test selector
    (document.getElementById (selector.replace /^#/gi, ''))
  else
    throw new Error 'Not supported by this implementation'

Object.create ?= (prototype, extensions) ->
  if extensions
    throw new Error 'Not supported by this implementation'
  else
    F = ->
  F.prototype = prototype
  new F()

Module

1
2
3
4
5
6
7
8
9
10
11
# -- controller.coffee –
class Controller
exports.Controller = Controller

# -- blog.coffee –-
controller = require './controller'
class Blog extends controller.Controller

exports.Post = Post
{Post} = require './post'

Cake

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
# Cakefile
{spawn} = require 'child_process'

## cake build

compile (directory) = ->
  coffee = spawn 'coffee', ['-c', '-o', "compiled/#{directory}", directory]
  
coffee.on 'exit', (code) ->
  console.log 'Build complete'

clean = (path, callback) ->
  exec "rm -rf #{path}", -> callback?()

forAllSpecsIn = (dir, fn) ->
  execFile 'find', [ dir ], (err, stdout, stderr) ->
    fileList = stdout.split '\n'
    for file in fileList
      fn file if /_spec.js$/.test file

runSpecs = (folder) ->
  forAllSpecsIn folder, (file) ->
    require "./#{file}"

task 'build', 'Compile the application', ->
  clean 'compiled', ->
    compile 'app', ->
      'Build complete'

task 'test' , 'Run the tests', ->
  clean 'compiled', ->
    compile 'app', ->
      compile 'spec', ->
        runSpecs 'compiled', ->
          console.log 'Tests complete'

## task 'deploy', ->
##   invoke 'build'
##   invoke 'test'
1
2
3
4
5
6
7
8
9
10
11
{describe, it} = require 'chromic'
{Post} = require '../../app/models/post'

describe 'Post', ->
  post = new Post 'A post', 'with contents'
  another = new Post 'Another post', 'with contents'
  it 'should return all posts', ->
    Post.all().length.shouldBe 2
  
  it 'should return a specific post', ->
    Post.get(post.slug).shouldBe 'a-post'
文章目录
  1. 1. 基础语法
    1. 1.1. PROPERTY ACCESS
    2. 1.2. Types, existential, and combining operators
    3. 1.3. Statements as expressions
    4. 1.4. Method
    5. 1.5. length, join, slice, and concat
    6. 1.6. In & Range & Comprehensions
  2. 2. Functions
    1. 2.1. do
  3. 3. object
  4. 4. class
  5. 5. compose
    1. 5.1. Style
  6. 6. DSL
    1. 6.1. html
    2. 6.2. css
  7. 7. 兼容
  8. 8. Module
  9. 9. Cake