typescript 学习笔记
一. 环境安装
使用 npm 全局安装 typescript
npm install -g typescript
安装完成后就可以使用 tsc 命令了
tsc --version
二. Hello world
Hello world 之前需要创建 tsconfig.json, 在其中定义编译时候的 严格类型检查编译选项, 和 Javascript 的版本.
{
"compilerOptions": {
"strict": true,
"target": "ES5"
}
}
创建 helloworld.ts
const greeting = 'hello world'
console.log(greeting)
编译并运行, 这里会使用当前工作目录的 tsconfig.json . 编译之后会生成 helloworld.js
tsc helloworld.ts
node helloworld.js
# hello world
你也可以指定某个位置 tsconfig.json 进行编译
// Compiles the TypeScript project located at the specified path.
tsc -p .\tsconfig.json
三. Javascript 基础知识
- 变量声明
- var:
没什么特别的,需要注意的是,var 声明变量的时候如果没有赋值, 那么默认值为 undefined - let:
它和 var 的区别在于,它是有一个块级作用域 - const:
javascript 的常量,声明的时候必须赋一个初始值,并且不能再次赋值。同样也是块级作用域 - 然后什么是块级作用域呢,块级作用域其实就是字面意思。块指的就是代码块,而变量的作用域的含义就是这个变量的可访问区间。 请看下面的例子:
- var:
let a = 1
const b = 2
{
const b = 3
const c = 4
console.log(a, b, c)
}
console.log(a, b)
数据类型
Undefined
Null
Boolean
String:
Javascript 使用 UTF-16 编码来表示一个字符。 UTF-16 以两个字节作为一个编码单元,每个字符使用一个编码单元或两个编码单元来表示。在底层存储中,字符串是由零个或多个16位无符号整数构成的有序序列。在获取字符串长度时,返回的是字符串中包含的编码单元的数量。Number
Javascript 使用双精度64位浮点数来表示数字, 因此所有数字本质上都是浮点数。Number.MAX_SAFE_INTEGER 是 Number 的最大值Symbol
每一个 Symbol 的值都是唯一且不可改变的。 Symbol() 函数可以想象成全局唯一标识符生成器, 每次调用都会生成一个完全不同的 Symbol 值。Object:
对象是属性的集合, Javascript 现在作为函数式编程语言,函数本身也是数据。 所以 Object 的属性只有两种,一种是数据属性, 另一种是存取器属性, 即 getter setter
字面量
- Null字面量
- null
- 布尔字面量
- true
- false
- 字符串字面量
- ‘’
- 数字字面量
- 二进制字面量 0b 或 0B开头, 只包含数字 0 或 1
- 八进制字面量 0o 或 0O 开头, 只包含数字 0 至 7
- 十进制
- 十六进制以 0x 或 0X 开头,可以包含数字 0 至 9 ,小写 a 至 f 以及 大写 A 至 F
- 数组字面量 []
- 对象字面量 {}
- 模板字面量 ``
- Null字面量
四. Javascript 进阶一点的知识
BigInt:
- BigInt 能表示任意精度的整数,尤其是大于 Number.MAX_SAFE_INTEGER 的数, 这也是引入 BigInt 的原因。
- 严格模式下 BigInt 和 Number 的值总是不相等
- 直接将 BigInt 转为 Number 可能会损失精度
展开运算符 …
- … 可以将 数组, 对象 还有函数参数进行展开
解构赋值
- 数组解构 let [a, b] = [1, 2]
- 对象结构 let {a, b} = { a: 1, b: 2}
可选链运算符
- 如果 obj 或者 fn 的值位 undefined 或 null 那么求值结果为 undefined 否则为调用的结果
- 可选的静态属性访问 obj?.prop
- 可选的计算属性访问 obj?.[expr]
- 可选的函数调用或方法调用 fn?.()
空值合并运算符
- 如果左边的值为 undefined 或 null 时返回右边的值, 否则返回左边的值
// 展开运算符的用法
let f: { a: number, b: number, c: number } = { a: 1, b: 2, c: 3 }
let g: { a: number, b: number, c: number } = { ...f }
console.log(g)
let d: [number, number, number] = [1, 2, 3]
let e: [number, number, number] = [...d]
console.log(e)
function h(a: any, b: any, c: any) {
console.log(a, b, c)
}
h(...e)
// 解构赋值的用法
let [i, j] = [1, 2]
console.log(i, j)
let { k, l } = { k: 1, l: 2 }
console.log(k, l)
// 可选链的用法
let obj: {
props: string,
method: Function
} = {
props: 'prop',
method: () => { console.log('method') }
}
console.log(obj?.props)
let expr: 'props' = 'props'
console.log(obj?.[expr])
obj?.method()
// 空值合并用法
console.log(null ?? undefined)
五. Typescript 类型系统的基础知识
- 类型注解
- 类型注解用来明确标识类型。 写起来很简单就是冒号加某种具体类型。 用法也很简单,就是写在被修饰的实体之后。 示例如下:
const text: string = 'text'
// 字面量也是类型之一, 所以下面的写法一点问题也没有。
const text: 'text' = 'text'
类型检查
- 严格类型检查
- 非严格类型检查
原始类型
- boolean
- string
- number
- bigint
- symbol 与 unique symbol, 这里之所有有 unique symbol 是因为 symbol 是没有字面量的, unique symbol 相当于 symbol 的字面量。你可能会想 symbol 的字面量不就是字符串么,但再仔细一想就能发现他们之间的区别。
- undefined 和 null
- void , void 只能用在函数,用来表示函数没有返回值。
枚举类型
- 数值型枚举, 成员的值为 number 的枚举就是数值型枚举, 不显式的声明枚举的值, 那么值就会为 0 ,1,2,3
- 字符串枚举, 成员的值为 string 的枚举就是数值型枚举
- 异构型枚举, 成员的值既有 number 又有 string 的枚举
- 常量枚举成员与计算枚举成员, 成员的值可以是确定的值, 也可以是一个函数。
- 联合枚举类型, 枚举不光自己是一个类型, 枚举的成员也是类型
- const 枚举类型, const枚举类型将在编译阶段被完全删除,并且在使用了const枚举类型的地方会直接将const枚举成员的值内联到代码中
// 数值型枚举
enum NumberEnum = {
First,
Second,
Third
}
const numberEnumImpl :NumberEnum = NumberEnum.First
// 字符串枚举
enum StringEnum = {
First: 'First',
Second: 'Second',
Third: 'Third'
}
const stringEnumImpl :StringEnum = StringEnum.First
// 联合枚举类型
const stringEnumImpl :StringEnum.Frist = StringEnum.First
字面量类型
- boolean
- string
- number
- bigint
- 枚举成员类型,即联合枚举类型
顶端类型
- any, 可以看成是所有类型的父类,一个标识符注明是 any 时,编译器就不会对他进行类型检查
- unknown, 同样可以看成是所有类型的父类,但是 unknown 不能向下转型,也就是他不像 any 一样,可以看成是任意类的实例。
尾端类型
- never, 可以看成是所有类型的子类。 也就是说他可以赋值给任意类型。更准确的来说 never 不能包含任意可能的值。any null undefined 也不行, 一个变量的类型如果是 never 那它真的是啥也不是。
数组类型
- 简便数组类型表示法, 数据类型 加 数组字面量 比如 number[], (string|number)[]
- 泛型数据类型表示法,Array 加 ElTypeName 表示某种类型, 实际应用中就是这样 Array
- 只读数组
const a :number[] = [1, 2, 3]
const b: Array<number> = [1, 2, 3]
const a: readonly number[] = [1, 2, 3]
const a: ReadonlyArray<number> = [1, 2, 3]
- 元组类型
- 元组类型是数组的子类, 它和数组的区别在于元组的长度是固定的。其实也并不完全是, 因为元组支持 剩余元素 写法, 当元组的长度不确定时 它的 length 的类型为 number, 其他情况下, 总是一个 number 的字面量。 然后元组类型的数据可以赋值给数组, 但是数组类型的数据不能赋值给元组
- 可选元素
- 剩余元素, 剩余元素表示,除了前面的几个元素,后面剩下的元素都是某种类型
const enumImpl :[number, number] = [1, 2]
const enumOptionalImpl :[number, number?] = [1]
const enumResidueImpl :[number, ...string[]] = [1, 2, 3]
- 对象类型
- Object, javascript 万物皆对象, 严格来说其实是万物都有 prototype 属性,所以只要有 prototype 属性,基本上可以视为 Object 类型的实例。
- object, javascript 万物皆对象,但是 typescript 有原始数据类型和非原始数据类型之分, 他们的差别有点像 java 的基本数据类型类型和包装数据类型的区别。但是因为 javascript 万物皆对象, 原始数据类型也是 Object, 所以为了区别原始数据类型和非原始数据类型增加了一个 object 类型。 这个类型用来表示非原始数据类型。
- 对象类型字面量, 通过对象类型字面量定义对象的类型
- 弱类型
- 多余属性, 实际应用中 有可能出现没有声明类型的属性, 被称为多余属性。
// Object 源码
interface Object {
/**
* The initial value of Object.prototype.constructor
* is the standard built-in Object constructor.
*/
constructor: Function;
/**
* Returns a string representation of an object.
*/
toString(): string;
/**
* Returns a date converted to a string using the
* current locale.
*/
toLocaleString(): string;
/**
* Returns the primitive value of the specified object.
*/
valueOf(): Object;
/**
* Determines whether an object has a property with
* the specified name.
* @param v A property name.
*/
hasOwnProperty(v: PropertyKey): boolean;
/**
* Determines whether an object exists in another
* object's prototype chain.
*/
isPrototypeOf(v: Object): boolean;
/**
* Determines whether a specified property is enumerable.
* @param v A property name.
*/
propertyIsEnumerable(v: PropertyKey): boolean;
}
// object
let obj:object;
// 下面的语句没有问题
obj = { x: 0, y: 0}
// 下面的语句会报错
// console.log(obj.x)
// 下面的语句也会报错 null 不是 object
// obj = null
// 只能访问 Object.prototype 的方法
console.log(obj.valueOf())
// 对象字面量
let obj: {
x: number,
// 计算属性的类型
['y']:number,
// 默认属性类型any
z,
// 可选的属性
a?: number,
// 只读属性
readonly b: number,
// 方法签名
sayHello(name: string) :string,
} = {
x:1,
['y']: 2,
b: 3,
z: null,
sayHello: (name)=>{
return name
},
}
- 函数类型
- 常规参数类型, 正常情况下 在参数的后面写冒号加某个类型,即可定义参数类型
- 可选参数类型, 和元组的可选元素写法相似, 类型后面加个问号就ok
- 默认参数类型, 如果参数有默认值, 那么编译器会自动计算参数类型, typescript 不允许一个参数既是可选参数又是默认参数
- 剩余参数类型
- 数组类型的剩余参数, 如果剩余参数的类型都一样, 那么 :number[] 这样就OK, 这时数组类型的剩余参数
- 元组类型的剩余参数, 如果剩余参数的类型都不一样
- 解构参数类型
- 返回值类型
- 函数类型字面量,因为javascript 万物皆对象,所以可以像定义一个对象的类型一样定义函数的类型,然后先定义函数的类型, 再创建函数对象的实例。 这里说实话有点夸张…
- 调用签名, 除了可以用函数字面量的方式定义函数的类型,因为函数本身也是对象, 所以可以用对象字面量的方式定义函数的类型, 然后因为对象具有多个属性, 所以 typescript 定义的函数类型不光可以执行方法体,还能有其他的函数属性。
- 构造函数, javascript 中构造函数也是一种特殊的函数,这部分有点不好理解, 你们自行查资料吧
- 重载函数, 就算没有 class ,函数本身也可以多态。 你可以创建多个参数列表不同,但名称相同的函数,通过传入不同的参数执行不同的方法。这里属实太夸张了。
- 函数中 this 的类型, 通常是 any。 但是编译器在某些选项打开的情况下会阻止 any 类型的使用。 这种情况需要在函数参数列表声明参数的类型
// 通常情况
function add(x: number, y=0, z?: number, ...args: number[]):void{
}
// 通过函数字面量定义
let add :(x: number, y:any, z?: number, ...args: number[]) => void
add = (x, y, z, a, b) => {}
// 通过调用签名定义, 可以看到, 这个函数 add 多了一个 times 属性
let add: {
(x: number, y: any, z?: number, ...args: number[]): void,
times: number
} = fuc
function fuc(x:number, y:number, z?:number) {
console.log(x, y, z)
}
fuc.times = 1
接口
- 因为 javascript 万物皆对象, 所以对象类型字面量,类,甚至函数,它们几个其实本质都一样。然后接口和其他几个的主要区别是, javascript 其实没有接口,在编译生成后的代码中不包含任何的接口代码。而且接口只能表示对象,不能表示原始类型。
- 接口声明 interface 关键字
- 属性签名, 当 interface 定义的接口为某个对象时,接口定义的类型声明为属性签名, 即对象属性的类型声明
- 调用签名, 当 interface 定义的接口为某个函数时,接口定义的类型声明为调用签名,即这个函数调用时的类型参数,参数列表,及返回值
- 构造签名, 当 interface 定义的接口为某个类时,接口内部定义的构造函数
- 方法签名, 当 interface 定义的接口为某个类时,接口内部定义的方法
- 索引签名, javascript 支持使用索引去访问对象的属性,比如正常 a.b 这样是直接访问属性, 但是 a[‘b’] 通常也可以, 就是因为 javascript 自动为这个对象创建了一个索引。 typescript 可以给这个索引创建类型声明,称为索引签名。一个接口只能创建一种索引签名。 而索引签名一共有两种,分为字符串索引签名和数值索引签名
- 可选属性与方法
- 只读属性和方法
- 接口的继承, 接口可以使用 extends 关键字进行继承。
类型别名
- 类型别名能够定义一个类型别名, 如 type AliasName = Type。 要注意避免出现递归别名的情况
类
- 类的定义
- 类声明, class className {}
- 类表达式, const classNmae = class {}
- 成员变量
- 成员函数
- 成员存取器,
- get
- set
- 索引成员
- 成员可访问性
- public
- protected
- private
- 构造函数
- 参数成员, 在类的构造方法中,给参数一个访问控制修饰符, 该形式参数就成了参数成员,进而会被声明未类的成员变量
- 继承
- typescript 的继承是单继承
- 子类必须调用父类的构造函数,否则将不能正确地实例化
- typescript 允许接口继承类
- 实现接口, 一个类可以实现多个接口
- 静态成员, static 关键字,typescript 的静态成员可以使用访问控制修饰符进行修饰
- 抽象类和抽象方法
- this 类型
- 类类型
- 类的定义
六. Typescript 类型系统的高阶一点的知识
- 泛型, 泛型其实很简单, 就是用 <> 包裹一个单词 T 表示泛型,你也可以使用其他的标识符, 这个 T 表示某一个类型,它和 any 和 unknow 类似,都可以有任意类型的含义。 不同的是它这个任意类型只在代码编写时是任意的,编译之后,运行时,这个泛型就会被某个具体的类型所代替。 好比传入参数是一个泛型 T ,那么在代码块作用域中,所有的 T 的类型都是传入参数时的那个 T 的类型。然后在运行时传入参数有了确定的值, 并可以推算出类型, 所有的 T 也就有了确定的类型。
- 局部类型
- 联合类型
- 交叉类型
- 索引类型
- 映射对象类型
- 条件类型
- 内置工具类型
- 类型查询
- 类型断言
- 类型细化
七. 深入了解 Typescript 类型系统
- 子类型兼容性
- 赋值兼容性
- 类型推断
- 类型放宽
- 命名空间
- 模块
- 外部声明
- 使用声明文件
- 模块解析
- 声明合并
本作品采用《CC 协议》,转载必须注明作者和本文链接