数据类型
函数
类
"strictPropertyInitialization": true 启用类属性初始化的严格检查
/*** 当我们写一个类的时候,会得到2个类型* 1. 构造函数类型的函数类型* 2. 类的实例类型*/class Component {static myName: string = '静态名称属性'myName: string = '实例名称属性'}let com = Component//Component类名本身表示的是实例的类型//ts 一个类型 一个叫值//冒号后面的是类型//放在=后面的是值let c: Component = new Component()let f: typeof Component = com
readonly
- readonly修饰的变量只能在构造函数中初始化
- 允许将 interface、type、 class 上的属性标识为 readonly
- readonly 实际上只是在编译阶段进行代码检查。而 const 则会在运行时检查(在支持 const 语法的 JavaScript 运行时环境中) ```typescript class Animal { public readonly name: string constructor(name: string) { this.name = name } changeName(name: string) { this.name = name // error } }
let a = new Animal(‘lc’) a.changeName(‘cl’)
<a name="VXSWF"></a>### 修饰符```typescriptclass Father {public name: string //类里面 子类 其它任何地方外边都可以访问protected age: number //类里面 子类 都可以访问,其它任何地方不能访问private money: number //类里面可以访问, 子类和其它任何地方都不可以访问constructor(name: string, age: number, money: number) {//构造函数this.name = namethis.age = agethis.money = money}getName(): string {return this.name}setName(name: string): void {this.name = name}}class Child extends Father {constructor(name: string, age: number, money: number) {super(name, age, money)}desc() {console.log(`${this.name} ${this.age} ${this.money}`)}}let child = new Child('lc', 10, 1000)console.log(child.name)console.log(child.age) // errorconsole.log(child.money) // error
抽象类
- 抽象描述一种抽象的概念,无法被实例化,只能被继承
- 无法创建抽象类的实例
抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现
abstract class Animal {name!: stringabstract speak(): void}class Cat extends Animal {speak() {console.log('喵喵喵')}}let animal = new Animal() // 无法创建抽象类的实例animal.speak()let cat = new Cat()cat.speak()
抽象方法
抽象类和方法不包含具体实现,必须在子类中实现
- 抽象方法只能出现在抽象类中
子类可以对抽象类进行不同的实现
abstract class Animal {abstract speak(): void}class Dog extends Animal {speak() {console.log('汪汪汪')}}class Cat extends Animal {speak() {console.log('喵喵喵')}}let dog = new Dog()let cat = new Cat()dog.speak()cat.speak()
重写(override) vs 重载(overload)
重写是指子类重写继承自父类中的方法
- 重载是指为同一个函数提供多个类型定义
继承 vs 多态
- 继承(Inheritance)子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
多态(Polymorphism)由继承而产生了相关的不同的类,对同一个方法可以有不同的行为
装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为
- 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器
- 装饰器的写法分为普通装饰器和装饰器工厂
装饰器使用 @expression 的形式,其中 expression 必须能够演算为在运行时调用的函数,其中包括装饰声明信息。在不改变对象自身的基础上,动态增加额外的职责。把对象核心职责和要装饰的功能分开了。非侵入式的行为修改。
Typescript 中的装饰器
expression 求值后为一个函数,它在运行时被调用,被装饰的声明信息会被做为参数传入。
class Person {@timesay() {console.log('hello')}}
Javascript规范里的装饰器目前处在 建议征集的第二阶段,https://github.com/tc39/proposal-decorators 也就意味着不能在原生代码中直接使用,浏览器暂不支持。
TypeScript 工具在编译阶段,把装饰器语法转换成浏览器可执行的代码。
// tsconfig 中开启"experimentalDecorators": true
装饰器分类
类装饰器
-
属性装饰器
属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数
- 属性装饰器用来装饰属性
- 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数是属性的名称
方法装饰器用来装饰方法
会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元数据
- 第1个参数对于静态成员是类的构造函数,对于实例成员是类的原型对象
- 第2个参数的名称
- 第3个参数在函数列表中的索引
装饰器执行顺序
- 有多个参数装饰器时(复合装饰):从最后一个参数依次向前执行
- 方法和方法参数中参数装饰器先执行。
- 类装饰器总是最后执行
- 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行
- 类比React组件的componentDidMount 先上后下、先内后外
装饰器执行演示
```typescript function d1(target: Function) { console.log(‘———————-d1类装饰器——————————-‘) console.log(target) console.log(typeof target) }
function d2(target: any, name: string) { console.log(‘———————-d2属性装饰器——————————-‘) console.log(typeof target, name) }
function d3(target: any, name: string, descriptor: PropertyDescriptor) { console.log(‘———————-d3访问器装饰器——————————-‘) console.log(typeof target, name) console.log(descriptor) } function d4(target: any, name: string, descriptor: PropertyDescriptor) { console.log(‘———————-d4方法装饰器——————————-‘) console.log(typeof target, name) console.log(descriptor) } function d5(target: any, name: string, index: number) { // name 是当前参数所在的方法 console.log(‘———————-d5参数装饰器——————————-‘) console.log(typeof target, name) console.log(index) }
@d1 class MyClass { @d2 static property1: number
@d2 a: number
@d3 get b() { return 1 } @d3 static get c() { return 2 }
@d4 public method1(@d5 x: number, @d5 y: number) {} @d4 public static method2() {} }
```shell---------------d2属性装饰器---------------------object a---------------d3访问器装饰器---------------------object b{get: [Function: get],set: undefined,enumerable: false,configurable: true}---------------d5参数装饰器---------------------object method11---------------d5参数装饰器---------------------object method10---------------d4方法装饰器---------------------object method1{value: [Function],writable: true,enumerable: true,configurable: true}---------------d2属性装饰器---------------------function property1---------------d3访问器装饰器---------------------function c{get: [Function: get],set: undefined,enumerable: false,configurable: true}---------------d4方法装饰器---------------------function method2{value: [Function],writable: true,enumerable: true,configurable: true}---------------d1类装饰器---------------------[Function: MyClass] { method2: [Function] }function
TS装饰器原理
- 装饰器本质就是一个函数
利用被装饰目标的prototype来对其进行扩展
function Path(baseUrl: string) {return function (target) {target.prototype.$Meta = {baseUrl: baseUrl}}}
接口interface
interface中可以用分号或者逗号分割每一项,也可以什么都不加
对象接口
//接口可以用来描述`对象的形状`,少属性或者多属性都会报错interface Speakable {speak(): voidname?: string //?表示可选属性}let speakman: Speakable = {speak() {}, //少属性会报错name,age, //多属性也会报错}
行为抽象
//接口可以在面向对象编程中表示为行为的抽象interface Speakable {speak(): void}interface Eatable {eat(): void}//一个类可以实现多个接口class Person implements Speakable, Eatable {speak() {console.log('Person说话')}eat() {}}class TangDuck implements Speakable {speak() {console.log('TangDuck说话')}eat() {}}
任意属性
//无法预先知道有哪些新的属性的时候,可以使用 `[propName:string]:any`,propName名字是任意的interface Person {readonly id: numbername: string[propName: string]: any}let p1 = {id: 1,name: 'lc',age: 10,}
接口继承
interface Speakable {speak(): void}interface SpeakChinese extends Speakable {speakChinese(): void}class Person implements SpeakChinese {speak() {console.log('Person')}speakChinese() {console.log('speakChinese')}}
可索引接口
interface UserInterface {[index: number]: string}let arr: UserInterface = ['value1', 'value2']console.log(arr)interface UserInterface2 {[index: string]: string}let obj: UserInterface2 = { name: 'value' }
类接口
interface Speakable {name: stringspeak(words: string): void}class Dog implements Speakable {name!: stringspeak(words: string) {console.log(words)}}let dog = new Dog()dog.speak('汪汪汪')
interface vs type
相同点
- 描述一个对象或者函数
- 二者都可以被继承
- interface和type都能够被扩展,
不同点
- type可以定义基本类型别名、联合类型、元组,但是interface无法定义
- interface声明可以合并(多次声明),type不行
- interface可以拓展type,但是type不能继承interface,type可以使用&联合类型来实现类似的功能
扩展(extends)与交叉类型(intersection types)
- interface 可以 extends,type 不允许 extends和implement的,type可以通过交叉类型实现 interface 的extends行为。
- 并且两者并不是相互独立的,也就是说 interface 可以 extends type , type也可以与 interface类型交叉。
推荐使用interface无法实现的功能再用type
命名空间
- 在代码量较大的情况下,为了避免命名空间冲突,可以将相似的函数、类、接口放置到命名空间内
- 命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象,命名空间内通过export向外导出
- 命名空间是内部模块,主要用于组织代码,避免命名冲突
export namespace zoo {export class Dog {eat() {console.log('zoo dog')}}}export namespace home {export class Dog {eat() {console.log('home dog')}}}let dog_of_zoo = new zoo.Dog()dog_of_zoo.eat()let dog_of_home = new home.Dog()dog_of_home.eat()
原理
- 其实一个命名空间本质上一个对象,它的作用是将一系列相关的全局变量组织到一个对象的属性
namespace Numbers {export let a = 1export let b = 2export let c = 3}var Numbers;(function (Numbers) {Numbers.a = 1Numbers.b = 2Numbers.c = 3})(Numbers || (Numbers = {}))
可合并
namespace k1 {let a = 10export var obj = {a,}}namespace k1 {let b = 20export var obj2 = {b,}}namespace k2 {console.log(k1)}
类型声明
- 声明文件可以让我们不需要将JS重构为TS,只需要加上声明文件就可以使用系统
- 类型声明在编译的时候都会被删除,不会影响真正的代码
- 关键字 declare 表示声明的意思,我们可以用它来做出各种声明:
declare let b: {v: number}export default g
TypeScript 模块解析策略
TypeScript 现在使用了与 Node.js 类似的模块解析策略,但是 TypeScript 增加了其它几个源文件扩展名的查找(.ts、.tsx、.d.ts),同时 TypeScript 在 package.json 里使用字段 types来表示查找路径
