js实现ts中类的继承

737 阅读3分钟

一、TS中类的继承的写法

TS中类通过关键字extends实现继承。

class Parent {}
class Son extends Parent {}

二、js实现类的继承

js实现继承有四种方式。

1.原型链继承

function Parent(name,age){
    this.name=name
    this.age=age
}
Parent.prototype.eat = function() {
    console.log('eat')
}
function Child(favor,sex){
    this.favor=favor 
    this.sex=sex
}
Child.prototype= new Parent("张三",23) 
Child.prototype.constructor = Child
let child = new Child('篮球', '男')
child.eat()

原型链继承是通过将子类构造函数的prototype属性指向父类的实例实现继承。这样子类Child的实例可以访问父类Parent构造函数的原型对象上的属性和方法。

原型链继承的不足:不能通过子类构造函数向父类构造函数传递参数

2.借用构造函数继承

function Parent(name, age) {
    this.name = name
    this.age = age
}
Parent.prototype.eat = function () {
	console.log('eat')
}
function Child(name, age, favor, sex) {
    this.favor = favor 
    this.sex = sex
    Parent.call(this, name, age)
}
let child = new Child("王二麻子", 34, "打篮球", "男");
console.log("child.eat", child.eat);//undefined

借用构造函数可以向父类构造函数传递参数,但是不足是:子类Child的实例无法访问父类Parent构造函数的原型对象上的属性和方法。

3.借用构造函数+原型链继承组合模式

function Parent(name, age) {
    this.name = name
    this.age = age
}
Parent.prototype.eat = function () {
	console.log('eat')
}
function Child(name, age, favor, sex) {
    this.favor = favor 
    this.sex = sex
    Parent.call(this, name, age)//第二次调用构造函数
}
Child.prototype= new Parent("张三",23) //第一次调用构造函数
Child.prototype.constructor = Child
let child = new Child("王二麻子", 34, "打篮球", "男");
console.log("child.eat", child.eat);

该种组合模式,既有原型链继承的优点:子类可以调用父类构造函数的原型对象上的属性和方法。也具有借用构造函数继承的优点:可以向父类构造函数传递参数。但是不足之处是会调用两次父类的构造函数,且第一次调用父类构造函数传递的参数毫无意义。

4.寄生组合式继承

寄生组合式继承即寄生继承和借用构造函数继承的组合方式。其中寄生继承有多种实现方式。

方式一:借用辅助函数

function _extends(Parent, Child) {
    function Middle() {
        this.constructor = Child//将Child.prototype的constructor指向Child
    }
    Middle.prototype = Parent.prototype
    Child.prototype = new Middle()
}

方式二:利用Object.create

function _extends(Parent, Child) {
    Child.prototype = Object.create(Parent.prototype)
    Child.prototype.constructor = Child
} 

方式三:利用Object.setPrototypeOf

function _extends(Parent, Child) {
    Object.setPrototypeOf(Child.prototype, Parent.prototype)
}

方式四:利用__ proto __

function _extends(Parent, Child) {
    Child.prototype.__proto__ = Parent.prototype
} 

寄生组合式继承保留了借用构造函数+原型链继承的优点,解决了借用构造函数+原型链继承调用两次父类构造函数的缺陷。

三、js实现类的静态方法和静态属性的继承

静态方法定义在构造函数上,例如下面中的Parent类上就具有静态方法eat。

function Child() {}
function Parent() {}
Parent.eat = function () {}

js实现类的静态方法和静态属性的继承也有多种方式。

方式一:

for(let key in Parent) {
    console.log('key', key)
    //判断是否是父类构造函数上自有的属性
    if(Object.prototype.hasOwnProperty.call(Parent, key)) {
        console.log('key', key)
        Child[key] = Parent[key]
    }
}

方式二:

Object.keys(Parent).forEach(key => {
    Child[key] = Parent[key]
})

方式三:

Child.__proto__ = Parent

方式四:

Object.setPrototypeOf(Child, Parent)

其中前两种方式是将父类上的静态方法或静态属性复制到子类上,后两种实现方式是修改子类的原型链。

四、ts类继承编译成js源码

下面是一个ts类的继承例子

class Pet {
    public name: string
    public age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
    eat() {
        console.log('eat food')
    }
    static run() {
        console.log('run')
    } 
}
class Dog extends Pet {
    public favouriteFood: string
    constructor(name: string, age: number, favouriteFood: string) {
        super(name, age)
        this.favouriteFood = favouriteFood
    }
    eat() {
        super.eat()
        console.log('eat food:' + this.favouriteFood)
    }
}

编译成es5之后,代码为

var __extends = (this && this.__extends) || (function () {
    //extendStatics实现了静态方法和静态属性的继承
    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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        //借用辅助函数实现寄生继承
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Pet = /** @class */ (function () {
    function Pet(name, age) {
        this.name = name;
        this.age = age;
    }
    Pet.prototype.eat = function () {
        console.log('eat food');
    };
    Pet.run = function () {
        console.log('run');
    };
    return Pet;
}());
var Dog = /** @class */ (function (_super) {
    __extends(Dog, _super);
    function Dog(name, age, favouriteFood) {
        //借用构造函数向父类构造函数传递参数
        var _this = _super.call(this, name, age) || this;
        _this.favouriteFood = favouriteFood;
        return _this;
    }
    Dog.prototype.eat = function () {
        _super.prototype.eat.call(this);
        console.log('eat food:' + this.favouriteFood);
    };
    return Dog;
}(Pet));