面向对象
面向对象是程序中一个非常重要的思想,简而言之就是程序中所有的操作都需要通过对象来完成,对象中有属性和方法
举例:
操作浏览器要使用window对象
操作网页要使用document对象
操作控制台要使用console对象
要想面向对象,操作对象,首先要拥有对象
要创建对象,必须要先定义类,所谓的类可以理解为对象的模型
程序中可以根据类创建指定类型的对象
举例来说:
可以通过Person类来创建人的对象,通过Dog类创建狗的对象,不同的类可以用来创建不同的对象
定义类
/* 使用class关键字定义类 属性有2种: 1、实例属性 定义:直接定义 使用:通过new关键字创建一个实例对象,对象去调用该属性 2、静态属性,也叫类属性 定义:在定义该属性前加上static关键字 使用:直接通过类名访问该属性 方法和属性一样,加上static就是静态方法 */ class Person { readonly name: string = '小明' // 只读属性 age: number = 19 static age: number = 18 sayHello() { console.log('hello') } } const p = new Person() console.log(p) // Person {name: '小明', age: 19} // p.name = 'xx' // 只读属性不可赋值 p.age = 88 console.log(p.name) // 小明 console.log(p.age) // 88 p.sayHello() // hello console.log(Person.age) // 18
构造函数和this
在new一个对象时会调用constructor函数
class Dog { // 1、在类中定义属性 name: string age: number bark() { // 方法中的this指向调用该方法的对象 console.log('汪汪汪', this) } // 2、在构造函数中进行赋值 constructor(name: string, age: number) { // new Dog()时执行构造函数,这里的this指向实例对象 this.name = name this.age = age } } const dog = new Dog('旺财', 3) const dog1 = new Dog('阿黄', 4) console.log(dog) // {name: '旺财', age: 3} console.log(dog1) // {name: '阿黄', age: 4} dog.bark() // 汪汪汪 {name: '旺财', age: 3} dog1.bark() // 汪汪汪 {name: '阿黄', age: 4}
继承
使用extends继承后,子类拥有父类所有的属性和方法
通过继承可以将多个类中公有的代码写在父类中,重复的代码只需写一次
如果子类中添加了和父类重名的属性和方法,则会覆盖父类中的属性和方法,这种形式叫重写
;(function () { class Animal { name: string age: number constructor(name: string, age: number) { this.name = name this.age = age } sayHello() { console.log('hello~') } } class Dog extends Animal { age: number = 100 // 和父类属性名重名,这里的优先级高 // 和父类方法名重名,这里的优先级高 sayHello() { console.log('汪汪汪') } run() { console.log(`${this.name}在跑`) } } class Cat { name: string age: number constructor(name: string, age: number) { this.name = name this.age = age } sayHello(str: string) { console.log('喵喵喵', str) } } const dog = new Dog('旺财', 3) console.log(dog) dog.sayHello() dog.run() const cat = new Cat('小花', 2) console.log(cat) cat.sayHello('123') })()
super
在类的方法中,super表示当前类的父类(也叫超类)
如果在子类中写了构造函数,子类的构造函数中必须对父类的构造函数进行调用,也就是执行super()
;(() => { class Animal { name: string constructor(name: string) { this.name = name } sayHello() { console.log('hello') } } class Dog extends Animal { age: number // 如果在子类中写了构造函数,在子类的构造函数中必须对父类的构造函数进行调用 constructor(name: string, age: number) { super(name) // 调用父类的构造函数 this.age = age } sayHello() { // super.sayHello() // 在类的方法中,super表示当前类的父类 console.log('汪汪汪') } } const dog = new Dog('旺财', 3) console.log(dog) dog.sayHello() })()
多态
父类定义了一个方法不去实现,让继承它的子类去实现(方法重写),每一个子类都有不同的表现
;(() => { // 多态:父类型的引用指向了类型的对象,不同类型的对象针对相同的方法,产生了不同的行为 class Animal { constructor(public name: string) { this.name = name } run(distance: number = 0) { console.log(`${this.name}跑了 ${distance} 米`) } } class Dog extends Animal { constructor(name: string) { super(name) // 调用父类的构造函数,实现子类中属性的初始化 } // 重写父类中的实例方法 run(distance: number = 5) { console.log(`${this.name}跑了 ${distance} 米`) } } class Pig extends Animal { constructor(name: string) { super(name) } run(distance: number = 10) { console.log(`${this.name}跑了 ${distance} 米`) } } const a: Animal = new Animal('动物') a.run() const dog: Dog = new Dog('阿黄') dog.run() const pig: Pig = new Pig('八戒') pig.run() console.log('--------------') // 父类类型创建子类的对象 const dog1: Animal = new Dog('哮天犬') dog1.run() const pig1: Animal = new Pig('猪猪侠') pig1.run() console.log('>>>>>>>>>>>>>>>') function showRun(obj: Animal) { obj.run() } showRun(dog1) showRun(pig1) })()
抽象类abstract(ts新增)
一般用来给子类继承的父类,也可以被当做构造函数去创建对象使用,在该父类前加上abstract就可以将该类变为抽象类,即不能用来创建对象,抽象类是专门用来被继承的类
抽象方法:抽象类中可以添加抽象方法,就是在方法名前加上abstract
抽象方法没有方法体,派生类必须对抽象方法进行重写
抽象方法只能写在抽象类中
;(function () { abstract class Animal { name: string constructor(name: string) { this.name = name } abstract sayHello(): void // 抽象方法没有方法体,只能定义在抽象类中 } class Dog extends Animal { sayHello() { // super.sayHello() // 在类的方法中,super表示当前类的父类 console.log('汪汪汪') } } class Cat extends Animal { sayHello() {} // 子类必须对抽象方法进行重写 } const dog = new Dog('旺财') console.log(dog) dog.sayHello() })()
接口(ts新增)
接口可以当成类型声明去使用
接口用来定义一个类的结构,指定一个类中应该包含哪些属性的方法
接口中所有的属性都没有实际的值;所有的方法都是抽象方法,没有方法体
接口和抽象类的区别:
抽象类既可以有抽象方法也可以有普通方法,接口只能有抽象方法
抽象类使用extends继承,接口使用implement实现
;(function () { // 类型声明 type myType = { name: string age: number } // 接口当成类型声明去使用:接口定义一个对象的结构 interface myInterface { name: string age: number } // 接口可以重复定义 interface myInterface { gender: string } const obj: myType = { name: '小明', age: 18 } const obj1: myInterface = { name: '小红', age: 17, gender: '男' } // 接口在定义类的时候限制类的结构 接口中所有的属性都没有实际的值;所有的方法都是抽象方法,都没有方法体 interface myInter { name: string sayHello(): void } // 定义类时,使类实现一个接口,满足接口的要求 class Person implements myInter { name: string constructor(name: string) { this.name = name } sayHello() { console.log('hello') } } })()
属性的封装
ts可以在类中的属性前添加属性的修饰符(之前用过static和readonly)
1、public 默认值,修饰的属性可以在任意位置(当前类、子类、类的外部)访问和修改
2、private 私有属性,只能在当前类中访问和修改(子类、类外部无法访问和修改),但可以通过在类中添加方法使得私有属性可以被外部访问和修改
3、protected 受保护的属性,只能在当前类和当前类的子类中访问和修改(类外部无法访问和修改)
封装属性
封装属性是什么:
js中,属性封装就是给属性加上getter和setter方法,在访问和修改时使用对应的getter和setter方法
封装属性的原因:
由于属性是在new对象时设置的,属性可以被任意的修改,存在安全隐患,因此需要对属性进行封装
js和ts封装属性的区别:
1、js封装的属性存取器使用时需要调用对应的getter和setter方法,较为麻烦
2、ts封装的属性存取器使用时直接当做变量来访问和修改,比较简洁
封装属性的使用场景:
1、如果需要对属性的值做安全判断,就需要使用属性封装
2、在类中使用private修饰的属性,进行访问和修改的时候需要用到封装属性
;(() => { class Person { private _name: string private _age: number constructor(name: string, age: number) { this._name = name this._age = age } // 被private修饰的属性,在外部不可以直接访问,可以在类里面定义一个方法使得私有属性可以被外部访问 getName() { return this._name } // 定义一个方法使得私有属性可以被外部修改 setName(name: string) { this._name = name } // getName和setName是js中的属性存取器,getter方法访问,setter设置 ts中提供了更简便属性存取器的方式,使用时直接p.name进行访问和设置 get age() { return this._age } set age(age: number) { if (age >= 0) this._age = age // 设置时对属性进行一些判断,提高安全性 } } const p = new Person('小明', 12) // p._name = 'xx' // public修饰的属性可以直接这么修改 console.log(p) // console.log(p._name) // public修饰的属性可以直接这么访问 // 被private修饰的属性,访问和修改需要通过类中定义的方法来进行,这样做的好处是,可以在设置时对属性进行一些判断,提高安全性 console.log(p.getName()) p.setName('小红') console.log(p.getName()) console.log(p.age) p.age = 100 console.log(p.age) })()
protected
;(() => { class A { protected age: number constructor(age: number) { this.age = age } } class B extends A { getName() { return this.age // 被protected修饰的属性,可以在当前类和它的子类中进行访问和修改 } } const b = new B(20) console.log(b) console.log(b.age) // 被protected修饰的属性,在类的外部无法访问和修改 })()
在定义类时可以直接将属性定义在构造函数中,实际上是语法糖
;(() => { class Dog0 { name: string age: number constructor(name: string, age: number) { this.name = name this.age = age >= 0 ? age : 0 } } // 在定义类时可以直接将属性定义在构造函数中,实际上是语法糖 class Dog { constructor(public name: string, public age: number) { this.age = age >= 0 ? age : 0 // 如果需要对属性进行初始值判断可以加上这句 } } const dog = new Dog('大黄', -2) console.log(dog) })()
泛型(ts新增)
// 泛型:不确定的类型,在定义的时候不确定,在执行的时候明确类型。 // 使用场景:在定义函数或类时,遇到类型不明确时使用泛型 function fn<T>(a: T): T { return a } const res = fn(10) // 可以直接调用具有泛型的函数,不指定泛型,ts可以自动推断类型 console.log(res) const res1 = fn<string>('hello') // 手动指定类型(严谨,推荐手动指定) console.log(res1) // 泛型可以同时指定多个 function fn1<A, B>(a: A, b: B): A { console.log(b) return a } fn1<number, string>(100, 'hello') // 可以对泛型的类型进行限制 interface Inter { length: number } // A extends Inter 表示泛型A必须是Inter实现类(Inter的子类) fn2的参数必须要有length属性,可以是数组,对象,字符串... 参数没有length属性会报错,比如数字 function fn2<A extends Inter>(a: A): number { return a.length } fn2([]) fn2({ length: 10 }) fn2('xxx') // fn2(100) // 报错 // 在类中使用泛型 class A<A> { name: A constructor(name: A) { this.name = name } } const a = new A<string>('小明') console.log(a) // {name: '小明'} const a1 = new A<number>(10) console.log(a1) // {name: 10}