typescript 学习记录—进阶篇
类型别名
类型别名用来给一个类型起个新名字
type Name = string
type NameResolver = () => string
type NameOrResolver = Name | NameResolver
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n
} else {
return n()
}
}
字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个
type EventNames = 'click' | 'scroll' | 'mousemove'
function handleEvent(ele: Element, event: EventNames) {}
// right
handleEvent(document.getElementById('hello'), 'scroll')
// error
handleEvent(document.getElementById('world'), 'dbclick')
// index.ts(10,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.
ps: 类型别名与字符串字面量类型都是使用 type 进行定义
元组
数组合并了相同类型的对象,而元祖 (Turple) 合并了不同类型的对象
// 定义一对值分别为 string 和 number 的元祖
let tom: [string, number] = ['Tom', 25]
// 赋值与访问
let tom1: [string, number]
tom1[0] = 'Tom'
tom1[1] = 25
// 可以只赋值其中一项
let tom2: [string, number]
tom2[0] = 'Tom'
直接对元祖类型初始化或直接赋值时,需要提供所有元祖类型中指定的项
let tom3: [string, number]
tom3 = ['Tom']
// Property '1' is missing in type '[string]' but required in type '[string, number]'.
当添加越界的元素时,它的类型会被限制为元祖中类型
let tom4: [string, number]
tom4 = ['Tom', 25]
tom4.push('male')
tom4.push(355)
tom4.push(true)
// Argument of type 'true' is not assignable to parameter of type 'string | number'.
枚举
枚举类型用于取值被限定在一定范围内的场景,如:一周只能有七天等
enum Days {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
}
枚举成员会被赋值为从 0 开始递增的数字, 同时也会对枚举值到枚举名进行反向映射
enum Days {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
}
console.log(Days['Sun'] === 0) // true
console.log(Days['Mon'] === 1) // true
console.log(Days['Tue'] === 2) // true
console.log(Days[0] === 'Sun') // true
console.log(Days[1] === 'Mon') // true
console.log(Days[2] === 'Tue') // true
编译后的结果
;(function(Days) {
Days[(Days['Sun'] = 0)] = 'Sun'
Days[(Days['Mon'] = 1)] = 'Mon'
Days[(Days['Tue'] = 2)] = 'Tue'
Days[(Days['Wed'] = 3)] = 'Wed'
Days[(Days['Thu'] = 4)] = 'Thu'
Days[(Days['Fri'] = 5)] = 'Fri'
Days[(Days['Sat'] = 6)] = 'Sat'
})(Days || (Days = {}))
手动赋值同时未手动赋值的枚举会接着上一个枚举项递增
enum Days {
Sun = 7,
Mon = 1,
Tue,
Wed,
Thu,
Fri,
Sat
}
如果手动赋值的枚举项与手动赋值的重复了 , ts 是不会察觉到的
enum Days {
Sun = 3,
Mon = 1,
Tue,
Wed,
Thu,
Fri
}
console.log(Days['Sun'] === 3) // true
console.log(Days['Wed'] === 3) // true
console.log(Days[3] === 'Sun') // false
console.log(Days[3] === 'Wed') // true
// 当递增至 3 时 Wed 的值将 Sun 手动赋值的 3 覆盖了
编译结果
var Days
;(function(Days) {
Days[(Days['Sun'] = 3)] = 'Sun'
Days[(Days['Mon'] = 1)] = 'Mon'
Days[(Days['Tue'] = 2)] = 'Tue'
Days[(Days['Wed'] = 3)] = 'Wed'
Days[(Days['Thu'] = 4)] = 'Thu'
Days[(Days['Fri'] = 5)] = 'Fri'
Days[(Days['Sat'] = 6)] = 'Sat'
})(Days || (Days = {}))
手动赋值可以不是数字,可以使用类型断言让 tsc 无视类型检查
enum Days {
Sun = 7,
Mon = 1.5,
Tue,
Wed,
Thu,
Fri,
Sat
}
console.log(Days['Sun'] === 7) // true
console.log(Days['Mon'] === 1.5) // true
console.log(Days['Tue'] === 2.5) // true
console.log(Days['Sat'] === 6.5) // true
常数项和计算所得项
enum Color {
Red,
Green,
Blue = 'blue'.length
}
“blue”.length 就是一个计算所得项,上面的例子是不会报错的,但是如果紧接在后面的是未赋值的项,那么它会因此无法获得初始值而报错
enum Color1 {Red='red'.length , Green, Blue}
index.ts(120,33): error TS1061: Enum member must have initialize
常数枚举
常数枚举与普通枚举的区别是,常数枚举会在编译中删除,并且不能计算成员
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
]
编译结果
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]
外部枚举
外部枚举是使用 declare enum
定义的枚举类型
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
]
declare 定义的类型只会用于编译时的检查,编译结果中会被删除
编译结果
var directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
]
同时使用 declare
与 const
declare const enum Directions2 {
Up,
Down,
Left,
Right
}
let directions2 = [
Directions2.Up,
Directions2.Down,
Directions2.Left,
Directions2.Right
]
编译结果
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]
类
javascript 通过构造函数实现类的概念,通过原型链实现继承, ts 除了实现了所有 es6 中的功能外,并添加了一些新的功能
类的概念
- 类(Class):定义了一个事件特点,包含它的属性和方法
- 对象: 类的实例通过 new 生成
面向对象编程的三大特性: 封装、继承、多态
封装
将对数据的操作细节隐藏起来,只暴露对外的接口。使用端不需要知道实现细节,使用端无法任意更改对象内部数据
继承
子类继承父类。子类除了拥有父类的特性外,还有一些具体的属性
多态
由继承产生的相关的不同的类。 如: Cat、Dog 类, 继承自 Animal 类, 但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法
存取器
用以改变属性的读取和赋值行为
修饰符
使用一些关键字限定成员或类型的性质,如 public 等
抽象类
抽象类是供其他类继承的基类,抽象类不允许被实例化,抽象类中抽象方法必须在子类中实现
接口
不同类之间公有的属性或方法可以抽象成一个接口。接口可以被类实现。一个类智能继承另一个类,但是可以实现多个接口
类的定义
class Animal {
constructor(name) {
this.name = name
}
sayHi() {
return `my name is ${this.name}`
}
}
let a = new Animal('jack')
console.log(a.sayHi())
类的继承
使用 extends 关键字实现继承, 子类中使用 super 关键字来调用父类的构造函数和方法
class Cat extends Animal {
constructor(name) {
super(name)
console.log(this.name)
}
sayHi() {
return `Meow, ${super.sayHi()}`
}
}
存取器
class Animal {
constructor(name) {
this.name = name
}
get name() {
return 'jack'
}
set name(value) {
console.log('setter: ' + value)
}
}
let a = new Animal('kitty') // setter: Kitty
a.name = 'Tom' // setter: Tom
console.log(a.name) // Jack
静态方法
static 修饰符修饰的方法为静态方法,他们不需要进行实例化,而是直接通过类来调用
class Animal {
constructor(name) {
this.name = name
}
static isAnimal(a) {
return a instanceof Animal
}
}
let b = new Animal('jack')
Animal.isAnimal(b) // true
b.isAnimal(b) // TypeError: a.isAnimal is not a function
es6 继承 实现 class 原理 (es5)
- class 的构造函数必须使用 new 进行调用,普通构造函数不用 new 也可执行
- class 不存在变量提升,es5 中的 function 存在变量提升
- class 内部定义的方法不可枚举,es5 在 prototype 上定义的方法可以枚举
function inherit(subType, superType) {
subType.prototype = Object.create(superType.prototype, {
constructor: {
enumerable: false,
configurable: true,
writable: true,
value: subType
}
})
Object.setPrototypeOf(subType, superType)
}
// Object.setPrototypeOf 方法的作用与 `__proto__` 相同,用来设置一个对象的 prototype 对象,返回参数对象本身。该方法等同于下面的方法
function (obj, proto) {
obj.__proto__ = proto
return obj
}
es7 中类的用法
实例属性
es6 中属性使用 this.xxx 定义 es7 中可以直接在类里进行定义
class Animal {
name = 'Jack'
constructor() {
// ...
}
}
静态类型
class Animal {
static num = 42
constructor() {
// ...
}
}
修饰符 public、private、protected
public
public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性都是 public
class Animal {
public name
public constructor(name) {
this.name = name
}
}
let a6 = new Animal('Tom')
console.log(a6.name) // Tom
a6.name = 'Jack'
private
private 修饰的属性和方式是私有的,不能在声明它的类的外部访问(包括子类中)
class Animal {
private name
public constructor(name) {
this.name = name
}
}
let a7 = new Animal('Tom')
console.log(a7.name)
// error TS2341: Property 'name' is private and only accessible within class 'Animal'.
a7.name = 'Jack'
// error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// private 在子类中也是不允许访问的
class Cat7 extends Animal {
constructor(name) {
super(name)
console.log(this.name)
// error TS2341: Property 'name' is private and only accessible within class 'Animal'.
}
}
// 当构造函数被修饰为 private 时,它既不能被继承也不能被实例化
class Animal {
public name
private constructor(name) {
this.name = name
}
}
class Cat8 extends Animal {
constructor(name) {
super(name)
}
}
// TS2675: Cannot extend a class 'Animal'.Class constructor is marked as private.
let a8 = new Animal('Tom')
// TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration.
protected
修饰的属性和方法是受保护的,它和 private 类似区别是它在子类中是允许被访问的但是无法在实例中访问
class Animal {
protected name
public constructor(name) {
this.name = name
}
}
class Cat9 extends Animal {
constructor(name) {
super(name)
// protected 允许在子类中访问
console.log(this.name)
}
}
// 当构造函数被修饰为 protected 时,它既只能被继承也不能被实例化
class Animal {
public name
protected constructor(name) {
this.name = name
}
}
class Cat10 extends Animal {
constructor(name) {
super(name)
}
}
let a10 = new Animal()
// TS2674: Constructor of class 'Animal' is protected and only accessible within the class declaration.
readonly
readonly 只读属性关键字,只允许出现在属性声明或索引签名中
class Animal {
readonly name
public constructor(name) {
this.name = name
}
}
let a12 = new Animal('Tom')
console.log(a12.name) // Tom
a12.name = 'Jack'
// TS2540: Cannot assign to 'name' because it is a read-only property.
如果 readonly 与其他的访问修饰符同时存在的话 需要写在后面
class Animal {
// public readonly name
public constructor(public readonly name) {
this.name = name
}
}
抽象类(abstract)
abstract 用于定义抽象类和其中的抽象方法, 抽象类不允许被实例化
abstract class Animal {
public name
public constructor(name) {
this.name = name
}
public abstract sayHi()
}
let a14 = new Animal('Tom')
// error TS2511: Cannot create an instance of the abstract class 'Animal'
抽象类中的方法必须被子类实现
class Cat14 extends Animal {
public eat() {
console.log(`${this.name} is eating.`)
}
}
// error TS2515: Non - abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.
class Cat141 extends Animal {
public sayHi() {
console.log(`${this.name} hello.`)
}
}
let cat14 = new Cat141('Tom')
类的类型
class Animal {
name: string
constructor(name: string) {
this.name = name
}
sayHi(): string {
return `My name is ${this.name}`
}
}
let a15: Animal = new Animal('Tom')
console.log(a15.sayHi())
类与接口
类实现接口,实现是面向对象中的一个重要概念。一般讲一个类只能继承另一个类,有时候不同类之间可以有一些公有的特性,就可以把特性提取成接口,用 implements 关键字来实现,可以大大提高灵活性。
interface Alarm {
alert()
}
class Door {}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert')
}
}
class Car implements Alarm {
alert() {
console.log('Car alert')
}
}
一个类可以实现多个接口
interface Alarm {
alert()
}
interface Light {
lightOn()
lightOff()
}
class Car1 implements Alarm, Light {
alert() {
console.log('car alert')
}
lightOn() {
console.log('car light on')
}
lightOff() {
console.log('car light off')
}
}
接口继承接口
interface LightableAlarm extends Alarm {
lightOn()
lightOff()
}
接口继承类
class Point {
x: number
y: number
}
interface Point3d extends Point {
z: number
}
let point3d: Point3d = { x: 1, y: 2, z: 3 }
混合类型
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1
}
有时候一个函数还可以有自己的属性和方法
interface Counter {
(start: number): string
interval: number
reset(): void
}
function getCounter(): Counter {
let counter = <Counter>function(start: number) {}
counter.interval = 123
counter.reset = function() {}
return counter
}
let c = getCounter()
c(10)
c.reset()
c.interval = 5.0
泛型
泛型是指在定义函数。接口或类时,不预先指定类型,而是在使用时再指定
创建一个指定长度的数组同时每一项添加一个默认值
function createArray(length: number, value: any): Array<any> {
let result = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
// Array<any> 允许数组的每一项为任何值,但是预期的是数组的每一项都应是输入的 value 值
使用泛型实现
function createArray1<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray1<string>(3, 'x') // ['x', 'x', 'x']
T 用来指代任意输入的类型, 后面的参数类型 value:T 和 Array
上例中 createArray1
createArray1(3, 'x') // ['x', 'x', 'x']
多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
swap([7, 'seven']) // ['seven', 7]
泛型约束
于不知道是哪种类型,所以不能随意操作它的属性和方法
// 由于不知道是哪种类型,所以不能随意操作它的属性和方法
function loggingIdentity<T>(arg: T): T {
console.log(arg.length)
return arg
}
// error TS2339: Property 'length' does not exist on type 'T'.
通过接口可以对泛型进行约束,只允许掺入包含某个属性如上例中 length 属性的变量
interface Lengthwise {
length: number
}
function loggingIdentity1<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
loggingIdentity1(7)
// error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
多个类型参数之间的相互约束
function copyFields<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = (<T>source)[id]
}
return target
}
let x = { a: 1, b: 2, c: 3, d: 4 }
copyFields(x, { b: 10, d: 20 })
泛型接口
接口定义函数形状
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1
}
含有泛型的接口定义
nterface CreateArrayFunc {
<T>(length: number, value: T): Array<T>
}
let createArray2: CreateArrayFunc
createArray2 = function<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray2(3, 'x') // ['x','x','x']
进一步优化
interface CreateArrayFunc1<T> {
(length: number, value: T): Array<T>
}
let createArray3: CreateArrayFunc1<any>
createArray3 = function<T>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
createArray3(3, 'x')
泛型类
与接口类似
class GenericNumber<T> {
zeroValue: T
add: (x: T, y: T) => T
}
let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function(x, y) {
return x + y
}
// 泛型参数的默认类型
// 当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
function createArray4<T = string>(length: number, value: T): Array<T> {
let result: T[] = []
for (let i = 0; i < length; i++) {
result[i] = value
}
return result
}
声明合并
如果定义了两个相同的名字的函数、接口或类,那么他会合并成一个类型
函数的合并
function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(
x
.toString()
.split('')
.reverse()
.join('')
)
} else if (typeof x === 'string') {
return x
.split('')
.reverse()
.join('')
}
}
接口合并
接口的属性在合并时会简单的合并到一个接口中
// 1
interface Alarm {
price: number
}
interface Alarm {
weight: number
}
// 相当于
interface Alarm {
price: number
weight: number
}
// 2
// 合并的属性的类型必须是唯一的
interface Alarm {
price: number
}
interface Alarm {
price: number
weight: number
}
// 以为 price 类型重复所以不会报错
interface Alarm {
price: string
weight: number
}
// error TS2403: Subsequent variable declarations must have the same type. Variable 'price' must be of type 'number', but here has type 'string'
// 3
// 接口中方法的合并与函数的合并一样
interface Alram {
price: number
alert(s: string): string
}
interface Alarm {
weight: number
alert(s: string, n: number): string
}
// 相当于
interface Alram {
price: number
alert(s: string): string
weight: number
alert(s: string, n: number): string
}