认识类与对象

类是对象具体事务的一个抽象,对象是类的具体表现。

举个例子,比如说,有人给你介绍对象,会问你的要求。那么,你的要求是:身高165以上,体型偏瘦,长头发,大眼睛,从事正当稳定的工作,会做饭等等。这些要求就是对你心中理想伴侣的一个抽象,就是类。介绍人按照你的要求给你找的这些女生,就是类的实例,就是对象。

ES5中的类和继承

// es5中的类是通过构造函数来实现的

function Animal(name, age) {
    this.name = name;
    this.age = age;
}
// 静态方法
Animal.sayName = function() {
    console.log('我是静态方法');
}

let cat = new Animal('小花', 1);
1
2
3
4
5
6
7
8
9
10
// es5中的类是通过构造函数来实现的
function Animal(name, age) {
    this.name = name;
    this.age = age;
    this.run = function() {
        console.log(`${this.name}在抓老鼠`);
    }
}

Animal.prototype.eat = function() {
    console.log(`${this.name}在吃饭啊`);
}

// 静态方法
Animal.sayName = function() {
    console.log('我是静态方法');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// es5继承:对象冒充继承模式
function Cat(name, age) {
    // 对象冒充实现继承
    Animal.call(this);
}
let cat = new Cat('小花', 1);
// 对象冒充可以继承构造函数里面的属性和方法,但是无法继承原型链上的属性和方法
cat.run(); // 小花在抓老鼠
cat.eat(); // 报错,eat是原型上的方法
1
2
3
4
5
6
7
8
9
// 原型链+对象冒充的组合继承模式
function Cat(name, age) {
    Animal.call(this, name, age); // 对象冒充继承,实例化子类可以给父类传参
}
// Cat.prototype = Object.create(Animal.prototype);
// 原型链实现继承
Cat.prototype = new Animal();
// 纠正构造函数指向
Cat.prototype.constructor = Cat;
let cat = new Cat('小花', 1);
console.log(cat.constructor);
cat.run(); // 小花在抓老鼠
cat.eat(); // 小花在吃饭啊
1
2
3
4
5
6
7
8
9
10
11
12
13
  • 对象冒充继承:对象冒充可以继承构造函数里面的属性和方法,但是无法继承原型链上的属性和方法。

  • 原型链继承:原型链继承可以继承构造函数里面的属性和方法和原型链上的属性和方法,但是子类在实例化的时候无法给父类传参。原型链上的属性和方法会被所有实例所共享,而构造函数中定义的属性和方法是每个实例自己的。

  • 方法建议定义在原型上,如果放到构造函数中的话,每新建一个实例都会创建新的方法,增加内存开销。

  • 属性建议定义在构造函数中,防止引用类型的属性被实例共享,一个实例的属性发生改变,其它实例的该属性也会受到影响。

  • 因此就有了构造函数+原型链的组合继承模式

ES5原型继承

function Parent(name, age) {
    this.name = 'lisi';
    this.age = 12;
}

Parent.prototype.getName = function() {
    console.log(this.name);
}

function Child() {}

// 只继承父类原型上的属性和方法
Child.prototype = Object.create(Parent.prototype);
const c = new Child();
console.log(c);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent(name, age) {
    this.name = 'lisi';
    this.age = 12;
}

Parent.prototype.getName = function() {
    console.log(this.name);
}

function Child() {}
// 同时继承父类原型和实例上的属性的方法
Child.prototype = new Parent();
const c = new Child();
console.log(c);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

ts中的类

构造函数:实例化类的时候调用的方法

类的定义

class Person {
    static name2:any = 'wangwu'; // 静态属性
    name:string; // 类的属性 前面省略了public关键字
    age:number;
    constructor(name:string, age:number) { // 构造函数,实例化类的时候调用该方法
        // 在上面必须用name:string;和age:number;声明,否则这里会报错
        this.name = name;
        this.age = age;
    }
    run():void {
        console.log(`${this.name}-${this.age}`);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(Person.name2); // wangwu
let p = new Person('lisi', 12);
p.run(); // lisi-12
1
2
3
class Person {
    name:string; // 属性 前面省略了public关键字
    constructor(name:string) { // 构造函数 实例化类的时候调用的方法
        this.name = name;
    }
    getName():string {
        return this.name;
    }
    setName(newName:string):void {
        this.name = newName;
    }
}

let p = new Person('lisi');
console.log(p.getName()); // lisi
p.setName('wangwu');
console.log(p.getName()); // wangwu
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

继承

// ts继承:通过extends和super两个关键字
class Person {
    name:string; //属性 前面省略了public关键字
    constructor(name:string) { // 构造函数:实例化类的时候调用的方法
        this.name = name;
    }
    run():string {
        return `${this.name}在运动`;
    }
}

let p = new Person('lisi');
console.log(p.run()); // lisi在运动

class Man extends Person {
    constructor(name:string) {
        super(name); // 初始化父类的构造函数
    }
}
let man = new Man('男人');
console.log(man.run()); // 男人在运动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ts继承 通过extends和super两个关键字
class Person {
    name:string; //属性 前面省略了public关键字
    constructor(name:string) { // 构造函数 实例化类的时候调用的方法
        this.name = name;
    }
    run():string {
        return `${this.name}在运动`;
    }
}

let p = new Person('lisi');
console.log(p.run()); // lisi在运动

class Man extends Person {
    constructor(name:string) {
        super(name); // 初始化父类的构造函数
    }
    run():string { // 子类自己的run方法
        return `${this.name}在运动-子类`;
    }
    eat():string { // 子类扩展方法
        return `${this.name}在吃饭`;
    }
}
let man = new Man('男人');
// 先去子类自己中找,找不到再去父类中找
console.log(man.run()); // 男人在运动
console.log(man.eat()); // 男人在吃饭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

类的修饰符

  • public:公有修饰符,可以在类内、类外或者子类里面使用public修饰的属性或者行为,默认修饰符。
  • protected:受保护的修饰符,可以类内和子类中使用protected修饰的属性和行为,在类外部无法访问
  • private:私有修饰符,只可以在类内使用private修饰的属性和行为。子类和类外部都无法访问

需要注意:属性如果不加修饰符,默认就是公有的(public)。

类外部访问公有属性:

class Persons {
    public name:string; // 公有属性
    constructor(name:string) {
        this.name = name;
    }
    run():string {
        // 类内部访问公有属性
        return `${this.name}在吃饭`;
    }
}
const person = new Persons('lisi');
// 类外部访问公有属性
console.log(person.name); // lisi
console.log(person.run()); // lisi在吃饭
1
2
3
4
5
6
7
8
9
10
11
12
13
14

类外部访问私有属性:

class Person {
    private name:string; // 私有有属性
    constructor(name:string) {
        this.name = name;
    }
    run():string {
        return `${this.name}在吃饭`;
    }
}
const person = new Persons('lisi');
console.log(person.name); // 报错(私有属性只能在类内访问)
console.log(person.run()); // lisi在吃饭
1
2
3
4
5
6
7
8
9
10
11
12

bc4bbf9aa0bca7f9cb8ee53222cca648.png

在子类中访问公有属性:

class Persons {
    public name:string; // 公有属性
    constructor(name:string) {
        this.name = name;
    }
    run():string {
        return `${this.name}在运动`;
    }
}
const person = new Persons('lisi');
console.log(person.name); // lisi
console.log(person.run()); // lisi在吃饭
class Man extends Persons {
    constructor(name:string) {
        super(name);
    }
    eat():string {
        // 在子类中访问公有属性
        return `${this.name}在吃饭`;
    }
}

const man = new Man('男人');
console.log(man.run()); // 男人在运动
console.log(man.eat()); // 男人在吃饭
console.log(man.name); // 男人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

在子类中访问私有属性:

class Persons {
    private name:string; // 私有有属性
    constructor(name:string) {
        this.name = name;
    }
    run():string {
        return `${this.name}在运动`;
    }
}
const person = new Persons('lisi');
console.log(person.name); // 报错
console.log(person.run()); // lisi在吃饭
class Man extends Persons {
    constructor(name:string) {
        super(name);
    }
    eat():string {
        return `${this.name}在吃饭`; // 报错
    }
}

const man = new Man('男人');
// 先在子类自己中找对应的属性和方法,找不到再去父类中找
console.log(man.run()); // 男人在运动
console.log(man.eat()); // 男人在吃饭
console.log(man.name); // 报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Person1 {
    public name:string;
    protected age:number;
    private sex:string;
    constructor(name:string, age:number, sex:string) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    public sayName():string {
        return `我叫${this.name}`;
    }
    public sayAge():string {
        // 受保护属性age能在类内访问
        return `我今年${this.age}`;
    }
}

const person1 = new Person1('lisi', 12, '男');
console.log(person1.name);
console.log(person1.age); // 报错,受保护属性只能在类内和子类中访问
console.log(person1.sex); // 报错,私有属性只能在类内访问
console.log(person1.sayName());
console.log(person1.sayAge());

class Man1 extends Person1 {
    constructor(name:string, age:number, sex:string) {
        super(name, age, sex);
    }
    public childSayAge():string {
        // 受保护属性age能在子类中访问
        return `我今年${this.age}岁了`;
    }
}
const man1 = new Man1('男人', 13, '男');
console.log(man1.name);
console.log(man1.age); // 报错,受保护属性只能在类内和子类中访问
console.log(man1.sex); // 报错,私有属性只能在类内访问
console.log(man1.sayName());
console.log(man1.sayAge());
console.log(man1.childSayAge());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

只读属性

使用readonly修饰符将属性设置为只读,只读属性必须在属性声明时或者构造函数里被初始化。

我们声明一个Man的抽象类,里边只有一个属性name,并且是只读。

class Man {
    public readonly name:string = 'lisi';
}

const man2:Man2 = new Man2();
console.log(man2.name); // lisi
man2.name = 'wangwu'; // 报错,只读属性不能被修改
1
2
3
4
5
6
7

静态属性和方法

// ES5静态属性和方法
function Person() {
    // 实例方法
    this.run = function() {}
}
// 静态属性
Person.name = 'lisi';
// 静态方法
Person.run = function() {};
1
2
3
4
5
6
7
8
9

静态属性和方法可以直接通过类名来调用,实例方法需要通过实例来调用。

需要注意:在静态方法中无法直接调用类里面的属性,只能访问静态属性。

class Person {
    public name:string;
    static age:number = 12;
    constructor(name:string) {
        this.name = name;
    }
    // 实例方法
    run():string {
        return `${this.name}在运动`;
    }
    // 静态方法
    static eat():string {
        // 在静态方法中无法直接调用类里面的属性,只能访问静态属性
        console.log(this.name); // 报错,在静态方法中无法直接调用类里面的属性
        return `${Person.age}`;
    }
}

let p = new Person('lisi');
console.log(p.run());
console.log(Person.eat()); // 12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

静态方法应用:

jQuery中的`$.get()`就是静态方法。
1

抽象类、继承和多态

继承和重写

继承:允许我们创建一个类(子类),从已有的类(父类)上继承所有的属性和方法,子类可以新建父类中没有的属性和方法。

class Parent {
    name:string;
    age:number;
    skill:string;
    constructor(name:string, age:number, skill:string) {
        this.name = name;
        this.age = age;
        this.skill = skill;
    }
    sayName():string {
        return `我叫${this.name}`;
    }
}

// 实现继承
class Child extends Parent {
    // 子类增加了属性xingxiang和方法work
    public xingxiang:string = '很帅';
    public work() {
        console.log('我热爱工作');
    }
}

const child = new Child('lisi', 12, '编程');
console.log(child.sayName()); // 我叫lisi
child.work(); // 我热爱工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

类方法的重写

重写就是在子类中重写父类的方法。

class Parent {
    name:string;
    age:number;
    skill:string;
    constructor(name:string, age:number, skill:string) {
        this.name = name;
        this.age = age;
        this.skill = skill;
    }
    sayName():string {
        return `我叫${this.name}`;
    }
}

class Child extends Parent {
    public xingxiang:string = '很帅';
    public work() {
        console.log('我热爱工作');
    }
    public sayName():string {
    // 通过super关键字调用了父类的方法,实现了技能的增加
        console.log(super.sayName()); // 我叫lisi
        return '我是子类的方法';
    }
}

const child = new Child('lisi', 12, '编程');
console.log(child.sayName()); // 我是子类的方法
child.work(); // 我热爱工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

先是继承了父类的方法,然后通过super关键字调用了父类的方法,实现了技能的增加。

多态

多态:父类定义一个方法不去实现,让继承它的子类去实现,每一个子类有不同的表现。多态属于继承,也是继承的一种表现

// 多态
class Animal {
    public name:string;
    constructor(name:string) {
        this.name = name;
    }
    // 具体吃什么由继承它的子类实现,每一个子类的表现不一样
    eat() {
        console.log('吃的方法');
    }
}

class Dog extends Animal {
    constructor(name: string) {
        super(name);
    }
    eat():string {
        return `${this.name}吃骨头`;
    }
}

class Cat extends Animal {
    constructor(name: string) {
        super(name);
    }
    eat():string {
        return `${this.name}吃老鼠`;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

抽象类(不能直接被实例化)

Typescript中的抽象类是提供给其它类继承的基类,不能直接被实例化

abstract关键字用来定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。 需要注意:抽象方法只能放在抽象类中。

// 定义一个抽象类
// 抽象类和抽象方法用来定义标准,比如说:Animal这个类要求它的子类必须包含eat方法
abstract class Animal {
    public name:string;
    constructor(name:string) {
        this.name = name;
    }
    // 抽象方法(不包括具体实现),必须在子类(派生类)中具体实现
    // 抽象方法中不包含具体实现并且必须在派生类中实现
    abstract eat():any;
    run() {
        // 非抽象方法,在子类中可以不实现
    }
}

// let a = new Animal(); // 错误写法,抽象类无法实例化

class Dog extends Animal {
    constructor(name:string) {
        super(name);
    }
    // 抽象类的子类必须实现抽象类中的抽象方法
    eat():string {
        return `${this.name}吃骨头`;
    }
}

let dog = new Dog('xiaogou');
console.log(dog.eat());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

de2742d095e692d088a4a9f4b287e57d.png