Every language feature in CoffeeScript has been designed using this kind of process:
attempt to take the beautiful dynamic semantics of JavaScript—object literals, function expressions, prototypal inheritance—and express them in a clean, readable, minimal way.
by Jeremy Ashkenas, author of CoffeeScript

CoffeeScript是一门简洁的,构架于JavaScript之上的预处理器语言,可以静态编译成JavaScript,语法主要受ruby和python影响,目前已经为众多rails和node项目采用。

为什么要用CoffeeScript?

  • 更少,更紧凑,和更清晰的代码
  • 通过规避和改变对JavaScript中不良部分的使用,只留下精华,让代码减少出错率,更容易维护
  • 在很多常用模式的实现上采用了JavaScript中的最佳实践
  • CoffeeScript生成的JavaScript代码都可以完全通过JSLint的检测

什么情况下不推荐使用CoffeeScript?

  • CoffeeScript不是JavaScript的超集,也不是完全替代品,不应该在不会JavaScript的情况下使用CoffeeScript工作

CoffeeScript是一种需要预编译的语言,不能在运行时(Runtime)解释,这造成了她普遍被人质疑的一点,就是如果代码中出现运行时错误时难以调试,不过从实际使用上来看,因为CoffeeScript的编译结果大部分情况下自然而合理,至少我从来没有发现从生成的JavaScript代码回溯到对应的CoffeeScript代码有什么困难之处,我们稍后会看到这种对应关系的细节

这种静态编译还有一个额外的好处,就是CoffeeScript和现有的环境(浏览器,Node,Rhino等)与库完全兼容

最简单的安装和测试CoffeeScript的方法,是使用*node.js*的*npm*安装,然后使用命令行脚本实时编译

npm install -g coffee-script
# watch and compile
coffee -w --output lib --compile src

这里假设你的coffee代码在src目录下,这个daemon会自动检测文件的改变,并编译成js文件放到lib目录下


语法

与SASS/LESS和CSS的关系不同,CoffeeScript不是JavaScript的超集,不能在CoffeeScript程序中写JavaScript代码,比如function等关键字

格式

在js中,如果认为当前语句和随后语句是一个整体的话,就不会自己加;,比如以下javascript代码

//javascript code
var y = x+f
(a+b).toString()//parsed to:
var y = x+f(a+b).toString();

很多js中的问题由此引起(实际上现在把;放在哪里,在js社区内也是个争论的话题)

而CoffeeScript在编译时为每条语句加上;,因此在代码中不需要写;

CoffeeScript中的注释采用#

# single line comment
###
  multi line comment
###

CoffeeScript中对空白敏感,这种做法来自python,任何需要({})的场合下,可以用缩进代替

作用域

在js中最糟糕的设计就是全局变量,当你忘记用var声明变量的时候,这个变量会成为全局对象上的一个属性

CoffeeScript避免了这点

foo = "bar"

会编译成

(function() {var foo;foo = "bar";
}).call(this);

任何的代码都会使用*Immediate Function*包装,这样foo成为了本地变量,并且,可以通过call指定的this引用全局对象

为了方便起见,之后的编译后代码描述不会再加上这个包装

实际上在CoffeeScript中,你也不需要再用var声明变量,编译后会自动加上var,并且将声明*hoisting*,即放到作用域的顶部,看一个来自官方文档的例子

outer = 1
change = ->inner = -1outer = 10
inner = change()

->是函数定义的简写方式,之后我们会探讨

编译后的js如下:

  var change, inner, outer;outer = 1;change = function() {var inner;inner = -1;return outer = 10;};inner = change();

这是类似ruby中的自然的作用域实现方式,innerchange()内定义成了局部变量,因为在代码中之前没有定义过

赋值

首先是字符串可以用类ruby的语法内嵌

target = "world"
alert "hello, #{target}"

其次是字面量,可以用类似*YAML*的方法定义对象字面量

object1 = one: 1, two: 2
object2 =one: 1two: 2class: "numbers"

注意保留字class,现在可以直接作为对象的key了

数组也可以分行

arr = [12
]

也可以解构赋值(Destructuring)

obj = {a:"foo", b:"bar"}
{a, b} = obj
arr = [1, 2]
[a, b] = arr

