1.基础
1.1 介绍
- TypeScript是一种由微软开发的开源,跨平台的编程语言, 最终会编译为JavaScript代码.
- 2012年10月微软发布了首个公开版的TypeScript, 2013年6月19日,在经历了预览版之后微软正式发布了正式版的TypeScript.
- TepyScript的作者是安德斯.海尔斯伯格,C#的首席架构师.它是开源和跨平台的编程语言.
- TepyScript扩展了JavaScript的语法,所以任何现有的JavaScript程序都可以运行在TepyScript环境中.
- TepyScript是为大型应用的开发而设计, 并且可以编译为JavaScript.
- TepyScript是JavaScript的一个超集.主要是提供了类型系统和对Es6+ 的支持,它由Microsoft,代码开源于GitHub上.
1.2 TepyScript的特点
- 始于JavaScript,归于JavaScript
- 强大的类型系统
- 先进的JavaScript
1.3 安装
- npm install -g typescript
- tsc -V
1.4 ts代码编译
(1) html中直接引入了ts的文件,浏览器会报错. 但谷歌浏览器可以运行
如果.ts文件中只有单纯的js代码所有浏览器都可以运行.
(2) 如果ts文件中有ts代码, 那么需要把这个ts文件编译成js文件, 手动编译: tsc xxx.ts
(3) ts 中形参有类型修饰, 编译成js后无类型修饰. let会被编译成var
(4) vscode自动编译: tsc
outDir: 编译后,js文件的输出目录
strict: false 取消严格模式
启动: 终端
vscode将会自动的 把ts文件 编译成js文件
tsc
tsc
(5)
tsc是TypeScript compiler
(6)
测试ts代码有两种方案: 第一个,通过webpack搭建一个ts环境
第二个, 安装ts-node, 使用ts-node aaa.ts, 之后编译再跑 node.
npm install ts-node -g
npm install tslib @types/node -g
1.5 ts新特性
- 类型的注解: 一种轻量级的为函数或变量添加的约束
- 接口: interface
- 类
- 通过webpack打包ts
npm init -y 产生package.json
tsc --init 产生tsconfig.json
yarn add -D typescript
yarn add -D webpack webpack-cli
yarn add -D webpack-dev-server
yarn add -D html-webpack-plugin clean-webpack-plugin
yarn add -D ts-loader
yarn add -D cross-env
2.语法
2.1 基础类型
javaScript 基本类型: number, string, boolean, null, sambal, BigInt, undefined
JavaScript 引用类型: function, object, Date, ......
ts中变量一开始是什么类型,后期赋值时,只能用当前类型的数据进行赋值,是不允许用其他类型的数据赋值给当前这个变量的.
(1) 布尔类型: boolean, let flag: boolean =true
(2) 数字类型: number,
* 十进制: 10
* 二进制: 0b1010
* 八进制: 0o12
* 十六进制: 0xa
(3) 字符串类型: string
(4) null和undefined类型: 值与变量类型不同不能赋值, 但是undefined null, 可以把这两个赋值给其他类型的变量.
let abc = null 如果不指定abc的类型,那么abc将会被推导出来为any类型.
let abc:string = null
let abc:string = null 如果开启严格模式,这行将会报错
let aa: null = null 如果指定为null类型,那么只能赋值为null
(5)
数组定义: let arr1: number[] = [10,20,30,40] 这种写法是推荐的.
数组定义: let arr2: Array<number> = [100,200,300] 这种写法是不推荐的.
(6) object类型:
cosnt info = {
name: "Tom",
age: 18
}
这个info变量的类型默认是会被推导出来的, 这样info.name 是可以取出来值的.
可以指定为object类型
const info:object = {
name: "Tom",
age: 18
}
但是取值, info.name 是取不出来值的, 虽然指定的是object类型,但是不代表有name这个属性.
对象类型:
function test(point: {x:number,y:number, z?:number}) {}
注: ----------以上是JavaScript里边有的类型, 以下是TS特有的类型----------
(7) any类型:
let aa: any = 234 any类型的变量可接收任意类型的数据,也可以赋值给任意类型的变量
let arr: any[] = [111,'weefg',null]
let name 不指定类型将会被推断为any类型
函数的参数不指定类型也会被推断为any类型
在开发中,通常情况下可以不写返回值的类型,不写的话他会自动推导的,他会把我们return 后边这里的返回值类型,来作为我们函数的返回值类型的.
函数对参数的类型有限制,对参数的个数也是有限制的
(8) unknown类型: 它表示不确定的类型
unknown类型变量只能赋值给any和unknown类型
(9) void类型: 函数类型没有返回值类型时,用void.
function showMag():void {
console.log("hello,nasdfiooasf,tom,jack")
}
当一个函数没有返回值的时候, 默认返回undefined的.
这里返回undefined和null, 这个void都是可以接收的.
let dd: void = undefined, 定义一个void的变量,可以接收一个undefined的值,但是意义不是很大.
(10) never类型: 表示永远不会返回值的类型, 比如一个函数:
如果一个函数中是一个死循环或者抛出一个异常,name这个函数会返回东西吗?
不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型.
(11) 元组类型定义后,里面的数据必须和定义数组的时候类型是一致的 ---> (类型和个数一致)
* 元组类型: let arr3: [string, number, boolean] = ['tom', 100, true]
(12) 联合类型: number|string
(13) 字面量类型
"hello" 这个字符串也是可以作为一个类型的,叫做字面量类型
const msg:"hello" = "hello"
const num:123 = 123
字面量类型的意义,就是结合 联合类型
let align: 'left'|'right'|'center' = 'left'
(14) 字面量推导: 没指定类型时, 会将这个值的类型赋值给这个变量.
let ttt = 100
ttt = 'jack'
* let aaa
* console.log(aaa) aaa是any类型
* aaa= 999
* aaa = 'tom'
(15) 类型断言:
有时候TypeScript 无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions). 比如我们通过document.getElementById, TypeScript只知道该函数会返回HTMLElement,但并不知道它具体的类型.
在一个宽泛的类中断言他是某个小类
if( (<string>msg).length ) {}
或者
if( (msg as string).length ) {}
如果断定msg一定是有值的一定是一个非空的不是null也不是undefined
if(msg!.length) {} 叹号保证msg一定有值, 这个就是非空类型断言
!!操作符: 将一个其他类型转换成boolean类型,类似于Boolean(变量)的方式. 类似于
(16) 类型别名
type yyy = {
x:number,
y:number,
z?: number,
}
type ttt = string | number | boolean
function test(id: ttt) {}
(17) 可推导的this类型???
(18) 枚举类型:
枚举类型是为数不多的TypeScript特性之一
枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
枚举允许开发者定义一组命名常量,常量可以是数字,字符串类型;
枚举类型: enum Color{
RED=10,
GREEN,
BLUE
}
let color: Color = Color.RED
通过下标访问枚举 console.log(Color[3])
里面的每个数据值都可以叫元素,每个元素都有自己的编号,编号是从0开始的,一次的递增加1
2.2 类型缩小
(1)什么是类型缩小呢?
类型缩小的英文是Type Narrowing
我们可以通过类似于typeof padding === "number"的判断语句,来改变TypeScript的执行路径
在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为缩小
而我们编写的typeof padding === "number"可以称之为类型保护(type guars)
(2)常见的类型缩小(类型保护)有如下几种:
typeof
平等缩小
instanceof
in
等等
(3) 具体说明
typeof的使用:
type IDType = number | string
function printID(id: IDType) {
console.log(id)
if(typeof id === 'stirng') {
console.log(id.toUpperCase())
}
else {
console.log(id)
}
}
平等类型缩小 ==, !=, switch:
type Dir = 'left'|'right'|'top'|'bottom'
function printDir(d: Dir) {
if(d == 'left') { console.log(d) }
}
instanceof, 某一种实例是不是某一种类型:
function printTime(time: string|Date|Reg) {
if(time instanceof Date) {
console.log(time.toUTCString)
}
else { }
}
in :
type Fish = {
name: string,
age: number,
swimming: () => void
}
type Dog = {
running: () => void
}
function walk (animal: Fish | Dog) {
if("swimming" in animal) {
animal.swimming()
}
else {
animal.running()
}
}
const fish:Fish = {
swimming() {
console.log("swimming")
}
}
walk(fish)
2.3 类
2.3.1 基础
(1) 定义
class Person {
name:string,
age: number,
gender: string,
constructor(name: string='tom', age: number=16, gender: string='女') {
this.name = name
thia.age = age
thia.gender = gender
}
sayHi(str: string) {
console.log(`我是${this.name}, 今年已经${this.age}岁了, 是个${this.gender}生`, str)
}
}
(2) 继承
* 子类,派生类 父类,基类,超类
class Student extends Person {
code: string
constructor(name: string, age: number, gender: string, code: string) {
super(name, age, gender)
this.code = code
}
sayHi() {
super.sayHi('呵呵呵')
console.log('我是学生类中的sayHi方法')
}
}
(3) 多态: 父子各个类中,针对相同的方法,会产生不同的行为
父类的类型创建子类的对象 const aa:Animal = new Dog('小黄')
父类型的引用 指向了 子类型的对象
function showRun(ttt: Animal) {
ttt.run()
}
showRun(dog1)
showRun(pig1)
2.3.2 类的成员修饰符
(1) 修饰符, 按照访问范围划分
public: 修饰的是在任何地方可见,公有的属性或方法. 不写时默认是public.
private: 修饰的是仅在同一类中可见, 私有属性或私有方法.
不想被外部实例对象调用, 也不想被子类继承的 属性或方法.
protected 修饰的是仅在类和其子类中可见,受保护的属性或方法.
不想被外部实例对象调用, 子类可以继承的 属性或方法
readonly : 只读的,不能被修改, 类中的普通方法也不能修改这个值, 实例对象点 也不能修改这个值.
构造函数可以修改这个值
如果这个值是对象类型的,那么可以更改里边的某个属性
构造函数中的name参数, 一旦使用readonly进行修饰后, 那么该name参数可以叫参数属性
构造函数中的name参数, 一旦使用readonly进行修饰后, 那么Person类中就有了一个name的属性成员
构造函数中的name参数, 一旦使用readonly进行修饰后, 外部也是无法修改类中的name属性的
构造函数中的name参数, 一旦使用public进行修饰后, 那么Person类中 也有了一个name的属性成员---这个可以被修改
构造函数中的name参数, 一旦使用private进行修饰后, Person类中就有了一个私有的name属性了
构造函数中的name参数, 一旦使用protected进行修饰后, Person类中就有了一个 受保护的name属性了
(2) 修饰符, 按照归属划分
* 实例方法 或 实例属性 : 定义在构造函数中, 实例化时可以通过传参来赋值
* 类方法 或 类属性: 定义在类中, 属于类的, 类名点调用 也可以 对象点调用. 只要更改各个实例化出来的对象访问该 属性或方法 都会被更改.
* 静态方法 或 静态属性 static: 既不访问类属性和类方法, 也不访问实例属性和实例方法. 用类名点调用
(3) 存取器: get() set()
class Person {
fname: string,
lname: string,
constructor(fname: string, lname: string) {
this.fname = fname
this.lname = lname
}
get fullname() {
return this.fname + '_' + this.lname
}
set fullname(value) {
let arr = value.split('_')
this.fname = fname
this.fname = lname
}
}
2.3.3 抽象类
- 包含抽象方法, 也可以包含实例方法, 抽象类不能被实例化
- 接口里只能有抽象方法, 不能有实例方法, 接口也不能被实例化.
abstract class Animal {
abstract eat()
}
2.4 接口
- 通过类型(type)别名来声明对象类型
- type MyType = {name: string, age: number}
- 另外一种方式声明对象类型: 接口interface
- 接口是对象的属性和方法(行为) 的抽象. 接口是一种类型,是一种规范, 是一种规则, 是一个能力,是一种约束
1.对象中使用 接口指定类型
interface Iperson {
readonly id: number,
name: string,
age: number,
sex?: string,
}
const person: IPerson = {
id: 1,
name: 'tom',
age: 11,
sex: '女'
}
2. 索引类型 中 使用接口
interface Taa {
[index: number] :string
}
const frontLanguage: Taa = {
0: "HTML",
1: "CSS",
2: "JavaScript",
3: "Vue",
"abc": "cba"
}
3.函数的类型 (可选参数放在最后。 默认值参数可以随便放,最好也放在后边。 剩余参数。 函数重载)
函数重载: 定义同名的函数, 函数名相同,参数个数或类型不同。
function add(num1: number, num2: number): number;
function add(num1: string, num2: string): string;
function add(num1:any, num2: any): any {
return num1 + num2
}
cosnt result = add(20, 11)
如果没有匹配到我们的重载函数,也是不能去调用到我们的实现的函数的.
在函数重载的过程当中,实现函数是不能直接被调用的. 这个是TypeScript语法规定好的.
(1) 在JavaScript开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数 也可以作为返回值进行传递).
function foo(n: number) { console.log(n) }
function bar(myfoo: (num:number) => void) {
myfoo(33)
}
bar(foo)
(2) 函数中 使用接口来约束类型
interface ISearchFunc {
(source:string, subString:string):boolean
}
const searchString: ISearchFunc = function (source:string, subString:string):boolean {
return source.search(subString) > -1
}
4. 类类型
(1)
class Person {
name: stirng = "tom"
sing() {}
}
function(p: Person) {}
(2) 类中使用接口来指定类型
interface IFly {
fly()
}
interface ISwim {
swim()
}
class Person implements IFly,ISwim {
fly() {
console.log('我会飞了, 我是超人')
}
swim() {
console.log('游泳')
}
}
总结: 类可以通过接口的方式,来定义当前这个类的类型
类可以实现一个接口, 类也可以实现多个接口, 要实现接口中的方法
5. 接口可以继承其他的多个接口
interface IFly {
fly()
}
interface ISwim {
swim()
}
interface IMyFlyAndSwim extends IFly, ISwim {
}
class Person3 implements IMyFlyAndSwim{
fly() {
console.log('我飞了')
}
swim() {
console.log('游泳')
}
}
总结: 接口和接口之间叫继承extend
接口和类之间叫实现implements
类和类之间叫继承extend
6. 交叉类型
交叉类型也可以把 两个类型给结合在一起
type wtype = number & string
interface Iaaa {
swimming: () => void
}
interface Ibbb {
flying: () => void
}
type Myccc2 = Iaaa | Ibbb
type Myccc = Iaaa & Ibbb
const obj: Myccc = {
swimming() {},
flying() {}
}
7. interface 和 type的区别
我们发现interface和type都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢?
(1)
如果是定义非对象类型,通常推荐使用type, 比如Direction,Alignment,一些function; 联合类型尽量 也用 type来定义.
(2)
如果是定义对象类型,那么他们是有区别的:
interface可以重复的对某个接口来定义属性和方法
而type定义的是别名,别名是不能重复的
interface IFoo {
name: string
}
interface IFoo {
age: number
}
定义接口的时候如果你名称相同, 它相当于会把我们所有属性做一个合并的
const foo: IFoo = {
name: "why",
age: 18
}
8. 字面量赋值 (擦除操作)
interface IPerson {
name: string
age: number
height: number
}
const p: IPerson {
name: "tom",
age: 18,
height: 1.99,
address: "广州市"
}
一下这种方式就不会报错
const info = {
name: "tom",
age: 18,
height: 1.99,
address: "广州市"
}
const p:IPerson = info
2.5 泛型 (类型的参数化)
2.5.1 泛型函数
function sum<Type33>(arg: Type33, p: Type33): Type33 { return arg }
把用到Type33的地方提取到前边,进行一个参数化而已.
调用者告诉我Type是一个什么类型
sum<number>(20, 30) 可以简写成sum(20, 30)
编译器会自动的根据我们传入的 值的参数来自动的进行 "参数推断"
Array<Type> 表示Type类型的数组
Type[] 表示Type类型的数组
2.5.2 泛型接口
interface GenericIdentity<Type> {
(arg: Type): Type;
}
2.5.3 泛型类
class GNumber<Type> {
value: Type
add: (x:Type, y: Type) => Type
}
2.5.4 泛型约束
(1)
interface Leng {
length: number
}
function test<Type extends Leng>(arg: Type):Type {
return arg
}
(2)泛型约束中使用类型参数
<Key extends keyof Type>
function getProperty<Type, Key extends keyof Type>(obj:Type, key: Key) {
return obj[key]
}
let x = {
a: 1,
b: 2,
c: 3,
d: 4,
}
getProperty(x, 'a')
2.5.5 泛型中使用类类型
function test<Type>( c: {new ():Type} ):Type {
return new c()
}
2.5.6 keyof类型操作符
type Point = { x: number
type P = keyof Point
const p1:P = 'x'
const p2:P = 'y'
2.5.7 typeof类型操作符
let s = "hello"
let n: typeof s
n = 'world'
根据值 取他的类型
2.5.8 索引访问类型
(1)
type Person = {
age: number
name: string
alive: boolean
}
type age = Person["age" | "name"]
type aaa = Person[keyof Person]
type p = 'alive' | 'name'
type bbb = Person[p]
(2)
const MyTest = [
{name: 'tom', age: 11},
{name: 'kobe', age: 7},
{name: 'jack', age: 9},
]
type mm = typeof MyTest[number]
const t:mm = {name: 'Jams', age: 13}
type age = typeof[number]['age']
const a:age = 13
2.5.9 条件类型
(1)
interface Animal {
live(): void
}
interface Dog extends Animal {
woof(): void
}
type aaa = Dog extends Animal ? number:string
(2)
type NameOrId<T extends number | string> = T extends number? IdLabel : NameLabel
function createLabel<T extend number | string> (idOrName: T): NameOrId<T> {
}
(3) 条件类型约束
type MessageOf<T> = T["message"] 错误
type MessageOf<T extends {message: unknown}> = T["message"] 正确
interface Email {
message: string
}
interface Dog {
bark():void
}
type EmailMessageContents = MessageOf<Email>
const emc: EmailMessageContents = "balabala..."
type DogMessageContents = MessageOf<Dog>
const dmc: DogMessageContents = 'error' as never
(4) 条件类型内推理???
(5) 分布式条件类型???
3.其他
类型的查找: 之前我们所有的TypeScript中的类型,几乎都是我们自己编写的,但是我们也有用到一些其他的类型.
const imageEl = document.getElementById("image") as HTMLImageElement
大家是否会奇怪,我们的HTMLImageElement类型来自哪里呢?甚至是document为什么可以有getElementById的方法呢?
其实这里就涉及到TypeScript 对类型的管理和查找规则了:
我们这里先给大家介绍另外一种TypeScript文件: .d.ts文件
我们之前编写的TypeScript文件 都是.ts文件, 这些文件最终会输出.js文件, 也是我们通常编写代码的地方
还有另外一种文件.d.ts文件 它是用来做类型的声明(declare).
它仅仅用来做类型检测,告知typescript我们有哪些类型.
(1) 内置类型声明:
是TypeScript自带的,帮助我们内置了JavaScript运行时的一些标准化API的声明文件
包括比如Math,Date等内置类型,也包括DOM API,比如Window Document等
(2) 外部定义类型声明:(第三方库)
方式一: 在自己库中进行类型声明(编写.d.ts文件), 比如axios
方式二:通过社区的一个公有库DefinitelyTyped存放类型声明文件
该库的github地址: https
该库查找声明安装方式的地址: https:
比如我们安装react的类型声明: npm i @types/react -D
(3) 自定义类型声明:
自己建.d.ts文件
declare module 'lodash' {
export function join(arr: any[]):void {}
}
declare 可以声明 模块/函数/类/
声明文件:
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'
declare module '*.vue' {
import { DefineComponent } from 'vue'
const ...
}
声明命名空间:
declare namespace $ {
export function ajax(setting: any):any
}
就是一个方法,可以注入到类, 方法, 属性参数上来. 扩展类,属性,方法,参数的功能.
(1) 类装饰器 (无法传递参数)
function fun1(target: any) {
target.prototype.userName = "张三"
}
@fun1
class Person1 {
}
let p1 = new Person1 {}
p1.userName会打印张三
(2) 装饰器工厂
function fun2(options: any) {
return function(target: any) {
target.prototype.userName = options.name
target.prototype.userAge = options.age
}
}
@fun2({
name: '张三',
age:18
})
class Person2 {}
let p2 = new Person2()
console.log(p2)
(3) 装饰器组合 (就是多个装饰器)
如果有多个装饰器装饰某个类的时候,会先从上至下执行所有的装饰器工厂,
拿到所有真正的装饰器, 然后再从下至上的执行所有的装饰器:
(4) 属性装饰器
function fun4(target: any, attr: any) {
}
class Person4 {
@fun4
userName: string
}
(5) 方法装饰器
function fun5(target: any,propertyKey: string, descriptor: PropertyDescriptor) {
}
class Person5 {
@fun5
sing() {
console.log('singing')
}
}
注: 用之前要在config.ts里边先配置
"compilerOptions" : {
"experimentalDecorators": true
}