在前慢的课程中我们学习了react组件的相关知识,并且学习了函数式组件的创建与使用,按照顺序,接下来我们应该来学习类式组件,但是为了更扎实地学习并理解类式组件的相关知识,我们先来简单复习一下js类的相关知识。

回顾

当然,老规矩,在复习类相关知识之前,我们先来对上节课内容来做一个简单的回顾。

  • 模块是向外提供特定功能的js程序
  • 组件是实现局部功能效果的代码和资源的集合
  • 如果一个应用是用模块或组件来编写的,那么这个应用就是模块化应用或者组件化应用
  • 组件分为函数式组件和类式组件
  • 定义组件必须首字母大写
  • 如果定义函数式组件,必须要有返回值
  • 渲染组件必须写成标签形式且必须为闭合标签

以上便是对上节课内容的一个回顾。

那么接下来我们正式开始复习一些类相关的知识

定义类

  • 定义类的关键字是class
  • 类名首字母要大写

通过类创建对象

当我们创建好了一个类,但是我们怎么用呢?

我们都知道,类的用途就是用来实例化对象的,所以我们就是通过类来创建实例化对象。

  • 通过类创建对象
  • 创建对象使用new关键字
class Person{};
let p1 = new Person();
console.log(p1);

我们来看一下啊输出的结果是什么:

image-20211213140524705

可能有些人要问了,为什么吧我们的类打印出来了啊?不应是一个对象吗?这么问的人看来前面类的知识都忘得差不多了。

其实这打印出来的就是一个对象,后面的{}就是我们实例化的Person对象,儿前面的Person其实是太提醒我们这个对象是由哪个类实例化出来的,而不是说直接把我们定义的Person类打印出来了。

给类添加属性

之前我们定义了一个简单的类,里面是空的,我们实例化了这个类,打印在控制台里面也没啥毛病。但是我们在看下面这段代码:

class Person{};
let p1 = new Person();
let p2 = new Person();
console.log(p1);
console.log(p2);

我们再看控制台的结果

image-20211213142835752

这两个可以说是一模一样了,我们都不知道他们谁是谁。所以说如果我们相对每个对象有个初始化的操作。比如这个是Person类实例化出来的,最起码得有nameage,所以我就需要在实例化的时候来传入相关的参数。比如let p1 = new Person("jingxun", 27)但是这样就对吗?

我穿了参数的话我得接啊,不是说传了就可以的,但是在哪里接呢?这下就要用到我们曾经学习过的一个叫做构造器方法。所以代码应该这么写:

class Person { 
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
};
let p1 = new Person("jingxun", 27);
let p2 = new Person("dschow", 24);
console.log(p1);
console.log(p2);

这一次我们再看一下啊控制台结果:

image-20211213145423938

这一次我们就可以看见我们实例化的对象的详细信息了。

至于又有人问了,那么我这个构造器方法可不可以不写呢?理论上是可以不写的,但是就目前我们这个Person类如果不写的话,那么在实例化对象的时候,都接收不到任何的信息。当然关于这个构造器方法我们到了类继承的时候再详细说一下。

给类添加方法

我们这个Person类除了有名字,年龄这种属性还有行为啊,比如人可以吃饭,睡觉啊,这些各种各样的行为其实就是我们所说的方法。比如说我来创建一个方法:

class Person { 
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    introducing() { 
        console.log(`我叫{this.name},年龄{this.age}`);
    }
};
let p1 = new Person("jingxun", 27);
let p2 = new Person("dschow", 24);
console.log(p1);
console.log(p2);
p1.introducing();
p2.introducing();

我们在定义方法的时候一定要切记一点,方法和函数是两码事,或许有些学Python 或者其他语言的人会把这两个概念给弄混淆,然后在方法名前面加了一个function关键字。这样是不对的。

好了我们现在再来看一下控制台的结果:

image-20211213151058247

我们控制台中也正常显示了我们的相关信息,但是有人要问了,为什么我们打印出来的对象里面我们根本都看不见这个对象有introducing方法啊。

那我们的introducing方法到底放在哪里呢?其实这个方法是放在Person类的原型对象上了,而存储在原型对象上的方法是供实例对象来使用的。所以我们的对象可以直接调用到这个方法。

通过Person实例对象调用introducing方法,那么introducing方法中的this就是该Person实例对象。但是不能直接说introducing方法中的this就是Person实例对象。比如

class Person { 
    ...
};
let p1 = new Person("jingxun", 27);
let p2 = new Person("dschow", 24);
console.log(p1);
console.log(p2);
p1.introducing();
p2.introducing();
p1.introducing.call({a: 1, b: 2});

image-20211213153033426

这下我们的结果就是变成了undefined,因为call是可以改变this指向的,所以不能笼统地说一个方法里的this就一定指向这个类的实例对象。

类继承

我们现在已经定义了一个类,这个类代表了人,那么我们想要定义一个Student类,学生肯定也是人这个类里的一个子集啊。那么我们就可以用到类继承。

那么类继承有什么用呢?我们的Person类有了nameage属性,Student不用说也是有这两个属性的,那么我让Student类继承Person类,那么Student类则可以直接使用父类中定义好的属性和方法。

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    introducing() {
        console.log(`我叫{this.name},年龄{this.age}`);
    }
};

