CoffeeScript 详解
序
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中的自然的作用域实现方式,inner
在change()
内定义成了局部变量,因为在代码中之前没有定义过
赋值
首先是字符串可以用类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提供了?
操作符解决这个问题,她只有在变量为null
或undefined
时才为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中,任何语句都是表达式(除了
break
和continue
),都有返回值,因此像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中的initializename
是实例变量,可以通过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函数绑定实例(用=>
定义的情况)create
和CITY
这样的类成员使用构造函数的属性实现,重复一下,在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 详解相关推荐
- Velocity魔法堂系列二:VTL语法详解
一.前言 Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本处理能力.而且Velocity被移植到不 ...
- Vuex使用详解,附加项目遇到的问题(简单明了)
Vuex的定义.个人理解和结构解析 vuex定义:vuex是一个专门为vue.js设计的集中式状态管理架构. vuex的个人理解: 是存储公有状态数据state的一个仓库(store):解决了大型应用 ...
- NodeJs学习笔记002--npm常用命令详解
npm 常用命令详解 npm是什么 npm install 安装模块 npm uninstall 卸载模块 npm update 更新模块 npm outdated 检查模块是否已经过时 npm ls ...
- vscode中setting.json配置详解
vscode中的setting.json配置文件配置详解 话不多说上配置文件 大家按需复制到自己的setting.json配置文件中即可 [{// 控制是否在编辑器中显示 CodeLens." ...
- IDEA的安装、配置与使用详解
IDEA的安装.配置与使用详解 目录结构 IDEA的安装.配置与使用详解 一.IDEA介绍 1. Jet Brains公司介绍 2. IDEA介绍 3. IDEA的主要功能介绍 4. IDEA的主要优 ...
- 如何开发优秀的HTML5游戏?-迪斯尼《寻找奥兹之路》游戏技术详解(二)
(接上文)桌面游戏通常创建于一个核心的物理引擎.因此,要在3D世界中模拟一个柔软的物体,需要一个完整的物理模拟器,并且建立一种可信的行为. WebGL和JavaScript还不能奢华到可以运行一个完全 ...
- 从命令行到IDE,版本管理工具Git详解(远程仓库创建+命令行讲解+IDEA集成使用)
首先,Git已经并不只是GitHub,而是所有基于Git的平台,只要在你的电脑上面下载了Git,你就可以通过Git去管理"基于Git的平台"上的代码,常用的平台有GitHub.Gi ...
- JVM年轻代,老年代,永久代详解
秉承不重复造轮子的原则,查看印象笔记分享连接↓↓↓↓ 传送门:JVM年轻代,老年代,永久代详解 速读摘要 最近被问到了这个问题,解释的不是很清晰,有一些概念略微模糊,在此进行整理和记录,分享给大家.在 ...
- docker常用命令详解
docker常用命令详解 本文只记录docker命令在大部分情境下的使用,如果想了解每一个选项的细节,请参考官方文档,这里只作为自己以后的备忘记录下来. 根据自己的理解,总的来说分为以下几种: Doc ...
- 通俗易懂word2vec详解词嵌入-深度学习
https://blog.csdn.net/just_so_so_fnc/article/details/103304995 skip-gram 原理没看完 https://blog.csdn.net ...
最新文章
- C++/CLI中的资源清理(Destructor,Finalizer)
- 右下角android sdk content loader 加载很慢的解决方法
- [USACO07NOV]牛栏Cow Hurdles
- android 颜色选择类
- @j1 bootstrap
- 【微信小程序使用阿里巴巴矢量图标库】
- 软考中级网络工程师知识目录
- win10系统版本更新旧版本文件清理:如何安全删除win10中的Windows.old文件夹
- 介绍会议中控系统模块化构成及功能作用
- python 因果推断_Causal inference (因果推断)
- Imagenet的中英对应分类
- DISALLOW_COPY_AND_ASSIGN DISALLOW_IMPLICIT_CONSTRUCTORS.
- 企业引入人脸识别考勤 想要代打卡?没门!
- 是德科技34461a万用表
- SpringBoot整合Flowable工作流引擎框架
- C语言正确的输入格式和输入方式
- Father gay = new Son()代码分析
- 半年卖出4.6亿只粽子,老字号的“想象空间”有多大?
- 武汉大学计算机学院深造率,2017届本科毕业生深造率排名与分析
- 《网管员必读——网络应用》(第2版)试读样章下载
热门文章
- 全面正面解读:nmn的副作用和危害怎么样?nmn副作用及应对方法?
- import clip时Cannot re-initialize CUDA in forked subprocess
- 小程序使用腾讯视频插件及插件未授权使用
- android版iphone6s,不必羡慕iPhone6S!安卓也能用Live Photo
- UE4 各种玻璃材质制作汇总
- win7 ShuipFCMS 配置 及问题
- M1 MacBook Pro外扩多屏显示器方法
- 为什么一个还没毕业的大学生能够把 IO 讲的这么好?
- 计算机个人市场调查实验报告,市场调查实验报告(一).doc
- C语言typedef和define、字节对齐的问题