CoffeeScript 更優美的Javascript

CoffeeScript 是一門編譯到 JavaScript 的小巧語言. 在 Java 般笨拙的外表下, JavaScript 其實有着一顆華麗的心臟. CoffeeScript 嘗試用簡潔的方式展現 JavaScript 優秀的部分.javascript

CoffeeScript 的指導原則是: "她僅僅是 JavaScript". 代碼一一對應地編譯到 JS, 不會在編譯過程當中進行解釋. 已有的 JavaScript 類庫能夠無縫地和 CoffeeScript 搭配使用, 反之亦然. 編譯後的代碼是可讀的, 且通過美化, 能在全部 JavaScript 環境中運行, 而且應該和對應手寫的 JavaScript 同樣快或者更快.php

最新版本: 1.7.1html

sudo npm install -g coffee-script

概覽

左邊是 CoffeeScript, 右邊是編譯後輸出的 JavaScript.java

# 賦值: number = 42 opposite = true # 條件: number = -42 if opposite # 函數: square = (x) -> x * x # 數組: list = [1, 2, 3, 4, 5] # 對象: math = root: Math.sqrt square: square cube: (x) -> x * square x # Splats: race = (winner, runners...) -> print winner, runners # 存在性: alert "I knew it!" if elvis? # 數組 推導(comprehensions): cubes = (math.cube num for num in list) 
var cubes, list, math, num, number, opposite, race, square, __slice = [].slice; number = 42; opposite = true; if (opposite) { number = -42; } square = function(x) { return x * x; }; list = [1, 2, 3, 4, 5]; math = { root: Math.sqrt, square: square, cube: function(x) { return x * square(x); } }; race = function() { var runners, winner; winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : []; return print(winner, runners); }; if (typeof elvis !== "undefined" && elvis !== null) { alert("I knew it!"); } cubes = (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = list.length; _i < _len; _i++) { num = list[_i]; _results.push(math.cube(num)); } return _results; })(); 
run: cubes

安裝

CoffeeScript 編譯器自己是 CoffeeScript 寫的, 使用了 Jison parser generator. 命令行版本的 coffee 是一個實用的 Node.js 工具. 不過編譯器並不依賴 Node, 而是能運行於任何 JavaScript 執行環境, 好比說在瀏覽器裏(看上邊的"試一試 CoffeeScript").node

安裝前你須要最新穩定版 Node.js, 和 npm (Node Package Manager). 藉助 npm 能夠安裝 CoffeeScript:python

npm install -g coffee-script

(若是不想全局安裝能夠去掉 -g 選項.)git

若是你但願安裝 master 分支上最新的 CoffeeScript, 你能夠從源碼倉庫 克隆 CoffeeScript, 或直接下載源碼. 還有經過 npm 方式安裝 master 分支最新的 CoffeeScript 編譯器:github

npm install -g http://github.com/jashkenas/coffee-script/tarball/master

或者你想將其安裝到 /usr/local, 而不用 npm 進行管理, 進入 coffee-script 目錄執行:web

sudo bin/cake install

用法

安裝以後, 你應該能夠運行 coffee 命令以執行腳本, 編譯 .coffee 文件到 .js 文件, 和提供一個交互式的 REPL. coffee 命令有下列參數:正則表達式

