天天看點

CoffeeScript快速入門

CoffeeScript快速入門

CoffeeScript是最近比較流行的一個小的程式設計語言,它有自己的文法(受Python和Ruby影響比較多,個人覺得更象Ruby),其編譯器将其編譯輸出javascript。至于生成的javascript則可以在浏覽器運作也可以在伺服器端運作(NodeJS)。例如最簡單的helloWorld函數(你可以點選這個連結進入CoffeeScript提供的線上工具)

# ===========
# CoffeeScript
# ===========
helloWorld = (name)->
  alert "Hello World #{name}"

// ==========
// Javascript
// ==========
var helloWorld;

helloWorld = function(name) {
  return alert("Hello World " + name);
};
           

下面我們可以就上面的代碼簡單介紹CoffeeScript的文法。

簡單文法

  1. 空格/縮進很重要

    跟Ruby一樣,沒有分号作為語句的分隔符,而是以換行;也沒有花括号來表示一個語句塊,而是以縮進來表示。是以在上面的代碼中

    alert

    語句是縮進了兩個空格。當然你可以縮進三個,四個,任意個,隻是同一個語句塊要有同樣的縮進。
  2. 變量不需要聲明

    所有的變量都會自動定義成局部變量,以消除全局變量。例如上面例子的helloWorld函數。熟悉javascript的都知道,沒有var聲明的變量,自動成為全局變量,那樣容易造成變量名的混亂。而不聲明var是可能發生的手誤,尤其對于新手來說。是以如果想要定義全局變量,習慣上可能會以

    g_

    作為字首,這樣便于代碼的閱讀和維護。當然現在的Javascript講究的是盡量不要用全局變量,最大的理由就是變量的覆寫,尤其是在采用了很多類庫的情況下,如果大家都用全局變量就完蛋了。舉個例子來說,大家都知道大名鼎鼎的

    $

    ,JQuery的簡寫。如果我自己的代碼也定義一個全局變量為

    $

    ,并且加載在jQuery之後,那麼就覆寫了jQuery的

    $

    ,你再想用它就不是jQuery了。
  3. 函數聲明采用箭頭(->)

    究其原因是沒有的,反正文法就是這麼規定,是以需要記住。類似與上面的例子,在箭頭後面我們定義了函數的實作。至于函數的參數需要在箭頭的左邊聲明,并且置于括号之中。如果沒有參數的話,你可以在括号中置空,也可以連括号都不要。CoffeeScript支援設定預設參數,可變參數,具體可以參考CoffeeScript。
  4. 函數調用可以不要括号

    你可以看到alert函數調用是沒有加括号的,這也是Ruby裡面的習慣用法。這樣更佳符合英文的書寫和閱讀,對于我們中國人來說可能有括号更加容易了解,至少象我這樣從C語言過來的。但是要注意,如果一個函數沒有參數的話,那麼調用的時候一定要加括号,應為CoffeeScript它不确定你是當當要引用這個函數還是要調用它。其實Ruby是預設認為調用。這裡也可以看到CoffeeScript隻是實作了Javascript的一個子集,因為它也可以調用alert方法。
  5. 雙引号的字元串中可插值

    在調用alert時候生成的字元串,我們直接在字元串中引入了變量name。這個方法還是很不錯的。注意一定是雙引号的字元串,單引号是不行的。
  6. 函數傳回值預設是最後一句的值

    在生成的javascript版本helloWorld,你可以發現有return語句,盡管我們在CoffeeScript中并沒有return。這就是預設的,CoffeeScript函數傳回最後一句的值。有的時候這減少了寫return了。但是如果在函數中間要傳回值,那還是得要return的。

class

其實我最喜歡的是類的支援。jQuery這麼流行的庫為什麼不支援面向對象呢?我認為基本的面向對象就是類,類的繼承,方法的覆寫。這些CoffeeScript都給出了很好的支援。譬如,

class Animal
  @count: 0
  @getCount: ->
    @count

  constructor: (@name)->    
    Animal.count++
  getName: ->
    @name

  say: ->
    alert @getName()

