面向对象
面向对象是程序中一个非常重要的思想,他被很多同学理解成了一个比较难,比较深奥的问题,其实不然,面向对象很简单,简而言之就是程序之中所有的操作都需要通过对象来完成
- 举例来说:
- 操作浏览器要使用window对象
- 操作网页要使用document对象
- 操作控制台要使用console对象
一切操作都要通过对象,也就是说所谓的面相对象,那么对象到底是什么?这就要说到程序是什么,计算机程序的本质就是对现实事物的抽象,抽象的反义词是具体,比如:照片是对一个具体的人的抽象,汽车模型是对具体汽车的抽象等等,程序也是对事物的抽象,在程序中我们可以表示一个人、一条狗、一把枪、一颗子弹等等所有的事物,一个事物到了程序中就变成了一个对象。
在程序中所有的对象都被分成了两个部分数据和功能,以人为例,人的姓名、性别、年龄、身高、体重都属于数据,人可以说话、走路、吃饭、睡觉这些属于人的功能,数据在对象中被称为属性,而功能则被称为方法。所以简而言之,在程序中一切皆是对象。
1. 类(class)
要想面对对象,操作对象,首先便要要么拥有对象,那么下一个问题就是如何创建对象,要创建对象,必须要先定义类,所谓的类可以理解为对象的模型,程序中可以根据类创建指定类型的对象,举例来说:可以通过Person类来创建人的对象,通过Dog类创建狗的对象,通过Car类来创建汽车的对象,不同的类可以用来创建不同的对象。
-
定义类:
-
class 类名 { 属性名:类型; constructor(参数:类型){ this.属性名 = 参数; } 方法名(){ ··· } }
-
-
示例
-
// 使用class关键字来定义一个类 /** * 对象中主要包含两个部分 * 属性 * 方法 */ class Person { /** * 直接定义的属性是实例属性,需要通过对象的实例去访问 * const per = new Person(); * per.name * * 使用static开头的属性是静态属性(类属性)。可以直接通过类去访问 * Person.age * * readonly开头的属性表示一个只读的属性,他无法修改 * * static readonly 表示只读静态属性 */ // 定义实例属性 // name: string; // age: number; name: string = "孙悟空"; // 在属性前面使用static关键字可以定义类属性(静态属性) static age: number = 18; // 表示一个只读的属性,他无法修改 readonly gender: string = "男"; // 表示只读静态属性 static readonly height: number = 188; // 定义类方法 // 如果方法以static开头,则该方法就是类方法,可以直接通过类去调用 sayHello() { console.log("大家好!!!"); } } const per = new Person(); console.log(per,Person.age); per.sayHello();
-
2.构造函数和this
可以使用constructor定义一个构造器方法
注:在ts中只能有一个构造器方法!
例如:
class Dog {
name: string;
age: number;
// constructor被称为构造函数
// 构造函数会在对象创建时调用
constructor(name: string, age: number) {
// 在实例方法中,this就表示当前实例
// 在构造函数中,当前对象就是当前新建的那个对象
// 可以通过this向新建的对象中添加属性
this.name = name;
this.age = age;
}
bark() {
// 在方法中可以通过this来表示当前调用方法对象
console.log(this);
console.log(this.name + "汪汪汪~~~~");
}
}
const dog = new Dog("小黑", 1);
const dog2 = new Dog("大黄", 2);
const dog3 = new Dog("哮天犬", 4);
const dog4 = new Dog("黑妞", 7);
dog.bark();
dog2.bark();
dog3.bark();
dog4.bark();
console.log(dog);
console.log(dog2);
console.log(dog3);
console.log(dog4);
3. 继承简介
开发过程中拿到一个类,但这个类并不完全满足我们的需求,如果直接修改这个类可能会导致其他使用这个类的地方出问题,所以在开发过程中不要轻易修改别人写的类活自己不熟悉的类,这样具有较高风险。
这种情况下可以创建一个新类去继承原来的类,所有新增与修改在新类中进行即可在不修改原类的基础上进行拓展。
(function () {
// 定义一个Animal类
class Animal {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
sayHello() {
console.log("动物在叫~");
}
}
// 定义一个表示狗的类
// 使Dog类继承Animal类
/**
* 此时,Animal被称为父类,Dog被称为子类
* 使用继承后,子类将会拥有父类所有的方法和属性
* 通过继承,可以将多个类共有的代码写在一个父类中,
* 这样只需要写一次即可让所有的子类都同时拥有父类中的属性和方法
* 如果希望在子类中添加一些父类中没有的属性和方法直接加即可
* 如果在子类中添加了和父类相同的方法,则子类方法会覆盖调父类的方法
* 这种子类覆盖掉父类方法的形式,我们称为方法重写
*/
class Dog extends Animal {
sayHello() {
console.log(this.name + "在叫~~~");
}
run() {
console.log(this.name + "在跑~");
}
}
// 定义一个表示猫的类
// 使Cat类继承Animal类
class Cat extends Animal {
sayHello() {
console.log(this.name + "喵喵喵~");
}
}
const dog = new Dog("旺财", 2);
dog.sayHello();
dog.run();
const cat = new Cat("咪咪", 1);
cat.sayHello();
})();
4. super
(function () {
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
console.log("动物在叫~~");
}
}
class Dog extends Animal {
age: number;
constructor(name: string, age: number) {
// 如果在子类中写了构造函数,在子类的构造函数中,我们必须对父类的构造函数进行调用
super(name); // 调用父类的构造函数
this.age = age;
}
sayHello() {
// 再累的方法中,super就表示当前类的父类
super.sayHello();
}
}
const dog = new Dog("旺财", 4);
dog.sayHello();
})();
5. 抽象类
当你不希望这个类被创建对象的时候,就可以使用抽象类
- 抽象类和其他类区别不大,只是不能用来创建对象(实例)
- 抽象类就是专门用来被继承的类
- 抽象类当中可以去添加抽象方法
- 抽象方法使用abstract开头,没有方法体
- 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
(function () {
/**
* abstract开头的类是抽象类
* 抽象类和其他类区别不大,只是不能用来创建对象(实例)
* 抽象类就是专门用来被继承的类
*
* 抽象类当中可以去添加抽象方法
*/
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
/**
* 定义一个抽象方法
* 抽象方法使用abstract开头,没有方法体
* 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写
*/
abstract sayHello(): void;
}
class Dog extends Animal {
sayHello() {
console.log("汪汪汪~~~~");
}
}
class Cat extends Animal {
sayHello() {
console.log("喵喵喵~~~");
}
}
const dog = new Dog("旺财");
dog.sayHello();
const cat = new Cat("小咪");
cat.sayHello();
})();
6. 接口
接口与抽象类的区别:
- 抽象类中可以有抽象方法也可以有普通方法,但接口只能有抽象方法
- 实现方面,抽象类用的是extends继承,而定义一个接口用的是implements
接口和抽象类都是ts中新增的东西,js中没有
(function () {
// 描述一个对象的类型
type myType = {
name: string;
age: number;
[propname: string]: any;
};
/**
* 接口用来定义一个类的结构,用来定义一个类中应该包含哪些属性和方法
* 同时接口可以当成类型声明去使用
* 接口可以重复声明,会合并
*/
interface myInterface {
name: string;
age: number;
}
interface myInterface {
gender: string;
}
const obj: myInterface = {
name: "ssss",
age: 8,
gender: "男",
};
/**
* 接口可以在定义类的时候限制类的结构
* 接口中的所有属性都不能有实际的值
* 接口之定义对象结构而不考虑实际值
* 在接口中的所有方法都是抽象方法
*/
interface myInter {
name: string;
sayHello(): void;
}
/**
* 其实接口就是对类的一个限制
* 定义类时,可以使类去实现一个接口
* 实现接口就是使类满足接口的要求
*/
class MyClass implements myInter {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello(): void {
console.log("大家好!");
throw new Error("Method not implemented.");
}
}
})();
7. 属性的封装
属性直接在对象中设置,属性可以任意的被修改,属性可以任意被修改将会导致对象中的数据变得非常不安全
(function () {
// 定义一个表示人的类
class Person {
// ts可以在属性前添加属性修饰符
/**
* public修饰的属性可以在任意位置访问和修改(默认)
*
* private 私有属性,私有属性只能在类内部进行访问和修改
*
* ——通过在类中添加方法使得私有属性可以被外部访问
*
* protected 受保护的属性,只能在当前类和当前类的子类中访问
*/
public _name: string;
private _age: number;
constructor(name: string, age: number) {
this._name = name;
this._age = age;
}
/**
* getter方法用来读取属性
* setter方法用来设置属性
* 他们被称为属性的存取器
*/
// // 定义方法用来获取age属性
// getAge() {
// return this._age;
// }
// // 定义方法,用来设置age属性
// setAge(age: number) {
// // 判断年龄是否合法
// if (age >= 0) {
// this._age = age;
// }
// }
// TS中设置getter方法的方式
get name() {
console.log("get name()执行了");
return this._name;
}
set name(value: string) {
this._name = value;
}
}
const per = new Person("孙悟空", 18);
/**
* 现在属性是直接在对象中设置,属性可以任意的被修改
* 属性可以任意被修改将会导致对象中的数据变得非常不安全
*/
// per.name = "猪八戒";
// per.age = -18;// 会报错
// per.setAge(-32);
// console.log(per, per.getAge());
per.name = "猪八戒";
console.log(per.name);
class A {
protected num: number;
constructor(num: number) {
this.num = num;
}
}
class B extends A {
test() {
console.log(this.num);
}
}
const b = new B(15);
b.test();
// console.log(b.num); // 无法在实例中访问protected属性
class C {
// 可以直接将属性定义在构造函数中
constructor(public name: string, public age: number) {}
}
const c = new C("Yoliu", 22);
console.log(c);
})();
8. 泛型
泛型其实就是一个不确定的类型,在定义函数或者类时,如果遇到类型不明确,就可以使用泛型
// function fn(a: number): number {
// return a;
// }
/**
* 在定义函数或者类时,如果遇到类型不明确,就可以使用泛型
*/
function fn<T>(a: T): T {
return a;
}
// 可以直接调用具有泛型的函数
let result = fn(10); // 不指定泛型,ts可以自动对类型进行判断
fn<string>("hello"); // 指定泛型
function fn2<T, K>(a: T, b: K): T {
console.log(b);
return a;
}
fn2(123, "hello");
interface Inter {
lenght: number;
}
// T extends Inter 表示泛型T必须是Inter实现类(子类)
function fn3<T extends Inter>(a: T): number {
return a.lenght;
}
fn3({ lenght: 8 });
class MyClass<T> {
name: T;
constructor(name: T) {
this.name = name;
}
}
const mc = new MyClass<string>("孙悟空");