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

理解 ECMAScript 6:类和继承

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2015年4月27日

CPOL

5分钟阅读

viewsIcon

17479

我想与大家分享一系列关于 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 代码(使用 letconst 关键字),因此你只需保留现有的 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 设备上进行远程测试。

© . All rights reserved.