使用CoffeeScrip优美方式编写javascript代码


Posted in Javascript onOctober 28, 2015

JavaScript无疑是在web最伟大的发明之一,几乎一切网页动态效果都是基于它丰富的计算能力。而且它的能力在各种新的JavaScript的Engine下也越来越强了,比如Google Chrome用的V8 Engine。

但是由于诞生的太早,有很多语法定义在今天看来有些效率低下了,一些更加先进的语法形式,由于历史原因,没办法加入到现在的JavaScript语言中,可以说一种遗憾。

世界上的很多天才都在为构建更好的JavaScript而努力。已经有了很多尝试,其中最有前途的,无非就是CoffeeScript和TypeScript了。面对CoffeeScript,我有一见如故的感觉;而TypeScript也激发了我极大的兴趣。CoffeeScript和TypeScript一样,都是编译为JavaScript的语言,它们都增强了JavaScript的表达能力。这篇文章是讲CoffeeScript的,TypeScript将放在下一篇再讲。

所谓编译为JavaScript,是指CoffeeScript和TypeScript没有实现自己的运行时,它们都是编译为等价的JavaScript代码,然后放在JavaScript的解释器上运行。

CoffeeScript

简洁性

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力求简洁。其简洁性首先表现在对一些仅用于语法控制的符号进行了去除。这其中包括:

取消分号

取消var声明

取消大括号包围内层代码,使用缩进取代

函数调用在没有歧义的情况下可以省略括号

var声明涉及到复杂又很鸡肋的JavaScript变量作用域机制。这部分内容先放着不讲。CoffeeScript通过完全取消var声明机制而使得问题得到简化。总之,在CoffeeScript世界里,变量不用事先声明,直接用就是了。而且这种用法基本没有什么危险。

缩进在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类的基础。

关于JavaScript的糟粕

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

CoffeeScript简化和增强了JavaScript的表达能力,尽可能地从语法层面上就能避免JavaScript的一些坑。用它写代码,会让人有更清晰舒适的感觉,而且不容易犯错。CoffeeScript的初衷就是提供更好的JavaScript。

然而,CoffeeScript与JavaScript是不兼容的。它既不是JavaScript的子集,也不是超集,而是与JavaScript有着显然不同思路的一种语言。用CoffeeScript编程就必然要转换观念,尽管这种观念更好更自然,但却是有些固步自封的人望而却步的主要原因了。

CoffeeScript并不是适合每一个人的。有些人对于用缩进组织代码层次完全不能接受,也不能接受用箭头函数表达法。对于他们来说,去掉function关键字和大括号的组织怎么看都怎么地不顺眼。

列表解析很强大,却也显得过于简洁了。对于习惯了构造冗杂JavaScript程序的人们来说,并不习惯这种表达方式。

总之,是不可强求别人去学习使用CoffeeScript。JavaScript已经足够强大,只要足够小心,完全可以使用JavaScript很好地完成工作。对于那些想要尝试CoffeeScript,我们也要给予鼓励的态度,他们是求新求变的勇士。CoffeeScript真的值得一试,而且它真的很小巧,完全掌握它不是件困难的事。

对于在团队推行CoffeeScript,我本人更是持有保守的看法。如果团队从一开始就使用CoffeeScript还好。如果是要从CoffeeScript转为JavaScript,就要谨慎行之。一种可行的方式是先尝试在一个小项目中使用CoffeeScrip,看看效果如何。

对于个人来说,就没有什么限制了。如果真的喜欢,就去尝试吧。你可以使用CoffeeScript写脚本,构建自己的网站,做一些小玩意。

以上内容是小编给大家介绍的使用CoffeeScrip优美方式编写javascript代码,希望大家喜欢。

