认识TypeScript
TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码。
TypeScript的编译环境
TypeScript最终会被编译成JavaScript来运行,所以我们需要搭建对应的环境。我们需要在电脑上安装TypeScript,这样就可以通过TypeScript的Compiler将其编译成JavaScript。
# 安装命令
npm install typescript -g
# 查看版本
tsc --version
# 编译TS文件
tsc ./index.ts
TypeScript的运行环境
- 通过tsc编译TypeScript到JavaScript代码,在浏览器或者Node环境下运行JavaScript代码;
- 通过webpack,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器上;
- 通过ts-node库,为TypeScript的运行提供执行环境;
使用ts-node
# 安装ts-node
npm install ts-node -g
# 另外ts-node需要依赖 tslib 和 @types/node 两个包
npm install tslib @types/node -g
# 通过 ts-node 来运行TypeScript的代码
ts-node index.ts
tip:安装ts-node后,可以使用nodemon直接运行TS文件。
变量的声明
声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解(Type Annotation)。完整的声明格式如下:var/let/const 标识符: 数据类型 = 赋值;
JavaScript和TypeScript的数据类型
JavaScript类型 – number类型
数字类型是我们开发中经常使用的类型,TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为 number类型。
ES6新增了二进制和八进制的表示方法,而TypeScript也是支持二进制、八进制、十六进制的表示:
let num: number
num = 100 // 十进制
num = 0b110 // 二进制
num = 0o555 // 八进制
num = 0xf23 // 十六进制
JavaScript类型 – boolean类型
boolean类型只有两个取值:true和false。
JavaScript类型 – string类型
string类型是字符串类型,可以使用单引号或者双引号表示,同时也支持ES6的模板字符串来拼接变量和字符串。
JavaScript类型 – Array类型
const list: string[] = ['aaa', 'bbb']
const list: Array<string> = ['ccc', 'ddd']
JavaScript类型 – Object类型
object对象类型可以用于描述一个对象,如下写法,object表示一个通用的对象,这个时候我们不能从info中获取数据,也不能设置数据。这种写法不推荐。
const info: object = {
name: 'heart'
}
console.log(info.name) // 错误
info.name = '小王' // 错误
JavaScript类型 – Symbol类型
在ES5中,如果我们是不可以在对象中添加相同的属性名称的,比如下面的做法:
const persion = {
name: '小王',
name: '老王'
}
通常我们的做法是定义两个不同的属性名字,比如name1,name2。但是我们也可以通过symbol来定义相同的名称,因为Symbol函数返回的是不同的值:
const s1: symbol = Symbol('name')
const s2: symbol = Symbol('name')
const person = {
[s1]: '小王',
[s2]: '老王',
}
JavaScript类型 – null和undefined类型
在 JavaScript 中,undefined 和 null 是两个基本数据类型,在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型。
JavaScript类型 –函数
参数的类型注解
声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型。
函数的返回值类型
注解在函数列表的后面,和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型。
匿名函数的参数
对象类型
可选类型
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?。
TypeScript类型 - any类型
在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型(类似于Dart 语言中的dynamic类型)
- 我们可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法;
- 我们给一个any类型的变量赋值任何的值,比如数字、字符串的值;
TypeScript类型 - unknown类型
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。和any类型有点类似,但是unknown类型的值上做任何事情都是不合法的。因此,unknown在进行任何操作的时候,都需要进行类型校验(缩小),才能根据具体的类型操作。
let foo: unknown = 'aaa'
foo = 123 // 合法,unknown可以是任何类型
console.log(foo.length) // 不合法,unknown进行操作时,需要进行类型缩小
if( typeof foo === 'string' ) {
console.log(foo.length) // 合法,类型缩小后,类型确定了
}
TypeScript类型 - void类型
void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型,我们也可以显示的来指定返回值是void。
tip:我们可以将undefined赋值给void类型,也就是函数可以返回undefined。
// 应用场景:用来定义函数的类型
type FooType = () => viod
const foo: FooType = () => {}
TypeScript类型 - never类型
TypeScript类型 - tuple类型
tuple和数组区别:
- 数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中);
- 元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型。
const info: [string, number, number] = ['heart', 18, 1.88]
TypeScript语法细节
联合类型
类型别名
接口的声明
interface和type区别
交叉类型
// 交叉类型: 两种(多种)类型要同时满足
type NewType = number & string // 没有意义
// 应用场景
interface IKun {
name: string
age: number
}
interface ICoder {
name: string
coding: () => void
}
type InfoType = IKun & ICoder
const info: InfoType = {
name: "why",
age: 18,
coding: function() {
console.log("coding")
}
}
类型断言as
TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换
非空类型断言!
字面量类型
类型缩小
-
typeof
-
平等缩小(比如===、!==)
-
instanceof
-
in
// 1.typeof: 使用的最多
function printID(id: number | string) {
if (typeof id === "string") {
console.log(id.length, id.split(" "))
} else {
console.log(id)
}
}
// 2.===/!==: 方向的类型判断
type Direction = "left" | "right" | "up" | "down"
function switchDirection(direction: Direction) {
if (direction === "left") {
console.log("左:", "角色向左移动")
} else if (direction === "right") {
console.log("右:", "角色向右移动")
} else if (direction === "up") {
console.log("上:", "角色向上移动")
} else if (direction === "down") {
console.log("下:", "角色向下移动")
}
}
// 3. instanceof: 传入一个日期, 打印日期
function printDate(date: string | Date) {
if (date instanceof Date) {
console.log(date.getTime())
} else {
console.log(date)
}
}
// 4.in: 判断是否有某一个属性
interface ISwim {
swim: () => void
}
interface IRun {
run: () => void
}
function move(animal: ISwim | IRun) {
if ("swim" in animal) {
animal.swim()
} else if ("run" in animal) {
animal.run()
}
}
TypeScript函数类型
在JavaScript开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值进行传递)。我们可以编写函数类型的表达式(Function Type Expressions),来表示函数类型。
调用签名(Call Signatures)
构造签名 (Construct Signatures)
参数的可选类型
默认参数
剩余参数
从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。
函数的重载
在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用;一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现。
tip:如果需求可以通过联合类型或重载来实现,尽量选择联合类型实现。
This
可推导的this类型
this的编译选项
指定this的类型
this相关的内置工具
this相关的内置工具 - ThisType
TypeScript面向对象
认识类的使用
类的定义
类的继承
类的成员修饰符
在TypeScript中,类的属性和方法支持三种修饰符: public、private、protected 。public是默认的修饰符。
- public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的;
- private 修饰的是仅在同一类中可见、私有的属性或方法;
- protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法;
只读属性readonly
如果有一个属性我们不希望外界可以任意的修改,只希望确定值后直接使用,那么可以使用readonly 。
getters/setters
在前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程,这个时候我们 可以使用存取器。
class Person {
// 私有属性: 属性前面会使用_
private _name: string
private _age: number
constructor(name: string, age: number) {
this._name = name
}
// setter/getter: 对属性的访问进行拦截操作
set name(newValue: string) {
this._name = newValue
}
get name() {
return this._name
}
}
const p = new Person("why", 100)
p.name = "kobe"
console.log(p.name)
参数属性(Parameter Properties)
抽象类abstract
类的类型
类本身也是可以作为一种数据类型的。下面代码没有报错的原因是:typeScript对于类型检测的时候使用的鸭子类型。
// TypeScript对于类型检测的时候使用的鸭子类型
// 鸭子类型: 如果一只鸟, 走起来像鸭子, 游起来像鸭子, 看起来像鸭子, 那么你可以认为它就是一只鸭子
// 鸭子类型, 只关心属性和行为, 不关心你具体是不是对应的类型
class Person {
constructor(public name: string, public age: number) {}
running() {}
}
class Dog {
constructor(public name: string, public age: number) {}
running() {}
}
function printPerson(p: Person) {
console.log(p.name, p.age)
}
printPerson(new Person("why", 18))
printPerson({name: "kobe", age: 30, running: function() {}})
printPerson(new Dog("旺财", 3))
const person: Person = new Dog("果汁", 5)
对象类型的属性修饰符(Property Modifiers)
索引签名(Index Signatures)
// 索引签名的基本使用
// 案例1
interface InfoType {
// 索引签名: 可以通过字符串索引, 去获取到一个值, 也是字符串
[key: string]: string
}
function getInfo(): InfoType {}
const info = getInfo()
console.log(info["name"], info.age, info.address)
// 案例2
interface ICollection {
[index: number]: string
length: number
}
function printCollection(collection: ICollection) {
for (let i = 0; i < collection.length; i++) {
const item = collection[i]
}
}
const array = ["abc", "cba", "nba"]
const tuple: [string, string] = ["why", "广州"]
printCollection(array)
printCollection(tuple)
// 索引签名的类型问题
interface IIndexType {
// 返回值类型的目的是告知通过索引去获取到的值是什么类型
// [index: number]: string
// [index: string]: any
[index: string]: string
}
// 索引签名: [index: number]: string
const names: IIndexType = ["abc", "cba", "nba"]
// 索引签名: [index: string]: any: 没有报错
// 1.索引要求必须是字符串类型 names[0] => names["0"]
const names: IIndexType = ["abc", "cba", "nba"]
// 索引签名: [index: string]: string: 会报错
// 严格字面量赋值检测: ["abc", "cba", "nba"] => Array实例 => names[0] names.forEach
const names: IIndexType = ["abc", "cba", "nba"]
// names["forEach/map/filter"] => function,并非string,所以报错
// 两个索引类型的写法
interface IIndexType {
[index: number]: string
[key: string]: any
// 要求一:下面的写法不允许: 数字类型索引的类型, 必须是字符串类型索引的类型的 子类型
// 结论: 数字类型必须是比如字符串类型更加确定的类型(需要是字符串类型的子类型)
// 原因: 所有的数字类型都是会转成字符串类型去对象中获取内容
// 数字0: number|string, 当我们是一个数字的时候, 既要满足通过number去拿到的内容, 不会和
// string拿到的结果矛盾
// 要求二: 如果索引签名中有定义其他属性, 其他属性返回的类型, 必须符合string类型返回的属性
// [index: number]: string
// [key: string]: number|string
// aaa: string
// bbb: boolean 错误的类型
}
const names: IIndexType = ["abc", "cba", "nba"]
const item1 = names[0]
const forEachFn = names["forEach"]
names["aaa"]
接口继承
接口的实现
抽象类和接口的区别
严格的字面量赋值检测
interface IPerson {
name: string
age: number
}
// 1.奇怪的现象一:
// 定义info, 类型是IPerson类型
const obj = {
name: "why",
age: 18,
height: 1.88 // 多了一个height属性
}
const info: IPerson = { name: "why", age: 18, height: 1.88 } // 报错
const info: IPerson = obj // 不报错
// 2.奇怪的现象二:
function printPerson(person: IPerson) {}
const kobe = { name: "kobe", age: 30, height: 1.98 }
printPerson({ name: "why", age: 18, height: 1.88 }) // 报错
printPerson(kobe) // 不报错
// 解释现象
// 第一次创建的对象字面量, 称之为fresh(新鲜的)
// 对于新鲜的字面量, 会进行严格的类型检测. 必须完全满足类型的要求(不能有多余的属性)
TypeScript枚举类型
枚举类型是为数不多的TypeScript特性有的特性之一:
- 枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
- 枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型。
// 定义枚举类型
enum Direction {
LEFT = 0,
RIGHT = 1
}
enum Direction {
LEFT = 100,
RIGHT
}
enum Direction {
LEFT = "LEFT",
RIGHT = "RIGHT"
}
enum Operation {
Read = 1 << 0, // 1
Write = 1 << 1, // 2
foo = 1 << 2 // 4
}