高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

CoffeeScript 基本篇(二)

邏輯判斷

其實就是if..else那類的事,沒什麼特別的。不過在CoffeeScript裡有幾個比較有趣的點:

  • 不需要特別寫then:除非是寫成一行,不然不需要特別加上”then”
  • 有unless可以用:unless就是not if,if就是not unless,也許你會覺得有if not了何必需要unless呢? 其實每個人對語意的理解都有些差別,有的人覺得”除非怎樣怎樣”比較通順,有的人覺得”如果不要怎樣怎樣..”比較通順,其實指的都是同一件事,隨個人喜好。
  • if後面的判斷句如果在不影響語意的情況下,可以不需要小括號,例如:
1
2
if age > 18
  console.log "Yes, You are Adult"
  • 如果if / unless接的內容只有一行的話,if / unless 可以像一般英文敘述句一樣,放到最後面,例如:
1
console.log "Yes, You are Adult" if age > 18

為什麼要放到後面? 有什麼好處? 通常把if丟後面去,除了縮短一行的行數、程式碼讀起來更像一般口語之外,以上面這個例子來說,會有特別”強調”或”希望它執行”前面那個動詞的意味(個人觀感)。 * 在比大小的時候,跟Python借來的大於、小於用法,讓語法更清楚,例如:

1
2
body_weight = 70
normal_body_weight = 65 < body_weight < 80

有if..else,當多個if..else判斷串在一起的時候,CoffeeScript也有switch可以用:

1
2
3
4
5
6
switch username
  when "eddie" then console.log "You are  the King"
  when "nayumi"
      console.log "Yes, My Lord.."
      console.log "You are the Queen"
  else console.log "You are nobody"

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
switch (username) {
  case "eddie":
      console.log("You are  the King");
      break;
  case "nayumi":
      console.log("Yes, My Load..");
      console.log("You are the Queen");
      break;
  default:
      console.log("You are nobody");
}

要注意的是,跟if..else一樣,如果是單行敘述,when後面請加上then,否則不需要加。話說還好這點像Ruby不像Python,Python並沒有switch之類的可以用,這並沒不好,只是我還是覺得有switch之類的語法可以讓程式碼變得比較清楚,純個人喜好。

迴圈

CoffeeScript裡的迴圈跟一般的程式語言沒什麼太大的差別,就差不多是那麼一回事。前面提到的Range,常會放在迴圈裡面來使用:

1
2
3
4
console.log i for i in [1..10]                  # 印出1到10
console.log i for i in [1..10] when i % 2 == 0  # 印出2, 4, 6, 8, 10
console.log i for i in [1..10] by 2             # 一次跳2,印出1, 3, 5, 7, 9
console.log i * 2 for i in [1..10]              # 對每個i值 x 2

還有類似Ruby的陣列操作:

1
[1..10].map (i) -> console.log i*2

編譯結果:

1
2
3
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function(i) {
  return console.log(i * 2);
});

跟if/unless一樣,while也有他的死對頭until (while = not until, until = not while),如果while / until的敘述句只有一行的話,同樣也可以放到句子的最後面。

1
console.log "Keep Walking" while miles < 100

編譯結果:

1
2
3
while (miles < 100) {
  console.log("Keep Walking");
}

語法糖衣

為了讓程式碼看起來更像一般的英文敘述句,在CoffeeScript裡特別做了一些alias,例如幫true值做了兩組alias:on以及yes,相對的,off跟no就是false的同義詞,其它的還有:

is    =>  ===
isnt  =>  !==
and   =>  &&
or    =>  ||
not   =>  !

所以,你可能可以寫出這樣的程式碼:

1
2
3
4
5
6
7
8
if light is off
  console.log "I can't see anything"

if eddie isnt handsome
  console.log "It's impossible!"

if girl is not single
  console.log "Don't Touch! Be Careful!"

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
if (light === false) {
  console.log("I can't see anything");
}

