JavaScirpt中的继承

1,125 阅读6分钟

前言


先看两张图

1.png

2.png

一、 先说说ES5中如何定义一个类


function Animal(name) {
    this.name = name

    // 实例方法  需要new才能调用
    this.eat = function () { 
        console.log(this.name + '在吃东西');
    }
}

// 原型链上的属性  会被实例共享,构造函数上的属性不会
Animal.prototype.age = '3'

// 原型链上的方法  会被实例共享,构造函数上的方法不会
Animal.prototype.sleep = function() {
    console.log(this.name + '在睡觉');
}

// 静态方法  可以直接调用
Animal.play = function () {  
    console.log('play');
}

let cat = new Animal('xiaomao')
cat.eat(); 
cat.sleep()
Animal.play(); 

二、再说说ES5中如何实现继承


1)构造函数继承

function Animal(name) {
    this.name = name
    this.eat = function() {
        console.log(this.name + '吃东西');
    }
} 
Animal.prototype.play = function() {
    console.log(this.name + '在玩');
}

function Dog(name) {
    Animal.call(this, name)
}
let taidi = new Dog('taidi')
taidi.eat() // daidi 吃东西
taidi.play() // 报错 构造函数继承无法继承原型链上的属性和方法

2)原型链继承、

function Animal(name) {
    this.name = name
    this.colors = ['red', 'yellow']
    this.eat = function() {
        console.log(this.name + '吃东西');
    }
} 
Animal.prototype.play = function() {
    console.log(this.name + '在玩');
}

function Dog() {
    
}
Dog.prototype = new Animal()

// 1.第一个问题:实例化子类的时候无法直接给父类传参
// 想要把name显示出来,需要在构造函数 Dog 中重新将name挂载到实例上 
let taidi = new Dog('taidi')
taidi.eat() // undefined 在吃东西
taidi.play() // undefined 在玩

// 2.第二个问题:父类的属性被共享
let dog1 = new Dog()
dog1.colors.push('blue')
console.log(dog1.colors); // ['red', 'yellow', 'blue']

let dog2 = new Dog()
console.log(dog2.colors); // ['red', 'yellow', 'blue']

3)混合继承

function Teacher(name, age) {
    this.name = name
    this.age = age
}
Teacher.prototype.sayName = function() {
    console.log(this.name)
}
Teacher.prototype.sayAge = function() {
    console.log(this.age)
}

function Student() {
    var args = arguments
    // Student对象 继承 Teacher对象的属性
    Teacher.call(this, args[0], args[1])
}
// 继承 Teacher 对象的方法(父类的属性和方法都会继承过来)
Student.prototype = new Teacher()

var students = new Student('xiaolan', 12)
student1.sayName() // xiaolan
student1.sayAge() // 12

缺点:调用了两次父类构造函数,造成性能浪费

4)寄生混合继承

function Parent(name) {
    this.name = name
}
Parent.prototype.getName = function () {
    console.log(this.name)
}
function Child(name) {
    this.sex = 'boy'
    Parent.call(this, name)
}
// 与组合继承的区别
Child.prototype = Parent.prototype

var child1 = new Child('child1')

console.log(child1) // Child{ sex: "boy", name: "child1" }
child1.getName() // "child1"

console.log(child1.__proto__ === Child.prototype) // true
console.log(Child.__proto__ === Function.prototype); // true

三、ES6 中如何定义一个类


class Info {}
const Info = class {}

ES6中类的两种写法

1)ES6中定义一个类

class Point {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
    // 原型对象上的方法 Point.prototype
    getPosition() { 
        return `${this.x}, ${this.y}`
    }
}
let p1 = new Point(2, 3)
console.log(p1);  // 实例对象
console.log(p1.getPosition()); // (2, 3)
console.log(p1.hasOwnProperty('x')); // true
console.log(p1.hasOwnProperty('getPosition')); // false
console.log(p1.__proto__.hasOwnProperty('getPosition'), p1.__proto__ === Point.prototype); // true

2)ES6可以使用set、get关键字,用法和对象中一样

