一. 复习题
- 执行
p2.sayHi(p0)时会打印什么
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('tom', 17);
const p1 = new Person('frank', 18);
const p2 = new Person('jack', 19);
p1.sayHi = function (target) {
console.log(`${target.name}你是个笨蛋`);
}; // 这里需要注意p1.SayHi会写到p1上,不会覆盖原型上的sayHi
p2.sayHi(p0); // 答案是1
// 1.你好,tom,我是jack
// 2.tom你是个笨蛋
- 通过console.dir来查看结构
二. 面向对象编程之class
-
面向对象目前有两个流派
- 基于原型,上一篇文章详细讲解的
- 基于类,这种目前是占上风
-
js中既然已经有了原型,为什么还要学习类
- 其它语言有类的概念,当其它语言使用者开始写js的时候,自然而然会需要class
- class本身是js保留字,迟早需要实现,于是在es6的时候实现
- 大部分前端并没有理解原型
三. class的语法
- class和interface的对比
- 都是对对象的描述
interface PointInterface {
x: number;
y: number;
}
// "strictPropertyInitialization": false, 让初始化的值变松散
class PointClass {
x: number;
y: number;
}
const p = new PointClass();
p.x = 1;
p.y = 1;
const p2: PointInterface = {
x: 1,
y: 1,
};
- 四种方式解决没有初始化表达式的报错
- 第一种赋值,那么这里的x的值是什么时候创建的?创建对象的时候,这里的x是p的属性
class PointClass {
x: number = 0;
y: number = 0;
}
const p = new PointClass()
- 第二种方式,使用类型推断
class PointClass {
x = 0;
y = 0;
}
- 第三种,最啰嗦的方式,明确的写出声明和初始化
class PointClass {
x: number;
y: number;
constructor() {
this.x = 0;
this.y = 0;
}
}
- 前三种几乎等价,第四种,使用
!告诉ts不要检查
class PointClass {
x!: number;
y!: number;
}
- 总结interface和class的区别,
- interface只有成员的类型,没有实现
- class必须同时有类型又有实现
四. class的构造函数
- class基本写法
class PointClass {
x: number;
y: number;
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
const p = new PointClass();
console.log(p.x, p.y); // 0, 0
- 简写语法,参数前加public进行初始化
class PointClass {
constructor(public x = 0, public y = 0) {}
}
const p = new PointClass();
console.log(p.x, p.y); // 0, 0
- 构造函数和函数重载结合,接收两种初始化的例子
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);
- class结合索引签名
class Hash {
[s: string]: unknown;
set(key: string, value: unknown) {
this[key] = value;
}
get(key: string) {
return this[key];
}
}
五. class实现接口
- 使用
implements关键字,来实现接口
interface Person {
name: string;
sayHi: (target: Person) => void;
}
class User implements Person {
constructor(public name: string) {}
sayHi(target: Person) {
console.log(`Hi ${target.name}`);
}
}
- 实现两个接口
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 {
constructor(public name: string) {}
tags: string[] = [];
addTag(tag: string) {
this.tags.push(tag);
}
removeTag(tag: string) {
const index = this.tags.indexOf(tag);
this.tags.splice(index, 1);
}
sayHi(target: Person) {
console.log(`Hi ${target.name}`);
}
}
- 思考题: u.age是多少?
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('frank');
// 问: u.age是什么? 3.会报错,implements不会实现任何东西,u没有age
// 1. 0
// 2. undefined
// 3. Typescript 报错
六. class继承class
- 基础例子
class Person {
constructor(public name: string) {}
sayHi() {
console.log(`你好, 我是${this.name}`);
}
}
class User extends Person {
constructor(public id: number, name: string) {
super(name); // super调用父类的构造函数
}
login() {}
}
const u = new User(1, 'frank');
u.sayHi();
u.login();
- 重写的例子,重写和重载的区别:重写(override)是一个方法覆盖另一个方法,重载(overload)是三个constructors一排写下来,它们的参数不同
class Person {
constructor(public name: string) {}
sayHi() {
console.log(`你好, 我是${this.name}`);
}
}
class User extends Person {
constructor(public id: number, name: string) {
super(name); // super调用父类的构造函数
}
login() {}
// 在这里进行重写
sayHi(target?: User) {
if (target === undefined) {
// 也可以使用之前的逻辑
super.sayHi();
} else {
console.log(`再见, ${target.name},我是${this.name}`);
}
}
}
- 当继承时属性的类型类型出现问题,使用
declare解决
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, 'frank');
const u2 = new User(1, 'jack', u1);
u2.friend; // 如果这里的类型不能正确的现实,可以通过declare来解决