if (eddie !== handsome) {
  console.log("It's impossible!");
}

if (girl === !single) {
  console.log("Don't Touch! Be Careful!");
}

這樣一來,程式碼看起來就更口語化了。

最後來看一下這個好用的問號:

1
age ?= 18

這個的意思是說,如果age這個變數還沒被定義或給值的話,就給這個變數設定為18。簡單的說,就是給預設值啦,所以上面這一行會被編譯成:

1
2
3
4
5
if (typeof age !== "undefined" && age !== null) {
  age;
} else {
  age = 18;
};

真的是相當方便啊!!

更多精彩內容,請參考CoffeeScript官網上文件。

CoffeeScript 基本篇(一)

首先,CoffeeScript的官網的文件寫得相當的詳細,強烈建議各位一定要看,只要看完一輪,大概就知道CoffeeScript是怎麼一回事了。

底下你看到的console.log,通常是拿來debug用的,並不會真的在網頁上產生結果,請搭配Firebug使用。

註解

CoffeeScript跟Ruby一樣,使用#字號來做註解,而且註解的內容在編譯過的js檔裡是看不到的。如果要做多行註解,則是使用3個#字號。

變數宣告

其實變數不用特別宣告,直接抓來用就好了,例如:

1
2
age = 18;
name = "CoffeeScript";

編譯結果:

1
2
3
var age, name;
age = 18;
name = "CoffeeScript";

看到了嗎? 它幫你宣告好了agename兩個變數,而且該加的分號都加上去了。

如果你要組合字串,除了用加號來串接外,CoffeeScript也從Ruby那邊借來了string interpolation:

1
greeting = "hello, #{name}"

編譯結果:

1
greeting = "hello, " + name;

要注意的是,如果你用的是單引號的話,string interpolation不會發生作用。

跟Ruby一樣,string interpolation可以放一個完整的expression,例如:

1
2
3
4
5
6
greeting = "hello, #{
  if name
      name
  else
      "CoffeeScript"
}"

編譯結果:

1
2
var greeting;
greeting = "hello, " + (name ? name : "CoffeeScript");

Function

1
2
3
4
say_hello = ->
  "Hello CoffeeScript"

say_hello()

編譯結果:

1
2
3
4
5
var say_hello;
say_hello = function() {
  return "Hello CoffeeScript";
};
say_hello();

像Ruby一樣,function的最後一個執行結果自動就是整個function的回傳值,所以這邊即使沒有特別寫return,該加上的return、大括號以及分號,都在編譯成js檔之後加上去了。(你要特別加上return也ok,不會變兩個return的)

再來,如果你要給function傳進參數的話:

1
2
3
4
5
6
7
8
9
10
11
12
age = 18;
name = "CoffeeScript";
greeting = "hello, #{name}"
say_hello = (name) ->
if name
  "hello, #{name}"
else
  greeting

console.log say_hello()
console.log say_hello("eddie")
console.log say_hello "eddie"

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var age, greeting, name, say_hello;
age = 18;
name = "CoffeeScript";
greeting = "hello, " + name;
say_hello = function(name) {
  if (name) {
      return "hello, " + name;
  } else {
      return greeting;
  }
};
console.log(say_hello());
console.log(say_hello("eddie"));
console.log(say_hello("eddie"));

這樣就可以傳參數進去了。

上面這段範例有兩件事要特別留意,首先,CoffeeScript像Python一樣使用縮排(identation)做為程式碼區塊的判斷,所以上面if..else..的縮排是有意義的。是的,if後面不用加then

第二,注意到在上面傳參數的時候,像Ruby一樣的省略掉了小括號嗎? 編譯出來的結果跟有小括號的是一樣的。那為什麼say_hello不傳參數的時候不能像Ruby一樣省略參數? 我想是因為CoffeeScript沒辦法真的像Ruby一樣辨識say_hello到底是一般的變數,還是沒有參數的方法呼叫,所以請記得,如果沒傳參數給function,請記得加上小括號