数组

数组的操作引入了来自ruby的Range概念,并且可以将字符串完全作为数组操作

numbers = [0..9]
numbers[3..5] = [-3, -4, -5]
my = "my string"[0..1]

判断一个值是否在数组内,在js中可以用Array.prototype.indexOf,不过IE8及以下不支持,CoffeeScript提供了跨浏览器的in操作符解决

arr = ["foo", "bar"]
"foo" in arr

具体的实现上,是一个对indexOf的Shim

   var arr,__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };arr = ["foo", "bar"];__indexOf.call(arr, "foo") >= 0;

for..in语法可以用在数组上了,背后是用js的for循环实现的,这比数组的迭代器方法要效率高一些

for name, i in ["Roger", "Roderick"]alert "#{i} - Release #{name}"

也具有过滤器when

prisoners = ["Roger", "Roderick", "Brian"]
release prisoner for prisoner in prisoners when prisoner[0] is "R"

看起来很像普通英语了,也可以用()收集遍历的结果

result = (item for item in array when item.name is "test")

遍历对象的属性可以用of,这是用js自己的for..in实现的

names = sam: seaborn, donna: moss
alert("#{first} #{last}") for first, last of names

流程控制

CoffeeScript使用来自ruby的省略语法,让控制流变得很紧凑,也引进了unless,not,then等语法糖式的关键字

result = if not true then "false"
result = unless true then "false"

CoffeeScript中非常好的一点,就是直接取消了js中的==判断,改成全部用===进行严格比较,js中的==会做大量诡异的类型转换,很多情况下是bug的来源

if "1" == 1 alert("equal")
elsealert("not equal")

在使用if来进行空值的判断时,js有时会让人困扰,因为""和0都会被转换成false,Coffee提供了?操作符解决这个问题,她只有在变量为nullundefined时才为false

""? #true
null? #false

也可以用常见的类似ruby中||=的方法,判断赋值,此外还可以用and,or,is关键字代替&&,||,==

hash or= {}
hash ?= {}

经常有当某个属性存在的时候,才会调用属性上的方法的情况,这时候也可以用?

knight.hasSword()?.poke()

只有当hasSword()返回对象不为空时,才会调用poke方法,以下是编译的js代码

var _ref;
if ((_ref = knight.hasSword()) != null) {_ref.poke();
}

另一种情况是当poke方法存在时才调用

knight.hasSword().poke?()

对应的js代码

var _base;
if (typeof (_base = knight.hasSword()).poke === "function") {_base.poke();
}

switch case语句也有了一些语法糖,并且会默认加上break

switch daywhen "Sun" then go relaxwhen "Sat" then go dancingelse go work

函数

CoffeeScript对JavaScript的函数做了很大的简化,举个例子,看一个求和函数

sum = (nums...) ->nums.reduce (x, y) -> x+ysum 1,2,3

对应JavaScript

var sum,__slice = [].slice;sum = function() {var nums;nums = 1 <= arguments.length ? __slice.call(arguments, 0) : [];return nums.reduce(function(x, y) {return x + y;});
};sum(1, 2, 3);
  • 可以使用和ruby 1.9类似的*lambda函数*写法->来代替function
  • 参数列表放在->的前边,且可省略
  • 取消了函数声明,只能将函数作为值定义
  • 在CoffeeScript中,任何语句都是表达式(除了breakcontinue),都有返回值,因此像ruby一样,不需要显式return
  • js的函数参数有一个很讨厌的地方,就是参数对象arguments不是一个真正的数组,要使用数组方法,必须转换成数组[].slice.call(arguments, 0)这样,而在CoffeeScript中收束(加...)的参数是一个真正的数组

CoffeeScript的函数可以有默认参数,如

times = (a = 1, b = 2) -> a * b

CoffeeScript的函数调用可以不用()语法包围参数,像ruby一样跟在函数名后面就可以,不过这也有时候会带来问题,特别是没有参数的调用

alert

对应的js

alert;

而不是alert(),这和ruby不同,需要注意

缩进的格式有时需要小心,比如用多个函数做参数的时候,需要这样写

$(".toggle").toggle ->"on"
, ->"off"

