CoffeeScript入门

官网文档传送门

CoffeeScript简介

CoffeeScript 是一门编译到 JavaScript 的小巧语言. 在 Java 般笨拙的外表下, JavaScript 其实有着一颗华丽的心脏. CoffeeScript 尝试用简洁的方式展示 JavaScript 优秀的部分.

CoffeeScript 的指导原则是: “她仅仅是 JavaScript”. 代码一一对应地编译到 JS, 不会在编译过程中进行解释. 已有的 JavaScript 类库可以无缝地和 CoffeeScript 搭配使用, 反之亦然. 编译后的代码是可读的, 且经过美化, 能在所有 JavaScript 环境中运行, 并且应该和对应手写的 JavaScript 一样快或者更快.

换而言之,CoffeeScript只是将JS给简化,使得JS的编码更加人性化。虽然CoffeeScript编译出来的JS看起来并不简单,但CoffeeScript编写的时候确实会减少不少代码量。具体可参照后文的例子。

CoffeeScript安装

CoffeeScript可以通过npm安装和管理。首先你要安装nodeJS,然后通过以下命令安装:

npm install -g coffee-script

coffee命令使用

安装之后, 你应该可以运行 coffee 命令以执行脚本, 编译 .coffee 文件到 .js 文件, 和提供一个交互式的 REPL. coffee命令有下列参数:

