TypeScript基础笔记(主要注意点)
- 类型
- 语法扩展
- 工具
强类型、弱类型:区别在于变量能否被赋值为其它类型
动态类型、静态类型:区别在于动态类型变量的类型是在编译阶段确定的,而静态类型变量类型是在运行时确定的
1. 基本数据类型
void 0返回一个undefined,undefined在JavaScript中不是保留字,可以当做变量使用
string、number、boolean、undefined、null、symbol、any、void、unknown、never
unknown可以被赋值为任意类型,unknown只能被赋值给any和unknown类型,unknown不能进行属性读取操作
let bool: boolean = false
let boolObj: Boolean = new Boolean(1) // 基本包装类型是函数,typeof object
let boolVal: boolean = Boolean(1) // 转型函数 typeof boolean
console.log(typeof bool)
console.log(typeof boolObj)
console.log(typeof boolVal)
let decLiteral: number = 10 // 十进制
let hexLiteral: number = 0xf00d // 十六进制以0x开头
let binaryLiteral: number = 0b0101 // ES6中二进制以0b开头
let octalLiteral: number = 0o070 // ES6中八进制以0o开头
let notANumber: number = NaN
let infinity: Number = Infinity
let str = `hell world${decLiteral}`
let sym: symbol = Symbol('hello')
let u: undefined = undefined
let n: null = null
// 设置strictNullChecks: false,可以把undefined和null赋值给其它类型,因为以上两种类型都是其它类型的子类型
str = null
decLiteral = undefined
// void类型表示没有返回值的函数,(在JavaScript中,undefined不是保留字,可以作为变量进行赋值,以void来代替其作为没有返回值的函数的返回值,void只能被赋值为undefined和null
let notReturn = (): void => {}
// any类型,任意类型
// 任意类型可以被赋值为其它类型,对任意类型的任何操作都将返回任意类型,如果一个变量没有声明为特定类型,那么他会被推断为任意类型,兜底类型,可以访问它的任意属性和方法
// never类型表示永远也不存在的类型,比如抛出异常的函数和无限循环函数,以及箭头函数表达式类型
const error = () => { throw new Error('err')}
const endless = () => {
while (true) {}
}
// 以上两个函数的返回值都是never类型
type Foo = string | boolean | number
const controlFlowAnalysisWithNever = (foo: Foo) => {
if (typeof foo === 'string') {
} else if (typeof foo === 'boolean') {
} else if (typeof foo === 'number') {
} else {
const bar: never = foo
}
}
// 防止联合类型中有一个类型没有被实现
// 类型推论
// 如果一个变量没有指定明确的类型,TypeScript会根据类型推论推断出一个类型
// 如果一个变量声明时没有指定类型,也没有赋值,那么他会被推论为any类型
2. 引用类型
1. tuple(元组)
tuple 可以越界添加,但不能越界访问
const tuple: [string, number] = ['hamling', 28]
tuple.push('hello') // 虽然能push成功
// tuple[2] 不能越界读取
2. enum (枚举)
// 1.数字枚举
// 值从0依次递增,可以设置初始值,如果设置了初始值,后续值会基于它递增
// 数字枚举可以通过键值互相访问
enum Directions {
North,
South,
East = 4,
West
}
// 2.字符串枚举
// 每一个值必须是字符串字面量或者另一个枚举的成员
// 字符串枚举在键值相同的情况下互相访问,否则不能互相访问
enum Status {
PENDING = 'PENDING',
FULFILLED = 'FULFILLED',
REJECTED = 'hello'
}
console.log(Status.REJECTED)
// console.log(Status.hello) // 不能访问
// 3.常量枚举
// 通过const关键字修饰的枚举,常量枚举会使用内联语法,不会为枚举类型编译生成任何JavaScript
const enum Color {
Red,
Green,
Blue
}
console.log(Color.Red === 0)
// 4.异构枚举
// 枚举成员是数字和字符串的混合
// 异构枚举可以反向映射,键值键可以相互访问
enum E {
A,
B,
C = 'C',
D = 4
}
// 常数项和计算所得项
// 编译时计算(静态)
// 运行时计算(动态)后面的成员必须进行初始化
enum F {
A,
B = 'blue'.length,
C = 3 + 1
}
console.log(F.B)
4. 函数和数组
数组类型定义的三种方式:类型+[]、数组泛型(Array)和接口定义。
// 数组类型
const arr1: number[] = [1, 2, 3]
const arr2: Array<string> = ['hello', 'world']
interface NamesArray {
[index: number]: string | number;
[key: string]: any; // 数字索引必须兼容字符串索引
}
// 函数类型
// 函数定义的几种形式
export function add (x: number, y: number): number {
return x + y
}
let add1: (x: number, y: number) => number = add
type Add = (x: number, y: number) => number
let add2: Add = add
interface Add1 {
(x: number, y: number): number;
}
let add3: Add1 = add2
interface SearchFunc {
(source: string, substring: string): boolean;
}
let search: SearchFunc = (source, substring) => source.search(substring) !== -1
// 函数重载
function getTotal (...rest: string[]): string
function getTotal (...rest: number[]): number
function getTotal (...rest: string[] | number[]): string | number {
const first = rest[0]
if (typeof first === 'string') return rest.join('')
return (rest as number[]).reduce((prev, cur) => prev + cur)
}
function reverse (x: string): string
function reverse (x: number): number
function reverse (x: string | number): string | number {
if (typeof x === 'string') return x.split('').reverse().join('')
return Number(x.toString().split('').join(''))
}
5. 接口
interface 接口描绘对象的形状;抽象类的行为,具体行为通过类去实现
interface IPerson {
name: string // 必选属性
age?: number // 可选属性
readonly id: number // 只读属性
[prop: string]: string | number | undefined // 任意属性,任意属性的类型为其他类型的联合类型
}
// 混合类型
interface Lib {
(): void;
version: string;
doSomething(): void;
}
function getLib(version: string) {
const lib: Lib = (() => { }) as Lib
lib.version = version
lib.doSomething = () => { }
return lib
}
6. 类
ES5通过构造函数实现类,通过原型实现继承
类(Class)定义了一件事物的抽象特点,包含它的属性和方法
对象(Object)类的实例,通过new生成
面向对象(OOP)的三大特性:封装、继承、多态
封装(Encapusulation)将对数据的操作细节隐藏起来,只暴露对外的接口。外部调用端不需要(也不可能知道)细节,就能通过对外的接口来访问对象,同时也保证了外界无法任意更改对象内部的特点
继承(inheritance)子类继承父类,子类除了拥有父类的所有特性外,还能又有更具体的特性
多态(Polymorphism)由继承而产生相关的不同类,对同一个方法可以有不同的响应
存储器(getter和setter)用于改变属性的读取和响应行为。
修饰符(Modifier)修饰符是一些关键字,用于限定成员和类型的性质。
抽象类(Abstract Class)抽象类是提供其他其他类继承的基类,抽象类不允许被实例化。抽象类的抽象方法必须在子类中被实现。
接口(interface)不同类之间的共有的属性或方法,可以抽象成一个接口,接口可以被类实现(implements)。一个类只能继承自另一个类,但可以实现多个接口
TypeScript可以使用三种修饰符分别是public、private和protected。
public修饰的属性和方法是公有的,可以在任何地方被访问到,默认所有的方法和属性都是公有的;
private修饰的属性和方法是私有的,不能在声明它的类的外部被访问;
protected修饰的属性和方法是受保护的,它和private类似,区别是它在子类中也是允许被访问到。
abstract用于定义抽象类和其中的抽象方法,抽象类不允许被实例化,抽象类中的抽象方法必须被子类实现
类实现接口。实现(implements)是面向对象中一个重要的概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共同点特性,这时就可以把特性提取成接口,用implements关键字来实现。这个特性大大提高了面向对象的灵活性。
接口继承接口、接口继承类。接口继承类和接口继承接口没有本质的区别,但构造函数、静态属性和静态方法是无法继承的(私有属性存疑)。
class Dog {
name: string // 实例属性必须声明才能赋值,也可以在构造函数参数中通过public声明
readonly id: number = 10010
constructor (name: string, public age: number) {
this.name = name
this.age = age
}
private eat () {
}
}
let dog = new Dog('wangwang', 10)
// 类成员修饰符
// 默认情况下类实例属性和原型方法默认都是通过public修饰符修饰的
// private 私有成员,只能在类中使用,不能在实例和子类中使用
// protect 受保护成员,只能在类中和子类中使用,不能在实例中使用
// readonly 只读属性
// static 静态属性 可以被子类继承
// private 修饰constructor,该类既不能被实例化也不能被继承
// protect 修饰constructor,该类不能被实例化但可以被继承(基类)
// 抽象类只能被继承,不能被实例化,抽象方法必须被子类实现
abstract class Person {
constructor (public name: string) {
this.name = name
}
eat () {}
abstract sayHi(): void
}
class Student extends Person {
constructor (name: string) {
super(name)
}
sayHi () {}
}
class Teacher extends Person {
sayHi () {}
}
const persons = [new Student('std'), new Teacher('tea')]
persons.forEach(item => item.sayHi())
class Flow {
step1 () {
return this
}
step2 () {
return this
}
}
let flow = new Flow()
flow.step1().step2()
class WorkFlow extends Flow {
next () {
return this
}
}
let workFlow = new WorkFlow()
workFlow.step1().next().step2().next()
interface Alarm {
alert(): void;
}
class Door {}
class SecurityDoor extends Door implements Alarm {
alert () {}
}
// 一个类可以继承多个接口
interface Light {
lightOff(): void;
lightOn(): void;
}
class Car implements Alarm, Light {
alert () {}
lightOff () {}
lightOn () {}
}
// 接口继承接口
interface LightableAlarm extends Alarm {
lightOn(): void
}
// 接口继承类
class Point {
x: number
y: number
// private pp: number
constructor (x: number, y: number) {
this.x = x
this.y = y
}
}
interface Point3D extends Point {
z: number;
}
let p: Point3D = { x: 1, y: 1, z: 1 }
// 为什么接口可以继承类?
// 通过class声明类的时候除了创建一个类,还会创建一个类型
// Point除了可以当做类来使用,同样可以当做类型来使用
// Point当做类型使用,不包含构造函数和静态成员
// 声明合并
// 函数合并 -> 重载
interface Alarm {
price: number;
alert(): void;
}
interface Alarm {
weight: number;
}
let alarm: Alarm = {
price: 10,
weight: 12,
alert() { }
}
// 类合并同接口合并一样
// 接口中的方法与类型一样
interface Rect {
width: number;
}
interface Rect {
height: number;
}
let rect: Rect = {
width: 100,
height: 100
}
7. 字符串字面量和类型别名
// 类型别名
type Name = string
type NameResolver = () => string
type NameOrResolver = Name | NameResolver
function getName (x: NameOrResolver): string {
if (typeof x === 'function') {
return x()
}
return x
}
// 字符串字面量
// 字符串字面量用来约束取值只能是某几个字符串中的一个
type eventName = 'click' | 'mousemove' | 'scroll'
function handleEvent (ele: Element, event: eventName) {}
8. 泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。在函数内部使用泛型变量的时候,由于事先不知道他是哪种类型,所以不能随意操作它的属性或方法。这时,我们可以对泛型进行一些约束。
泛型类、泛型接口和泛型函数,泛型可以传入默认值。
// 不预先确定类型,具体的类型在使用的时候确定
// 泛型可用于类、函数和接口定义
function log<T> (value: T): T {
console.log(value)
return value
}
log('hello')
log([1, 2])
type Log = <T>(value: T) => T
const log1: Log = log
function createArray<T> (x: T, length: number): T[] {
let result: T[] = []
for (let i = 0; i < length; i++) {
result.push(x)
}
return result
}
function swap<T, U> (tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
// 泛型约束接口
interface MyLog<T> {
(value: T): T;
}
interface CreateArray<T> {
(x: T, y: number): T[];
}
interface LengthWise {
length: number;
}
function loggingIdentify<T extends LengthWise> (arg: T): number {
return arg.length
}
// 函数和类可以轻松地支持多种类型,增强程序的可扩展性
// 不必写多条函数重载,冗长的联合类型声明,增强代码的可维护性
// 灵活地控制类型之间的约束
// 泛型类不能约束静态成员
class GenericNumber<T> {
zeroNo: number;
add: (x: T, y: T) => T
}
// 类型之间相互约束
function coypFields<T extends U, U> (target: T, source: U): T {
for (let id in source) {
target[id] = (source as T)[id]
}
return target
}
9. 内置对象
JavaScript中有很多内置对象,它们可以直接在TypeScript中当做定义好了的类型。内置对象是根据标准在全局作用域存在的对象。这里的标准是指ECMAScript和其他环境(比如DOM)的标准
ECMAScript标准提供的内置对象有:Boolean、Date、RegExp、Error等
BOM和DOM的内置对象有:Document、HTMLElement、Event、NodeList等
TypeScript核心库定义文件定义了浏览器所需用到的类型,并且预置在TypeScript中
// 内置对象
// ECMAScript内置对象
let e: Error = new Error('error')
let bool: Boolean = new Boolean(1)
let reg: RegExp = /[a-z]/
let date: Date = new Date()
// DOM内置对象
// Document、HTMLElement、Element、NodeList、Event
let body: HTMLElement = document.body
let lists: NodeList = document.querySelectorAll('li')
document.addEventListener('click', (e: MouseEvent) => {})
// 内置对象的定义文件都定义在TypeScript核心定义文件中
// node使用TypeScript,需要安装第三方定义文件@types/node
10. 类型断言
类型断言(TypeAssertion)可以用来指定一个值的类型
- 联合类型可以被断言为一个具体的类型
- 父类可以被断言为一个子类任意类型
- 任意一个类型可以被断言为any
- any类型也可以被断言为任意类型
并不是任意一个类型可以断言为任意一个类型
具体来说,若A类型兼容B类型,那么A类型可以被断言为B类型,B类型也可以被断言为A类型
Cat包含了Animal中所有属性,那么就可以看作Cat extends Animal,换言之就是说Animal兼容Cat(少的兼容多的)
当Animal兼容Cat时,那么他们就可以互相断言了
这样设计其实也很容易理解:
允许animal可以断言为Cat,是因为父类可以被断言为子类
允许cat断言为Animal,是因为既然子类拥有父类的属性和方法,那么断言为父类,获取父类的属性或调用父类的方法,就不会有任何问题,故子类可以被断言为父类
非空断言:x!将从x的值域中排除undefined和null
确定赋值断言,断言x会被赋值
let x!: number
console.log(x * 2)
function initialize () {
x = 10
}