class Info {
    constructor(age) {
        this._age = age
    }
    set age(newAge) {
        console.log('年龄:', newAge);
        this._age = newAge
    }
    get age() {
        return this._age
    }
}
let p1 = new Info(18)
console.log(p1.age);
p1.age = 17

3)ES6静态方法

类自身才能调用,不继承给实例

class Point {
    z = 0
    constructor(x, y) {
        this.x = x
        this.y = y
    }
    getPosition() {
        return `${this.x}, ${this.y}`
    }
    static getClassName() {
        return Point.name
    }
}
let p1 = new Point(2, 3)
console.log(p1);  // 实例对象
console.log(p1.getPosition()); // (2, 3)
console.log(p1.getClassName()); // 报错
console.log(Point.getClassName());  // Point (类名)

4)ES6 静态属性

ES6明确只有静态方法,没有静态属性。可以通过其他办法实现。
提案通过static设置静态属性,但是目前还没通过

class Point {
    constructor() {
        this.x = 0
    }
}
Point.y = 2
const p = new Point()
console.log(p.x); // 0
console.log(p.y); // undefined

5)ES6私有方法

ES6暂不支持私有属性、私有方法,可以通过一些技巧实现。

// 方法一:通过命名区分
class Point {
    func1() {}
    _func2() {}
}

// 方法二:将私有方法溢出模块
const _func2 = () => {}
class Point {
    func1() {
        _func2.call(this)
    }
}
const p = new Point()
p._func2()

// 方法三:利用Symbol值的唯一性
// a.js
const func1 = Symbol('func1')
export default class Point {
    static [func1]() {

    }
}
// b.js
import Point from 'a.js'
const p = new Point()
console.log(p);

6)ES6 私有属性

#号,提案中

class Point {
    #ownProp = 12
}

new.target 当前构造函数类

function Point() {
    console.log(new.target);  // Point{}
}

class Point {
    constructor() {
        console.log(new.target);  // Point{}
    }
}
const p = new Point()

new.target 子类继承父类,new.target表示的是子类构造函数

class Parent {
    constructor() {
        console.log(new.target); // class Child extends Parent{}
        if(new.target === Parent) {
            throw new Error('不能实例化')
        }
    }
}
class Child extends Parent {
    constructor() {
        super()
    }
}
const p = new Child()

四、ES6 继承


extends + super

class Parent {
    constructor(name) {
        this.name = name
    }
    getName() {
        return this.name
    }
    static getNames() {
        return this.name
    }
}
class Child extends Parent {
    constructor(name, age) {
        super(name)
        this.age = age
    }
}
const p = new Child('aaa', 18)
console.log(p);
console.log(p.getName());
console.log(Child.getNames()); // Child

super 作为函数
super 作为对象:在普通方法中,指向父类原型对象;在静态方法(static修饰)中指向父类。

class Parent {
    constructor() {
        this.type = 'parent'
    }
    getName() {
        return this.type
    }
}
Parent.getType = () => {
    return 'is parent'
}
class Child extends Parent {
    constructor() {
        super()
        console.log('constructor', super.getName());
    }
    getParentName() {
        console.log('getParentName', super.getName());
    }
    getParentType() {
        console.log('getParentType', super.getType());
    }
    static getStaticParentType() {
        console.log('getStaticParentType', super.getType());
    }
}
const p = new Child()
p.getParentName()
p.getParentType() // 报错 super指向父类原型对象,父类原型对象上没有getType方法
Child.getStaticParentType() // 'is parent'

super的this指向,指向的是子类

class Parent {
    constructor() {
        this.name = 'parent'
    }
    print() {
        console.log(this.name);
    }
}
class Child extends Parent {
    constructor() {
        super()
        this.name = 'child'
    }
    childPrint() {
        super.print()
    }
}
const p = new Child()
p.childPrint() // child  super的this指向的是子类

prototype proto

① 子类的__proto__指向父类本身。
② 子类的prototype属性的__proto__指向父类的prototype属性。
③ 子类实例的__proto__属性的__proto__指向父类实例的__proto__。

原生构造函数的继承

ES5中是不能继承的,ES6允许继承原生构造函数。
ES5中原生构造函数有:Boolean、Number、String、Array、Date、Function、RegExp、Error、Object