如果你要讓參數有預設值:

1
2
say_hello = (name = "CoffeeScript") ->
  console.log "Hello, #{name}"

編譯結果:

1
2
3
4
5
6
7
var say_hello;
say_hello = function(name) {
  if (name == null) {
      name = "CoffeeScript";
  }
  return console.log("Hello, " + name);
};

在Ruby裡傳參數,有叫做Splat的概念,可以一次傳進多個選擇性的參數,在CoffeeScript也有借來用,只是寫法有點不太一樣:

1
2
3
4
5
6
7
8
9
say_hello = (name, others...) ->
  if others.length > 0
      "Hello, #{name} and #{others}"
  else
      "Hello, #{name}"

console.log say_hello "eddie"
console.log say_hello "eddie", "kao"
console.log say_hello "eddie", "kao", "joanne"

編譯結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var say_hello;
var __slice = Array.prototype.slice;
say_hello = function() {
  var name, others;
  name = arguments[0], others = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
  if (others.length > 0) {
      return "Hello, " + name + " and " + others;
  } else {
      return "Hello, " + name;
  }
};

console.log(say_hello("eddie"));
console.log(say_hello("eddie", "kao"));
console.log(say_hello("eddie", "kao", "joanne"));

第二個參數others後面接三個點,表示它是一個「可有可無而且可以不定數量」的參數,而傳進來的參數變成一個陣列,看到編譯出來的程式碼就覺得如果要自己手工寫出這段程式碼要想好一下子的..

像Ruby一樣,CoffeeScript支援變數多重指定,但寫法有些不一樣,需要特別加上中括號,所以可以這樣玩:

1
2
3
4
[first, others..., last] = [1..10]
console.log first
console.log last
console.log others

編譯結果:

1
2
3
4
5
6
var first, last, others, _i, _ref;
var __slice = Array.prototype.slice;
_ref = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], first = _ref[0], others = 3 <= _ref.length ? __slice.call(_ref, 1, _i = _ref.length - 1) : (_i = 1, []), last = _ref[_i++];
console.log(first);     # 1
console.log(last);      # 10
console.log(others);    # [ 2, 3, 4, 5, 6, 7, 8, 9 ]

變數可以多重指派,當然也可以交換:

1
2
3
4
5
6
x = 1
y = 10
[x, y] = [y, x]

console.log x
console.log y

編譯結果:

1
2
3
4
5
6
var x, y, _ref;
x = 1;
y = 10;
_ref = [y, x], x = _ref[0], y = _ref[1];
console.log(x);
console.log(y);

可以用原來的方式寫function嗎?

你不一定要照著CoffeeScript的方式寫,如果CoffeeScript的語法讓你覺得不舒服,你希望可以用你習慣的JavaScript語法的話也ok,你可以這樣做:

1
2
3
say_hello = `function(name){
  return "Hello, " + name
}`

在反單引號(backtick)包起來的內容,在編譯的時候會照你寫的內容輸出,只是這樣一來用CoffeeSCript就沒意義啦!

陣列

1
heroes = ['Spider Man', 'Captain America', 'X-men', 'Iron Man']

在CoffeeScript裡,你可以用多行表示:

1
2
3
4
5
6
heroes = [
  'Spider Man',
  'Captain America',
  'X-men',
  'Iron Man'
]

編譯結果:

1
2
var heroes;
heroes = ['Spider Man', 'Captain America', 'X-men', 'Iron Man'];

甚至連每行後面的逗點也都可以省下來。

CoffeeScript從Ruby借來了Range

1
students = [1..10]

編譯結果:

1
2
var students;
students = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

如果是多一個點:

1
students = [1...10]

編譯結果:

1
2
var students;
students = [1, 2, 3, 4, 5, 6, 7, 8, 9];

