弱类型指的是,早声明一个变量的时候,不需要指定类型,在给这个变量重新赋值的时候也不需要是固定的类型。不像 java 等强类型的语言,声明一个变量的时候需要指定类型,且不能被赋予非指定类型的值。
动态类型检查
静态类型语言会在编译阶段就会抛出类型错误,避免了在线上出类型问题。而js 的类型检查不是在编译阶段进行,而是在执行阶段进行的。当产生类型检查的错误的时候,只有在执行的的时候才会显现出来。
例如:下面有一个分割字符串的函数,但是如果不小心在调用的时候传入其他类型的数据作为参数的话,在书写和编译的时候不会抛出错误,但是会在执行时抛出错误。这种错误往往会引起整个程序的崩溃。下面的代码因为不小心给函数 someFunc 传了个字符串类型的参数,所以运行时报错了......
const someFunc = (string) => {
return string.split('');
someFunc(3);
1.2 numberlet num: number = 2333
num = 'abc'
1.3 stringlet str: string = '嘿嘿嘿'
str = 0
1.4 null 和 undefined
在非严格空检查模式下,null 和 undefined 是所有类型的子类型,可以作为任何类型变量的值;
在严格空检查模式(strictNullChecks)下,其是独立的类型。
非严格空检查模式下:以下三种情况都不会报错:
严格空检查模式下:以下三种情况都会报错:
let str: string = undefined
let obj: object = undefined
let num: number = 2
num = null
1.5 void
void 表示空类型,void 类型只能赋值为 null || undefined。也可以在函数中表示没有返回值。
let v: void = null;
let func = (): void => {
alert('没有返回值');
1.6 never
never 表示其有无法达到的重点,never 是任何类型的子类型,但没有任何类型是 never 的子类型;
const error = (message: string): never => {
throw new Error(message);
// 虽然这个函数规定必须有 string 类型的返回值,但是由于 never 是任何类型的子类型,所以这里不会报错
const error = (message: string): string => {
throw new Error(message);
1.7 any
any 表示该值是任意类型,编辑时将跳过对他的类型检查。应在代码中尽量避免 any 类型的出现,因为这会失去 ts 的大部分作用 -- 类型检查。其使用的场景在于接受的数据时动态不确定的类型,或者用来在第三方库的 module.d.ts
声明文件文件里跳过对第三方库的编译。
componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
1.8 object
object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。其意义在于,在不知道传入的对象长什么样子的情况下,更容易表示Obiect的api,例如hasOwnProperty
const func = (arg: object): void => {
console.log(arg.hasOwnProperty('name'));
func({name: 'liuneng'})
1.9 数组与元组
有两种定义数组类型的方式,一种是直接在类型后面加上[], 表示元素为该类型的数组
let arr: number[] = []
arr.push(1)
arr.push('2')
复制代码
第二种是使用数组泛型, 这种方式可以在不想在外边声明类型时候使用
let list: Array<1 | 2> = []
list.push(3)
元组用来表示已知元素数量与类型的数组,在赋值时内部元素的类型必须一一对应,访问时也会得到正确类型。当给元组添加元素或者访问未知索引元素的时候,会使用他们的联合类型
let tuple: [string, number]
tuple = [1, 'a']
tuple = ['a', 1]
tuple.push(true)
2.0 类型断言
其表示在不确定该变量类型时,指定其类型,表示明确知道他的类型,不用去检查了
const func = (param: any) => {
return (param as string).length;
复制代码
2.枚举 (enum)
使用枚举类型可以为一组数值赋予有意义的名字
例如说,现在有一个接口用来过滤一个列表,其接受一个参数,0表示不过滤,1表示过滤男性, 2表示过滤女性
const param = {
filterType: 0,
fetch('/getfilterList', param)
.then((res: any[]) => {
console.log(res)
复制代码
上面的这行代码,用眼睛看根本不知道请求的是什么类型的列表,只能通过注释 与 文档来判断它的意义
enum filterMap {
All = 0,
Men = 1,
Women = 2,
const param = {
filterType: filterMap.Men,
fetch('/getfilterList', param)
.then((res: any[]) => {
console.log(res)
复制代码
上面这段代码,用枚举列出了所有过滤条件的选项,使用时直接像使用对象一样枚举,从语义上很容易理解这段代码想要获取的是男性列表,代码即是文档。尤其是当做常量使用更加统一与方便理解。
enum 的值
声明 enum 类型的时候,可以指定 value 也可以不指定 value。
不指定 value 的话他会从零后续依次递增 1。
当通过 value 访问 key 的时候,如果有相同的 value,取最后一个
enum AbcMap {
B = 1,
D = 2,
E = 2,
console.log(AbcMap.A)
console.log(AbcMap.C)
console.log(AbcMap.[2])
复制代码
3.接口 (interface)
interface 是对对象形状的描述,其规定这个类型的对象应该长什么样子,编译的时候回去检查以他为描述的对象符不符合其结构
interface IPerson {
name: string
readonly isMan: boolean
age?: number
const xiaohong: IPerson = {
name: '小红',
isMan: false,
xiaohong.isMan = true
xiaohong.love = '周杰伦'
复制代码
上面给小红添加 love 属性的时候报错了,因为 IPerson 中没有规定这个属性
但是有时候我们不确定在 interface 外有没有别的属性,这时候可以使用索引签名。但是此时已确定的类型必须是他的子类型
interface IPerson {
[key: string]: string;
const xiaoming: IPerson = {
name:
love:
复制代码
4.函数
ts 可以给函数的参数 与 返回值指定类型。使用时候不能使用多余参数
函数声明:
现在定义一个加法的函数表达式
const sum: (x: number, y: number) => number = (x: number, y: number): number => x + y;
sum(1, 2);
复制代码
上面的代码看起来可能有点不好理解,左边是给 sum 定义类型,右半部分是 一个具体函数,这是 ts 函数完整的写法。通过 ts 类型推论的特性,可以把左半部分省略掉;也可以给变量定义类型而省略右边
const sum = (x: number, y: number): number => x + y;
复制代码
上面的代码看起来就比较好理解了,但是如果我们有一个乘法的方法,还有减法的方法等等等等,其输入类型和输出的类型都是 number,这个时候如果感觉在每个方法上都去定义参数与返回值的类型会觉得有点麻烦。此时,可以单独抽出一种函数类型,在函数表达式中使用。
type INumFunc = (x: number, y: number) => number ;
const sum: INumFunc = (x, y) => x + y;
const sub: INumFunc = (x, y) => x - y;
const multip: INumFunc = (x, y) => x * y;
复制代码
上面的代码定义了一个函数类型,要求输入输出都为 number;此时 ts 会自动给右边的函数体确定函数类型。如果右边函数体与左边类型声明不一致就会报错。
ts 当不确定函数的参数的时候,可以定义可选参数,其余 interface 的可选属性使用方法类似,是一个问号。也可以使用默认参数,其与 ES6 的默认参数一致
const sub = (x: number, y: number = 5, y?: number): number => {
if (y) {
return x - y - z;
} else {
return 0;
sub(10, 1, 1)
sub(10, 1)
const sum = (x: number, y: number = 5): number => x + y;
sum(1, 2);
sum(1);
sum(1, null);
复制代码
js 里有 arguments 的存在,所以我们可以给一个函数传任意个参数。在 ts 里,不确定参数的个数的话,可以使用剩余参数,将多出的参数放入一个数组, 其和 ES6 的剩余参数使用方法一致
const sum = (x: number, ...y: number[]): number => {
console.log(y)
let sum = x
if (y && y.length > 0) {
for (let index = 0
sum = sum + y[index]
return sub
sum(1, 2, 3)
复制代码
5. 类(class)
ts 的类 与 ES6 中的类大体相同,不过 class 中的属性可以添加修饰符
static 静态属性,其是这个类的属性,而不是实例的属性
public: 访问该成员的时候没有限制;
protected: 在派生类中可以访问该属性,但是不能再外部访问;
private: 私有成员,只能自己访问
readonly: 只读属性
abstract: 用于修饰抽象类或属性,必须在派生类中方实现它,自己不能实现。
class Person {
static desc() {
console.log('It's a class of "person");
protected name: string;
private age: number = '8';
readonly sex: string = 'boy';
constructor (theName: string) {
this.name = theName;
public like() {
console.log('footbal');
abstract eat(): void;
class kids extends Person {
constructor(name) {
super(name);
sayName() {
console.log(this.name);
eat() {
console.log('面包');
const xiaohong = new kids('小红');
Person.desc();
xiaohong.like();
console.log(xiaohong.name);
xiaohong.sayName();
console.log(xiaohong.age)
console.log(xiaohong.sex);
xiaohong.sex = 'girl';
复制代码
6. 类型推论
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
let str = 'string'
str = 1
// Error: Type 'number' is not assignable to type 'string'.
// str 在声明的时候并没有指定类型,但是 ts 自动推断为 string, 所以在给它赋值为 number 的时候报错了
let someVar
someVar = 'string'
someVar = 3
// 如果在声明一个变量的时候并没有给它赋值,ts 自动给它推断为 any 类型,所以这里跳过了类型检查,没有报错。
const sum = (x: number, y: number) => x + y;
sum(1, 2);
对象字面量
const obj = {
a: 1,
b: 2,
obj.a = 'str'
// 虽然 obj 在声明的时候并没有指定类型,但是 ts 自动将其推断为 {a: number, b: number} 所以报错
// 解构也是一样的
let { a } = obj
a = 'str'
下面的代码将 arr 推断为了 Array<string | number>
const arr = ['a', 'b', 1]
arr[0] = true
ts 甚至能根据某些代码特征进行推断出正确的类型范围。
typeof
下面的 if 代码块中,param 被推断为类型 string
const func = (arg: number | string) => {
if (typeof arg === 'string') {
console.log(arg.split(''));
console.log(arg.split(''));
instanceof
下面的代码可以根据 instanceof 推断出其参数类型,甚至可以自动推断出 else 代码块中的类型
class A {
public name: string = 'hehe';
class B {
public age: number = 8;
const func = (arg: A | B) => {
if (arg instanceof A) {
console.log(arg.name);
console.log(arg.age);
} else {
console.log(arg.name);
console.log(arg.age);
7. 泛型
有时候,当时用一个组件的时候,并不能确定其数据类型是什么样子,或者说为了达到复用组件的目的,可以使用泛型来创建可重用的组件。
例如,现在需要一个函数,其要求可以输出任意类型的参数,但是输入与输出必须是同一类型。如果不使用泛型的话,只能使用联合类型,或者 any 来实现。使用泛型可以这样做:
function identity<T>(arg: T): T {
return arg;
identity<string>('str'); // -> 'str'
泛型既可以代表未来使用时的类型,也可以作为类型的一部分
function objToArr<T>(arg: T): T[] {
return [arg];
objToArr({a: 1});
复制代码
上面的代码表示输入 T 类型的参数时,返回一个 T 类型成员的数组
interface IData<T> {
list: T[];
status: number;
const numItemData: IData<number> = {
list: [1, 2, 3],
status: 1,
const strItemData: IData<number> = {
list: ['a', 'b', 'c'],
status: 1,
复制代码
上面的例子创建了接口 IData
,其在使用的时候,传入类型约束, 这样可以最大程度的复用 IData
接口。因为 strItemData 的赋值与泛型传入的类型不一致所以报错
配合 fetch 使用。
大部分情况下,我们在处理 feth 请求的时候,与后端约定,后端返回的数据格式是固定的,只不过 data 部分可能并不确定。
// 我们与后端约定, response 的格式如下,但 data 部分依具体使用场景而定, 泛型可以给个默认值 - any
interface IResponse<T = any> {
status: number
message: string
data: T
复制代码
import fetchData from 'XXX/fetchData';
import { IResponse } from 'XXX/response';
export const getUser<T> = (param: IInput): Promise<IResponse<T>> => {
return fetchData('xxx/getData').then((res: IResponse<T>) => {
return res;
复制代码
使用的时候:
import getUser from 'XXX/getUser'
// 定义 response 中 data 的类型
interface IData {
name: string
age: number
// 将 data 的类型约束传入泛型
const userInfo = getUser<IData>()
userInfo.data.name = '小刚'
userInfo.data.name = 666
// ts 推断出 data.name 是 string 类型,所以在赋值为 666 的时候报错了
class Person<TName, TAge> {
name: TName
age: TAge
let xiaoming = new Person<string, number>()
xiaoming.name = '小明'
xiaoming.age = '8'
复制代码
上面代码因为在创建 xiaoming 的时候规定了 age 类型必须为 number,所以报错了
可以对泛型的结构进行约束
interface IData {
a: number;
function objToArr<T extends IData>(arg: T): T[] {
console.log(arg.a);
return [arg];
objToArr({a: 1, b: 2}); // -> [{a: 1, b: 2}]
objToArr({b: 2}); // Error: 类型“{ b: number; }”的参数不能赋给类型“IData”的参数。
TS 为我们的 javascript 的内置对象提供了类型,并且在使用内置对象的时候自动为我们进行类型检测
let body: HTMLElement = document.body;
let div: HTMLDivElement = document.createElement('div');
document.addEventListener('click', function(e: MouseEvent) {
console.log('MouseEvent');
Math.round('3.3');
interface Math {
pow(x: number, y: number): number;
random(): number;
round(x: number): number;
sin(x: number): number;
// ......
declare const Math: Math;
不建议使用的内置类型
TS 也定义了 Number,String,Boolean, Object
, 但是并不推荐区用这些类型,而是应该使用 number, string, bollean, obiect
let str: String;
复制代码
类型别名
可以给类型起个名字
可以将字面量作为一个类型
type str = 'a'
type num = 1
const ab: str = 'ab'
const someNum: num = 2
可以像 interface 一样使用
虽然使用方式类似,但是类型别名并不能被继承、导出等操作。只能作为
type Person = {
name: string
age: number
const xiaoming: Person = {
name: '小明',
age: 18,
类型别名配合 泛型
type Person<T> = {
name: string;
like: <T>[];
const xiaohong: Person<string> = {
name: '小红',
like: ['dance', 'football'],
利用类型别名来进行遍历对象
当我们想要比那里一个对象的时候,需要指定每一项元素的 key 的索引签名,否则会报错,比如像下面这样
const obj = {a: 1, b: 2};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key]);
// Error: 元素隐式具有 "any" 类型,因为类型“{ a: number; b: number; }” 没有索引签名。
复制代码
可以使用 类型别名 + 索引类型来避免该问题
const obj = {a: 1, b: 2}
type ObjKey = keyof typeof obj
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(obj[key as ObjKey])
复制代码
声明文件
声明文件是对某个库的环境声明,文件内对库向外暴露的 API 进行类型注解,使其在使用的时候可以享受到 TS 类型系统带来的检查
声明变量与函数
declare var foo: number;
declare function add(x: number, y: number): number;
声明一个 interface
声明一个对象
declare namespace person {
let name: string;
let age: number;
interface IPerson {
name: string;
age: number;
declare const person: IPerson;
声明一个类
declare class Person {
constructor(name) {
this.name = name;
sayHi() {
console.log(`I'm ${this.name}`);
在安装 TypeScript 的时候,会自动安装 lib.d.ts 等声明文件。其内部包含了 JavaScript 内置对象及 DOM 中存在各种常见的环境声明。例如: es5.d.ts