从编译后的js代码看typescript中的面向对象

3,628 阅读3分钟

众所周知,ts目前是不能直接编译成机器码运行的
需要转换成js代码后运行
由于js的动态特性,所以我也很好奇编译后的代码是怎么样的呢

废话不多说,直接切入正题
注:(ts版本为:3.1,编译的js版本为:ES5)

封装

public/private/protected

首先写一个基类

class Person {

    public name: string;
    private _age: number;
    protected sex: string;

    constructor(name: string,  age: number, sex: string) {
        this.name = name;
        this.sex = sex;
        this._age = age;
    }

    public say():void {
        console.log(`my name is ${this.name}`)
    }
}

编译后:

var Person = /** @class */ (function () {
    function Person(name, age, sex) {
        this.name = name;
        this.sex = sex;
        this._age = age;
    }
    Person.prototype.say = function () {
        console.log("my name is " + this.name);
    };
    return Person;
}());

可以看到编译后的代码还少了很多
我本以为编译后的代码应该更复杂些,
比如用闭包实现private等等
后来一想,真没必要,ts是js的超集
只要保证在ts内,代码的安全性能得到保证就行
毕竟,我们不会在编译后的js内再进行编码

let p: Person = new Person('小明', 20, '男');
// error:属性“_age”为私有属性,只能在类“Person”中访问。ts(2341)
p._age = 21;

注意,构造函数也可以进行描述,private后将不能实例化对象,protected后只能由继承的类调用其构造函数

存取器与只读

我们再来看看其他修饰器编译之后的代码

class Person {

    public readonly age: number;

    private _height: number;
    get height(): number {
        return this._height;
    }

    constructor( age: number,  height: number) {
        this.age = age;
        this._height = height;
    }
}

编译后:

var Person = /** @class */ (function () {
    function Person(age, height) {
        this.age = age;
        this._height = height;
    }
    Object.defineProperty(Person.prototype, "height", {
        get: function () {
            return this._height;
        },
        enumerable: true,
        configurable: true
    });
    return Person;
}());

这里只有get ts能推断出属性是只读
我们代码主动设置属性readonly也是只读
但要注意他们编译出来的代码不同

let p: Person = new Person(20, 170);
// error:Cannot assign to 'age' because it is a read-only property.ts(2540)
p.age = 22;
// error:Cannot assign to 'height' because it is a read-only property.ts(2540)
p.height = 180;

继承

下面我们来写一个子类

class Person {

    public name: string;
    private _age: number;
    protected sex: string;

    constructor(name: string, age: number, sex: string) {
        this.name = name;
        this.sex = sex;
        this._age = age;
    }

    public say() {
        console.log(`my name is ${this.name}`)
    }
}


class Boy extends Person {

    constructor(name: string, age: number) {
        super(name, age, '男');
    }

    play() {

    }
}

编译后:

"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
var Person = /** @class */ (function () {
    function Person(name, age, sex) {
        this.name = name;
        this.sex = sex;
        this._age = age;
    }
    Person.prototype.say = function () {
        console.log("my name is " + this.name);
    };
    return Person;
}());
var Boy = /** @class */ (function (_super) {
    __extends(Boy, _super);
    function Boy(name, age) {
        return _super.call(this, name, age, '男') || this;
    }
    Boy.prototype.play = function () {
    };
    return Boy;
}(Person));

可以看到编译成ES5的js代码class的实现还是特别规范的
使用的组合寄生继承方实现式,
没有变量提升,静态属性也继承了

extend与implements

要注意ts和别的强类型语言不同点
类既能被继承,也能被当做接口去实现
而接口,只能去实现

如:

class Person {

    public name: string;

    constructor(name: string) {
        this.name = name;
    }

    public say() {
        console.log(`my name is ${this.name}`)
    }
}

interface IStudy {
    read(): void;
    write(): void;
}

class Boy extends Person implements IStudy {

    constructor(name: string) {
        super(name);
    }
    read(): void {
        console.log('boy read');
    }

    write(): void {
    }
}

class Girl implements Person, IStudy {
    public name: string;

    constructor(name: string) {
        this.name = name;
    }
    read(): void {
        console.log('girl read');
    }
    write(): void {

    }
    say(): void {
    }
}

Boy是继承自Person
而Girl是实现了Person

编译代码为:

var Person = /** @class */ (function () {
    function Person(name) {
        this.name = name;
    }
    Person.prototype.say = function () {
        console.log("my name is " + this.name);
    };
    return Person;
}());
exports.Person = Person;
var Boy = /** @class */ (function (_super) {
    __extends(Boy, _super);
    function Boy(name) {
        return _super.call(this, name) || this;
    }
    Boy.prototype.read = function () {
        console.log('boy read');
    };
    Boy.prototype.write = function () {
    };
    return Boy;
}(Person));
var Girl = /** @class */ (function () {
    function Girl(name) {
        this.name = name;
    }
    Girl.prototype.read = function () {
        console.log('girl read');
    };
    Girl.prototype.write = function () {
    };
    Girl.prototype.say = function () {
    };
    return Girl;
}());

这里编译后的js代码也直观的显示了继承和实现的区别
Boy继承自Person,所以原型链上能找到父类的方法属性
Girl实现了Person,所以只是自身拥有和Person一样结构的属性和方法

let s1: Boy = new Boy('小明');
let s2: Girl = new Girl('小红');

console.log(s1 instanceof Person); // true
console.log(s2 instanceof Person); // false

类型兼容

相较其他强类型语言,更有意思的是
ts中的类型兼容,更像是填鸭大法
只要两个类型拥有相同的结构,就可以兼容
比如接着上面的代码:


let boy: Boy = new Boy('小明');
let girl: Girl = new Girl('小红');

boy = girl;// ok
let tmp: Girl = new Boy('小黑'); // ok

联合类型

甚至还可以:

function factory(): IStudy & Person {
    // return new Boy('小绿'); // ok
    return new Girl('小紫'); // ok
}

由于js中弱类型的特性,广泛的使用了匿名对象,动态属性等等
所以ts中对类型的兼容的判定都是基于"结构",而不是它的名义类型

多态

多态是弱类型语言与生俱来的,或者说不应该有这个概念
而ts中也是与生俱来

let s1: IStudy = new Boy('小明');
let s2: IStudy = new Girl('小红');

s1.read();// boy read
s2.read();// girl read

因为多态的定义本来就是:

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

所以重写,实现,或是重载都能表现

总结

js本来就有面向对象的实现,ts只是更好的表现出来
ts对类型的兼容是基于“结构”的兼容
所以ts中的"类",既能被继承(extends),也能被实现(implements)
ts是js的超集,很多特征在编译后的js代码内并不能体现,但是能保证编译后js运行的安全性