class Student extends Person {};

上述代码就完成了一个Student类的创建,如果通过Student类来实例化对象,那么实例化出来的对象将直接用用nameage属性,并且可以调用introducing方法。

也许有人要问了,诶,你还没有些构造器方法呢,前文我们就说过了,必须要写构造器方法吗?任何类其实都可以不写构造器方法。

我们来创建一个Student的实例对象来看一下:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    introducing() {
        console.log(`我叫{this.name},年龄{this.age}`);
    }
};

class Student extends Person {};
let s1 = new Student("jingxun", 18);
console.log(s1);
s1.introducing();

image-20211213154526078

我们可以看到控制台中可以正常显示我们的详细信息。

给子类添加构造器方法

上面的情形我们虽然正常显示了,但是我们并没有在Student类中来写一个构造器方法来接收参数啊。因为在子类没有构造器方法的时候,子类会从父类直接把父类的构造器方法拿来用。

但是我们这个Student类在什么时候要自己写构造器方法呢?比如说,学生有一个特殊的属性,比如年级,这个属性在Person类里面也没有,那么,我们就需要在Student类里面来写我们的构造器方法了:

// 错误写法
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    introducing() {
        console.log(`我叫{this.name},年龄{this.age}`);
    }
};

class Student extends Person {
    constructor(name, age, grade) {
        this.name = name;
        this.age = age;
        this.grade = grade;
    }
};
let s1 = new Student("jingxun", 18, "高一");
console.log(s1);
s1.introducing();

我们来看一下结果:

image-20211213155404314

报错了,跟我们说必须要调用super,这是什么意思呢?

我们回头看:我们定义了Student,继承了Person,还在Student中写了构造器,当满足了这三个条件之后,Student中的构造器就必须调用super,不然就会报错。但是为什么呢?super是干吗的呢?

其实super就是用来帮子类来调用父类中的构造器,也就是说,在子类的构造器方法中将继承自父类的属性传给super,由super来调用父类的构造器函数,而不属于父类的属性则由子类的构造器来接收。

所以代码应该改为:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    introducing() {
        console.log(`我叫{this.name},年龄{this.age}`);
    }
};

class Student extends Person {
    constructor(name, age, grade) {
        super(name, age);
        this.grade = grade;
    }
};
let s1 = new Student("jingxun", 18, "高一");
console.log(s1);
s1.introducing();

现在再来看一下结果,就能够正常显示我们的信息了

image-20211213160133525

但是还有一点,就是子类的构造器方法中的第一步就是调用super方法。下面这种是不允许的:

// 错误写法,不允许
...
class Student extends Person {
    constructor(name, age, grade) {
        this.grade = grade;
        super(name, age); // super 方法必须在最前面
    }
};

类方法重载

前文我们也提到过,Student类继承了Person类,那么Student的实例对象也可以调用introducing方法了。之前我们说introducing方法放在了Person类的原型对象上供实例使用,但是Student的原型对象上有东西吗?

目前来说是没有的,因为我们并没有在Student类中定义我们自己的方法。但是为什么Student的实例对象可以使用introducing方法呢?

因为Student的实例对象在调用introducing方法的时候会首先从原型对象上找,找不到会通过原型链直接去父类的原型对象中找。所以从头至尾就只有一个introducing方法,所以类的方法要存放在原型对象上。从而可以让自身和子类都快要使用同一个方法。

但是事情并没有结束,如果我希望让Student对象在调用introducing方法的时候说出名字和年龄之后,也说出自己的年级。这个需求直接使用父类的introducing方法就无法满足了,那么就需要我们自己写。

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    introducing() {
        console.log(`我叫{this.name},年龄{this.age}`);
    }
};

class Student extends Person {
    constructor(name, age, grade) {
        super(name, age);
        this.grade = grade;
    }
    introducing() {
        console.log(`我叫{this.name},年龄{this.age},读${this.grade}`);
    }
};

let s1 = new Student("jingxun", 18, "高一");
console.log(s1);
s1.introducing();

现在我们来看一下结果:

image-20211213161938841

我们成功在控制台中展示了我们想要的信息,而且Student的原型对象上也有了introducing方法,但是我们怎么确定这里Student实例对象调用的到底是父类的introducing方法还是自己写得introducing方法呢?

我们前面就说了,当子类的实例对象调用方法时会先从子类的原型对象上面去找这个方法,找到了就会直接调用,找不到才会通过原型链去父类的原型对象上找。所以,我们看到了Student的原型对象上是有introducing方法的,所以Student实例对象所调用的是自身原型对象上的方法而不是父类的方法

以上的这一个步骤流程就叫做方法重载。

总结

  • 类中的构造器并不是必须要写的
  • 要对实例进行一些初始化的操作才需要写构造器
  • 如果A继承了B类,那么A类的构造器方法必须在第一步就调用super方法
  • 类中定义的方法是放在了类的原型对象上供实例对象来使用。
  • 类的实例对象在调用方法时会优先从自身原型对象上查找方法,如果没有找到则通过原型链去父类的原型对象中去查找

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/class_js/