TypeScript 面向对象中的属性和方法

这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战

属性和方法

接下来,我们学习面向对象中最基本的概念:属性和方法,在 TS 中是如何定义的?

class Cat {
    // 属性
    name;
    
    // 方法
    sayHi() {
        return `Meow, my name is ${this.name}`;
    }
}

let tom = new Cat();
tom.name = 'Tom';

console.log(tom.sayHi());
// Meow, my name is Tom
复制代码

修改下代码,我们就完成 TS 中对类的属性和方法的类型定义。

class Cat {
    // 属性
    name: string;
    
    // 方法
    sayHi(): string {
        return `Meow, my name is ${this.name}`;
    }
}

let tom = new Cat();
tom.name = 'Tom';

console.log(tom.sayHi());
// Meow, my name is Tom
复制代码

习题:关于类的属性和方法,下面用法正确的是?

// A
class Animal {
  age = 10;
  getAge() {
    return Animal.age;
  }
}
new Animal().getAge();

// B
class Animal {
  age = 10;
  getAge() {
    return this.age;
  }
}
new Animal().getAge();

// C
class Animal {
  age = 10;
  getAge() {
    return this.age;
  }
}
new Animal().age;

// D
class Animal {
  age = 10;
  getAge() {
    return this.age;
  }
}
Animal.age;
复制代码

答案:BC

解析:

类的属性默认为实例属性,类的方法默认为实例方法。访问控制修饰符章节会详细介绍。

new Animal()
复制代码

使用 new 关键字对类 Animal 进行了实例化并返回实例化结果。

new Animal().getAge()
复制代码

链式写法,可以看作是 (new Animal()).getAge() 的省略写法。使用 new Animal() 得到类 Animal 的实例,接着实例执行 getAge 方法。

Animal.age
复制代码

当属性为静态属性(方法)时,可以直接使用类名调用该属性(方法)。

下图示意了静态属性和实例属性的区别,实例属性(方法)存在于实例对象中,每个实例对象都会单独拥有。静态属性(方法)存在于类中,所有实例对象共享静态属性(方法)。

静态方法和实例方法的区别

我们开始分析四个选项:

  • A - age 是类的实例属性,选项中 Animal.age 是静态方法的调用方式。故错误。
  • B - 选项中使用 this.age 是正确的。 this 指向实例对象。
  • C - 选项中 new Animal().age 调用是正确的。因为 age 是类的实例属性。
  • D - 基于 C选项可以看出,此选项是错误的。

资料:值为箭头函数的属性

假设我们要设计一个 Person 类,代码如下

class Person {
    name: string
    age: number

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

    logName = function(name: string) {
        console.log('my name is ' + name)
    }

    getName = function() {
        this.logName(this.name)
    }
}

const p1: Person = new Person('Richard', 12);
const { getName } = p1; // 通过对象的解构赋值,提取出 getName 

p1.getName(); // my name is Richard
getName(); // TypeError: this.logName is not a function
复制代码

当我们将 getName 函数单独提取出来使用的时候,this 会指向 getName 运行时所在的作用域,这时候会因为找不到 logName 方法而报错。

一个可行的解决方案是用 bind 方法来为 getName 绑定作用域

class Person {
    name: string
    age: number

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

    logName = function(name: string) {
        console.log('my name is ' + name)
    } // 利用 bind 方法为 logName 绑定作用域

    getName = function() {
        this.logName(this.name)
    }.bind(this)
}

const p1: Person = new Person('Richard', 12);
const { getName } = p1; // 通过对象的解构赋值,提取出 getName 

p1.getName(); // my name is Richard
getName(); // my name is Richard
复制代码

与 bind 方法相比,更简明的方法是利用 ES6 中提供的箭头函数。

class Person {
    name: string
    age: number

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

    logName=function (name: string)  {
        console.log('my name is ' + name)
    } // 利用箭头函数为 logName 绑定作用域

    getName = () =>{
        this.logName(this.name)
    }
}

const p1: Person = new Person('Richard', 12);
const { getName } = p1 // 通过对象的解构赋值,提取出 getName 

p1.getName(); // my name is Richard
getName(); // my name is Richard
复制代码

资料:存取器

在 TypeScript 中,我们可以通过存取器来改变一个类中属性的读取和赋值行为

class User {
    constructor(name: string) {
        this.name = name;
    }
    get name() {
        return 'Evan';
    }
    set name(value) {
        console.log('setter: ' + value);
    }
}

let a = new User('Peter'); // setter: Peter
a.name = 'Richard'; // setter: Richard
console.log(a.name); // Evan
复制代码

类的构造函数

在面向对象中,有一个特殊的方法,就是构造函数。构造函数就是在创建一个函数的时候,会被调用的函数。

接着上一个例子,我们希望创建这个 tom 的时候,就是初始化好它的姓名。

那如何去实现呢?

class Cat {
    // 属性
    // name: string;
    // 这里可以定义好传入参数的类型
    constructor(name: string) {
        this.name = name;
    }
    
    // 方法
    sayHi(): string {
        return `Meow, my name is ${this.name}`;
    }
}

let tom = new Cat('Tom');
// tom.name = 'Tom';

console.log(tom.sayHi());
// Meow, my name is Tom
复制代码

资料:参数属性

通常我们定义一个类的时候会采用如下写法

class User {
    name: string;
    age: number;
    constructor() {
        //...
    }
}
复制代码

实际上我们也可以在构造函数的参数定义时即声明和赋值属性

class User1 {
    constructor(public name: string = 'Richard', private age: number = 18) {
        //...
    }
}
复制代码

习题:关于类的构造函数,下面描述正确的是?

A. 主要用于初始化类的成员变量属性。

B. 类的对象创建时自动调用执行。

C. 没有返回值。

答案:A B C

解析:

类的构造函数主要用于,创建类的时候初始化类的成员变量属性。构造函数会在类的对象创建时候自动调用。需要注意的是父类的构造函数需要手动调用,因为构造函数主要用于初始化操作,故没有返回值。

猜你喜欢

转载自juejin.im/post/7031024288270385189