如果你把Range反過來寫,例如[10..1],它就會變成從10到1的數字陣列。但如果把Range的數字放大一點:

1
students = [1..100]

編譯結果:

1
2
3
4
5
6
var students, _i, _results;
students = (function() {
  _results = [];
  for (_i = 1; _i <= 100; _i++){ _results.push(_i); }
  return _results;
}).apply(this, arguments);

只要Range大於20就會改用迴圈的方式來建構這個陣列。

陣列的操作也是很容易的

1
heroes[0..2]

編譯結果:

1
heroes.slice(0, 3);

如果利用Range來替換或新增陣列的內容:

1
heroes[1..2] = ["Batman", "ThunderCat"]

編譯結果:

1
2
3
var _ref;
heroes = ['Spider Man', 'Captain America', 'X-men', 'Iron Man'];
[].splice.apply(heroes, [1, 2].concat(_ref = ["Batman", "ThunderCat"])), _ref;

物件

你可以用這種JSON的寫法:

1
eddie = { name: "Eddie Kao", age: 18, speciality: "eat" }

或是YAML式的寫法:

1
2
3
4
eddie =
  name: "Eddie Kao"
  age: 18
  speciality: "eat"

編譯結果:

1
2
3
4
5
6
var eddie;
eddie = {
  name: "Eddie Kao",
  age: 18,
  speciality: "eat"
};

特別是如果你要做巢狀的物件結構,用YAML的寫法會更清楚:

1
2
3
4
5
6
7
8
9
10
11
eddie =
  name: "Eddie Kao"
  age: 18
  speciality: "eat"
  lovers:
  nayumi:
      name: "Nayumi Hung"
      age: 18
  mary:
      name: "Mary Bloody"
      age: 20

CoffeeScript 使用篇

CoffeeScript裝好了,準備來泡咖啡了

假設我寫了一個檔名為app.coffee(內容怎麼寫請見CoffeeScript基礎篇),現在我想來編譯這個coffee檔:

> coffee --watch --compile app.coffee

這行指令會把app.coffee編譯產生一個同名的app.js

加上了–watch參數,CoffeeScript的編譯器會一直監看這個檔案,只要檔案一有變動,編譯器就會再重新編譯產生一份新的.js檔案。如果你覺得–watch、–compile太囉嗦或不想打那麼多字,也可以用-w、-c來替代。

如果你希望把幾個coffee檔案在編譯的時候合併成一個.js檔,以減少http request的數量,你可以這樣做:

> coffee --join application.js --compile *.coffee

這樣就會把同一個目錄裡的coffee檔編譯並組合成同一個js檔,這裡–join也可以簡寫成-j。

更多內容請見coffee –help

CoffeeScript Console

CoffeeScript也有像Ruby的irb一樣的REPL,提供一個像shell的互動介面可以讓你試玩,你只要不帶參數的執行CoffeeScript編譯器:

> coffee
coffee> name = "eddie"
'eddie'

這樣你就可以在這個環境裡面試一些語法。如果你曾經使用過Ruby的irb,你對這個結果應該不陌生。

一定要編譯成js檔嗎?

上面講的是如何把coffee檔編譯成js檔,編譯成js檔當然放到網頁上當然沒問題,但是不是一定要編譯呢? 不編譯的話,coffee檔能不能直接拿來用?

當然是可以的,只要先引入一行CoffeeScript的編譯器,就可以在HTML的頁面裡直接寫CoffeeScript:

1
<script type="text/javascript" src="http://jashkenas.github.com/coffee-script/extras/coffee-script.js"></script>

然後你要這樣寫:

1
2
3
<script type="text/coffeescript">
  alert 'test'
</script>

或是寫在外部檔案:

1
<script type="text/coffeescript" src="myapp.coffee"></script>

都可以,這樣就能讓你的瀏覽器就看得懂CoffeeScript,只是這樣一來的速度一定比直接編譯成.js檔要來得慢,所以實務上還是會先編譯成.js檔再拿來使用。

