理解 ECMAScript 6:类和继承





5.00/5 (1投票)
我想和大家分享一系列关于 ECMAScript 6 的文章,分享我对它的热情,并解释它如何能为你们服务。
首先,我在微软从事浏览器渲染引擎的工作,为 Microsoft Edge 提供支持。它比我们过去(曾经喜欢?)的 Internet Explorer 引擎有了巨大的改进。我个人最喜欢它的一个特性是它支持大量的 ECMAScript 6 特性。对我来说,这对编写大型 Web 应用程序来说是一个巨大的优势。
根据 http://kangax.github.io/compat-table/es6/ 和 ES6 on status.modern.IE 的数据,目前 Microsoft Edge 已经支持了将近 70% 的 ECMAScript 6 特性。
我热爱 JavaScript,但当涉及到像 Babylon.js 这样的庞大项目时,我更倾向于使用 TypeScript,它现在(顺便说一句)为 Angular 2 提供支持。原因在于,JavaScript(或称 ECMAScript 5)没有我习惯于编写大型项目的其他语言中的所有语法特性。例如,我怀念类和继承。
那么,废话不多说,让我们直接进入主题。
创建类
JavaScript 是一种面向原型的语言,在 ECMAScript 5 中可以模拟类和继承。
JavaScript 中函数的高度灵活性允许我们在处理类时模拟我们习惯的封装。我们可以使用的技巧是扩展对象的原型。
var Animal = (function () { function Animal(name) { this.name = name; } // Methods Animal.prototype.doSomething = function () { console.log("I'm a " + this.name); }; return Animal; })(); var lion = new Animal("Lion"); lion.doSomething();
我们在这里可以看到,我们定义了一个带有“属性”和“方法”的“类”。
构造函数由函数本身(`function Animal`)定义,我们可以在其中实例化属性。通过使用原型,我们可以定义被视为实例方法的函数。
这确实可行,但它假设你了解原型继承,而且对于来自基于类的语言的开发者来说,它看起来非常令人困惑。奇怪的是,JavaScript 有一个 `class` 关键字,但它并没有任何作用。ECMAScript 6 现在让这一切生效,并允许使用更简短的代码。
class AnimalES6 { constructor(name) { this.name = name; } doSomething() { console.log("I'm a " + this.name); } } var lionES6 = new AnimalES6("Lion"); lionES6.doSomething();
结果是相同的,但对于习惯编写类的开发人员来说,这样更容易编写和阅读。不再需要原型,并且可以使用“new”关键字来定义构造函数。
此外,类还引入了许多 ECMAScript 5 等效版本中不存在的新语义。例如,你不能在没有 `new` 的情况下调用构造函数,也不能尝试使用 `new` 来构造方法。另一个变化是方法是不可枚举的。
这里有一个有趣的观点:两个版本可以并存。
归根结底,即使有了新的关键字,你最终还是会得到一个函数,其中有一个原型,并且添加了一个函数。这里的“方法”就是对象上的一个函数属性。
基于类的开发的另一个核心特性,getter 和 setter,在 ES6 中也得到了支持。这使得一个方法应该做什么变得更加明显。
class AnimalES6 { constructor(name) { this.name = name; this._age = 0; } get age() { return this._age; } set age(value) { if (value < 0) { console.log("We do not support undead animals"); } this._age = value; } doSomething() { console.log("I'm a " + this.name); } } var lionES6 = new AnimalES6("Lion"); lionES6.doSomething(); lionES6.age = 5;
很方便,对吧?
但我们在这里可以看到 JavaScript 的一个常见陷阱:“不是真正私有”的私有成员(`_age`)。我之前写过一篇关于这个话题的文章。
值得庆幸的是,现在我们有了一种更好的方法来做到这一点,这就是 ECMAScript 6 的一个新特性: Symbols。
var ageSymbol = Symbol(); class AnimalES6 { constructor(name) { this.name = name; this[ageSymbol] = 0; } get age() { return this[ageSymbol]; } set age(value) { if (value < 0) { console.log("We do not support undead animals"); } this[ageSymbol] = value; } doSomething() { console.log("I'm a " + this.name); } } var lionES6 = new AnimalES6("Lion"); lionES6.doSomething(); lionES6.age = 5;
那么 Symbol 是什么?它是一种独特且不可变的原始数据类型,可以用作对象属性的标识符。如果你没有 Symbol,你就无法访问该属性。
这导致了更“私有”的成员访问。
或者,至少,不太容易访问。Symbol 在名字的唯一性方面很有用,但唯一性并不意味着隐私。唯一性只是意味着,如果你需要一个不会与其他任何键冲突的键,就创建一个新的 Symbol。
但这仍然不是真正的私有,因为通过 `Object.getOwnPropertySymbols`,下游消费者可以访问你的 Symbol 属性。
处理继承
一旦我们有了类,我们也想要继承。在 ES5 中,模拟继承也是可能的,但这样做相当复杂。
例如,这是 TypeScript 模拟继承生成的代码。
var __extends = this.__extends || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } __.prototype = b.prototype; d.prototype = new __(); }; var SwitchBooleanAction = (function (_super) { __extends(SwitchBooleanAction, _super); function SwitchBooleanAction(triggerOptions, target, propertyPath, condition) { _super.call(this, triggerOptions, condition); this.propertyPath = propertyPath; this._target = target; } SwitchBooleanAction.prototype.execute = function () { this._target[this._property] = !this._target[this._property]; }; return SwitchBooleanAction; })(BABYLON.Action);
不太容易阅读。
但 ECMAScript 6 的替代方案更好。
var legsCountSymbol = Symbol(); class InsectES6 extends AnimalES6 { constructor(name) { super(name); this[legsCountSymbol] = 0; } get legsCount() { return this[legsCountSymbol]; } set legsCount(value) { if (value < 0) { console.log("We do not support nether or interstellar insects"); } this[legsCountSymbol] = value; } doSomething() { super.doSomething(); console.log("And I have " + this[legsCountSymbol] + " legs!"); } } var spiderES6 = new InsectES6("Spider"); spiderES6.legsCount = 8; spiderES6.doSomething();
借助“extends
”关键字,你可以将一个类专门化为子类,同时使用“super
”关键字保留对根类的引用。
有了所有这些优秀的补充,现在就可以创建类并处理继承,而无需处理原型魔术。
为什么使用 TypeScript 比以前更重要…
随着这些新功能在我们的浏览器中可用,我认为使用 TypeScript 来生成 JavaScript 代码变得更加重要了。
首先,所有最新版本的 TypeScript (1.4) 都开始支持 ECMAScript 6 代码(使用 `let` 和 `const` 关键字),所以你只需要保留现有的 TypeScript 代码并启用此新选项即可开始生成 ECMAScript 6 代码。
但如果你仔细看看一些 TypeScript 代码,你会发现它看起来像 ECMAScript 6,只是没有类型。因此,今天学习 TypeScript 是理解明天的 ECMAScript 6 的绝佳途径!
结论
使用 TypeScript,你可以现在就在所有浏览器上拥有这一切,因为你的代码会被转换为 ECMAScript 5。如果你想直接在浏览器中使用 ECMAScript 6,可以升级到 Windows 10 并在其中使用 Microsoft Edge 的渲染引擎进行测试。如果你不想这样做只是为了尝试一些新的浏览器功能,也可以在 remote.modern.ie 上访问一台运行 Microsoft Edge 的 Windows 10 计算机。这在你的 MacOS 或 Linux 系统上同样适用。
当然,Microsoft Edge 并不是唯一支持 ES6 这个开放标准的浏览器。其他浏览器也在积极跟进,你可以在以下位置跟踪支持程度:http://kangax.github.io/compat-table/es6/
ECMAScript 6 带来的 JavaScript 未来是光明的,老实说,我迫不及待地想看到它在所有现代浏览器中得到广泛支持!
本文是微软 Web 开发技术系列的一部分。我们很高兴与您分享 Microsoft Edge 及其 新的渲染引擎。在 modern.IE 获取免费的虚拟机或在你的 Mac、iOS、Android 或 Windows 设备上远程测试。