面向对象和面向过程都属于编写程序的指导思想 一个非常重要的思想 面向对象很简单 就是程序之中所有的操作都需要通过对象来完成。
举例:
操作浏览器要使用 window 对象
操作网页要使用 document 对象
操作控制台要使用 console 对象
一切操作都要通过对象 也就是所谓的面向对象
对象到底是什么?
这就要先说到程序是什么 计算机程序的本质就是对现实事物的抽象 抽象的反义词是具体 比如 照片是对一个具体的人的抽象 汽车模型是对具体汽车的抽象等等 程序也是对事物的抽象 在程序中我们可以表示一个人 一条狗 一把枪 一颗子弹所有的事物
一个事物到了程序中就变成一个对象!!!
在程序中所有的对象都被分成了两个部分数据和功能 以人为例 人的姓名 性别 年龄 身高 体重等属于数据 人可以说话 走路 吃饭 睡觉 这些属于人的功能 数据在对象中被成为属性 而功能就被称为方法 所以简而言之 在程序中一切都是对象
在面向对象中通过对象来表示具体的事物 对象才可以干具体的事情 那么我们如何创建对象 比如创建一个表示车的对象
现实世界中第一步是画车辆图纸 在图纸中定义车的特征和车的能力 比如车是什么颜色 有多少座位 车可以驾驶 第二步是根据图纸造真实车辆
面向对象中使用类表示图纸 定义类就是画图纸 类中包含两部分 属性和方法 属性就是特征 方法就是能力 通过类可以创造对象 对象能干什么完全取决于你的类是如何定义的
class Vehicle {
// 特性:颜色
color = "白色";
// 特性:最高速度
maxSpeed = 220;
// 能力:驾驶
drive() {
console.log("run");
}
// 能力:鸣笛
honk() {
console.log("didi")
}
}
const vehicle = new Vehicle();
console.log(vehicle);
console.log(vehicle.color);
console.log(vehicle.maxSpeed);
vehicle.drive();
vehicle.honk();
通过同一个类可以创建多个相同的实物对象 所以属性就是这一类事物拥有的共同特性 方法就是这一类事物拥有的共同的能力
const v1 = new Vehicle();
const v2 = new Vehicle();
const v3 = new Vehicle();
通过类创建出来的对象也被称为实例对象
构造函数用来为对象属性赋初始值或者执行初始化操作 构造函数的名字是固定的 即 constructor
class Vehicle {
constructor () {}
}
在使用 new 关键字创建对象时可以传递参数 该参数在类的内部通过构造函数接收 参数值一般就是对象中某一个属性的初始值
class Vehicle {
constructor (color: string, maxSpeed: number) {
}
}
const vehicle = new Vehicle ("略略略", 240);
在构造函数中 this 关键字指向的是通过类创建出来的对象 所以通过 this 可以找到对象的属性 然后就可以为属性赋值了
class Vehicle {
color: string;
maxSpeed: number;
constructor (color: string, maxSpeed: number) {
this.color = color;
this.maxSpeed = maxSpeed;
}
}
const Vehicle = new Vehicle("略略略", 240);
类的构造函数是被自动调用的 在使用 new 关键字创建对象时被自动调用
只读属性是指属性一旦被赋值 该值就不能被修改
在类中通过 readonly 修饰符把类属性设置为设置为只读属性
class Vehicle {
readonly maxSpeed: number;
constructor(maxSpeed: number) {
this.maxSpeed = maxSpeed;
}
drive() {
this.maxSpeed = 900;
}
}
const vehicle = new Vehicle(240);
vehicle.maxSpeed = 300;
在面向对象编程中 很多对象都会具有相同的特征和能力 比如猫对象和狗对象 它们都具有五官特征都具备吃饭和奔跑的能力 如果我们分别在猫类和狗类中定义五官特征和奔跑能力的话 那么程序中出现大量的重复代码
要解决上面问题 我们可以对类别进行更高级别的抽象 比如我们可以在定义一个动物类 把动物具备的通用特性定义在动物类中 比如五官特征和奔跑吃饭的能力 那么猫类和狗类如何拥有这些特征和能力呐?
答案就是继承 通过继承可以让具体的猫类和狗类拥有这些特征和能力 这样程序就不会出现重复代码了 然后我们还可以在猫类和狗类中继续定义属于猫和狗的独有特征和能力
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
以上代码中 Animal 是父类 Dog 和 Cat 是子类
在程序中分别创建汽车分类 轿车分类 面包车分类 在汽车分类下描述所有汽车的通过特征 在细化分类下描述分类独有的特征
// 汽车分类
calss Vehicle {
drive() {
console.log("run");
}
}
class Car extends Vehicle {
brand = "BMW";
}
class Van extends Vhehice {
brand = "五菱宏光"
}
let car = new Car()
car.drive();
car.brand;
let van = new Van()
van.drive();
van.brand;
在子类继承父类后 如果父类需要在初始化的时候传递参数 该参数由子类接收 在子类的构造函数中通过 super 调用父类把参数传递给父类
class Person {
constructor(public name: string) {}
}
class Student extends Person {
// 注意 在子类继承父类后 如果要在子类中使用构造函数 即使父类不需要传递参数
// 在构造函数中的第一件事情也是必须先调用 super 方法
constructor(public studentId: number, name: string) {
super(name);
}
}
const p1 = new Student(1, "张三");
console.log(p1.studentId)
console.log(p1.name);
子类在继承父类后 还可以通过重写父类方法对功能进行扩展
class Person {
walk() {
console.log("person walk")
}
}
class Student extends Person {
// override 表示该方法是覆盖的父类方法 作为标识提升代码的可阅读性 不会对代码的实际执行产生任何副作用
override walk() {
console.log("student walk");
// 通过 super 调用父类 walk 方法
super.walk();
}
}
const p1 = new Student();
p1.walk();
通过访问权限修饰符可以指定类中的属性 方法能够在哪些范围内被访问
| 修饰符 | 作用 |
|---|---|
| pubilc | 被pubilc 关键字修饰的类属性和类方法可以在任何地方使用(当钱类,子类,实例对象) |
| private | 被private 关键字修饰的类属性和类方法只能在当前类中使用 |
| protected | 被protected 关键字修饰的类属性和类方法可以在当前类和子类中使用 |
pubilc 公开的
类属性和类方法在不加任何权限修饰符的情况下 它就是可以在任何地方被访问的 也就是说 pubilc 是默认值 是可以被省去的
class Vehicle {
constructor() {
// 在本类中的其他方法中使用
this.drive();
}
public drive() {
console.log("run")
}
}
class Car extends Vehicle {
drive() {
// 在子类中使用
super.drive();
}
}
const car = new Car();
// 在实例对象中使用
car.drive();
private 私有的
class Vehicle {
constructor() {
// 在本类中的其他方法中使用
this.drive();
}
private drive() {
console.log("run")
}
}
class Car extends Vehicle {
drive() {
// 属性 drive 为私有属性 只能在类 Vehicle 中访问
super.drive();
}
}
const vehicle = new Vehicle();
// 属性 drive 为私有属性 只能在类 Vehicle 中访问
vehicle.drive();
protected 受保护的
class Vehicle {
constructor() {
// 在本类中的其他方法中使用
this.drive();
}
protected drive() {
console.log("run")
}
}
class Car extends Vehicle {
drive () {
// 在子类中使用
super.drive();
}
}
const vehicle = new Vehicle();
// 属性 drive 受保护 只能在类 Vehicle 及其子类中访问
vehicle.drive();
Getter 和 Setter 是对属性访问(获取和设置)的封装 获取属性值时走 Getter 修改属性值时走 Setter
class Employee {
private _salary: number;
constructor(salary: number) {
this._salary = salary;
}
get salary() {
return this._salary;
}
set salary(salary: number) {
this._salary = salary;
}
}
const employee = new Employee(4000);
console.log(employee.salary);
employee.salary = 6000;
console.log(employee.salary);
// 通过选择的方式指定 target
tsc -t es2016 index.ts
// 通过配置文件的方式指定 target
tsc --init
// 要走配置文件 直接执行 tsc 命令
tsc
TypeScript 提供了特殊的语法把构造函数参数转换为具有相同名称的类属性
通过在构造函数参数的前面加上权限修饰符 public,private,protected 或 readonly 创建
class Params {
x: number;
y: number;
z: number;
constructor (x: number, y: number, z: number) {
this.x = x;
this.y = y;
this.z = z;
}
}
class Params {
constructor(
public x: number;
public y: number;
public z: number;
) {}
log() {
console.log(this.x);
console.log(this.y);
console.log(this.z);
}
}
const p = new Params(10, 20, 30);
p.log();
在 JavaScript 中 为对象动态添加属性是被允许的 但是在 TypeScript 中是不允许的 因为在 TypeScript 中对象要有严格的类型限制
let person = {};
person.name = "张三";
在 TypeScript 中动态为对象添加属性 需要使用索引签名 通过索引签名可以在不确定属性名称的情况下限制对象属性的类型以及对象值得类型
在不确定属性名称的情况下限制属性的类型意味我们可以动态的为对象添加任意属性 但属性类型要符合要求
class SeatAssignment {
A1: string;
A2: string;
}
class SeatAssignment {
// seatNumber 只是用来的 实际属性的名称开发者可以自定义 而且 seatNumber 也可以换成其他名称
[searNumber: string]: string;
}
let seats = new SeatAssignemnt();
seats.A1 = "张三"
seats.A2 = "李四"
seats[true] = "hello";
seats.A3 = 12
在类被 static 关键字修饰的类属性和类方法被叫做静态成员 静态成员属类 所以访问静态成员的方式是类名点上静态成员名称
class Rich {
static count: number = 0;
}
// 通过 类名 + 静态成员名称 访问静态成员
Rich.count;
// 静态成员不属于类的实例对象 所以不通过类的实例对象访问静态成员
const r = new Rich();
r.count;
class Rich {
static count: number = 0;
getCount () {
return Rich.count;
}
}
对类的任何一个对象而言 静态属性就是公共的存储单元 任何对象访问它时 取到的都是相同值 也都是在对同一个内存单元做操作 所以静态属性主要用在各个对象都要共享的数据
class Rich {
private static _count: number = 0;
constructor() {
Rich._countt ++;
}
getCount() {
return Rich._count;
}
}
const r1 = new Rich();
const r2 = new Rich();
console.log(r1.getCount());
console.log(r2.getCount());
因为静态成员存在内存 而非静态成员需要实例化可以分配到内存 所以静态成员不能访问静态成员 静态成员可以访问类中的静态成员
class Rich {
private static _count: number = 0;
static fn() {
}
getCount () {
return Rich._count;
}
}
抽象类因继承而存在 通过抽象类可以约束子类必须实现哪些成员 如果抽象类不被继承 它毫无意义
在抽象类中可以只定义成员 具体的成员实现由子类完成且必须完成 所以抽象类不能被直接实例化
abstract class Shape {
abstract color: string;
abstract render(): void;
}
class Circle extends Shape {
constructor (public color: string) {
super();
}
override render(): void {
console.log("render")
}
}
// TS2511: Cannot create an instance of an abstract class
// new Shape();
const circle - new Circle("red");
console.log(circle.color);
circle.render();
接口用于声明类型 用于对复杂的数据构造进行类型描述 比如对象 函数 类
声明接口需要使用 interface 关键字
interface User {
name: string;
age: number;
}
使用接口约束对象的类型
const user: User = {
name: "张三",
age: 20
}
使用接口约束函数的类型
interface Sum {
(n: number, m: number): number;
}
const sum: Sum = function(a, b) {
return a + b;
};
使用接口约束类的成员
interface Calendar {
name: string;
addEvent(): void;
removeEvent(): void;
}
class GoogleCalendar implements Calendar {
name: string = "test";
addEvent(): void {}
removeEvent(): void{}
}
TypeScript 接口检查是宽松的 当变量满足了 接口规范以后 即使变量中存在接口规范以外的属性也是可以的
interface User {
name: string;
age: number;
}
let user: User = {
name: "张三"
age: 20
};
let someone = {
name: "李四",
age: 50,
sex: "男"
}
user = someone;
interface Reportable {
summary(): void;
}
function printSummary(item: Reportable): void {
item.summary()
}
const person = {
name: "张三",
summary() {
console.log(`你好我的名字叫${this.name}`);
}
}
printSummary(person);
对于宽松的接口检查政策字面量是个例外 也就是说于字面量的接口类型检查是严格的 不能出现接口规范以外的其他属性
interface User {
name: string;
age: number;
}
const user: User = {name: "张三", age: 20};
// 不能把类型 {name: string; age: number; sex: string;} 分配给类型 User
// 对象字面量只可以指定已知属性 sex 不在类型User 中
const another: User = {name: "李四", age: 40, sex: "男"};
interface Reportable {
summary(): void;
}
function printSummary(item: Reportable): void {
item.summary();
}
// 类型"{ name: string; summary(): void; }"的参数不能赋给类型"Reportable"的参数。
// 对象字面量只可以指定已知属性, "name"不在类型"Reportable"中。ts(2345)
printSummary({
name: "张三",
summary() {
console.log(`您好, 我的名字叫${this.name}`);
}
})
那么如何绕过字面量严格类型检查模式
// 使用类型断言
interface User{
name: string;
age: nunmber;
}
const another: User = {name: "李四", age: 40, sex: "男"} as User;
// 使用索引签名
interface User {
name: string;
age: number;
[key: string]: string | number;
}
const another: User = { name: "李四", age: 40, sex: "男" };
接口具有继承特性即接口与接口之间可以存在继承关系,而且一个接口可以继承多个接口。
// 接口继承示例
interface Sizes {
sizes: string[];
getAvailableSizes(): string[];
}
interface Shape {
color: string;
}
interface Pizza extends Sizes, Shape {
name: string;
}
let pizza: Pizza = {
name: "张三",
color: "skyblue",
sizes: ["large", "small"],
getAvailableSizes() {
return this.sizes;
},
};
在继承了接口以后可以对被继承接口中的属性进行重写 但是重写的类型一定要在原有类型的范围以内
interface User{
// name: string | number | boolean;
name: any;
age: number;
}
interface MyUser extends User {
name: boolean;
}
接口具有声明合并特性 即多个相同名称的接口会自动合并
interface Box {
height: number;
width: number;
}
interface Box {
scale: number,
}
let box: Box = {height: 5, width: 6, scale: 10},
在TypeScript 中 type 可以定义对象 联合类型 基本数据类型 而 interface 只能定义对象类型
type ISBN = number | string;
type PublicationT = {
isbn: ISBN;
author: string;
publisher: string;
}
// 接口只能定义对象类型
interface PublicationI {
isbn: ISBN;
author: string;
publisher: string;
}
函数重载是指多个同名函数具有不同的调用签名 通过不同的调用签名决定执行哪一个具体的函数 执行的函数不同做的事情可以不同
// java 中的函数重载
public class Sum {
public int sum(int x, int y) {
return (x + y)
}
public int sum(int x, int y, int z) {
return (x + y + z);
}
public double sum(double x, double y) {
return (x + y);
}
public static void main(String args[]) {
Sum s = new Sum();
System.out.println(s.sum(10, 20))
System.out.println(s.sum(10, 20, 30))
System.out.println(s.sum(10.5, 20.5))
}
}
JavaScript 没有函数函数重载特性 虽然没有但是我们可以通过代码模拟实现
需求: 定义sum 函数 接收两个参数 n 和 m 当 n 和 m 都是数值时进行数值相加 当 n 和 m 都是字符串时进行字符串连接
// 在 JavaScript 中通过代码模拟函数重载功能
function sum(n, m) {
if (typeof n === "number" && typeof m === "number") {
// 数值相加
return n + m;
} else if (typeof n === "string" && typeof m === "string") {
// 字符串连接
return n + "_" + m;
}
}
sum(10, 20); // 30
sum("hello", "world"); // hello_world
// 在 TypeScript 中实现函数重载
function add (a: number, b: number): number;
function add (a: string, b: string): string;
function add (a: any, b: any) {
if (typeof a === "string" && typeof b === "string") {
return a + b
} else if (typeof a === "number" && typeof b === "number") {
return a + "-" + b
}
}
const result1 = add(10, 20)
const result2 = add("a", "b")
需求: 调用 geMessage 方法获取消息 如果传递的参数是数值类型 表示根据消息 id 获取消息对象 如果传递的参数是字符串类型 表示根据消息种类获取消息列表
// 消息种类的类型
type MessageType = "string" | "image" | "audio"
// 消息对象的类型
type Message = {
id: number;
type: MessageType;
content: string;
};
// 消息数组
const data: Message[] = [
{id: 1, type: "string", content: "hello-1"},
{id: 2, type: "image", content: "hello-2"},
{id: 3, type: "audio", content: "hello-3"},
];
function getMessage(id: number): Message | undefined;
function getMessage(type: MessageType): Message[];
function getMessage(query: any): any {
if (typeof query === "number") {
return data.find(message => message.id === query);
} else {
return data.filter(message => message.type === query);
}
}
// const r1: Message | undefined
const r1 = getMessage(1);
// const r2: Message[]
const r2 = getMessage("image");
interface GetMessage {
(id: number): Message | undefined;
(type: MessageType): Message[];
}
const getMessage: GetMessage = (query: any): any => {
if (typeof query === "number") {
return data.find((message) => message.id === query)
}else {
return data.filter((message) => message.type === query);
}
}