对应js

  $(".toggle").toggle(function() {return "on";}, function() {return "off";});

模式

使用CoffeeScript的一个重要理由,就是她用自己的语法实现了很多很常用的js编程模式,而且,通常是在社区内广泛被承认的最佳实践,如果不熟悉JavaScript的这些模式,可能会在调试代码上遇到一些麻烦,不过,基本上来说还是比较简单易懂的,下面我们会花一些时间研究一下CoffeeScript是用什么样的方法来封装这些通用编程模式的

闭包

在js中,普遍会使用闭包实现各种事件的handler或封装模块,以下是CoffeeScript对这一普遍模式的实现

closure = do ->_private = "foo"-> _privateconsole.log(closure()) #=> "foo"

do关键词可以产生一个Immediate Function,下面是对应js代码

  var closure;closure = (function() {var _private;_private = "foo";return function() {return _private;};})();

闭包中经常需要绑定this的值给闭包的私有变量,CoffeeScript使用特殊的=>语法省去了这个麻烦

@clickHandler = -> alert "clicked"
element.addEventListener "click", (e) => @clickHandler(e)

使用=>生成函数,可以看到生成代码中会加上对this的绑定

var _this = this;this.clickHandler = function() {return alert("clicked");
};element.addEventListener("click", function(e) {return _this.clickHandler(e);
});

这里CoffeeScript对于this有简单的别名@

扩展

在js中,所有的对象都是开放的,有时候会扩展原有对象的行为(比如对数组的ECMA5 shim),这也称为Monkey patching

String::dasherize = -> @replace /_/g, "-"

::代表原型的引用,js代码如下

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

在js中是否要模拟传统编程语言的类,是个一直以来都有争议的话题,不同的项目,不同的团队,在类的使用上会有不同的看法,不过,一旦决定要使用类,那么至少需要一套良好的实现,CoffeeScript在语言内部实现了类的模拟,我们来看一看一个完整的例子

class Gadget@CITY = "beijing"@create: (name, price) ->new Gadget(name, price)_price = 0constructor: (@name, price) ->_price = pricesell: =>"Buy #{@name} with #{_price} in #{Gadget.CITY}"iphone = new Gadget("iphone", 4999)
console.log iphone.name #=> "iphone"
console.log iphone.sell() #=> "Buy iphone with 4999 in beijing"ipad = Gadget.create("ipad", 3999)
console.log ipad.sell() #=> "Buy ipad with 3999 in beijing"

这个Gadget类具有通常语言中类的功能:

  • constructor是构造函数,必须用这个名称,类似ruby中的initialize
  • name是实例变量,可以通过iphone.name获取
  • 构造函数中如果给实例变量赋值,直接将@name写在参数中即可,等价于在函数体中的@name = name
  • _price是私有变量,需要赋初始值
  • sell是实例方法
  • create是类方法,注意这里使用了@create,这和ruby有些像,在定义时的this指的是这个类本身
  • CITY是类变量

要注意的是,对于实例方法,要用=>来绑定this,这样可以作为闭包传递,比如

iphone = new Gadget("iphone", 4999)
$("#sell").click(iphone.sell())

如果不用=>,闭包被调用时就会丢失实例对象的值(iphone)

对于熟悉基于类的面向对象编程的人,CoffeeScript的类是一目了然的,下面来看看对应的js代码

  var Gadget,__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };Gadget = (function() {var _price;Gadget.name = 'Gadget';Gadget.CITY = "beijing";Gadget.create = function(name, price) {return new Gadget(name, price);};_price = 0;function Gadget(name, price) {this.sell = __bind(this.sell, this);this.name = name;_price = price;}Gadget.prototype.sell = function() {return "Buy " + this.name + " with " + _price + " in " + Gadget.CITY;};return Gadget;})();

以上的代码有很多值得注意的地方

  • 整体上来说,CoffeeScript的类模拟使用的是一个*构造函数闭包*,这是最常用的模拟类的模式,好处是可以完整地封装内部变量,且可以使用new来生成实例对象
  • _price就是被封装在闭包内部的私有变量
  • sell这样的实例方法是原型方法,并且在初始化时使用自定义的bind函数绑定实例(用=>定义的情况)
  • createCITY这样的类成员使用构造函数的属性实现,重复一下,在CoffeeScript类定义中的this指的是整个闭包Gadget
  • Gadget.name是额外定义的类名属性

