本文概述
我已经成为CoffeeScript迷了两年多了。我发现自己编写CoffeeScript的工作效率更高, 犯了一些愚蠢的错误, 并且借助源代码映射, 调试CoffeeScript代码完全没有问题。
最近, 我一直在使用Babel玩ES6, 总体而言, 我是一名粉丝。在本文中, 我将从CoffeeScript转换的角度汇编关于ES6的发现, 了解我喜欢的东西, 并查看我仍然缺少的东西。
句法上重要的缩进:恩惠还是祸根?
我一直都对CoffeeScript语法上重要的缩进有一种爱恨交加的感觉。当一切顺利时, 你会得到”瞧瞧, 没有手!”使用这种极简主义语法的吹牛权利。但是, 当事情出错并且缩进不正确会导致真正的错误时(相信我, 这确实发生了), 这似乎并没有带来好处。
if iWriteCoffeeScript
if iAmNotCareful
badThings = 'happen'
通常, 这些错误是由于你的代码块中包含一些嵌套语句而导致的, 并且你不小心缩进了一条语句。是的, 它通常是由疲倦的眼睛引起的, 但我认为它更可能在CoffeeScript中发生。
当我开始编写ES6时, 我不太确定自己的直觉会如何。事实证明, 再次开始使用大括号实际上感觉非常好。使用现代的编辑器也有帮助, 因为通常你只需要打开支架, 而你的编辑器就足够为你关闭它。感觉就像是在享受了CoffeeScript的伏都教魔法之后, 回到了一个平静, 清晰的宇宙。
if (iWriteES6) {
if (iWriteNestedStatements) {
let badThings = 'are less likely to happen'
}
}
当然, 我坚持要扔分号。如果我们不需要它们, 我就说把它们扔掉。我觉得它们很丑, 这是多余的打字。
类支持
ES6中的课堂支持非常棒, 如果你从CoffeeScript搬迁, 你会感到宾至如归。让我们通过一个简单的示例比较两者之间的语法:
ES6级
class Animal {
constructor(numberOfLegs) {
this.numberOfLegs = numberOfLegs
}
toString() {
return `I am an animal with ${this.numberOfLegs} legs`
}
}
class Monkey extends Animal {
constructor(bananas) {
super(2)
this.bananas = bananas
}
toString() {
let superString = super.toString()
.replace(/an animal/, 'a monkey')
return `${superString} and ${this.bananas} bananas`
}
}
CoffeeScript类
class Animal
constructor: (@numberOfLegs) ->
toString: ->
"I am an animal with #{@numberOfLegs} legs"
class Monkey extends Animal
constructor: (@numberOfBananas) ->
super(2)
toString: ->
superString = super.toString()
.replace(/an animal/, 'a monkey')
"#{superString} and #{@numberOfLegs} bananas"
第一印象
你可能会注意到的第一件事是, ES6仍然比CoffeeScript更为冗长。在CoffeeScript中非常方便的一种快捷方式是支持在构造函数中自动分配实例变量:
constructor: (@numberOfLegs) ->
这等效于ES6中的以下内容:
constructor(numberOfLegs) {
this.numberOfLegs = numberOfLegs
}
当然, 这并不是世界末日, 如果有的话, 这种更明确的语法更易于阅读。另一个缺少的小事情是, 我们没有@快捷方式, 从Ruby的背景来看, 它总是让人感觉很好, 但又不是一个大问题。总的来说, 我们在这里感觉很自在, 实际上我更喜欢ES6语法来定义方法。
超级好!
在ES6中有一个处理超级问题的小陷阱。 CoffeeScript以Ruby方式处理超级(“请向同名的超类方法发送消息”), 但是唯一可以在ES6中使用”裸”超级的方法是在构造函数中。 ES6遵循更常规的类似Java的方法, 其中super是对超类实例的引用。
我会回来
在CoffeeScript中, 我们可以使用隐式返回来构建漂亮的代码, 如下所示:
foo = ->
if Math.random() > 0.5
if Math.random() > 0.5
if Math.random() > 0.5
"foo"
在这里, 当你调用foo()时, 获得” foo”的机会很小。 ES6没有这些, 迫使我们使用return关键字返回:
const foo = () => {
if (Math.random() > 0.5 ) {
if (Math.random() > 0.5 ) {
if (Math.random() > 0.5 ) {
return "foo"
}
}
}
}
正如我们在上面的示例中看到的那样, 这通常是一件好事, 但是我仍然发现自己忘记了添加它, 到处都是意料之外的未定义的返回值!关于箭头函数, 我遇到了一个例外, 在这种情况下, 你会像这样的一种班轮获得隐式回报:
array.map( x => x * 10 )
这有点方便, 但是如果添加花括号, 你可能会感到有些困惑, 因为你需要返回:
array.map( x => {
return x * 10
})
但是, 这仍然有意义。原因是, 如果添加了花括号, 则是因为要使用多行, 而如果有多行, 则有必要弄清楚要返回的内容。
ES6类语法奖金
我们已经看到, CoffeeScript在定义类时有一些语法技巧, 但是ES6也有一些技巧。
吸气剂和二传手
ES6对通过getter和setter进行封装提供了强大的支持, 如以下示例所示:
class BananaStore {
constructor() {
this._bananas = []
}
populate() {
// fetch our bananas from the backend here
}
get bananas() {
return this._bananas.filter( banana => banana.isRipe )
}
set bananas(bananas) {
if (bananas.length > 100) {
throw `Wow ${bananas.length} is a lot of bananas!`
}
this._bananas = bananas
}
}
感谢getter, 如果我们访问bananaStore.bananas, 它将仅返回成熟的香蕉。这确实很棒, 因为在CoffeeScript中, 我们需要通过诸如bananaStore.getBananas()之类的getter方法来实现此目的。当我们习惯使用JavaScript直接访问属性时, 这根本感觉不到。这也使开发混乱, 因为我们需要考虑每个属性如何访问它-是.bananas还是getBananas()?
设置器同样有用, 因为我们可以清理数据集, 或者在设置无效数据时甚至抛出异常, 如上面的玩具示例所示。对于任何有Ruby背景的人来说, 这都是非常熟悉的。
我个人认为这并不意味着你应该为所有内容使用吸气剂和吸气剂而发疯。如果你可以通过使用getter和setter封装实例变量来获得一些收益(如上述示例中所示), 请继续这样做。但是想象一下, 你只有一个布尔变量, 例如isEditable。如果你不会通过直接公开isEditable属性而损失任何东西, 那么我认为这是最好的方法, 因为这是最简单的方法。
静态方法
ES6支持静态方法, 这使我们可以完成这个顽皮的技巧:
class Foo {
static new() {
return new this
}
}
现在, 如果你不想编写新的Foo(), 现在可以编写Foo.new()。纯粹主义者会抱怨, 因为new是保留字, 但是经过非常快速的测试, 它似乎可以在Node.js和现代浏览器中正常工作。但是你可能不想在生产中使用它!
不幸的是, 由于ES6中不支持静态属性, 因此, 如果要定义类常量并以静态方法访问它们, 则必须执行以下操作, 这有点怪异:
class Foo {
static imgPath() {
return `${this.ROOT_PATH}/img`
}
}
Foo.ROOT_PATH = '/foo'
有一个ES7提案, 用于实现声明性实例和类属性, 以便我们可以执行以下操作, 这将非常不错:
class Foo {
static ROOT_PATH = '/foo'
static imgPath() {
return `${this.ROOT_PATH}/img`
}
}
这已经可以在CoffeeScript中非常优雅地完成:
class Foo
@ROOT_PATH: '/foo'
@imgPath: ->
@ROOT_PATH
字符串插值
现在终于可以使用Javascript进行字符串插值了!
`I am so happy that in the year ${new Date().getFullYear()} we can interpolate strings`
来自CoffeeScript / Ruby, 这种语法有点让人讨厌。也许是由于我的Ruby背景, 其中使用反引号来执行系统命令, 所以一开始感觉很不对劲。同样使用美元符号感觉八十年代-也许那只是我。
我想, 出于对向后兼容性的考虑, 无法实现CoffeeScript样式字符串插值。 “真是#{expiveive}的耻辱”。
箭头函数
CoffeeScripter中的箭头函数是理所当然的。 ES6几乎实现了CoffeeScript的胖箭头语法。因此, 我们得到了一个很好的简短语法, 并且得到了词法范围的限制, 因此在使用ES5时我们不必像这样跳过循环:
var that = this
doSomethingAsync().then( function(res) {
that.foo(res)
})
ES6中的等效项是:
doSomethingAsync().then( res => {
this.foo(res)
})
注意我如何在一元回调中省略括号, 因为我可以!
对象常量
ES6中的对象文字具有一些严重的性能增强。这些天他们可以做各种事情!
动态属性名称
直到撰写本文, 我才真正意识到自CoffeeScript 1.9.1起, 我们现在可以做到这一点:
dynamicProperty = 'foo'
obj = {"#{dynamicProperty}": 'bar'}
这比以前需要做的事情要痛苦得多:
dynamicProperty = 'foo'
obj = {}
obj[dynamicProperty] = 'bar'
ES6有一种替代语法, 我认为这不错, 但并不是一个大赢家:
let dynamicProperty = 'foo'
let obj = { [dynamicProperty]: 'bar' }
时髦的捷径
这实际上取自CoffeeScript, 但是直到现在我还是不了解它。无论如何, 这非常有用:
let foo = 'foo'
let bar = 'bar'
let obj = { foo, bar }
现在obj的内容是{foo:’foo’, bar:’bar’}。这非常有用, 值得记住。
班上我能做的一切!
对象文字现在可以执行类可以执行的几乎所有操作, 甚至包括:
let obj = {
_foo: 'foo', get foo() {
return this._foo
}, set foo(str) {
this._foo = str
}, isFoo() {
return this.foo === 'foo'
}
}
不太确定为什么要开始这样做, 但是, 嘿, 现在可以了!你当然不能定义静态方法…因为那根本没有意义。
循环让你迷惑
ES6 for循环语法将为经验丰富的CoffeeScripter引入一些不错的肌肉记忆错误, 因为ES6的形式转换为CoffeeSCript的形式, 反之亦然。
ES6
for (let i of [1, 2, 3]) {
console.log(i)
}
// 1
// 2
// 3
CoffeeScript
for i of [1, 2, 3]
console.log(i)
# 0
# 1
# 2
我应该切换到ES6吗?
我目前正在使用100%CoffeeScript进行一个相当大的Node.js项目, 但我仍然非常满意并且非常高效。我要说的是, 我在ES6中唯一真正嫉妒的就是吸气剂和吸气剂。
今天在实践中使用ES6仍然有些痛苦。如果你能够使用最新的Node.js版本, 那么你将获得几乎所有的ES6功能, 但是对于较旧的版本以及在浏览器中, 情况仍然不那么乐观。是的, 你可以使用Babel, 但这当然意味着将Babel集成到你的堆栈中。
话虽这么说, 我可以在接下来的一两年中看到ES6的发展, 并且希望在ES7中看到更多的东西。
我对ES6感到非常满意的是, “普通的JavaScript”几乎像CoffeeScript一样友好且功能强大。作为一名自由职业者, 这对我来说非常棒-过去, 我过去对JavaScript项目的工作有点反感-但是使用ES6时, 一切似乎都变得更加光亮。