译:使用ES6的Symbol原生实现JavaScript接口
翻译了2017年的一篇文章,介绍如何用Symbol实现JavaScript接口。
原文:Interfaces in JavaScript with ES6 Symbol. Naive implementation
众所周知,JavaScript用独特的方式实现了OOP(面向对象),与C#和Java的实现方式非常不同。在ES6之前,JavaScript是不支持类(class
)的,因此OOP是通过原型(prototype
)和其他一些自定义的方式实现的。现在class
类已经是JavaScript语言的一部分,而且逐渐被广泛使用,人们开始想要更进一步,在JavaScript中使用接口(interface
)。为了满足这个需求,有人提出了first class protocols。但这一提案仍处于Stage 1,所以距离成为语言标准还有很长的时间。我们在JavaScript的现状下,如何实现这一目的呢:
不要简单的使用鸭子类型去实现。有时候,这种方式也满足使用,但在许多情况下会导致应用发生不可预测的问题。
使用接口编译库。目前已经有许多库可以在JavaScript代码中实现接口,但我不满足于这种方式,因为我们的项目已经确定了不依赖任何第三方库。
使用TypeScript。Typescript作为JavaScript的超集,原生支持接口,但对我们的项目而言,切换到TypeScript成本太大。
使用代理(
Proxy
)。不幸的是,Babel不支持Proxy语法:由于ES5的限制,代理无法被转译。详见不同JavaScript引擎的支持情况
。使用Symbol数据类型。ES6标准增加了新的
Symbol
数据类型,具有非常重要的特性:
Symbol()返回的每个symbol的值都是唯一的
基于这一特性,我们的项目形成了一种实现接口的方式。
我们在类中,添加一个属性,以Symbol
作为属性名,返回一个带有接口实现的对象。使用Symbol
可以防止对象中已有的属性与接口属性命名冲突。
1 | const myInterface = Symbol('myInterface'); |
1 | import myInterface from 'myInterface'; |
1 | import myInterface from 'myInterface'; |
哈哈,非常原生的实现,而且可以检查对象是否实现了接口。我们可以更进一步,把接口中的每个函数以Symbol
命名,但最终决定还是只在接口名使用Symbol
。
实际上,ES6的生成器也是用这种方式,可以在任何对象中实现迭代器/生成器:
1 | const objWithIterator = { |