这是我参与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
解析:
类的构造函数主要用于,创建类的时候初始化类的成员变量属性。构造函数会在类的对象创建时候自动调用。需要注意的是父类的构造函数需要手动调用,因为构造函数主要用于初始化操作,故没有返回值。