Javascript 相关文章推荐
实现连缀调用的map方法(prototype)
Aug 05 Javascript
javascript显式类型转换实例分析
Apr 25 Javascript
js实现n秒倒计时后才可以点击的效果
Dec 20 Javascript
JS获取当前脚本文件的绝对路径
Mar 02 Javascript
Node.js环境下编写爬虫爬取维基百科内容的实例分享
Jun 12 Javascript
React-router中结合webpack实现按需加载实例
May 25 Javascript
基于vue2.0动态组件及render详解
Mar 17 Javascript
JavaScript实现读取与输出XML文件数据的方法示例
Jun 05 Javascript
Javascript实现异步编程的过程
Jun 18 Javascript
vue项目中,main.js,App.vue,index.html的调用方法
Sep 20 Javascript
基于Vue实现图片在指定区域内移动的思路详解
Nov 11 Javascript
Javascript查看大图功能代码实现
May 07 Javascript
开启Javascript中apply、call、bind的用法之旅模式
Oct 28 #Javascript
JavaScript多并发问题如何处理
Oct 28 #Javascript
JS实现双击屏幕滚动效果代码
Oct 28 #Javascript
基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式
Oct 28 #Javascript
jQuery实现滑动页面固定顶部显示(可根据显示位置消失与替换)
Oct 28 #Javascript
jquery实现的动态回到顶部特效代码
Oct 28 #Javascript
JavaScript如何调试有哪些建议和技巧附五款有用的调试工具
Oct 28 #Javascript
You might like
关于在php.ini中添加extension=php_mysqli.dll指令的说明
2007/06/14 PHP
php实现的css文件背景图片下载器代码
2014/11/11 PHP
PHPCMS手机站伪静态设置详细教程
2017/02/06 PHP
Yii2.0使用阿里云OSS的SDK上传图片、下载、删除图片示例
2017/09/20 PHP
PHP封装的mysqli数据库操作类示例
2019/02/16 PHP
PHP的Trait机制原理与用法分析
2019/10/18 PHP
isArray()函数(JavaScript中对象类型判断的几种方法)
2009/11/26 Javascript
javascript中xml操作实现代码
2011/11/21 Javascript
JQuery Highcharts 动态生成图表的方法
2013/11/15 Javascript
js获取select标签的值且兼容IE与firefox
2013/12/30 Javascript
JavaScript及jquey实现多个数组的合并操作
2014/09/06 Javascript
JQuery简单实现锚点链接的平滑滚动
2015/05/03 Javascript
js获取form的方法
2015/05/06 Javascript
学习Bootstrap组件之下拉菜单
2015/07/28 Javascript
Javascript字符串常用方法详解
2016/07/21 Javascript
jQuery使用正则验证15/18身份证的方法示例
2017/04/27 jQuery
LayerClose弹窗关闭刷新方法
2018/08/17 Javascript
最简单的vue消息提示全局组件的方法
2019/06/16 Javascript
[02:12]DOTA2英雄基础教程 变体精灵
2013/12/16 DOTA
[00:13]天涯墨客二技能展示
2018/08/25 DOTA
Python采用raw_input读取输入值的方法
2014/08/18 Python
Python黑魔法Descriptor描述符的实例解析
2016/06/02 Python
python实现K最近邻算法
2018/01/29 Python
使用python切片实现二维数组复制示例
2019/11/26 Python
python GUI库图形界面开发之PyQt5信号与槽的高级使用技巧装饰器信号与槽详细使用方法与实例
2020/03/06 Python
Python3 filecmp模块测试比较文件原理解析
2020/03/23 Python
解决Keras使用GPU资源耗尽的问题
2020/06/22 Python
Python绘制词云图之可视化神器pyecharts的方法
2021/02/23 Python
关于css兼容性问题及一些常见问题汇总
2016/05/03 HTML / CSS
雅萌 (YA-MAN) :日本美容家电领域的龙头企业
2017/05/12 全球购物
软件测试笔试题
2012/10/25 面试题
庆元旦广播稿
2014/02/10 职场文书
工商治理实习生的自我评价分享
2014/02/20 职场文书
学位证书委托书
2014/09/30 职场文书
MySQL数字类型自增的坑
2021/05/07 MySQL
spring cloud eureka 服务启动失败的原因分析及解决方法
2022/03/17 Java/Android