-c, --compile 編譯一個 .coffee 腳本到一個同名的 .js 文件.
-m, --map 隨 JavaScript 文件一塊兒生成 source maps. 而且在 JavaScript 里加上 sourceMappingURL 指令.
-i, --interactive 啓動一個交互式的 CoffeeScript 會話用來嘗試一些代碼片斷. 等同於執行 coffee 而不加參數.
-o, --output [DIR] 將全部編譯後的 JavaScript 文件寫到指定文件夾. 與 --compile 或 --watch 搭配使用.
-j, --join [FILE] 編譯以前, 按參數傳入順序鏈接全部腳本到一塊兒, 編譯後寫到指定的文件. 對於編譯大型項目有用.
-w, --watch 監視文件改變, 任何文件更新時從新執行命令.
-p, --print JavaScript 直接打印到 stdout 而不是寫到一個文件.
-s, --stdio 將 CoffeeScript 傳遞到 STDIN 後從 STDOUT 獲取 JavaScript. 對其餘語言寫的進程有好處. 好比:
cat src/cake.coffee | coffee -sc
-l, --literate 將代碼做爲 Literate CoffeeScript 解析. 只會在從 stdio 直接傳入代碼或者處理某些沒有後綴的文件名須要寫明這點.
-e, --eval 直接從命令行編譯和打印一小段 CoffeeScript. 好比:
coffee -e "console.log num for num in [10..1]"
-b, --bare 編譯到 JavaScript 時去掉頂層函數的包裹.
-t, --tokens 不對 CoffeeScript 進行解析, 僅僅進行 lex, 打印出 token stream:[IDENTIFIER square] [ASSIGN =] [PARAM_START (] ...
-n, --nodes 不對 CoffeeScript 進行編譯, 僅僅 lex 和解析, 打印 parse tree:
Expressions
  Assign
    Value "square"
    Code "x"
      Op *
        Value "x"
        Value "x"
--nodejs node 命令有一些實用的參數, 好比
--debug--debug-brk--max-stack-size, 和 --expose-gc. 用這個參數直接把參數轉發到 Node.js. 重複使用 --nodejs 來傳遞多個參數.

例子:

  • 編譯一個 .coffee 文件的樹形目錄 src 到一個同級 .js 文件樹形目錄 lib:
    coffee --compile --output lib/ src/
  • 監視一個文件的改變, 每次文件被保證時從新編譯:
    coffee --watch --compile experimental.coffee
  • 合併一組文件到單個腳本:
    coffee --join project.js --compile src/*.coffee
  • 從一個 one-liner 打印編譯後的 JS:
    coffee -bpe "alert i for i in [0..10]"
  • 如今所有一塊兒, 在你工做時監視和重複編譯整個項目:
    coffee -o lib/ -cw src/
  • 運行 CoffeeScript REPL (Ctrl-D 來終止, Ctrl-V 激活多行):
    coffee

Literate CoffeeScript

除了被做爲一個普通的編程語言, CoffeeScript 也能夠在 "literate" 模式下編寫。 若是你以 .litcoffee 爲擴展名命名你的文件, 你能夠把它看成 Markdown 文件來編寫 — 此文檔剛好也是一份可執行的 CoffeeScript 代碼, 編譯器將會把全部的縮進塊 (Markdown 表示源代碼的方式) 視爲代碼, 其餘部分則爲註釋.

Just for kicks, a little bit of the compiler is currently implemented in this fashion: See it as a documentraw, and properly highlighted in a text editor.

I'm fairly excited about this direction for the language, and am looking forward to writing (and more importantly, reading) more programs in this style. More information about Literate CoffeeScript, including an example program, are available in this blog post.

語言手冊

這份手冊所設計的結構, 方便從上往下進行閱讀. 後邊的章節使用前面介紹的語法和手法. 閱讀這份手冊須要對 JavaScript 比較熟悉. 如下全部的例子, CoffeeScript 源碼將在左邊顯示, 並在右側直接編譯到 JavaScript.

不少例子能夠經過點擊右邊的 run 按鈕直接運行(代碼有意義的話), 也能夠經過點擊左邊的 load 按鈕載入"試一試 CoffeeScript"的控制檯.

首先, 一些基礎, CoffeeScript 使用顯式的空白來區分代碼塊. 你不須要使用分號 ; 來關閉表達式, 在一行的結尾換行就能夠了(儘管分號依然能夠用來把多行的表達式簡寫到一行裏). 不須要再用花括號來{ } 包裹代碼快, 在 函數if 表達式switch, 和 try/catch 當中使用縮進.

傳入參數的時候, 你不須要再使用圓括號來代表函數被執行. 隱式的函數調用的做用範圍一直到行尾或者一個塊級表達式. 
console.log sys.inspect object → console.log(sys.inspect(object));

函數函數經過一組可選的圓括號包裹的參數, 一個箭頭, 一個函數體來定義. 一個空的函數像是這樣: ->

square = (x) -> x * x cube = (x) -> square(x) * x 
var cube, square; square = function(x) { return x * x; }; cube = function(x) { return square(x) * x; }; 
load
run: cube(5)

一些函數函數參數會有默認值, 當傳入的參數的不存在 (null 或者 undefined) 時會被使用.

fill = (container, liquid = "coffee") -> "Filling the #{container} with #{liquid}..." 
var fill; fill = function(container, liquid) { if (liquid == null) { liquid = "coffee"; } return "Filling the " + container + " with " + liquid + "..."; }; 
load
run: fill("cup")

對象和數組CoffeeScript 中對象和數組的字面量看起來很像在 JavaScript 中的寫法. 若是單個屬性被寫在本身的一行裏, 那麼逗號是能夠省略的. 和 YAML 相似, 對象能夠用縮進替代花括號來聲明.

song = ["do", "re", "mi", "fa", "so"] singers = {Jagger: "Rock", Elvis: "Roll"} bitlist = [ 1, 0, 1 0, 0, 1 1, 1, 0 ] kids = brother: name: "Max" age: 11 sister: name: "Ida" age: 9 
var bitlist, kids, singers, song; song = ["do", "re", "mi", "fa", "so"]; singers = { Jagger: "Rock", Elvis: "Roll" }; bitlist = [1, 0, 1, 0, 0, 1, 1, 1, 0]; kids = { brother: { name: "Max", age: 11 }, sister: { name: "Ida", age: 9 } }; 
load
run: song.join(" ... ")

JavaScript 裏, 你不能使用不添加引號的保留字段做爲屬性名稱, 好比 class. CoffeeScript 裏做爲鍵出現的保留字會被識別並補上引號, 因此你不用有額外的操心(好比說, 使用 jQuery 的時候).

$('.account').attr class: 'active'

log object.class
$('.account').attr({ "class": 'active' }); log(object["class"]); 
load

詞法做用域和變量安全CoffeeScript 編譯器會考慮全部變量, 保證每一個變量都在詞法域裏適當地被定義 — 你永遠不須要本身去寫 var.

outer = 1 changeNumbers = -> inner = -1 outer = 10 inner = changeNumbers()
var changeNumbers, inner, outer; outer = 1; changeNumbers = function() { var inner; inner = -1; return outer = 10; }; inner = changeNumbers(); 
load
run: inner

注意全部變量的定義都被推到相關的頂層做用域, 也就是第一次出現的位置. outer 在內層的函數裏沒有被從新定義, 由於它已經存在於做用域當中了. 同時, 內層函數裏的 inner 不該該改變外部的同名的變量, 因此在這裏有本身的聲明.

其行爲和 Ruby 的局部變量的做用域其實是一致的. 因爲你沒有對 var 關鍵字的直接訪問, 根據須要隱藏一個外部變量就很容易, 你只能引用它. 因此在寫深層的嵌套的函數時, 注意不要意外用到和外部變量相同的名字.

儘管要說清楚會受到文檔長度限制, 函數的全部 CoffeeScript 結果都被一個匿名函數包裹:(function(){ ... })(); 這層安全的封裝, 加上自動生成的 var 關鍵字, 使得不當心污染全局命名空間很難發生.

若是你但願建立一個其餘腳本也能使用的頂層變量, 那麼將其做爲賦值在 window 上, 或者在 CommonJS 裏的 exports 上. 存在操做符(existential operator)能夠幫你寫出一個可靠的方式找到添加位置; 好比你的目標是同時知足 CommonJS 和瀏覽器: exports ? this

if, else, unless 和條件賦值if/else 表達式能夠不用圓括號和花括號就寫出來. 就像函數和其餘塊級表達式那樣, 多行的條件能夠經過縮進來代表. 另外還有一個順手的後綴形式, 在行尾使用 if or unless.

CoffeeScript 會嘗試編譯 if 語句到 JavaScript 表達式, 或者一個封裝的閉包. CoffeeScript 裏不存在直白的三元表達式. — 你只要在一行內使用普通的 if 語句.

mood = greatlyImproved if singing if happy and knowsIt clapsHands() chaChaCha() else showIt() date = if friday then sue else jill 
var date, mood; if (singing) { mood = greatlyImproved; } if (happy && knowsIt) { clapsHands(); chaChaCha(); } else { showIt(); } date = friday ? sue : jill; 
load

變參(splats)...使用 JavaScript 的 arguments 對象是一種處理接收不定數量個參數的函數經常使用辦法. CoffeeScript 在函數定義和調用裏提供了變參(splats) ... 的語法, 讓不定個數的參數使用起來更愉悅一些.

gold = silver = rest = "unknown" awardMedals = (first, second, others...) -> gold = first silver = second rest = others contenders = [ "Michael Phelps" "Liu Xiang" "Yao Ming" "Allyson Felix" "Shawn Johnson" "Roman Sebrle" "Guo Jingjing" "Tyson Gay" "Asafa Powell" "Usain Bolt" ] awardMedals contenders... alert "Gold: " + gold alert "Silver: " + silver alert "The Field: " + rest 
var awardMedals, contenders, gold, rest, silver, __slice = [].slice; gold = silver = rest = "unknown"; awardMedals = function() { var first, others, second; first = arguments[0], second = arguments[1], others = 3 <= arguments.length ? __slice.call(arguments, 2) : []; gold = first; silver = second; return rest = others; }; contenders = ["Michael Phelps", "Liu Xiang", "Yao Ming", "Allyson Felix", "Shawn Johnson", "Roman Sebrle", "Guo Jingjing", "Tyson Gay", "Asafa Powell", "Usain Bolt"]; awardMedals.apply(null, contenders); alert("Gold: " + gold); alert("Silver: " + silver); alert("The Field: " + rest); 
load
run

循環和推導式你可使用CoffeeScript將大多數的循環寫成基於數組、對象或範圍的推導式(comprehensions)。 推導式替代(編譯爲)for循環,而且可使用可選的子句和數組索引值。 不一樣於for循環,數組的推導式是表達式,能夠被返回和賦值。

# 吃午餐. eat food for food in ['toast', 'cheese', 'wine'] # 精緻的五道菜. courses = ['greens', 'caviar', 'truffles', 'roast', 'cake'] menu i + 1, dish for dish, i in courses # 注重健康的一餐. foods = ['broccoli', 'spinach', 'chocolate'] eat food for food in foods when food isnt 'chocolate' 
var courses, dish, food, foods, i, _i, _j, _k, _len, _len1, _len2, _ref; _ref = ['toast', 'cheese', 'wine']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { food = _ref[_i]; eat(food); } courses = ['greens', 'caviar', 'truffles', 'roast', 'cake']; for (i = _j = 0, _len1 = courses.length; _j < _len1; i = ++_j) { dish = courses[i]; menu(i + 1, dish); } foods = ['broccoli', 'spinach', 'chocolate']; for (_k = 0, _len2 = foods.length; _k < _len2; _k++) { food = foods[_k]; if (food !== 'chocolate') { eat(food); } } 
load

推導式能夠適用於其餘一些使用循環的地方,例如each/forEachmap,或者select/filter,例如:shortNames = (name for name in list when name.length < 5)
若是你知道循環的開始與結束,或者但願以固定的跨度迭代,你能夠在範圍推導式中 指定開始與結束。

countdown = (num for num in [10..1]) 
var countdown, num; countdown = (function() { var _i, _results; _results = []; for (num = _i = 10; _i >= 1; num = --_i) { _results.push(num); } return _results; })(); 
load
run: countdown

注意:上面的例子中咱們展現瞭如何將推導式賦值給變量,CoffeeScript老是將 每一個循環項收集到一個數組中。可是有時候以循環結尾的函數運行的目的就是 它們的反作用(side-effects)。這種狀況下要注意不要意外的返回推導式的結果, 而是在函數的結尾增長一些有意義的返回值—例如true — 或 null

在推導式中使用by子句,能夠實現以固定跨度迭代範圍值: evens = (x for x in [0..10] by 2)

推導式也能夠用於迭代對象中的key和value。在推導式中使用of 來取出對象中的屬性,而不是數組中的值。

yearsOld = max: 10, ida: 9, tim: 11 ages = for child, age of yearsOld "#{child} is #{age}" 
var age, ages, child, yearsOld; yearsOld = { max: 10, ida: 9, tim: 11 }; ages = (function() { var _results; _results = []; for (child in yearsOld) { age = yearsOld[child]; _results.push("" + child + " is " + age); } return _results; })(); 
load
run: ages.join(", ")

若是你但願僅迭代在當前對象中定義的屬性,經過hasOwnProperty檢查並 避免屬性是繼承來的,能夠這樣來寫:
for own key, value of object

CoffeeScript僅提供了一種底層循環,即while循環。與JavaScript中的while 循環的主要區別是,在CoffeeScript中while能夠做爲表達式來使用, 並且能夠返回一個數組,該數組包含每一個迭代項的迭代結果。

# 經濟 101 if this.studyingEconomics buy() while supply > demand sell() until supply > demand # 搖籃曲 num = 6 lyrics = while num -= 1 "#{num} little monkeys, jumping on the bed. One fell out and bumped his head." 
var lyrics, num; if (this.studyingEconomics) { while (supply > demand) { buy(); } while (!(supply > demand)) { sell(); } } num = 6; lyrics = (function() { var _results; _results = []; while (num -= 1) { _results.push("" + num + " little monkeys, jumping on the bed. One fell out and bumped his head."); } return _results; })(); 
load
run: lyrics.join(" ")

爲了更好的可讀性,until關鍵字等同於while notloop關鍵字 等同於while true

使用 JavaScript 循環生成函數的時候, 常常會添加一個閉包來包裹代碼, 這樣作目的是爲了循環的變量被保存起來, 而不是全部生成的函數搜去訪問最後一個循環的變量. CoffeeScript 提供了一個 do 關鍵字, 用來直接調用跟在後邊的函數, 而且傳遞須要的參數.

for filename in list do (filename) -> fs.readFile filename, (err, contents) -> compile filename, contents.toString()
var filename, _fn, _i, _len; _fn = function(filename) { return fs.readFile(filename, function(err, contents) { return compile(filename, contents.toString()); }); }; for (_i = 0, _len = list.length; _i < _len; _i++) { filename = list[_i]; _fn(filename); } 
load

數組的切片和用 range 進行拼接Range 也能夠被用來展開數組的切片. 經過兩個點號的寫法 (3..6), range 會包含最後一個數據 (3, 4, 5, 6); 經過三個點號的寫法 (3...6), range 不會包含最後一個數據 (3, 4, 5). 切片的索引位置存在不錯的默認值. 前面的索引位置省略的話, 默認會是 0, 後面的索引位置被省略的話, 默認值是數組的大小.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9] start = numbers[0..2] middle = numbers[3...-2] end = numbers[-2..] copy = numbers[..] 
var copy, end, middle, numbers, start; numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; start = numbers.slice(0, 3); middle = numbers.slice(3, -2); end = numbers.slice(-2); copy = numbers.slice(0); 
load
run: middle

一樣的語法還能夠用在數組的片斷上賦值一些新的值, 進行拼接.

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] numbers[3..6] = [-3, -4, -5, -6] 
var numbers, _ref; numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; [].splice.apply(numbers, [3, 4].concat(_ref = [-3, -4, -5, -6])), _ref; 
load
run: numbers

注意 JavaScript 的 string 是不可變的, 因此不能用被拼接.

一切都是表達式 (至少儘量成爲)讀者大概有注意到上面的代碼 CoffeeScript 函數是不須要寫 return 語句的, 可是也會返回最終的結果. CoffeeScript 編譯器會盡量保證語言中全部的表達式均可以被看成表達式使用. 觀察一下下面的函數, return 是怎樣儘量地插入到執行的分支當中的.

grade = (student) -> if student.excellentWork "A+" else if student.okayStuff if student.triedHard then "B" else "B-" else "C" eldest = if 24 > 21 then "Liz" else "Ike"
var eldest, grade; grade = function(student) { if (student.excellentWork) { return "A+"; } else if (student.okayStuff) { if (student.triedHard) { return "B"; } else { return "B-"; } } else { return "C"; } }; eldest = 24 > 21 ? "Liz" : "Ike"; 
load
run: eldest

儘管函數老是會自動 return 其最終的值, 你能夠在函數體前面顯式地寫上 (return value), 這個作法也是值得借鑑的, 前提是你明確你在作的事情是什麼.

因爲變量聲明是生成在做用域頂部, 因此在表達式內部也能夠寫賦值, 即使是前面沒寫到過的變量.

six = (one = 1) + (two = 2) + (three = 3) 
var one, six, three, two; six = (one = 1) + (two = 2) + (three = 3); 
load
run: six

有些代碼在 JavaScript 當中要寫很多的語句, 而在 CoffeeScript 中只是表達式的一部分, 這些代碼的編譯結果會自動生成一個閉包. 這個寫法頗有用, 好比把列表解析的結果賦值給變量:

# 前十個全局屬性(變量). globals = (name for name of window)[0...10]
var globals, name; globals = ((function() { var _results; _results = []; for (name in window) { _results.push(name); } return _results; })()).slice(0, 10); 
load
run: globals

結果是一些原來明確是語句的東西也能夠像, 好比把 try/catch 語句直接傳給函數調用:

alert(
  try nonexistent / undefined catch error "And the error is ... #{error}" ) 
var error; alert((function() { try { return nonexistent / void 0; } catch (_error) { error = _error; return "And the error is ... " + error; } })()); 
load
run

有一些 JavaScript 語句是不能編譯到表達式的對應的語義的, 好比 breakcontinue 和 return. 若是你的代碼當中用到了它們, CoffeeScript 是步驟嘗試去進行轉換的.

操做符和 aliase因爲操做符 == 經常帶來不許確的約束, 不容易達到效果, 並且跟其餘語言當中意思不一致, CoffeeScript 會把 == 編譯爲 ===, 把 != 變異爲 !==. 此外, is 編譯我 ===, 而 isnt 編譯爲 !==.

not 能夠做爲 ! 的 alias 使用.

邏輯操做方面, and 編譯爲 &&, 而 or 編譯爲 ||.

在 whileif/elseswitch/when 的語句當中, then 能夠被用來分隔判斷條件跟表達式, 這樣就不用強制寫換行或者分號了.

就像 YAMLon 和 yes 跟 true 是同樣的, 而 off 和 no 是布爾值 false.

unless 能夠認爲是 if 相反的版本.

this.property 簡短的寫法能夠用 @property.

能夠用 in 判斷數據在數組中是否出現, 而 of 能夠探測 JavaScript 對象的屬性是否存在.

爲了簡化數學表達式, ** 能夠用來表示乘方, // 表示整除, %% 提供數學的模運算(譯註: true mathematical modulo?).

完整的列表:

CoffeeScript JavaScript
is ===
isnt !==
not !
and &&
or ||
trueyeson true
falsenooff false
@this this
of in
in no JS equivalent
a ** b Math.pow(a, b)
a // b Math.floor(a / b)
a %% b (a % b + b) % b
launch() if ignition is on volume = 10 if band isnt SpinalTap letTheWildRumpusBegin() unless answer is no if car.speed < limit then accelerate() winner = yes if pick in [47, 92, 13] print inspect "My name is #{@name}" 
var volume, winner; if (ignition === true) { launch(); } if (band !== SpinalTap) { volume = 10; } if (answer !== false) { letTheWildRumpusBegin(); } if (car.speed < limit) { accelerate(); } if (pick === 47 || pick === 92 || pick === 13) { winner = true; } print(inspect("My name is " + this.name)); 
load

存在性操做符在 JavaScript 裏檢測一個變量的存在性有點麻煩. if (variable) ... 比較接近答案, 可是對 `0` 不成立. CoffeeScript 的存在性操做符 ? 除非是 null 或者 undefined, 不然都返回 true, 這大體是模仿 Ruby 當中的 nil?.

這也能夠用在比 ||= 更安全的條件賦值當中, 有些狀況你會須要處理數字跟字符串的.

solipsism = true if mind? and not world? speed = 0 speed ?= 15 footprints = yeti ? "bear" 
var footprints, solipsism, speed; if ((typeof mind !== "undefined" && mind !== null) && (typeof world === "undefined" || world === null)) { solipsism = true; } speed = 0; if (speed == null) { speed = 15; } footprints = typeof yeti !== "undefined" && yeti !== null ? yeti : "bear"; 
load
run: footprints

存在性操做符 ?. 的訪問器的變體能夠用來吸取鏈式屬性調用中的 null. 數據多是 null 或者undefined 的狀況下能夠用這種寫法替代訪問器 .. 若是全部屬性都存在, 那麼你會獲得想要的結果, 若是鏈式調用有問題, 會返回 undefined 而不是拋出 TypeError.

zip = lottery.drawWinner?().address?.zipcode
var zip, _ref; zip = typeof lottery.drawWinner === "function" ? (_ref = lottery.drawWinner().address) != null ? _ref.zipcode : void 0 : void 0; 
load

吸取 null 數據的作法相似 Ruby 的 andand gem, 和 Groovy 的 safe navigation operator.

class, 繼承, superJavaScript 的原型集成有點燒腦, 存在大量的類庫用於在 JavaScript 的原型之上實現更清晰的 class 繼承好比: Base2Prototype.jsJS.Class. 這些類庫提供了語法糖, 但若是不是由於一些例外的話原生的繼承徹底是可用的, 例外好比: 很難調用 super(當前函數的原型上的實現), 很難正確設置原型鏈.

相比重複地設置函數的原型, CoffeeScript 提供了一個基礎的 class 結構, 你能夠在一個定義的表達式裏完成命名 class, 定義父類, 賦值原型上的屬性, 定義構造器.

構造函數被命名, 這對查看調用棧有更好的支持. 下面例子中的第一個類, this.constructor.name is "Animal".

class Animal constructor: (@name) -> move: (meters) -> alert @name + " moved #{meters}m." class Snake extends Animal move: -> alert "Slithering..." super 5 class Horse extends Animal move: -> alert "Galloping..." super 45 sam = new Snake "Sammy the Python" tom = new Horse "Tommy the Palomino" sam.move() tom.move() 
var Animal, Horse, Snake, sam, tom, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; Animal = (function() { function Animal(name) { this.name = name; } Animal.prototype.move = function(meters) { return alert(this.name + (" moved " + meters + "m.")); }; return Animal; })(); Snake = (function(_super) { __extends(Snake, _super); function Snake() { return Snake.__super__.constructor.apply(this, arguments); } Snake.prototype.move = function() { alert("Slithering..."); return Snake.__super__.move.call(this, 5); }; return Snake; })(Animal); Horse = (function(_super) { __extends(Horse, _super); function Horse() { return Horse.__super__.constructor.apply(this, arguments); } Horse.prototype.move = function() { alert("Galloping..."); return Horse.__super__.move.call(this, 45); }; return Horse; })(Animal); sam = new Snake("Sammy the Python"); tom = new Horse("Tommy the Palomino"); sam.move(); tom.move(); 
load
run

若是你不喜歡用 class 的裁判法定義原型, CoffeeScript 提供了一些低級的方便寫法. extends 操做符能夠用來恰當地定義任何一對構造函數的原型鏈; 用 :: 能夠快速訪問對象的原型; super() 能夠編譯爲一個父類上同名方法的調用.

String::dasherize = -> this.replace /_/g, "-" 
String.prototype.dasherize = function() { return this.replace(/_/g, "-"); }; 
load
run: "one_two".dasherize()

最後, class 定義是可執行的代碼, 這樣就可能進行元編程. 由於在 class 定義的上下文當中, this 是類對象自己(構造函數), 能夠用 @property: value 賦值靜態的屬性, 也能夠調用父類的方法: @attr 'title', type: 'text'.

解構賦值CoffeeScript 實現 ECMAScript Harmony 的提議 解構賦值 語法, 這樣從複雜的數組和對象展開數據會更方便一些. 當你把數組或者對象的字面量賦值到一個變量時, CoffeeScript 把等式兩邊都解開配對, 把右邊的值賦值給左邊的變量. 最簡單的例子, 能夠用來並行賦值:

theBait   = 1000 theSwitch = 0 [theBait, theSwitch] = [theSwitch, theBait] 
var theBait, theSwitch, _ref; theBait = 1000; theSwitch = 0; _ref = [theSwitch, theBait], theBait = _ref[0], theSwitch = _ref[1]; 
load
run: theBait

用來處理函數多返回值也很方便.

weatherReport = (location) -> # 發起一個 Ajax 請求獲取天氣... [location, 72, "Mostly Sunny"] [city, temp, forecast] = weatherReport "Berkeley, CA" 
var city, forecast, temp, weatherReport, _ref; weatherReport = function(location) { return [location, 72, "Mostly Sunny"]; }; _ref = weatherReport("Berkeley, CA"), city = _ref[0], temp = _ref[1], forecast = _ref[2]; 
load
run: forecast

解構賦值能夠用在深度嵌套的數組跟對象上, 取出深度嵌套的屬性.

futurists =
  sculptor: "Umberto Boccioni" painter: "Vladimir Burliuk" poet: name: "F.T. Marinetti" address: [ "Via Roma 42R" "Bellagio, Italy 22021" ] {poet: {name, address: [street, city]}} = futurists 
var city, futurists, name, street, _ref, _ref1; futurists = { sculptor: "Umberto Boccioni", painter: "Vladimir Burliuk", poet: { name: "F.T. Marinetti", address: ["Via Roma 42R", "Bellagio, Italy 22021"] } }; _ref = futurists.poet, name = _ref.name, (_ref1 = _ref.address, street = _ref1[0], city = _ref1[1]); 
load
run: "name + "-" + street"

解構賦值還能夠跟 splats 搭配使用.

tag = "<impossible>" [open, contents..., close] = tag.split("") 
var close, contents, open, tag, _i, _ref, __slice = [].slice; tag = "<impossible>"; _ref = tag.split(""), open = _ref[0], contents = 3 <= _ref.length ? __slice.call(_ref, 1, _i = _ref.length - 1) : (_i = 1, []), close = _ref[_i++]; 
load
run: contents.join("")

展開式(expansion)能夠用於獲取數組結尾的元素, 而不須要對中間過程的數據進行賦值. 它也能夠用在函數參數的列表上.

text = "Every literary critic believes he will outwit history and have the last word" [first, ..., last] = text.split " " 
var first, last, text, _ref; text = "Every literary critic believes he will outwit history and have the last word"; _ref = text.split(" "), first = _ref[0], last = _ref[_ref.length - 1]; 
load
run: "first + " " + last"

解構賦值也能夠用在 class 的構造器上, 從構造器配置對象賦值到示例屬性上.

class Person constructor: (options) -> {@name, @age, @height} = options tim = new Person age: 4 
var Person, tim; Person = (function() { function Person(options) { this.name = options.name, this.age = options.age, this.height = options.height; } return Person; })(); tim = new Person({ age: 4 }); 
load
run: tim.age

函數綁定JavaScript 當中 this 關鍵字被動態地設定爲當前函數掛載所在的對象上. 若是你把函數看成回調, 或者掛載到別的對象, 那麼原先的 this 就丟失了. 若是你不瞭解這個行爲, If you're not familiar with this behavior, this Digital Web article 對怪異模式作了很好的回顧.

Fat arrow => 能夠同時定義函數, 綁定函數的 this 到當前的值, 正是咱們須要的. 這有助於在 Prototype 或者 jQuery 這種基於回調的類庫當中使用, 用於建立迭代器函數傳遞給 each, 或者藉助 bind 的事件處理器函數. Fat arrow 定義的函數能夠訪問到他們建立位置的 this 對象的屬性.

Account = (customer, cart) -> @customer = customer @cart = cart $('.shopping_cart').bind 'click', (event) => @customer.purchase @cart
var Account; Account = function(customer, cart) { this.customer = customer; this.cart = cart; return $('.shopping_cart').bind('click', (function(_this) { return function(event) { return _this.customer.purchase(_this.cart); }; })(this)); }; 
load

若是上邊用的是 this@customer 會指向一個 DOM 元素的 undefined "customer" 屬性, 而後強行調用上面的 purchase() 時會拋出一個異常.

對於類的定義, 實例建立的過程當中 fat arrow 定義的方法會自動綁定到類的每一個示例上去.

嵌入 JavaScript這個寫法應該不會被用到, 但若是何時須要在 CoffeeScript 中穿插 JavaScript 片斷的話, 你能夠用反引號直接傳進去.

hi = `function() { return [document.title, "Hello JavaScript"].join(": "); }` 
var hi; hi = function() { return [document.title, "Hello JavaScript"].join(": "); }; 
load
run: hi()

Switch/When/ElseJavaScript 裏的 Switch 語句有點難看. 你須要在每一個 case 寫 break 防止自動進入默認的 case. CoffeeScript 會阻止掉意外的 fall-through. 並且 switch 編譯的結果會是能夠帶 return, 能夠被用於賦值的表達式. 格式這樣寫: switch 判斷條件, when 而後子句, else 而後默認的 case.

就像 Ruby, CoffeeScript 裏邊 switch 語句對於每一個子句能夠帶多個值. 任何一個值匹配的狀況下, 子句就會執行.

switch day when "Mon" then go work when "Tue" then go relax when "Thu" then go iceFishing when "Fri", "Sat" if day is bingoDay go bingo go dancing when "Sun" then go church else go work
switch (day) { case "Mon": go(work); break; case "Tue": go(relax); break; case "Thu": go(iceFishing); break; case "Fri": case "Sat": if (day === bingoDay) { go(bingo); go(dancing); } break; case "Sun": go(church); break; default: go(work); } 
load

Switch 語句也能夠不寫控制條件, 看成 if/else 調用鏈的一個更整潔的可選寫法.

score = 76 grade = switch when score < 60 then 'F' when score < 70 then 'D' when score < 80 then 'C' when score < 90 then 'B' else 'A' # grade == 'C' 
var grade, score; score = 76; grade = (function() { switch (false) { case !(score < 60): return 'F'; case !(score < 70): return 'D'; case !(score < 80): return 'C'; case !(score < 90): return 'B'; default: return 'A'; } })(); 
load

Try/Catch/FinallyTry/catch 語句基本上 JavaScript 的同樣(儘管它們是表達式執行).

try allHellBreaksLoose() catsAndDogsLivingTogether() catch error print error finally cleanUp() 
var error; try { allHellBreaksLoose(); catsAndDogsLivingTogether(); } catch (_error) { error = _error; print(error); } finally { cleanUp(); } 
load

Chained ComparisonsCoffeeScript 從 Python 學習了 鏈式對比 — 這樣判斷數值是否在某個範圍內在寫法上更容易.

cholesterol = 127 healthy = 200 > cholesterol > 60 
var cholesterol, healthy; cholesterol = 127; healthy = (200 > cholesterol && cholesterol > 60); 
load
run: healthy

字符串替換, 塊級的字符串, 塊級的註釋Ruby 風格的字符串替換也在 CoffeeScript 實現了. 雙引號包裹的字符串容許數據替換, 用 #{ ... }語法, 而單引號包裹的字符串僅僅是字面量.

author = "Wittgenstein" quote = "A picture is a fact. -- #{ author }" sentence = "#{ 22 / 7 } is a decent approximation of π" 
var author, quote, sentence; author = "Wittgenstein"; quote = "A picture is a fact. -- " + author; sentence = "" + (22 / 7) + " is a decent approximation of π"; 
load
run: sentence

CoffeeScript 支持多行字符串. 行與行會用一個空格拼接, 除非結尾用了反斜槓. 其中縮進會被忽略.

mobyDick = "Call me Ishmael. Some years ago -- never mind how long precisely -- having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world..." 
var mobyDick; mobyDick = "Call me Ishmael. Some years ago -- never mind how long precisely -- having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world..."; 
load
run: mobyDick

塊級的字符串能夠用於書寫格式化的或者對縮進敏感的文本(或者你只是不想轉義單引號雙引號). 代碼塊開始的位置的縮進層級會被保留, 用在後面的代碼中, 因此這部分代碼依然能夠跟總體的代碼一塊兒對齊.

html = """ <strong> cup of coffeescript </strong> """ 
var html; html = "<strong>\n cup of coffeescript\n</strong>"; 
load
run: html

塊級的字符串用雙引號, 跟普通的雙引號字符串同樣, 支持替換.

有時候你想把整塊的註釋傳給生成的 JavaScript. 好比在文件頂部嵌入協議. 塊級的註釋, 仿照了塊級字符串的語法, 將會在生成的代碼當中保留.

### SkinnyMochaHalfCaffScript Compiler v1.0 Released under the MIT License ### 
/* SkinnyMochaHalfCaffScript Compiler v1.0 Released under the MIT License */ 
load

塊級的正則表達式相似塊級的字符串跟註釋, CoffeeScript 支持塊級的正則 — 擴展了正則表達式, 能夠忽略內部的空格, 能夠包含註釋和替換. 模仿了 Perl 的 /x 修飾符, CoffeeScript 的塊級正則以 /// 爲界, 讓正則表達式得到了很大程度的可讀性. 引用一下 CoffeeScript 源碼:

OPERATOR = /// ^ ( ?: [-=]> # 函數 | [-+*/%<>&|^!?=]= # 複合賦值 / 比較 | >>>=? # 補 0 右移 | ([-+:])\1 # 雙寫 | ([&|<>])\2=? # 邏輯 / 移位 | \?\. # soak 訪問 | \.{2,3} # 範圍或者 splat ) /// 
var OPERATOR; OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?\.|\.{2,3})/; 
load