class CustomerArray extends Array {
    constructor(...args) {
        super(...args)
    }
}
const p = new CustomerArray(1, 2, 3)
console.log(p); // [1, 2, 3]

ES5和ES6实现继承在机制上的差异

ES5是先创建子构造函数的实例this,然后再把父构造函数的属性和方法添加到实例this上。
ES6是先从父类取到实例对象this,然后调用super函数之后,再将子类的属性和方法加到this上

五、TS实现继承


1.TS定义一个类

class Person {
    name: string;
    constructor(name: string) {
        this.name = name
    }
    getName(): string {
        return this.name
    }
    setName(name: string): void {
        this.name = name
    }
}

var p = new Person('xiaoming')
console.log(p.getName());

p.setName('xiaohong')
console.log(p.getName());

2.TS实现继承

extends + super

class Person {
    name: string;
    constructor(name: string) {
        this.name = name
    }
    getName(): string {
        return this.name
    }
    setName(name: string): void {
        this.name = name
    }
}

var p = new Person('xiaoming')
console.log(p.getName());

p.setName('xiaohong')
console.log(p.getName());

// 继承 extends super
class Student extends Person {
    constructor(name: string) {
        super(name)
    }
}
var stu = new Student('student')
console.log(stu.getName());

3.类里的修饰符

  • public:公有- 在类里面、子类、类外面都可以访问
  • protected:保护类型 - 在类里面、子类里面可以访问,在类外面没法访问
  • private:私有 - 在类里面可以访问,在子类、类外部没法访问
class Person {
    public name: string;
    constructor(name: string) {
        this.name = name
    }
    run(): string {
        return this.name
    }
}

class Student extends Person {
    constructor(name: string) {
        super(name)
    }
}

let pp = new Person('xiaoming')
let ppt = new Student('student')

console.log(pp.run()); // xiaoming  -类中访问 
console.log(ppt.run());// student   -子类中也可以访问
console.log(pp.name);  // xiaoming  -外部访问,public可以,如果换做private,则编译失败

4.静态方法与实例方法

class Person {
    public name: string;
    static age: number = 20; // 静态属性
    constructor(name: string) {
        this.name = name
    }
    getName() {
        return this.name
    }
    static getAge() { // 静态方法
        return 'age:' + this.age
    }
}
// 调用实例方法
let xiaoming = new Person('xiaoming')
console.log(xiaoming.getName()); // 小明

// 调用静态属性和静态方法
console.log(Person.age); // 20
console.log(Person.getAge()); // age: 20

5. 多态

父类定义一个方法不去实现, 让继承他的子类去实现,每个子类有不同的表现

class Animal {
    name:string;
    constructor(name:string) {
        this.name=name
    }
    eat() {
        
    }
}
class Dog extends Animal {
    constructor(name:string) {
        super(name)
    }
    eat() {
        return this.name + '吃狗粮'
    }
}
class Cat extends Animal {
    constructor(name:string) {
        super(name)
    }
    eat() {
        return this.name + '吃鱼'
    }
}
let taidi = new Dog('taidi')
console.log(taidi.eat()); // taidi吃狗粮

let tom = new Cat('tom')
console.log(tom.eat());   // tom吃鱼

6. 抽象类 抽象方法

typeScript中的抽象类,提供其他类继承的基类,不能直接被实例化。
用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。

abstract class Animal {
    public name: string;
    constructor(name: string) {
        this.name = name
    }
    abstract eat(): any;
}

class Dog extends Animal {
    constructor(name:string) {
        super(name)
    }
    // 抽象类的子类必须实现抽象类里面的抽象方法
    eat() {
        return this.name + '吃狗粮';
    }
}
class Cat extends Animal {
    constructor(name:string) {
        super(name)
    }
    // 抽象类的子类必须实现抽象类里面的抽象方法
    eat() {
        return this.name + '吃鱼';
    }
}
let taidi = new Dog('taidi')
console.log(taidi.eat()); // taidi吃狗粮

let tom = new Cat('tom')
console.log(tom.eat());   // tom吃鱼