第一章
什么是TypeScript?
TypeScript是JavaScript的超集,具有可选的类型并可以编译为纯JavaScript。从技术上讲TypeScript就是具有静态类型的 JavaScript 。那么,向JavaScript添加静态类型的原因是什么?
我想原因至少有三个:
- 您可以避免经典的错误 ‘undefined’ is not a function.
- 在不严重破坏代码的情况下,重构代码更容易。
- 使大型、复杂的应用程序源码更易阅读。
实际上,一项研究表明,TypeScript可以检测到所有JavaScript错误的15%。
动态类型的自由特性经常会导致错误,这些错误不仅会降低程序员的工作效率,而且还会由于增加新代码行的成本增加而使开发陷入停顿。因此,JavaScript无法合并类型以及编译时缺乏错误检查,使它不适合作为企业和大型代码库中服务器端代码。
安装 Typescript
npm install -g typescript
使用 tsc 全局命令
// 查看 tsc 版本
tsc -v
// 编译 ts 文件
tsc fileName.ts
第二章
原始数据类型
Boolean
Null
Undefined
Number
BigInt
String
Symbol
// 布尔值 (可以用boolean(xxx)来做判断是否为真)
let isDone: boolean = false;
console.log(isDone)
if( Boolean(1)){
console.log(1)
}else{
console.log(0)
}
// 接下来来到 number,注意 es6 还支持2进制和8进制,让我们来感受下
let age: number = 10
let binaryNumber: number = 0b1111
// 之后是字符串,注意es6新增的模版字符串也是没有问题的
let firstName: string = 'viking'
let message: string = `Hello, ${firstName}, age is ${age}`
// 还有就是两个奇葩兄弟两,undefined 和 null
let u: undefined = undefined
let n: null = null
// 注意 undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:
let num: number = undefined
// object
let obj: object = {
name: 'xiaoji',
age: '18'
}
// 空值 (void代表没有返回值,不能return值)
function Name(): void {
console.log('我叫小鸡')
}
Name()
any 类型 (任意类型)
let notSure: any = 4
notSure = 'maybe it is a string'
notSure = true
// 在任意值上访问任何属性都是允许的:
notSure.myName
// 也允许调用任何方法:
notSure.getName()
第三章
Array 和 Tuple(元祖)
//最简单的方法是使用「类型 + 方括号」来表示数组:
let arrOfNumbers: number[] = [1, 2, 3, 4]
//数组的项中不允许出现其他的类型:
//数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:
arrOfNumbers.push(3)
arrOfNumbers.push('abc')
// 元祖的表示和数组非常类似,只不过它将类型写在了里面 这就对每一项起到了限定的作用
let user: [string, number] = ['viking', 20]
//但是当我们写少一项 就会报错 同样写多一项也会有问题
user = ['molly', 20, true]
let NumArr: number[] = [1,2,3,4]; // 数组里必须为number
let NumStr: string[] = ['1','2','3','4']; // 数组里必须为字符串
let Arr: any[] = [1,'A',{name:1}] // 这里是任意类型
let typeArr: [string | number] = [1] // 数组里只能有一个元素,且类型为string 或 number
let agmArr: [string,number] = ['1',1] // 根据下标来对应值 (这里注意:多出下标会报错,但是可以用方法push,其中一种是OK的)
// 数组泛型 (泛型是可以自定义的,<T>) // 满足其中一项即可
let ArrGen: Array<number | String | object> = [{name:'1'},{name:2},2,'3',{b:'bb'}]
第四章
interface (接口)
// 我们定义了一个接口 Person ( 定义接口首字母大写I 是主要区分对否为接口定义)
interface Iperson {
name: string; // name 属性只能是字符串
age: number; // age 属性只能是number
}
//有时我们希望不要完全匹配一个形状,那么可以用可选属性:
interface Iperson {
age?: number; // 这个属性可有可无
}
// 有时候我们希望自定义属性 ([] 可以自定义属性,但是类型必须是所有子属性的集合)
interface Iperson {
[key: string]: string;
}
//接下来还有只读属性,有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性
interface Iperson {
readonly id: number; // 只能读取不能修改
}
viking.id = 9527 // 这里会报错
第五章
函数(到了咱们最长用的函数)
// (参数约束,和输出约束)
function add(x: number, y: number): number {
return x + y
}
// interface 描述函数
// 定义一个接口
interface ISum {
(x: number, y: number): number
}
// 定义一个函数
const sum = (x: number, y: number) => {
return x + y
}
// 用ISum 接口验证 sum 函数
const sum2: ISum = sum
// 设置默认参数值
function buildName(firstName: string, lastName: string = 'Cat') {
// 这里判断lastName参数是否存在
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat'); // 有值显示值
let tom = buildName('Tom'); // 没有值则显示默认值
// 这里我们实现一个给数组添加元素的方法
function push(array, ...items) {
// items 是除了第一个参数后所有的参数集合类型为数组
items.forEach(function(item) {
array.push(item);
});
return array
}
// 类型定义为任何类型
let arr: any[] = [];
// 调用我们写好的方法
push(arr, '1','2', '3');
console.log(arr)
第六章
类型推论,联合类型 和 类型断言
/**
* ==========
* 联合类型
* ==========
*/
// 我们只需要用中竖线来分割两个
let numberOrString: number | string
// 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
numberOrString.length // 这里会报错因为length 不是number 和 string 的共有属性
numberOrString.toString() // 这个是共有属性,所以不会报错
/**
* ==========
* 类型断言
* ==========
*/
// 这里我们可以用 as 关键字,告诉typescript 编译器,你没法判断我的代码,但是我本人很清楚,这里我就把它看作是一个 string,你可以给他用 string 的方法。
function getLength(input: string | number): number {
const str = input as string
if (str.length) {
return str.length
} else {
const number = input as number
return number.toString().length
}
}
/**
* ==========
* 类型守卫
* ==========
*/
// 和断言思路差不多,只是拿typeof作为判断 typescript 在不同的条件分支里面,智能的缩小了范围,这样我们代码出错的几率就大大的降低了。
function getLength2(input: string | number): number {
if (typeof input === 'string') {
return input.length
} else {
return input.toString().length
}
}
第七章
class 类(构造函数)
class Animal {
name: string;
// constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
constructor(name: string) {
this.name = name
}
run() {
return `${this.name} is running`
}
}
const snake = new Animal('lily')
// 继承的特性
class Dog extends Animal {
bark() {
return `${this.name} is barking`
}
}
const xiaobao = new Dog('xiaobao')
console.log(xiaobao.run())
console.log(xiaobao.bark())
// 这里我们重写构造函数,注意在子类的构造函数中,必须使用 super 调用父类的方法,要不就会报错。
// extends Class的继承
class Cat extends Animal {
constructor(name) {
super(name) // 调用父类的constructor(x, y)
console.log(this.name)
}
arr = [1,2,3] // 静态资源s
run() {
return 'Meow, ' + super.run()
}
}
const maomao = new Cat('maomao')
console.log(maomao.run())
// class 类型检测都在下面 我就不举例说明了,根据上面的class 去做检查
// 在方法前添加该修饰符
// public (公用,可以暴露给外面用)属性前或方法前添加改字段
// private (私有的,不可以暴露给外面)
// protected (受保护的) 这个只有自己的子女(方法)后才可以使用或访问
// readonly (只能读不能修改)
第八章
枚举
// 枚举
enum direction{
top,
righ,
bottom,
left
}
// 如果不赋值,默认返回下标(可以通过下标获取或者直接.属性获取)
console.log(direction.top) // 0
console.log(direction[0]) // 'top'
enum direction2{
top = 10,
righ,
bottom,
left
}
// 如果给第一个赋值任何下标如:10 ,那么它一下的都会递增+1
console.log(direction2.righ) // 11
console.log(direction[12]) // 'bottom'
// 这里我们模拟后台返回的值,去做判断
// 后台返回的值
let adminVal = 'down'
enum direction3{
top = 'up',
righ = 'right',
bottom = 'down',
left = 'left'
}
function keys (data) {
let val
for (const item in data) {
console.log(item)
if(data[item] == adminVal){
val= item
}
}
return val
}
// 调用keys 方法
keys(direction3)
第9章
泛型
// 泛型解决了什么问题?,当类型不确定的时候,由外部解决! 或者 用联合类型去约束,
// 当我们的需求是传入什么,返回什么,并检查类型
function echo(arg) {
return arg
}
const result = echo(123)// 这时候我们发现了一个问题,我们传入了数字,但是返回了 any 并不是我们想要的
// 使用泛型解决此问题
function echo1<T>(arg: T): T {
return arg
}
const result1 = echo1(123) // 根据类型推论 检查类型
// const result2: string = echo1(123) // 这里就会报错,返回的是number类型,不能赋值给string类型
// 泛型也可以传入多个值 也可以使用联合
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
const result3 = swap(['string', 123])
// 上例中,泛型 T 不一定包含属性 length,我们可以给他传入任意类型,当然有些不包括 length 属性,那样就会报错
// [] {},number 等去约束 一个泛型只能传入的类型
function echoWithArr<T>(arg: T[]): T[] {
console.log(arg.length)
return arg
}
echoWithArr([1,2,3])
//echoWithArr('str') // 我们会发现,这样只能输入数组参数 不符合我们的预期
// 接口约束 <T extends 接口 >
interface IWithLength {
length: number; // 返回的类型必须为number
}
// 注意 extends 关键字 来使用接口
function echoWithLength<T extends IWithLength>(arg: T): T {
console.log(arg.length)
return arg
}
echoWithLength('str') // 这个是可以使用的 因为string 有length 属性
const result4 = echoWithLength({length: 10}) // 我们发现这个也是可以使用的,只要有length 且 类型必须为number 关键字就可以使用
const result5 = echoWithLength([1, 2, 3]) // 这个是可以使用的 因为数组 有length 属性
// 定义一个队列构造函数
class Queue {
private data = [];
push(item) {
return this.data.push(item)
}
pop() {
return this.data.shift()
}
}
const queue = new Queue()
queue.push(1)
queue.push('str')
console.log(queue.pop().toFixed())
console.log(queue.pop().toFixed()) // 这里输出的时候会报错 (因为string类型没有toFixed方法)
//在上述代码中存在一个问题,它允许你向队列中添加任何类型的数据,
// 当然,当数据被弹出队列时,也可以是任意类型。在上面的示例中,看起来人们可以向队列中添加string 类型的数据,但是那么在使用的过程中,就会出现我们无法捕捉到的错误,
// 我们使用 泛型来封装一个队列,在创建实例的时候传入想要的类型
class Queue1<T> {
private data = [];
push(item: T) {
return this.data.push(item)
}
pop(): T {
return this.data.shift()
}
}
const queue1 = new Queue1<number>() // 决定他想要的类型 这样就完美的解决了以上问题
//泛型和 interface
interface KeyPair<T, U> {
key: T;
value: U;
}
let kp1: KeyPair<number, string> = { key: 1, value: "str"}
let kp2: KeyPair<string, number> = { key: "str", value: 123}
第10章
类型别名
// 类型别名 (type 关键字)
// 假如有多个方法多次用到同一个类型检测,那么就可以申明一个类名(相当于javascript 常量)
type fn = (x:number,y:number) => number
let fn1 = fn(1,2)
let fn2 = fn('a','b') // 这里会报错
// 字符串字面量类型
// 什么是字面量呢?就是定义好的常量类型必须和值全等不可修改
const str: 'name' = 'name'
// const str1: 'name1' = 'name2' // 因为不全等所以会报错
// 用别名定义了字符串,字面量类型
type colors = 'green' | 'red' | 'yellow' | 'black'
// 使用字面量类型
let bg: colors = 'black'
// 我们不难发现,别名和接口 形式好像差不多(其实它们是有区别的,下章补)
// 那么我们什么时候用别名,什么时候用接口呢?
// 组合形式的,采用别名(可以理解为快捷方式)
// 独特的的类型,采用interface
第11章
交叉类型
// 交叉类型 (也称为 & )
interface Iobj {
name: string,
}
// 用别名和交叉类型组合成新的类型
type Newobj = Iobj & {age: number}
// 使用新的别名
let Aobj: Newobj = {name: 'xiaoj',age:18}
第10章
内置类型
const a: Array<number> = [1,2,3]
// 大家可以看到这个类型,不同的文件中有多处定义,但是它们都是 内部定义的一部分,然后根据不同的版本或者功能合并在了一起,一个interface 或者 类多次定义会合并在一起。这些文件一般都是以 lib 开头,以 d.ts 结尾,告诉大家,我是一个内置对象类型欧
const date: Date = new Date()
const reg = /abc/
// 我们还可以使用一些 build in object,内置对象,比如 Math 与其他全局对象不同的是,Math 不是一个构造器。Math 的所有属性与方法都是静态的。
Math.pow(2,2)
// DOM 和 BOM 标准对象
// document 对象,返回的是一个 HTMLElement
let body: HTMLElement = document.body
// document 上面的query 方法,返回的是一个 nodeList 类型
let allLis = document.querySelectorAll('li')
//当然添加事件也是很重要的一部分,document 上面有 addEventListener 方法,注意这个回调函数,因为类型推断,这里面的 e 事件对象也自动获得了类型,这里是个 mouseEvent 类型,因为点击是一个鼠标事件,现在我们可以方便的使用 e 上面的方法和属性。
document.addEventListener('click', (e) => {
e.preventDefault()
})
//Typescript 还提供了一些功能性,帮助性的类型,这些类型,大家在 js 的世界是看不到的,这些类型叫做 utility types,提供一些简洁明快而且非常方便的功能。
// partial,它可以把传入的类型都变成可选
interface IPerson {
name: string
age: number
}
let viking: IPerson = { name: 'viking', age: 20 }
type IPartial = Partial<IPerson>
let viking2: IPartial = { }
// Omit,它返回的类型可以忽略传入类型的某个属性
type IOmit = Omit<IPerson, 'name'>
let viking3: IOmit = { age: 20 }
文档
https://www.typescriptlang.org/docs/handbook/tsconfig-json.html // 官方文档
https://www.tslang.cn/docs/handbook/typescript-in-5-minutes.html // 中文文档
https://ts.xcatliu.com/basics/primitive-data-types.html // 阮一峰教程
https://www.runoob.com/typescript/ts-tutorial.html // 菜鸟教程