Cake, and Cakefiles

CoffeeScript includes a (very) simple build system similar to Make and Rake. Naturally, it's called Cake, and is used for the tasks that build and test the CoffeeScript language itself. Tasks are defined in a file named Cakefile, and can be invoked by running cake [task]from within the directory. To print a list of all the tasks and options, just type cake.

Task definitions are written in CoffeeScript, so you can put arbitrary code in your Cakefile. Define a task with a name, a long description, and the function to invoke when the task is run. If your task takes a command-line option, you can define the option with short and long flags, and it will be made available in the options object. Here's a task that uses the Node.js API to rebuild CoffeeScript's parser:

fs = require 'fs' option '-o', '--output [DIR]', 'directory for compiled code' task 'build:parser', 'rebuild the Jison parser', (options) -> require 'jison' code = require('./lib/grammar').parser.generate() dir = options.output or 'lib' fs.writeFile "#{dir}/parser.js", code
var fs; fs = require('fs'); option('-o', '--output [DIR]', 'directory for compiled code'); task('build:parser', 'rebuild the Jison parser', function(options) { var code, dir; require('jison'); code = require('./lib/grammar').parser.generate(); dir = options.output || 'lib'; return fs.writeFile("" + dir + "/parser.js", code); }); 
load

If you need to invoke one task before another — for example, running build before test, you can use the invoke function: invoke 'build'. Cake tasks are a minimal way to expose your CoffeeScript functions to the command line, so don't expect any fanciness built-in. If you need dependencies, or async callbacks, it's best to put them in your code itself — not the cake task.

相關文章
相關標籤/搜索