理解Ecmascript 6中的类和继承

分享一系列关于 ECMAScript 6 的文章,分享我对它的热爱并解释如何让它为你所用。希望你在阅读的时候能和我写作的时候一样享受快乐。


首先,我是在微软负责 Spartan 浏览器 渲染引擎项目,该引擎是在我们多年来熟悉的(并爱上的?)Internet Explorer  引擎的基础上,做出巨大改进。我最喜欢该引擎的特性是支持大部分 ECMAScript 6。对我而言,这对编写大型 web 应用程序会带来巨大好处。

到目前为止,已有将近 7 成 ECMAScript 6 特性在 Spartan 项目上得到支持,通过 http://kangax.github.io/compat-table/es6/ 和 ES6 on status.modern.IE 可查看相关信息。


我喜欢 JavaScript,但运用在大型项目上,如 Babylon.js,我更情愿用 TypeScriptAngular 2 现在正是基于它实现的。没用 JavaScript 的原因是它没有我习惯使用其它语言编写大型程序的所有语法特性比如类和继承。

所以事不宜迟,让我们开始吧。

创建类

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();

在上面的代码中,定义了一个带有“属性”和“方法”的“类”。

这个构造器通过函数(函数 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();

运行结果和前面的一样,但对于习惯于写类的开发者来说,更易编写和阅读。因为这不需要原型,并且能使用 “constructor” 关键字定义构造器。

此外,类引入了一些 ECMAScript 5 新的语法。例如,必须通过 new 调用构造函数,或者不能用 new 尝试去构造类方法。另一个改变是,方法都是不可枚举的。

有趣的地方是:两个版本可同时共存使用。

归根结底,即使用 new 关键字实例化一个添加在原型的函数。“方法”在这里仅仅是对象的一个函数属性。

另一个基于类开发的核心特性是,getter 和 setter 都在 ECMAScript 6 得到支持。这使方法的用途变得更加明显:

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 (符号)对于创建唯一的名字是很有用的,但唯一性不意味着私有性。唯一性只意味着,为一把钥匙添加一个新符号后,当你需要它的时候,肯定不会被其它钥匙所干扰。

但这还不是正真的私有,因为通过 Object.getOwnPropertySymbols,downstream consumers  能获取 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 代码更靠谱。

首先,所有 1.4 版本以上的 TypeScript 支持 ECMAScript 6 代码(有 let 和 const 关键字)所以只要保留 TypeScript 代码,就能使用这些新选项去产生 ECMAScript 6 代码。

但如果你仔细观察一些 TypeScript 代码,会发现这些代码看起来像 ECMAScript 6 。所以现在开始学习 TypeScript的话 ,以后就能很好地理解 ECMAScript 6 了!

总结

使用 TypeScript ,代码会被转换成能跨浏览器运行的 ECMAScript 5 。如果想直接在浏览器上使用 ECMAScript 6 ,可升级到 Windows 10并在 Spartan 项目的渲染引擎进行测试。如果不想这么麻烦,只想测试一些新浏览器特性,那么也可以在 Windows 10的电脑上,用 Spartan 项目打开 http:// remote.modern.ie 。这可以在 MacOS 或 Linux 机器上运行。

当然, Spartan 项目并不是唯一支持公开标准的 ES6 。其它浏览器也已经支持了,你可在 http://kangax.github.io/compat-table/es6/ 查看支持程度。

ECMAScript 6 的前途是光明的,老实说我已情不自禁地想看到,它在所有现代浏览器上得到广泛支持。

这篇文章是 Microsoft web 开发教程系列的一部分。我们激动地向你们分享 Spartan 项目 和 它新渲染引擎 。获取免费虚拟机器或在 Mac、IOS、Android 或 Windows 设备上进行远程测试 @ http:// modern.IE

原文地址: 点击打开链接
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值