class Dog extends Animal
  constructor: (name)->
    super(name)
    @type = 'Dog'

  getName: ->
    @type + ": " + super()

animal = new Animal('A')
# alert('A')
animal.say()
# 1
alert Animal.getCount()

# alert('Dog: D')
dog = new Dog('D')
dog.say()
# 2
alert Animal.getCount()

# 0
alert Dog.getCount()

# error, no method getCount for animal
alert animal.getCount()
# error, no method getCount for dog
alert dog.getCount()
           

對應輸出的Javascript則是

var Animal, Dog, animal, dog,
  __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() {

  Animal.count = 0;

  Animal.getCount = function() {
    return this.count;
  };

  function Animal(name) {
    this.name = name;
    Animal.count++;
  }

  Animal.prototype.getName = function() {
    return this.name;
  };

  Animal.prototype.say = function() {
    return alert(this.getName());
  };

  return Animal;

})();

Dog = (function(_super) {

  __extends(Dog, _super);

  function Dog(name) {
    Dog.__super__.constructor.call(this, name);
    this.type = 'Dog';
  }

  Dog.prototype.getName = function() {
    return this.type + ": " + Dog.__super__.getName.call(this);
  };

  return Dog;

})(Animal);

animal = new Animal('A');

animal.say();

alert(Animal.getCount());

dog = new Dog('D');

dog.say();

alert(Animal.getCount());

alert(Dog.getCount());

alert(animal.getCount());

alert(dog.getCount());
           

從這個代碼的對比來看,CoffeeScript的确是相當高效的.具體說來class實作有如下要點

  1. 關鍵字class聲明一個對象

    在對象内部的定義,則是采用了函數名後面加冒号(不是等号),然後再是函數的定義。
  2. constructor為構造函數 new一個對象的時候就會調用該函數,相當于Ruby中的initialize函數。顯然每個class隻有一個構造函數。在成員函數中的@為this的簡寫,例如getName函數中的@name表示就是this.name,也就是類執行個體的name變量。在constructor中我們采用了一個更加簡單的寫法,就是直接将傳入的name參數賦給成員變量name,是以我們直接在參數name前面加了@符号。
  3. super 子類的方法可以通過super調用父類的方法,例如Dog的constructor和getName,都有這樣的用法。
  4. 成員函數或變量聲明前加@表示靜态方法或變量 例如getCount和count,它們都是直接轉化成Animal的屬性。是以在調用getCount的時候,隻有通過Animal.getCount()才起作用。通過執行個體animal和dog進行調用,都會報錯說方法不存在。而通過Dog類調用的時候,你會發現該方法是存在的,但是值可能不是你想要的。這是因為CoffeeScript生成的 JS代碼__extends會将父類的所有屬性複制一份到子類,也就是說在最開始的時候會将Animal的count和getCount屬性複制到Dog裡面去。而我們在Animal的構造函數中,隻是增加了Animal.count的計數,而不是Dog.count的計數。

CoffeeScript的負面評價

更多其它的細節可以在CoffeeScript找到。在好評如潮的情況下,我也找到一些負面的評價。如這兩篇文章,

http://ryanflorence.com/2011/case-against-coffeescript/

http://ceronman.com/2012/09/17/coffeescript-less-typing-bad-readability/

我覺得寫得相當不錯。我是不太喜歡There is more than one way to do it (TIMTOWTDI) (不止一種方法來實作一個功能),因為這樣勢必造成學習曲線增加,難記,也導緻難以維護。每個人用不同的方法來做,那維護的人就要痛苦死了。而Java就是因為簡單是以才會有這麼多人使用,開發效率也才會高,才會容易閱讀和維護。

安裝CoffeeScript

最後介紹一下如何在本地安裝CoffeeScript,雖然在官網都有,而且也可以在官網找到線上工具,但是為了文章的完整性,在最後還是附上。

  1. 安裝NodeJS
  2. npm install -g coffee-script

     運作安裝coffee script
  3. coffee -o lib/ -cw src/

     這個表示運作自動監控src下面的".coffee"檔案,然後以相同的檔案名編譯輸出到lib目錄下面,隻是檔案名字尾為js。

繼續閱讀