OK! 可以開始喝咖啡了!

CoffeeScript 安裝篇

要安裝CoffeeScript的話,會需要先安裝Node.js以及Node.js的套件管理工具 – npm (Node Package Manager)

說明:

以下將會以Mac及Ubuntu為主要的平台,Windows就對不起了,我沒有機器可以測試..在安裝過程中如果出現權限不足的錯誤訊息,請使用sudo暫時取得安裝的權限。

安裝Node.js

官網:http://nodejs.org/

> git clone git://github.com/joyent/node.git
> cd node
> ./configure
> make
> sudo make install

更多細節請參考:https://github.com/joyent/node/wiki/Installation

安裝npm

官網:http://npmjs.org/

> curl http://npmjs.org/install.sh | sh

一行搞定!(如果你沒有curl的話請另外安裝)

安裝CoffeeScript

如果你的Node.js以及npm都順利安裝完成的話,接下來,在安裝之前先更新一下npm的套件資訊(不一定更新,只是確保npm會幫你裝最新的套件版本):

> npm update
npm info it worked if it ends with ok
npm info using npm@0.3.18
npm info using node@v0.4.10
npm WARN Invalid range in engines.node.  Please see `npm help json` ~v0.4.9
npm WARN Invalid range in engines.node.  Please see `npm help json` v0.4.8~v0.4.9
npm info outdated Everything up-to-date.
npm info update Nothing to update
npm ok

再來安裝CoffeeScript:

> npm install coffee-script
npm info it worked if it ends with ok
npm info using npm@0.3.18
npm info using node@v0.4.10
npm info preinstall coffee-script@1.1.1
npm info install coffee-script@1.1.1
npm info postinstall coffee-script@1.1.1
npm info preactivate coffee-script@1.1.1
npm info activate coffee-script@1.1.1
npm info postactivate coffee-script@1.1.1
npm info build Success: coffee-script@1.1.1
npm ok

其實如果你是Mac的使用者的話,上面這些動作可以直接用homebrew一行搞定,超幸福的:

> brew install coffee-script

homebrew會幫你把需要的東西一起處理好(誠心推薦,不要再用ports了)。如果你發現用homebrew安裝的CoffeeScript不是最新版本的話,請先用brew update指令,更新一下homebrew的fomula再重新安裝即可。

看看有沒裝好:

> coffee -v
CoffeeScript version 1.1.1

就這樣,可以準備開工了!

補充:

在 Windows 上可以用這個 https://github.com/alisey/CoffeeScript-Compiler-for-Windows

在 NetBeans 上可以用這個 https://github.com/dstepanov/coffeescript-netbeans (注意, Windows 7 要用 0.9.1 版才不會有問題)

感謝jace補充 :)

CoffeeScript 簡介

什麼是CoffeeScript?

簡單的說,它就是JavaScript啦,只是用不同的程式語言來寫而已。

為什麼選擇CoffeeScript?

不少JavaScript社群,包括JavaScript的老爸Brendan Eich,也對它讚譽有加,有社群及大神的背書,加上Rails 3.1之後會把CoffeeScript變成預設支援,是我選擇它的原因之一。不過對我個人來說,CoffeeScript的語法感覺像是PythonRuby的混合體,從這些語言身上借了一些我個人很愛的元素,加上對我個人來說學習的負擔不算太大,這才是我選擇它最主要的原因(聽說CoffeeScript還有向Haskell取經,不過我跟它不熟)。

從Python借來的:

  • 使用縮排(identation)來取代大括號,這一直是我的菜,我有程式碼潔癖!
  • 簡單好用的List Comprehension語法,在CoffeeScript也可以用(雖然用起來有那麼一點點不太一樣)。

