世界上的不少天才都在爲構建更好的JavaScript而努力。已經有了不少嘗試,其中最有前途的,無非就是CoffeeScript和TypeScript了。面對CoffeeScript,我有一見如故的感受;而TypeScript也激發了我極大的興趣。CoffeeScript和TypeScript同樣,都是編譯爲JavaScript的語言,它們都加強了JavaScript的表達能力。這篇文章是講CoffeeScript的,TypeScript將放在下一篇再講。javascript
所謂編譯爲JavaScript,是指CoffeeScript和TypeScript沒有實現本身的運行時,它們都是編譯爲等價的JavaScript代碼,而後放在JavaScript的解釋器上運行。java
CoffeeScript給人最大的印象就是其簡潔的表達。下面代碼是我從CoffeeScript中文摘抄下來的:編程
# 賦值: 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)
上面的代碼會編譯爲等價的JavaScript代碼:數組
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力求簡潔。其簡潔性首先表如今對一些僅用於語法控制的符號進行了去除。這其中包括:app
取消分號函數
取消var
聲明學習
取消大括號包圍內層代碼,使用縮進取代網站
函數調用在沒有歧義的狀況下能夠省略括號編碼
var
聲明涉及到複雜又很雞肋的JavaScript變量做用域機制。這部份內容先放着不講。CoffeeScript經過徹底取消var
聲明機制而使得問題獲得簡化。總之,在CoffeeScript世界裏,變量不用事先聲明,直接用就是了。並且這種用法基本沒有什麼危險。code
縮進在CoffeeScript中不只僅在於美化代碼,其表明了代碼層次的組織,是有特別的含義的。簡單地說就是,不適用大括號包圍內層代碼,而是內層代碼要縮進。不一樣的縮進表明了不一樣的代碼層次。形式和內容是一致的。
縮進的例子:
#if縮進 if true 'true' else 'false' #while縮進 while true 'true' #函數縮進 (n) -> n * n #對象字面量縮進 kids = brother: name: "Max" age: 11 sister: name: "Ida" age: 9
在不引發歧義的狀況下,CoffeeScript的函數調用能夠省略括號。例如console.log(object)
能夠簡化爲console.log object
。所謂引發歧義的一個例子就是無參數的狀況下,console.log
就不知道是取出函數型屬性log
仍是調用函數log
了。
CoffeeScript的函數表達式也作了極致的精簡精簡。一個單行函數的定義能夠這樣:
square = (x) -> x * x
而多行函數也是經過縮進來組織的。一個空的函數最爲簡潔,是這樣:->
。
函數的這種簡潔表達使得傳遞迴調函數很是便利。一個數組的map
可能像這樣就足夠了:
list = [1, 2, 3] list.map (e) -> e+1
而等效的JavaScript代碼就不能這麼馬虎:
list = [1, 2, 3]; list.map(function(e) { return e + 1; });
CoffeeScript提供了JavaScript所沒有的一些強大的表達語法,這也是被稱爲語法糖的東西。在我印象中,這種加強性是不少的,我舉出兩個有表明性的例子:
字符串插值法
列表解析
其中字符串插值法是對現有字符串能力的一種擴充和語法上的簡化;而列表解析就要涉及到觀念上的改變了。前者是一種改良,後者則是一種變革。
在CoffeeScript的字符串裏,能夠用#{…}
嵌入一個表達式。例如:
"#{ 22 / 7 } is a decent approximation of π"
等價於:
"" + (22 / 7) + " is a decent approximation of π";
插值在這裏起到佔位的做用,使得動態內容的字符串更容易構建。我想人人都能接受這樣的表達。
列表解析是CoffeeScript的世界裏的重要一員。它改變了循環的思路。CoffeeScript沒有提供像JavaScript那樣的for循環結構,而是通通轉化爲列表解析。一個常規的JavaScript for循環,像下面這樣:
food_list = ['toast', 'cheese', 'wine']; for (i = 0, len = food_list.length; i < len; i++) { food = food_list[i]; eat(food); }
用CoffeeScript實現就是:
food_list = ['toast', 'cheese', 'wine'] eat food for food in food_list #作個小補充,for循環的單條語句的寫法
單單是上面的例子不足以顯示列表解析的強大(卻看到它的簡潔了)。在繼續這個話題以前,我以爲我有必要補充一下另外一個涉及到CoffeeScript理念的東西了:一切皆是表達式。
在CoffeeScript世界裏,一切語句都是表達式語句,都會返回一個值。函數調用默認會返回最後一條語句的值。if條件結構也會返回值,其返回的是執行的最後一條語句的值。循環結構有些不一樣,其會將每次循環的結果都保存在一個數組裏,做爲此循環結構的值。例以下面代碼的list
結果就是[5, 4, 3, 2, 1]
。
num = 6 list = while num -= 1 num
回到列表解析的主題。與while同樣,for結構也是一種循環的表達,其結果也是一個數組。回到先前的例子,下面的小代碼的list
結果就是['t', 'c', 'w']。
food_list = ['toast', 'cheese', 'wine'] list = (food[0] for food in food_list)
咱們已經看到for循環的each
形式
eat food for food in food_list
以及它的map
形式
(food[0] for food in food_list)
下面給出它的filter形式
(food for food in food_list when food is 'wine')
列表解析的特點的地方在於它改變了咱們組織循環的方式和解析數組的模式。這是一種聲明式的編程方法,告訴程序你想要什麼而不去關心構建的過程。
類是CoffeeScript對JavaScript的一個很重要的補充。JavaScript的原型功能很強大,寫法上又恨彆扭。正確地設置原型鏈以實現繼承關係也是個很大的挑戰。CoffeeScript從語法上直接支持類的定義,天然且隱藏細節。
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()
從實現上來講,CoffeeScript的類與JavaScript的構造函數和原型鏈那一套並沒有二致。因此,理解原型機制也是理解CoffeeScript類的基礎。
CoffeeScript的另外一個目標是從語法層面上直接消除JavaScript的被人詬病的一些糟粕部分。前面已經說過關於分號的部分。關於var
聲明的部分。分號的機制暫且不去例會,總之CoffeeScript不用再去寫分號了。
在JavaScript當中,最爲人詬病的糟粕部分有兩處,由於它們使用的狀況最多並且容易出錯。
全局變量
相等比較
JavaScript的做用域規則很複雜,涉及到var
聲明機制和變量提高。在JavaScript裏,構造一個全局變量是很容易的,有三種方式:
在全局的環境裏用var
聲明
var name = 'name';
在函數內用省略var
的方式定義
function foo() { name = 'name'; }
綁定到window的屬性
window.name = 'name';
其中第1種和第2種方式是最多見的錯誤用法。首先不推薦直接在全局環境中編碼,而是應該用一個匿名函數包裹起來,將程序的做用域限制在這個匿名函數中。第二種用法完徹底全就是忘記了var
聲明。而我在實際的JavaScript編碼中,忘記var
聲明是常有的事(就像常常忘記行末補上分號同樣)。
而在CoffeeScript裏面就徹底沒有這種擔憂了。首先,編譯後的JavaScript代碼不會暴露在全局環境裏,全部的代碼都是自動包裹在一個匿名函數(function(){ ... })();
內。而後,全部的變量都會自動加上var
聲明。這就使得不當心污染全局的狀況很難發生,除非使用賦值到window
上。
咱們都知道JavaScript有兩種比較運算符:==
和===
。咱們也知道==
在使用的過程當中會很坑,因此平時都寧願多打一個字符而使用===
。CoffeeScript的只有一種比較運算符==
,而它會編譯成JavaScript的===
,從而很好地避過了這道坑。
CoffeeScript簡化和加強了JavaScript的表達能力,儘量地從語法層面上就能避免JavaScript的一些坑。用它寫代碼,會讓人有更清晰溫馨的感受,並且不容易犯錯。CoffeeScript的初衷就是提供更好的JavaScript。
然而,CoffeeScript與JavaScript是不兼容的。它既不是JavaScript的子集,也不是超集,而是與JavaScript有着顯然不一樣思路的一種語言。用CoffeeScript編程就必然要轉換觀念,儘管這種觀念更好更天然,但倒是有些固步自封的人望而卻步的主要緣由了。
CoffeeScript並非適合每個人的。有些人對於用縮進組織代碼層次徹底不能接受,也不能接受用箭頭函數表達法。對於他們來講,去掉function關鍵字和大括號的組織怎麼看都怎麼地不順眼。
列表解析很強大,卻也顯得過於簡潔了。對於習慣了構造冗雜JavaScript程序的人們來講,並不習慣這種表達方式。
總之,是不可強求別人去學習使用CoffeeScript。JavaScript已經足夠強大,只要足夠當心,徹底可使用JavaScript很好地完成工做。對於那些想要嘗試CoffeeScript,咱們也要給予鼓勵的態度,他們是求新求變的勇士。CoffeeScript真的值得一試,並且它真的很小巧,徹底掌握它不是件困難的事。
對於在團隊推行CoffeeScript,我本人更是持有保守的見解。若是團隊從一開始就使用CoffeeScript還好。若是是要從CoffeeScript轉爲JavaScript,就要謹慎行之。一種可行的方式是先嚐試在一個小項目中使用CoffeeScrip,看看效果如何。
對於我的來講,就沒有什麼限制了。若是真的喜歡,就去嘗試吧。你可使用CoffeeScript寫腳本,構建本身的網站,作一些小玩意。