面向对象之 class(上)
思考题:写入属性时会覆盖共有属性吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function Person(name, age) { this.name = name this.age = age } Person.prototype = { constructor: Person, sayHi(target) { console.log(`你好,${target.name},我是 ${this.name}`) } } const p0 = new Person("ClariS", 17) const p1 = new Person("vivy", 18) const p2 = new Person("k423", 19) p1.sayHi = function (target) { console.log(`我是 ${target.name} `) } p2.sayHi(p0)
|
上面代码的打印结果是 1 还是 2 ?
答案是 1,当我们向实例对象上写入属性时,并不会覆盖原型上的共有属性
我们把实例对象的结构打印出来看看
可以看到,读取与写入的规则是不一样的
- 读取
p0
、p1
、p2
的 sayHi
属性时,会先看自身上的独有属性中是否存在 sayHi
,再去看原型上的共有属性;
- 而当向
p0
、p1
、p2
上写入属性时,会直接把属性写入到实例对象上,并不会覆盖原型上的 sayHi
方法。
interface 和 class 的区别
interface
和 class
都是用来描述对象的
1 2 3 4
| interface PointInterface {}
class PointClass {} const p = new PointClass();
|
当 tsconfig.json
中设置 "strictPropertyInitialization": false
时
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface PointInterface { x: number; y: number; }
class PointClass { x: number; y: number; } const p = new PointClass(); p.x = 1; p.y = 1;
|
当 tsconfig.json
中设置 "strictPropertyInitialization": true
或 "strict": true
时
1 2 3 4 5 6 7 8 9 10
| interface PointInterface { x: number; y: number; }
class PointClass { x: number; y: number; }
|
区别:interface
只有成员的类型没有实现,class
须同时有成员的类型和实现
class 的 4 种初始化方法
① 声明类型并给初始值
1 2 3 4
| class PointClass { x: number = 0; y: number = 0; }
|
② 只给初始值,类型让 TS 自动推断
1 2 3 4
| class PointClass { x = 0; y = 0; }
|
③ 声明类型,并在构造函数中给初始值
1 2 3 4 5 6 7 8
| class PointClass { x: number; y: number; constructor() { this.x = 0; this.y = 0; } }
|
④ 断言,告诉 TS 别检查,我自己一定能保证初始值存在
1 2 3 4 5 6 7 8 9 10 11 12
| class PointClass { x!: number; y!: number; constructor() { this.init(); } init() { this.x = 0; this.y = 0; } }
|
class 的构造函数
1 2 3 4 5 6 7 8 9 10 11 12
| class Point { x: number; y: number; constructor(x = 0, y = 0) { this.x = x; this.y = y; } } const p = new Point() console.log(p.x, p.y)
|
public 缩写
可以使用以下的缩写语法来代替上述写法,和上述的写法是完全等价的
1 2 3 4 5 6
| class Point { constructor(public x = 0, public y = 0) { } } const p = new Point() console.log(p.x, p.y)
|
不写初始值也不报错
1 2 3 4 5 6
| class Point { constructor(public x?: number, public y?: number) { } } const p = new Point() console.log(p.x, p.y)
|
结合函数重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Point { x!: number; y!: number;
constructor(x: number, y: number); constructor(s: string);
constructor(xs: number | string, y?: number) { if (typeof xs === 'number' && typeof y === 'number') { this.x = xs this.y = y } else if (typeof xs === 'string') { const parts = xs.split(',') this.x = parseFloat(parts[0]) this.y = parseFloat(parts[1]) } } }
const p = new Point('1,2') console.log(p.x, p.y)
|
结合索引签名
1 2 3 4 5 6 7 8 9 10 11
| class Hash { [s: string]: unknown
set(key: string, value: unknown) { this[key] = value } get(key: string) { return this[key] } }
|
class 实现接口
实现一个接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| interface Person { name: string sayHi: (target: Person) => void }
class User implements Person { constructor(public name: string) { } sayHi(target: Person) { console.log(`Hi ${target.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
| interface Person { name: string sayHi: (target: Person) => void }
interface Taggable { tags: string[] addTag: (tag: string) => void removeTag: (tag: string) => void }
class User implements Person, Taggable { tags: string[] = [] constructor(public name: string) { } sayHi(target: Person) { console.log(`Hi ${target.name}`) } addTag(tag: string) { this.tags.push(tag) } removeTag(tag: string) { const index = this.tags.indexOf(tag) this.tags.splice(index, 1) } }
|
即使接口中存在可选属性,也必须得在 class
中实现它,不然会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| interface Person { name: string age?: number sayHi: (target: Person) => void }
class User implements Person { constructor(public name: string) { } sayHi(target: Person) { console.log(`Hi ${target.name}`) } }
const u = new User('ClariS') u.age
|
class 继承 class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Person { constructor(public name: string) { } sayHi() { console.log(`你好,我是${this.name}`); } }
class User extends Person { constructor(public id: number, name: string) { super(name) } login() { } }
const u = new User(1, 'ClariS');
u.sayHi() u.login()
|
注意:
- 只能继承一个
class
,无法同时继承多个 class
- 在调用
super()
之前不能使用 this
重写函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Person { constructor(public name: string) {} sayHi() { console.log(`你好,我是${this.name}`); } } class User extends Person { constructor(public id: number, name: string) { super(name); } login() {} sayHi(target?: User) { if (target === undefined) { super.sayHi(); } else { console.log(`你好,${target.name},我是 ${this.name}`); } } }
|
注意函数参数类型之间的兼容关系,比如此处 Person
的 syaHi
的参数为空,那么重写的 sayHi
函数的参数 target
只能是可选的
重写属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Person { friend?: Person; constructor(public name: string, friend?: Person) { this.friend = friend; } } class User extends Person { declare friend: User; constructor(public id: number, name: string, friend?: User) { super(name, friend); } } const u1 = new User(1, 'ClariS'); const u2 = new User(1, 'vivy', u1); u2.friend;
|
注意:declare
后面声明 friend
的类型必须是兼容父类中 friend
的类型的,比如此处 User
是兼容 Person
的