参数 作用
-c, –compile 编译一个 .coffee 脚本到一个同名的 .js 文件
-m, –map 随 JavaScript 文件一起生成 source maps. 并且在 JavaScript 里加上 sourceMappingURL 指令
-i, –interactive 启动一个交互式的 CoffeeScript 会话用来尝试一些代码片段. 等同于执行 coffee 而不加参数.
-o, –output [DIR] 将所有编译后的 JavaScript 文件写到指定文件夹. 与 –compile 或 –watch 搭配使用.
-j, –join [FILE] 编译之前, 按参数传入顺序连接所有脚本到一起, 编译后写到指定的文件. 对于编译大型项目有用.
-w, –watch 监视文件改变, 任何文件更新时重新执行命令.
-p, –print JavaScript 直接打印到 stdout 而不是写到一个文件.
-s, –stdio 将 CoffeeScript 传递到 STDIN 后从 STDOUT 获取 JavaScript. 对其他语言写的进程有好处. 比如:cat src/cake.coffee coffee -sc
-l, –literate 将代码作为 Literate CoffeeScript 解析. 只会在从 stdio 直接传入代码或者处理某些没有后缀的文件名需要写明这点.
-e, –eval 直接从命令行编译和打印一小段 CoffeeScript. 比如:coffee -e "console.log num for num in [10..1]"
-b, –bare 编译到 JavaScript 时去掉顶层函数的包裹.
-t, –tokens 不对 CoffeeScript 进行解析, 仅仅进行 lex, 打印出 token stream: [IDENTIFIER square] [ASSIGN =] [PARAM_START (]
–nodejs node 命令有一些实用的参数, 比如--debug, --debug-brk, --max-stack-size, 和 --expose-gc. 用这个参数直接把参数转发到 Node.js. 重复使用 --nodejs 来传递多个参数.
-n, –nodes 不对 CoffeeScript 进行编译, 仅仅 lex 和解析, 打印 parse tree:
Expressions
  Assign
    Value "square"
    Code "x"
      Op *
        Value "x"
        Value "x"

函数

函数体组成

coffee的函数通过一组可选的圆括号包裹的参数, 一个箭头, 一个函数体来定义

square = (x) -> x * x

等同于

square = function(x) {
  return x * x;
};

返回值

函数体的最后一条表达式会作为返回值返回。

无参数情况

如果不需要参数,则可以省略参数列表,表示为

square = -> 'empty'

相当于

square = function() {
  return 'empty';
};

多参数和默认值

如果需要多个参数,只需要按(参数1,参数2…)的形式写就好。
参数可以设置默认值,例如

add = (x = 4, y = 3) -> x + y
add 5

等同于

add = function(x, y) {
  if (x == null) {
    x = 4;
  }
  if (y == null) {
    y = 3;
  }
  return x + y;
};

add(5);

闭包表示

函数还有另一种表示方法,就是用=>代替->,两者的区别在于前者会解释成闭包函数

square = (x) => x * x

解释为:

square = (function(_this) {
  return function(x) {
    return x * x;
  };
})(this);

变参

使用 JavaScript 的 arguments 对象是一种处理接收不定数量个参数的函数常用办法. CoffeeScript 在函数定义和调用里提供了变参(splats) ...的语法, 让不定个数的参数使用起来更愉悦一些.

gold = silver = rest = "unknown"

awardMedals = (first, second, others...) ->
  gold   = first
  silver = second
  rest   = others

contenders = [
  "Michael Phelps"
  "Liu Xiang"
  "Yao Ming"
  "Usain Bolt"
]

awardMedals contenders... 

alert "The Field: " + rest

解释为:

var awardMedals, contenders, gold, rest, silver,
  __slice = [].slice;

gold = silver = rest = "unknown";

awardMedals = function() {
  var first, others, second;
  first = arguments[0], second = arguments[1], others = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
  gold = first;
  silver = second;
  return rest = others;
};

contenders = ["Michael Phelps", "Liu Xiang", "Yao Ming", "Usain Bolt"];

awardMedals.apply(null, contenders);

alert("The Field: " + rest);

输出为:[“Yao Ming”, “Usain Bolt”]

注意:参数调用的时候需要在后面加上...,否则会只传入一个参数

词法作用域和变量安全

CoffeeScript 编译器会考虑所有变量, 保证每个变量都在词法域里适当地被定义 — 你永远不需要自己去写 var.

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

解释为:

var changeNumbers, inner, outer;

outer = 1;

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

inner = changeNumbers();

所有变量的定义都被推到相关的顶层作用域, 也就是第一次出现的位置. outer 在内层的函数里没有被重新定义, 因为它已经存在于作用域当中了. 同时, 内层函数里的 inner 不应该改变外部的同名的变量, 所以在这里有自己的声明.

条件语句

这部分比较好理解,直接看代码就好。不同的一点是coffee中有了unless关键字,等同于 if not

mood = greatlyImproved if singing

unless happy and knowsIt
  clapsHands()
  chaChaCha()
else
  showIt()

date = if friday then sue else jill

相当于

var date, mood;

if (singing) {
  mood = greatlyImproved;
}

if (!(happy && knowsIt)) {
  clapsHands();
  chaChaCha();
} else {
  showIt();
}

date = friday ? sue : jill;

在 while, if/else, switch/when 的语句当中, then 可以被用来分隔判断条件跟表达式, 这样就不用强制写换行或者分号了.

循环

数组和对象

在很多时候,换行可以代替,

song = ["do", "re", "mi", "fa", "so"]

singers = Jagger: "Rock", Elvis: "Roll"

bitlist = [
  1, 0, 1
  0, 0, 1
  1, 1, 0
]

kids =
  brother:
    name: "Max"
    age:  11
  sister:
    name: "Ida"
    age:  9

range

range生成数组

数组也可以通过范围来表示,例如

a = [1..10]
a = [1...10]

分别编译为

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
a = [1, 2, 3, 4, 5, 6, 7, 8, 9];

也就是,..是包括最后一个数据,而...不包括最后一个数据。

数组切片

作用类似slice,和range生成数组有点相似,但作为下标范围时两端都有缺省值,左边缺省时默认为0,右边缺省时默认为最大下标。返回值是切割后的数组。

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]

start   = numbers[0..2]
middle  = numbers[3...-2]
end     = numbers[-2..]
copy    = numbers[..]
numbers[2..5] = [-3, -4, -5, -6]

编译为:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];

start = numbers.slice(0, 3); // [1,2,3]
middle = numbers.slice(3, -2); // [4,5,6,7]
end = numbers.slice(-2); // [8,9]
copy = numbers.slice(0); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
[].splice.apply(numbers, [2, 4].concat(_ref = [-3, -4, -5, -6])), _ref; // [1, 2, -3, -4, -5, -6, 7, 8, 9]

