ES6 有什么新功能? CoffeeScript 转换透视图
已发表: 2022-03-11两年多来,我一直是 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 }
当然,这并不是真正的世界末日,如果有的话,这种更明确的语法更容易阅读。 另一个缺失的小事情是我们没有@
快捷方式, this
在 Ruby 背景下总是感觉很好,但又不是一个大的交易破坏者。 总的来说,我们在这里感觉很自在,实际上我更喜欢 ES6 语法来定义方法。
多么超级!
在 ES6 中处理super
的方式有一个小问题。 CoffeeScript 以 Ruby 的方式处理super
(“请向同名的超类方法发送消息”),但是在 ES6 中唯一可以使用“裸”super 的情况是在构造函数中。 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 )
这有点方便,但可能会有点混乱,因为如果添加花括号,您需要return
:
array.map( x => { return x * 10 })
但是,它仍然是有道理的。 原因是如果你添加了花括号,那是因为你想使用多行,如果你有多行,那么清楚你要返回的内容是有意义的。
ES6 类语法奖励
我们已经看到,在定义类时,CoffeeScript 有一些句法技巧,但 ES6 也有它自己的一些技巧。
Getter 和 Setter
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()
?

setter 同样有用,因为我们可以清理数据集,甚至在设置无效数据时抛出异常,如上面的玩具示例所示。 这对于任何具有 Ruby 背景的人来说都是非常熟悉的。
我个人认为这并不意味着你应该为所有事情都使用 getter 和 setter 来发疯。 如果您可以通过 getter 和 setter(如上面的示例)封装您的实例变量来获得一些东西,那么请继续这样做。 但是想象一下,您只有一个布尔标志,例如isEditable
。 如果直接暴露isEditable
属性不会丢失任何东西,我认为这是最好的方法,因为它是最简单的。
静态方法
ES6 支持静态方法,这让我们可以做这个顽皮的把戏:
class Foo { static new() { return new this } }
现在如果你讨厌写new Foo()
你现在可以写Foo.new()
。 纯粹主义者会抱怨,因为new
是一个保留字,但经过非常快速的测试后,它似乎在 Node.js 和现代浏览器中都能正常工作。 但是您可能不想在生产中使用它!
不幸的是,因为在 ES6 中不支持静态属性,如果你想定义类常量并在你的静态方法中访问它们,你必须做这样的事情,这有点 hackish:
class Foo { static imgPath() { return `${this.ROOT_PATH}/img` } } Foo.ROOT_PATH = '/foo'
有一个 ES7 Proposal 来实现声明性实例和类属性,这样我们就可以做这样的事情,这会很好:
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 样式的字符串插值。 "What a #{expletive} shame"
。
箭头函数
箭头函数在 CoffeeScripter 中被认为是理所当然的。 ES6 几乎实现了 CoffeeScript 的粗箭头语法。 所以我们得到了一个很好的简短语法,我们得到了词法范围的this
,所以我们在使用 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' } }
不太清楚你为什么要开始这样做,但是嘿,现在你可以了! 你当然不能定义静态方法……因为那根本没有意义。
让你困惑的 for 循环
ES6 for-loop 语法将为有经验的 CoffeeScripter 引入一些不错的肌肉记忆错误,因为 ES6 的 for-of 转换为 CoffeeSCript 的 for-in,反之亦然。
ES6
for (let i of [1, 2, 3]) { console.log(i) } // 1 // 2 // 3
咖啡脚本
for i of [1, 2, 3] console.log(i) # 0 # 1 # 2
我应该切换到 ES6 吗?
我目前一直在使用 100% CoffeeScript 开发一个相当大的 Node.js 项目,我仍然对它非常满意并且非常高效。 我想说,在 ES6 中我唯一真正嫉妒的是 getter 和 setter。
而且今天在实践中使用 ES6 仍然有些痛苦。 如果您能够使用最新的 Node.js 版本,那么您几乎可以获得所有 ES6 功能,但对于旧版本和浏览器来说,情况仍然不那么乐观。 是的,你可以使用 Babel,但这当然意味着将 Babel 集成到你的堆栈中。
话虽如此,我可以看到 ES6 在未来一两年内取得很大进展,我希望在 ES7 中看到更大的成就。
我对 ES6 真正感到满意的是,“plain old JavaScript”几乎与 CoffeeScript 一样友好和强大,开箱即用。 作为一名自由职业者,这对我来说非常棒——在过去,我曾经有点不喜欢在 JavaScript 项目上工作——但在 ES6 中,一切似乎都变得更加闪亮了。