阅读(1211) (0)

CoffeeScript用法

2016-08-23 14:45:48 更新

像老板一样指挥你的代码


CoffeeScript提供了一堆酷毙了的数组迭代方法。最好的事莫过于这不仅仅能工作于数组,还能工作于jQuery对象了。来行诗一般的代码吧:


formValues = (elem.value for elem in $('.input')) 
这行代码将会被翻译为如下的Javascript:

var elem, formValues; 
formValues = (function() { 
 var _i, _len, _ref, _results; 
 _ref = $('.input'); 
 _results = []; 
 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 
  elem = _ref[_i]; 
  _results.push(elem.value); 
 } 
 return _results; 

})(); 


老实说最初这样写代码确实让人提心吊胆的,但是一旦你开始拥抱CoffeeScript的魔法时,你会爱上它的。


飞一般的方法绑定


在jQuery的回调中使用"=>"将会大大减省你手动绑定方法到对象的麻烦。还是来看段代码吧:

object = 

 func: -> $('#div').click => @element.css color: 'red'


下面是编译输出的Javascript:

var object; 
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 
object = { 
 func: function() { 
  return $('#div').click(__bind(function() { 
   return this.element.css({ 
    color: 'red'
   }); 
  }, this)); 
 } 

}; 


代码中的@element指向了一个jQuery的对象,该对象是在其他地方指定的——比如object.element = $('#some_div').
任何使用"=>"所指定的回调函数都会自动绑定到原来的对象上,没错,这很酷。
在2011年函数是这样调用的
瞅一眼这个:

$.post( 
 "/posts/update_title"
 new_title: input.val() 
 id: something 
 -> alert('done') 
 'json'


使用CoffeeScript,多个参数可以写成多行来调用,逗号和大括弧是可选的,这使得一些jQuery中签名比较长的方法比如$.post() 和 $.animate() 等更加易读。这儿还有一个例子:

$('#thing').animate 
 width: '+20px'
 opacity: '0.5'
 2000 

 'easeOutQuad'


很美味的Coffee不是吗?要注意第一个参数是一个匿名的对象,你甚至可以省略调用函数的元括弧。


让初始化来的更性感吧


我最初开始使用jQuery时我是这样做页面初始化的:

$(document).ready(function() { 
 some(); 
 init(); 
 calls(); 

}) 


CoffeeScript和新版的jQuery使得上面的代码进化的如此性感:

$-> 
 some() 
 init() 

 calls() 


函数定义语法在CoffeeScript里本身已经非常酷了,能在上面这些场合使用使得其更酷了。你会发现所有需要回调的函数调用在CoffeeScript中都是如此简单。

其实coffeescript这种语言因其可以一对一地翻译为javascript的特性,使用起来其实非常灵活。 将其引入项目的方式也不止一个。这里,我先就node项目引入coffeescript的方式作一个汇总,并对比一下各个方式的优劣性。


直接使用coffee指令运行纯coffeescript项目


一般提起coffeescript,自然而然地会想到他是javascript的小弟,总脱离不了js的阴影。其实你完全可以把它认作是独立的语言。 我们都知道,在node平台上全局安装完coffee-script包后,就可以通过coffee指令进入coffeescript的交互界面, 叫它repl也行。如果你的项目完全是用coffee写的,那就简单了,直接对你的入口脚本使用coffee指令就结了, 比如你的入口脚本名为“app.coffee”,那就执行:

coffee app.coffee

注意,这里的扩展名coffee是不能省略的。


这个方式应该说是使用coffeescript最“官方”的方式。简单,直接!而且,一旦你以一个coffee文件作为项目的入口, 那整个项目就同时兼容coffee和js了。你在项目里可以任意require js或coffee文件及模块, 甚至可以在项目中的js文件中随便require coffee文件。并且在你引用无论是coffee还是js文件的时候都无需扩展名, 只要前面部分名称不冲突就行。


这个方式有个最大的问题就是,如果它作为一个模块,只能被用于coffee项目;如果他作为一个应用, 运行环境必须安装coffee-script。毕竟coffeescript现在还是一个小众语言,它作为模块时丧失了js用户实在可惜。


另一个也许存在的缺点是性能方面的,毕竟node里面只有js引擎,coffee代码需要先编译为js再运行, 这个过程是要消耗一点点时间的,尽管coffee到js的编译速度其实挺快的。不过这应该不是什么大问题, 一般来说,require都是写在文件的顶部,也就是应用在启动的时候就一气儿把该require的文件都require了, require的时候coffee就被编译成了js放到了js引擎中,那么编译消耗的那点时间都集中在了应用启动时, 运行时几乎不会遇到require新的coffee的情况了。node最常见的使用场景是web服务器,这就更没问题了。


在javascript项目中引用coffeescript


npm中的coffee-script既可以全局安装,也可以作为项目的一个模块安装。那coffee-script作为项目的一个模块有啥意义呢? 其实是给项目添加了一个coffeescript的编译器,这个项目就可以在运行时随时编译coffee文件。


你一定希望像第一种方式里那样随便引用coffee文件。没问题,只需要注册一下。假如你的项目入口文件是app.js, 那么只需要在这个文件最前面加上这么一句:

require('coffee-script/register');

然后你就可以在项目中随便require coffee文件了。


这个方式本质上和第一种方式没啥区别,只不过coffee-script没安装在全局,因此你的模块可以独立存在, 作为应用也不需要环境安装好coffee-script了。


缺点就是容易让代码有些乱,一会儿js,一会儿coffee,当然第一种方式也可能会这样, 不过都用coffee启动了里面应该不会写js了吧……总之一个项目还是把语言统一起来比较好。

性能问题上跟第一种方式一样,不多说了。


正统的方式——编译


一说编译,就感觉回到了正儿八经的C或Java的时代。的确,作为一个编译型语言,编译后再运行才是正道。 c有gcc,java有javac,cofee有coffee -c。
要编译一个cofee文件很简单,比如要编辑app.coffee这个文件,就在文件的当前目录执行:

coffee -c app.coffee
一个名为app.js的文件就出现在当前目录下了。这个指令也可以应用于目录, 比如你把项目中所有的coffee源文件放到了src目录下,那就执行:

coffee -c src
src目录及其各级子目录下的所有coffee源文件都会编译成js文件,放到和源文件相同的目录中。
不过对于大型项目,把源文件和编译结果文件放到一起可不太好。指定一个输出目录就行了:

coffee -c -o outputs src
这个指令的参数顺序有点奇怪。在coffee的帮助里是这么定义的:
coffee [options] path/to/script.coffee -- [args]
注意,所有的选项(options)都在coffee和文件路径之间。而最后的args是把目标文件作为脚本执行时给传递的参数。 也就是说所有的选项都放在coffee和文件名之间就可以了。 而-c这个选项是单独的,没有自己的参数,它只表示要把指令最后面提供的那个文件给编译了,所以写成这样也行:

coffee -o outputs -c src
假如想再加个选项,让编译结果不被自执行函数体包围,就是:

coffee -o outputs -c -b src
再假如想把所有源文件编译成一个名为out.js的目标文件,就是:

coffee -o outputs -c -j out src
如果每次改点代码都要这么执行指令也挺烦人的。coffee指令有一个选项-w可以监视源文件的变动而自动编译:

coffee -o outputs -c -w src

对于大型项目来说,最好提前确定好编译方式,让所有开发人员只需要一个指令就搞定所有编译的事情,这就需要自动化构建了。


offee提供了一个自动化构建工具,cake,就像c世界的make。 不过就像官网上说的那样,cake是一个很简单的构建系统。实际上cake的功能就是执行一个名为cakefile的脚本, 而cakefile脚本是用coffeescript写的。这个脚本只提供非常有限的内建函数,比如task, 用于声明一个指令及其对应的描述和执行函数。其它的就是在写一个纯粹的node项目, 想完成编译要么使用node的fs模块输出coffee模块编译出来的字符串, 要么用child_process模块执行shell指令。其实cake构建的目标不一定必须是coffee,由于它实际是执行一个node脚本, 处理任何自动化的事情都可以。


另外还有一些更优秀的第三方自动化构建工具也可以完成coffee的自动编译,比如著名的Grunt,以及国内的fekit等。


这种正统的编译方式也许是看起来最可靠的,应该深受老程序员的喜爱。它可以让团队形成固定的开发模式。 另外,编译后的项目就成了纯的js项目,无论是作为应用直接运行还是作为模块被别的项目引用都不需要额外的依赖。 并且在运行时不需要编译,也就完全不存在编译导致的性能问题了。


缺点就是太麻烦。如果你是要做一个不太大的项目,光搞cakefile或者配置grunt就要费半天时间,不太值得。



CoffeeScript搭配Jquery的用法


Ajax请求

$ ?= require 'jquery' # For Node.js compatibility


$(document).ready ->
    # Basic Examples
 $.get '/', (data) ->
        $('body').append "Successfully got the page."


    $.post '/',
        userName: 'John Doe'
        favoriteFlavor: 'Mint'
        (data) -> $('body').append "Successfully posted to the page."


    # Advanced Settings
 $.ajax '/',
        type: 'GET'
        dataType: 'html'
        error: (jqXHR, textStatus, errorThrown) ->
            $('body').append "AJAX Error: #{textStatus}"
        success: (data, textStatus, jqXHR) ->
            $('body').append "Successful AJAX call: #{data}"


回调绑定

$ ->
  class Basket
    constructor: () ->
      @products = []


      $('.product').click (event) =>
        @add $(event.currentTarget).attr 'id'


    add: (product) ->
      @products.push product
      console.log @products


  new Basket()


链式调用

$("input")
  .val("")
  .css
    'z-index': 5
  .removeClass "fart"