從Ruby借來的:

  • 有if modifier可以用,可以把if寫在句子後面,也有unless可以用。
  • 在呼叫方法以及邏輯判斷的時候,可以視情況省略小括號。
  • 每行行尾不用分號(其實Python也是)。
  • 不用特別寫return,最後的執行結果就是回傳值。
  • 有類似irb的console介面模式可以用。

優缺點

好處?

  • 程式碼會變短,變得容易閱讀,也更容易抓出問題來,即使是以OOP的方式來寫js,語法看起來也是相當清楚。
  • 經過CoffeeScript編譯出來的JavaScript,因為它在編譯的時候有做最佳化,而且這些最佳化可能是我們這種小兵不知道的技巧,某些情況下的編譯出來的效能可能比我們一般手寫的要來得好。
  • 不會有因為變數scope而混淆程式語意或造成或memory leak的問題。
  • 編出來的js檔丟給JSLint檢查不會跳出一堆抱怨。

缺點?

  • 你可能得再花時間再熟悉一門程式語言,不過如果你本來就熟悉Python或Ruby的話,其實負擔並不大,而且老實說Python/Ruby也沒很難學。

FAQ

Q: 學CoffeeScript就可以取代一般的JavaScript嗎?

A: 也許可以取代,但這不代表你就不用學JavaScript,如果你並不熟悉JavaScript是怎麼一回事,你應該也不知道你要拿CoffeeScript來取代什麼東西。最一開始也提到,CoffeeScript就只是JavaScript而已,所以原本該花時間在學習JavaScript的,不會因為CoffeeScript而省下來的。

Q: CoffeeScript聽說要編譯成*.js檔才能用?

A: 是的。但如果你不想編譯,透過在瀏覽器裡直接引入compiler也可以讓瀏覽器看得懂*.coffee,只是通常實務上還是會把編成*.js檔。BTW, CoffeeScript的compiler本身也是用CoffeeScript寫的,自己寫自己,這點光是聽起來就很酷。

Q: 改用CoffeeScript的話,是不是之前寫的JavaScript都得整個打掉重寫?

A: 也不用啦,CoffeeScript的目的並不是取代原來的JavaScript,而是用更簡單、清楚的語法來寫JavaScript,所以原來寫好的就留著吧,除非你也覺得寫得不好,加上手癢想練功。而且如果你想在CoffeeScript裡直接使用一般JavaScript的語法也是ok的,CoffeeScript可以讓你把一般的JavaScript語法embed在CoffeeScript裡。

Q: CoffeeScript可以把.coffee檔編譯成.js檔,那也有工具可以把js檔轉成coffee檔的嗎?

A: 有的,有個工具叫做js2coffee,你可以在線上玩看看,也可以透過npm安裝到你的電腦裡。

Q: 有開發工具嗎?

A: CoffeeScript的官網有列了一些工具供參考。不過寫CoffeeScript只要一般的文字編輯器就行了,如果你沒有特別偏好的文字編輯器,我推薦你使用vim,它是個免費又功能強大的文字編輯器,投資時間學一下它保證不會後悔的。vim有一堆很威的plugin可以用,我從pct那邊fork了他的vim設定,也把CoffeeScript的外掛加上去了 https://github.com/kaochenlong/pct.vim (PS: 其實pct的版本就有加上這功能了..)

如果你是用Textmate,恭喜你CoffeeScript的作者有放出了個方便的bundle可以用。除了一些方便的snippet以及程式碼顏色亮度之外,還可以讓你很簡單的執行/檢視你的coffee檔到時候編譯成js檔的樣子。

Microsoft的Visual Studio 2010最近也有一個免費的web workbench套件可以用,讓VS也開始支援SassLess以及CoffeeScript

Q: 有書可以參考嗎?

A: 在這個當下,剛好有一本”CoffeeScript: Accelerated JavaScript Development“,不過我想官網的資料相當完整,看完你就差不多知道怎麼用了。

另外還有一些不錯的學習資源: