12面向对象之class上

52 阅读1分钟

一. 复习题

  • 执行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来查看结构

1.png

二. 面向对象编程之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来解决