类的继承

CoffeeScript中为方便地实现类的继承也定义了自己的语法,我们把上面的类简化,来看一下如何继承:

class Gadgetconstructor: (@name) ->sell: =>"Buy #{@name}" class IPhone extends Gadgetconstructor: -> super("iphone")nosell: =>"Don't #{@sell()}"iphone = new IPhone
iphone.nosell() #=> Don't Buy iphone
  • 使用extends关键字可以继承父类中的所有实例属性,比如sell
  • super方法可以调用父类的同名方法
  • 如果不覆盖constructor,则她被子类默认调用

来看一下对应的js代码,这有一些复杂,我们把和上边类定义中重复的地方去掉,只留下继承的实现部分

  var Gadget, IPhone,__extends = function(child, parent) { for (var key in parent) { if ({}.hasOwnProperty.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; };IPhone = (function(_super) {__extends(IPhone, _super);IPhone.name = 'IPhone';function IPhone() {this.nosell = __bind(this.nosell, this);IPhone.__super__.constructor.call(this, "iphone");}IPhone.prototype.nosell = function() {return "Don't " + (this.sell());};return IPhone;})(Gadget);

这里重点有三个,

  • __extends函数使用了代理构造函数ctor来实现继承,这是非常普遍的js中对象继承的实践模式,进一步解释一下

    • 使用代理构造函数的目的是为了避免子类被更改时父类受到影响
    • 使用ctor.prototype = parent.prototype的意义是只继承定义在prototype上的公用属性
  • 父类的类成员被直接引用拷贝到子类,而不是原型继承
  • super的实现方法是parent.prototype.constructor.call(this)

混入(Mixin)

在ruby语言中的Mixin,能够让你的类获得多个模块的方法,可以说是对多重继承一种很好的实现,虽然在CoffeeScript中并没有像ruby的include一样的内置功能,但很容易实现她

class Module@extend: (obj) ->for key, value of obj @[key] = value@include: (obj) ->for key, value of obj @::[key] = valueclassProperties =find: (id) ->console.log("find #{id}")instanceProperties =save: ->console.log("save")class User extends Module@extend classProperties@include instancePropertiesuser = User.find(1)
user = new User
user.save()
  • 继承了Module的类才可以Mixin,当然,这里也可以用组合或者直接为js的构造函数做Monkey patching
  • classProperties是类成员模块,使用@extend来Mixin,实现是简单的拷贝对象的属性
  • instanceProperties是实例成员模块,使用@include来Mixin,实现是拷贝对象原型的属性
  • 需要指出的是,这里的拷贝是引用拷贝,有可能外部会更改被Mixin的模块内部值,更好的方法是深层值拷贝(clone),包括JQuery在内的很多类库都实现了这类扩展方法

结语

CoffeeScript提供了一门比JavaScript更强大,优雅,表现力丰富的语言,但她毕竟架构于JavaScript之上,而且是静态地编译成JavaScript代码,也就是说,她不能完全避免对JavaScript中一些不良部分的滥用,比如eval,typeof,instanceof等,所以,在任何情况下,建议始终开启Strict Mode

"use strict"

严格模式是一个ECMA5标准提出的js子集,禁用了很多js设计中不好的方面,在未来会逐渐成为js的语言标准,详细介绍在这里

CoffeeScript 详解相关推荐

  1. Velocity魔法堂系列二:VTL语法详解

    一.前言 Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本处理能力.而且Velocity被移植到不 ...

  2. Vuex使用详解,附加项目遇到的问题(简单明了)

    Vuex的定义.个人理解和结构解析 vuex定义:vuex是一个专门为vue.js设计的集中式状态管理架构. vuex的个人理解: 是存储公有状态数据state的一个仓库(store):解决了大型应用 ...

  3. NodeJs学习笔记002--npm常用命令详解

    npm 常用命令详解 npm是什么 npm install 安装模块 npm uninstall 卸载模块 npm update 更新模块 npm outdated 检查模块是否已经过时 npm ls ...

  4. vscode中setting.json配置详解

    vscode中的setting.json配置文件配置详解 话不多说上配置文件 大家按需复制到自己的setting.json配置文件中即可 [{// 控制是否在编辑器中显示 CodeLens." ...

  5. IDEA的安装、配置与使用详解

    IDEA的安装.配置与使用详解 目录结构 IDEA的安装.配置与使用详解 一.IDEA介绍 1. Jet Brains公司介绍 2. IDEA介绍 3. IDEA的主要功能介绍 4. IDEA的主要优 ...

  6. 如何开发优秀的HTML5游戏?-迪斯尼《寻找奥兹之路》游戏技术详解(二)

    (接上文)桌面游戏通常创建于一个核心的物理引擎.因此,要在3D世界中模拟一个柔软的物体,需要一个完整的物理模拟器,并且建立一种可信的行为. WebGL和JavaScript还不能奢华到可以运行一个完全 ...

  7. 从命令行到IDE,版本管理工具Git详解(远程仓库创建+命令行讲解+IDEA集成使用)

    首先,Git已经并不只是GitHub,而是所有基于Git的平台,只要在你的电脑上面下载了Git,你就可以通过Git去管理"基于Git的平台"上的代码,常用的平台有GitHub.Gi ...

  8. JVM年轻代,老年代,永久代详解​​​​​​​

    秉承不重复造轮子的原则,查看印象笔记分享连接↓↓↓↓ 传送门:JVM年轻代,老年代,永久代详解 速读摘要 最近被问到了这个问题,解释的不是很清晰,有一些概念略微模糊,在此进行整理和记录,分享给大家.在 ...

  9. docker常用命令详解

    docker常用命令详解 本文只记录docker命令在大部分情境下的使用,如果想了解每一个选项的细节,请参考官方文档,这里只作为自己以后的备忘记录下来. 根据自己的理解,总的来说分为以下几种: Doc ...

  10. 通俗易懂word2vec详解词嵌入-深度学习

    https://blog.csdn.net/just_so_so_fnc/article/details/103304995 skip-gram 原理没看完 https://blog.csdn.net ...

最新文章

  1. C++/CLI中的资源清理(Destructor,Finalizer)
  2. 右下角android sdk content loader 加载很慢的解决方法
  3. [USACO07NOV]牛栏Cow Hurdles
  4. android 颜色选择类
  5. @j1 bootstrap
  6. 【微信小程序使用阿里巴巴矢量图标库】
  7. 软考中级网络工程师知识目录
  8. win10系统版本更新旧版本文件清理:如何安全删除win10中的Windows.old文件夹
  9. 介绍会议中控系统模块化构成及功能作用
  10. python 因果推断_Causal inference (因果推断)
  11. Imagenet的中英对应分类
  12. DISALLOW_COPY_AND_ASSIGN DISALLOW_IMPLICIT_CONSTRUCTORS.
  13. 企业引入人脸识别考勤 想要代打卡?没门!
  14. 是德科技34461a万用表
  15. SpringBoot整合Flowable工作流引擎框架
  16. C语言正确的输入格式和输入方式
  17. Father gay = new Son()代码分析
  18. 半年卖出4.6亿只粽子,老字号的“想象空间”有多大?
  19. 武汉大学计算机学院深造率,2017届本科毕业生深造率排名与分析
  20. 《网管员必读——网络应用》(第2版)试读样章下载

热门文章

  1. 全面正面解读:nmn的副作用和危害怎么样?nmn副作用及应对方法?
  2. import clip时Cannot re-initialize CUDA in forked subprocess
  3. 小程序使用腾讯视频插件及插件未授权使用
  4. android版iphone6s,不必羡慕iPhone6S!安卓也能用Live Photo
  5. UE4 各种玻璃材质制作汇总
  6. win7 ShuipFCMS 配置 及问题
  7. M1 MacBook Pro外扩多屏显示器方法
  8. 为什么一个还没毕业的大学生能够把 IO 讲的这么好?
  9. 计算机个人市场调查实验报告,市场调查实验报告(一).doc
  10. C语言typedef和define、字节对齐的问题