65.9K
CodeProject 正在变化。 阅读更多。
Home

理解 ECMAScript 6:类和继承

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2015年4月27日

CPOL

5分钟阅读

viewsIcon

17478

我想和大家分享一系列关于 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 设备上远程测试。

© . All rights reserved.