循环

eat food for food in ['toast', 'cheese', 'wine']

courses = ['greens', 'caviar', 'truffles', 'roast', 'cake']
menu i + 1, dish for dish, i in courses

foods = ['broccoli', 'spinach', 'chocolate']
eat food for food in foods when food isnt 'chocolate'

编译为:

var courses, dish, food, foods, i, _i, _j, _k, _len, _len1, _len2, _ref;

_ref = ['toast', 'cheese', 'wine'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  food = _ref[_i];
  eat(food);
}

courses = ['greens', 'caviar', 'truffles', 'roast', 'cake'];

for (i = _j = 0, _len1 = courses.length; _j < _len1; i = ++_j) {
  dish = courses[i];
  menu(i + 1, dish);
}

foods = ['broccoli', 'spinach', 'chocolate'];

for (_k = 0, _len2 = foods.length; _k < _len2; _k++) {
  food = foods[_k];
  if (food !== 'chocolate') {
    eat(food);
  }
}

遍历

数组遍历

nums = []
for num, i in nums

其中,i是下标。效果等同于:

nums = []
for (i = _i = 0, _len = nums.length; _i < _len; i = ++_i) {
  num = nums[i];
}

数组遍历还可以加by,达到固定跨度迭代的目的。例如:

nums = []
for num, i in nums by 2

对象属性遍历

yearsOld = max: 10, ida: 9, tim: 11

ages = for child, age of yearsOld
  "#{child} is #{age}"

编译为:

var age, ages, child, yearsOld;

yearsOld = {
  max: 10,
  ida: 9,
  tim: 11
};

ages = (function() {
  var _results;
  _results = [];
  for (child in yearsOld) {
    age = yearsOld[child];
    _results.push("" + child + " is " + age);
  }
  return _results;
})();

如果你希望仅迭代在当前对象中定义的属性,通过hasOwnProperty检查并 避免属性是继承来的,可以这样来写:

for own key, value of object

例如,上例可以改为

ages = for child, age of yearsOld

编译出来的结果就变成:

var age, ages, child, yearsOld,
  __hasProp = {}.hasOwnProperty;

yearsOld = {
  max: 10,
  ida: 9,
  tim: 11
};

ages = (function() {
  var _results;
  _results = [];
  for (child in yearsOld) {
    if (!__hasProp.call(yearsOld, child)) continue;
    age = yearsOld[child];
    _results.push("" + child + " is " + age);
  }
  return _results;
})();

底层循环

CoffeeScript仅提供了一种底层循环,即while循环。与JavaScript中的while 循环的主要区别是,在CoffeeScript中while可以作为表达式来使用, 而且可以返回一个数组,该数组包含每个迭代项的迭代结果。
CoffeeScript还提供了另一个关键字,until,效果等同于while not

buy()  while supply > demand
sell() until supply > demand

等同于

while (supply > demand) {
    buy();
}
while (!(supply > demand)) {
    sell();
}

遍历闭包

使用 JavaScript 循环生成函数的时候, 经常会添加一个闭包来包裹代码, 这样做目的是为了循环的变量被保存起来, 而不是所有生成的函数搜去访问最后一个循环的变量. CoffeeScript 提供了一个 do 关键字, 用来直接调用跟在后边的函数, 并且传递需要的参数.

for filename in list
  do (filename) ->
    fs.readFile filename, (err, contents) ->
      compile filename, contents.toString()

编译为:

var filename, _fn, _i, _len;

_fn = function(filename) {
  return fs.readFile(filename, function(err, contents) {
    return compile(filename, contents.toString());
  });
};
for (_i = 0, _len = list.length; _i < _len; _i++) {
  filename = list[_i];
  _fn(filename);
}

有些代码在 JavaScript 当中要写不少的语句, 而在 CoffeeScript 中只是表达式的一部分, 这些代码的编译结果会自动生成一个闭包. 这个写法很有用, 比如把列表解析的结果赋值给变量:

globals = (name for name of window)[0...10]

编译为:

var globals, name;

globals = ((function() {
  var _results;
  _results = [];
  for (name in window) {
    _results.push(name);
  }
  return _results;
})()).slice(0, 10);

操作符

算术操作符/逻辑操作符

由于操作符 == 常常带来不准确的约束, 不容易达到效果, 而且跟其他语言当中意思不一致, CoffeeScript 会把 == 编译为 ===, 把 != 变异为 !==. 此外, is 编译我 ===, 而 isnt 编译为!==.

not可以作为 !的 alias 使用.

逻辑操作方面, and编译为&&, 而 or编译为||.

onyestrue 是一样的, 而 offno 是布尔值 false.

unless 可以认为是 if 相反的版本.

this.property 简短的写法可以用 @property.

可以用 in 判断数据在数组中是否出现, 而 of 可以探测 JavaScript 对象的属性是否存在.

为了简化数学表达式, ** 可以用来表示乘方, // 表示整除, %% 提供数学的模运算(译注: true mathematical modulo?).

完整的列表:

CoffeeScript入门_第1张图片

CoffeeScript入门_第2张图片

存在性操作符

在 JavaScript 里检测一个变量的存在性有点麻烦. if (variable) ... 比较接近答案, 但是对 0 不成立. CoffeeScript 的存在性操作符?除非是null或者 undefined, 否则都返回 true

例如:

solipsism = true if mind? and not world?

speed = 0
speed ?= 15

footprints = yeti ? "bear"

编译为:

var footprints, solipsism, speed;

if ((typeof mind !== "undefined" && mind !== null) && (typeof world === "undefined" || world === null)) {
  solipsism = true;
}

speed = 0;

if (speed == null) {
  speed = 15;
}

footprints = typeof yeti !== "undefined" && yeti !== null ? yeti : "bear";

存在性操作符 ?. 的访问器的变体可以用来吸收链式属性调用中的 null. 数据可能是 null 或者 undefined 的情况下可以用这种写法替代访问器.。 如果所有属性都存在, 那么你会得到想要的结果, 如果链式调用有问题, 会返回 undefined 而不是抛出 TypeError.

zip = lottery.drawWinner?().address?.zipcode

编译为:

var zip, _ref;

zip = typeof lottery.drawWinner === "function" ? (_ref = lottery.drawWinner().address) != null ? _ref.zipcode : void 0 : void 0;

继承

CoffeeScript 提供了一个基础的class结构, 你可以在一个定义的表达式里完成命名 class, 定义父类, 赋值原型上的属性, 定义构造器。同时用@attr表示this.attr

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()

相当于:

var Animal, Horse, Snake, sam, tom,
  __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() {
  function Animal(name) {
    this.name = name;
  }

  Animal.prototype.move = function(meters) {
    return alert(this.name + (" moved " + meters + "m."));
  };

  return Animal;

})();

Snake = (function(_super) {
  __extends(Snake, _super);

  function Snake() {
    return Snake.__super__.constructor.apply(this, arguments);
  }

  Snake.prototype.move = function() {
    alert("Slithering...");
    return Snake.__super__.move.call(this, 5);
  };

  return Snake;

})(Animal);

Horse = (function(_super) {
  __extends(Horse, _super);

  function Horse() {
    return Horse.__super__.constructor.apply(this, arguments);
  }

  Horse.prototype.move = function() {
    alert("Galloping...");
    return Horse.__super__.move.call(this, 45);
  };

  return Horse;

})(Animal);

sam = new Snake("Sammy the Python");

tom = new Horse("Tommy the Palomino");

sam.move();

tom.move();

如果你不喜欢用 class 的裁判法定义原型, CoffeeScript 提供了一些低级的方便写法. extends 操作符可以用来恰当地定义任何一对构造函数的原型链; 用 :: 可以快速访问对象的原型; super() 可以编译为一个父类上同名方法的调用.

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

等价于:

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

其他

还有一些零碎的特性,例如字符串替换、块级正则表达式、块级字符串等特性,在这里不特殊说明了,建议查看官方文档

你可能感兴趣的:(前端)