世界上的不少天才都在为构建更好的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写脚本,构建本身的网站,作一些小玩意。