理解 ECMAScript 6:类和继承





5.00/5 (1投票)
我想与大家分享一系列关于 ECMAScript 6 的文章,分享我对它的热情,并解释它如何为你们服务。
首先,我在微软的浏览器渲染引擎部门工作,负责 Microsoft Edge。与我们多年来熟悉(并喜爱?)的 Internet Explorer 引擎相比,Edge 有了巨大的改进。我个人最喜欢的功能是它支持许多 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 的一个新功能——Symbol——有了一种更好的方法来做到这一点。
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 是什么?它是一种唯一且不可变的(immutable)数据类型,可以用作对象属性的标识符。如果你没有 Symbol,你就无法访问该属性。
这带来了更“私有”的成员访问。
或者,至少,不太容易访问。Symbol 有助于名称的唯一性,但唯一性并不意味着隐私。唯一性只是意味着,如果你需要一个不会与其他任何键冲突的键,就创建一个新的 Symbol。
但这仍然不是真正的私有,因为 thanks to 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();
thanks to “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 的未来是光明的,说实话,我迫不及待地想在所有现代浏览器中看到它得到广泛支持!
本文是 Microsoft Web 开发技术系列文章的一部分。我们很荣幸与您分享 Microsoft Edge 及其新的渲染引擎。在 modern.IE 上免费获取虚拟机或在 Mac、iOS、Android 或 Windows 设备上进行远程测试。