一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
往期文章
- 微前端 qiankun 实践与总结
- 前端 JavaScript 基础知识总结(上)
- 前端 JavaScript 基础知识总结(中)
- 前端 JavaScript 基础知识总结(下)
- 前端 ES6 十个新特性总结
- 前端 CSS 十个特性总结
前言
一、作为前端的必学语言JavaScript,随着项目的扩大以及业务的复杂度提升,难以避免的会出现维护成本高和代码质量的问题。这时,我们的TypeScript诞生了,作为JavaScript的超集。不仅增加了静态类型的约束,而且也同样支持面向对象的编程。
二、接下来的内容我将从TypeScript中的学习过程,并在此做出一些概念上的理解和总结
总结
1.理解和区别
一、定义
1、TypeScript
是 JavaScript
的类型的超集,支持ES6
语法,支持面向对象编程的概念,如类、接口、继承、泛型等
2、其是一种静态类型检查的语言,提供了类型注解,在代码编译阶段就可以检查出数据类型的错误
- 同时扩展了
JavaScript
的语法,所以任何现有的JavaScript
程序可以不加改变的在TypeScript
下工作 - 为了保证兼容性,
TypeScript
在编译阶段需要编译器编译成纯JavaScript
来运行,是为大型应用之开发而设计的语言
二、特性
- 类型批注和编译时类型检查 :在编译时批注变量类型
- 类型推断:ts 中没有批注变量类型会自动推断变量的类型
- 类型擦除:在编译过程中批注的内容和接口会在运行时利用工具擦除
- 接口:ts 中用接口来定义对象类型
- 枚举:用于取值被限定在一定范围内的场景
- Mixin:可以接受任意类型的值
- 泛型编程:写代码时使用一些以后才指定的类型
- 名字空间:名字只在该区域内有效,其他区域可重复使用该名字而不冲突
- 元组:元组合并了不同类型的对象,相当于一个可以装不同类型数据的数组
1、类型批注:
- 通过类型批注提供在编译时启动类型检查的静态类型,这是可选的,而且可以忽略而使用
JavaScript
常规的动态类型
function Add(left: number, right: number): number {
return left + right;
}
- 对于基本类型的批注是
number
、bool
和string
,而弱或动态类型的结构则是any
类型
2、类型推断:
- 当类型没有给出时,TypeScript 编译器利用类型推断来推断类型
let str = "string";
- 变量
str
被推断为字符串类型,这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时 - 如果缺乏声明而不能推断出类型,那么它的类型被视作默认的动态
any
类型
3、接口:
- 接口简单来说就是用来描述对象的类型 数据的类型有
number
、null
、string
等数据格式,对象的类型就是用接口来描述的
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: "Tom",
age: 25,
};
三、区别
- TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法
- TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译
- TypeScript 文件的后缀名 .ts (.ts,.tsx,.dts),JavaScript 文件是 .js
- 在编写 TypeScript 的文件的时候就会自动编译成 js 文件
2.数据类型
一、类型
1、和javascript
基本一致,也分成基本类型和引用类型,增添了void
、any
、emum
等原始类型
- boolean(布尔类型)、number(数字类型)、string(字符串类型)、array(数组类型)、tuple(元组类型)
- enum(枚举类型)、any(任意类型)、null 和 undefined 类型、void 类型、never 类型、object 对象类型
3.枚举类型
一、定义
1、枚举是一个被命名的整型常数的集合,用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型
- 枚举就是一个对象的所有可能取值的集合
2、枚举的说明与结构和联合相似,其形式为
enum 枚举名{
标识符①[=整型常数],
标识符②[=整型常数],
...
标识符N[=整型常数],
}枚举变量;
二、使用
1、枚举的使用是通过enum
关键字进行定义
enum xxx { ... }
2、声明关键字为枚举类型的方式如下
// 声明d为枚举类型Direction
let d: Direction;
三、类型
1、数字枚举:
- 当我们声明一个枚举类型是,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从0开始依次累加
enum Direction {
Up, // 值默认为 0
Down, // 值默认为 1
Left, // 值默认为 2
Right // 值默认为 3
}
console.log(Direction.Up === 0); // true
console.log(Direction.Down === 1); // true
console.log(Direction.Left === 2); // true
console.log(Direction.Right === 3); // true
- 如果我们将第一个值进行赋值后,后面的值也会根据前一个值进行累加1
enum Direction {
Up = 10,
Down,
Left,
Right
}
console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right); // 10 11 12 13
2、字符串枚举:
枚举类型的值其实也可以是字符串类型:
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
console.log(Direction['Right'], Direction.Up); // Right Up
- 如果设定了一个变量为字符串之后,后续的字段也需要赋值字符串,否则报错
enum Direction {
Up = 'UP',
Down, // error TS1061: Enum member must have initializer
Left, // error TS1061: Enum member must have initializer
Right // error TS1061: Enum member must have initializer
}
3、异构枚举:
- 即将数字枚举和字符串枚举结合起来混合起来使用
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
- 通常情况下我们很少会使用异构枚举
四、应用场景
1、就拿回生活的例子,后端返回的字段使用 0 - 6 标记对应的日期,这时候就可以使用枚举可提高代码可读性
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
- 包括后端日常返回0、1 等等状态的时候,我们都可以通过枚举去定义,这样可以提高代码的可读性,便于后续的维护
4.接口
一、定义
1、接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法
- 一个接口所描述的是一个对象相关的属性和方法,但并不提供具体创建此对象实例的方法
typescript
的核心功能之一就是对类型做检测,虽然这种检测方式是“鸭式辨型法”,而接口的作用就是为为这些类型命名和为你的代码或第三方代码定义一个约定
二、使用方式
1、接口定义
interface interface_name {
}
- 例如有一个函数,这个函数接受一个
User
对象,然后返回这个User
对象的name
属性
const getUserName = (user) => user.name
- 可以看到,参数需要有一个
user
的name
属性,可以通过接口描述user
参数的结构
interface User {
name: string
age: number
}
const getUserName = (user: User) => user.name
- 如果不想要
age
属性的话,这时候可以采用可选属性
interface User {
name: string
age?: number
}
- 这时候
age
属性则可以是number
类型或者undefined
类型 - 有些时候,我们想要一个属性变成只读属性,在
typescript
只需要使用readonly
声明
interface User {
name: string
age?: number
readonly isMale: boolean
}
- 这是属性中有一个函数
interface User {
name: string
age?: number
readonly isMale: boolean
say: (words: string) => string
}
2、如果传递的对象不仅仅是上述的属性,这时候可以使用
- 类型推断
interface User {
name: string
age: number
}
const getUserName = (user: User) => user.name
getUserName({color: 'yellow'} as User)
- 给接口添加字符串索引签名
interface User {
name: string
age: number
[propName: string]: any;
}
3、接口还能实现继承
interface Father {
color: String
}
interface Mother {
height: Number
}
interface Son extends Father,Mother{
name: string
age: Number
}
三、应用场景
1、例如在javascript
中定义一个函数,用来获取用户的姓名和年龄
const getUserInfo = function(user) {
// ...
return name: ${user.name}, age: ${user.age}
}
2、如果多人开发的都需要用到这个函数的时候,如果没有注释,则可能出现各种运行时的错误,这时候就可以使用接口定义参数变量
// 先定义一个接口
interface IUser {
name: string;
age: number;
}
const getUserInfo = (user: IUser): string => {
return `name: ${user.name}, age: ${user.age}`;
};
// 正确的调用
getUserInfo({name: "koala", age: 18});
5.类
一、定义:
- 类(Class)是面向对象程序设计(OOP,Object-Oriented Programming)实现信息封装的基础
- 类是一种用户定义的引用数据类型,也称类类型
- 传统的面向对象语言基本都是基于类的,
JavaScript
基于原型的方式让开发者多了很多理解成本
二、优点:
- 在
ES6
之后,JavaScript
拥有了class
关键字,虽然本质依然是构造函数,但是使用起来已经方便了许多
三、使用方式
1、定义类的关键字为 class
,后面紧跟类名,类可以包含以下几个模块(类的数据成员):
- 字段: 字段是类里面声明的变量。字段表示对象的有关数据
- 构造函数: 类实例化时调用,可以为类的对象分配内存
- 方法: 方法为对象要执行的操作
2、继承:
- 类的继承使用过
extends
的关键字 - 类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写,通过
super
关键字是对父类的直接引用,该关键字可以引用父类的属性和方法
3、修饰符:
- 公共 public:可以自由的访问类程序里定义的成员
- 私有 private:只能够在该类的内部进行访问
- 受保护 protect:除了在该类的内部可以访问,还可以在子类中仍然可以访问
4、静态属性:
- 这些属性存在于类本身上面而不是类的实例上,通过
static
进行定义,访问这些属性需要通过 类型.静态属性 的这种形式访问
5、抽象类:
- 抽象类做为其它派生类的基类使用,它们一般不会直接被实例化,不同于接口,抽象类可以包含成员的实现细节
abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法- 这种类并不能被实例化,通常需要我们创建子类去继承
6.函数
一、定义
1、函数是JavaScript
应用程序的基础,帮助我们实现抽象层、模拟类、信息隐藏和模块
- 在
TypeScript
里,虽然已经支持类、命名空间和模块,但函数仍然是主要定义行为的方式,TypeScript
为JavaScript
函数添加了额外的功能,丰富了更多的应用场景
二、使用方式
1、跟javascript
定义函数十分相似,可以通过funciton
关键字、箭头函数等形式去定义
const add = (a: number, b: number) => a + b
- 这个时候整个函数虽然没有被显式定义,但是实际上
TypeScript
编译器是能够通过类型推断到这个函数的类型
2、当我们没有提供函数实现的情况下,有两种声明函数类型的方式
// 方式一
type LongHand = {
(a: number): number;
};
// 方式二
type ShortHand = (a: number) => number;
- 当存在函数重载时,只能使用方式一的形式
3、可选参数
- 当函数的参数可能是不存在的,只需要在参数后面加上
?
代表参数可能不存在,如下
const add = (a: number, b?: number) => a + (b ? b : 0)
- 这时候参数
b
可以是number
类型或者undefined
类型,即可以传一个number
类型或者不传都可以
5、剩余类型
- 剩余参数与
JavaScript
的语法类似,需要用...
来表示剩余参数 - 如果剩余参数
rest
是一个由number
类型组成的数组
const add = (a: number, ...rest: number[]) => rest.reduce(((a, b) => a + b), a)
6、函数重载:
- 允许创建数项名称相同但输入输出类型或个数不同的子程序,它可以简单地称为一个单独功能可以执行多项任务的能力
- 关于
typescript
函数重载,必须要把精确的定义放在前面,最后函数实现时,需要使用|
操作符或者?
操作符,把所有可能的输入类型全部包含进去,用于具体实现 - 这里的函数重载也只是多个函数的声明,具体的逻辑还需要自己去写,
typescript
并不会真的将你的多个重名function
的函数体进行合并
三、区别
- 从定义的方式而言,typescript 声明函数需要定义参数类型或者声明返回值类型
- typescript 在参数中,添加可选参数供使用者选择
- typescript 增添函数重载功能,使用者只需要通过查看函数声明的方式,即可知道函数传递的参数个数以及类型
7.泛型
一、定义
1、泛型程序设计(generic programming)是程序设计语言的一种风格或范式
- 泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型 在
typescript
中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性
function returnItem<T>(para: T): T {
return para
}
二、使用方式
1、函数声明:
- 声明函数的形式如下
function returnItem<T>(para: T): T {
return para
}
- 定义泛型的时候,可以一次定义多个类型参数,比如我们可以同时定义泛型
T
和 泛型U
:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
2、接口声明:
- 声明接口的形式如下
interface ReturnItemFn<T> {
(para: T): T
}
- 那么当我们想传入一个number作为参数的时候,就可以这样声明函数
const returnItem: ReturnItemFn<number> = para => para
三、类声明
1、使用泛型声明类的时候,既可以作用于类本身,也可以作用与类的成员函数
2、下面简单实现一个元素同类型的栈结构,如下所示
class Stack<T> {
private arr: T[] = []
public push(item: T) {
this.arr.push(item)
}
public pop() {
this.arr.pop()
}
}
- 使用方式如下
const stack = new Stacn<number>()
3、如果上述只能传递 string
和 number
类型,这时候就可以使用 <T extends xx>
的方式猜实现约束泛型
4、除了上述的形式,泛型更高级的使用如下
- 要设计一个函数,这个函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,我们通过这两个参数返回这个属性的值
- 这时候就设计到泛型的索引类型和约束类型共同实现
四、索引类型、约束类型
1、索引类型 keyof T
把传入的对象的属性类型取出生成一个联合类型,这里的泛型 U 被约束在这个联合类型中,如下所示
function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
return obj[key] // ok
}
- 上述为什么需要使用泛型约束,而不是直接定义第一个参数为
object
类型 - 因为默认情况
object
指的是{}
,而我们接收的对象是各种各样的,一个泛型来表示传入的对象类型,比如T extends object
五、多类型约束
1、例如如下需要实现两个接口的类型约束
interface FirstInterface {
doSomething(): number
}
interface SecondInterface {
doSomethingElse(): string
}
- 可以创建一个接口继承上述两个接口,如下
interface ChildInterface extends FirstInterface, SecondInterface {
}
- 正确使用如下
class Demo<T extends ChildInterface> {
private genericProperty: T
constructor(genericProperty: T) {
this.genericProperty = genericProperty
}
useT() {
this.genericProperty.doSomething()
this.genericProperty.doSomethingElse()
}
}
2、通过泛型约束就可以达到多类型约束的目的
8.高级类型
一、定义
- 除了
string
、number
、boolean
这种基础类型外,在typescript
类型声明中还存在一些高级的类型应用 - 这些高级类型,是
typescript
为了保证语言的灵活性,所使用的一些语言特性。这些特性有助于我们应对复杂多变的开发场景
二、高级类型
1、交叉类型:
- 通过
&
将多个类型合并为一个类型,包含了所需的所有类型的特性,本质上是一种并的操作
T & U
- 适用于对象合并场景,如下将声明一个函数,将两个对象合并成一个对象并返回
function extend<T , U>(first: T, second: U) : T & U {
let result: <T & U> = {}
for (let key in first) {
result[key] = first[key]
}
for (let key in second) {
if(!result.hasOwnProperty(key)) {
result[key] = second[key]
}
}
return result
}
2、联合类型
- 联合类型的语法规则和逻辑 “或” 的符号一致,表示其类型为连接的多个类型中的任意一个,本质上是一个交的关系
function formatCommandline(command: string[] | string) {
let line = '';
if (typeof command === 'string') {
line = command.trim();
} else {
line = command.join(' ').trim();
}
}
3、类型别名
- 类型别名会给一个类型起个新名字,类型别名有时和接口很像,但可以作用于原始值、联合类型、元组以及其它任何需要手写的类型
- 此外类型别名可以是泛型
type Container<T> = { value: T };
- 也可以使用类型别名来在属性里引用自己
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
- 可以看到,类型别名和接口使用十分相似,都可以描述一个对象或者函数
- 两者最大的区别在于,
interface
只能用于定义对象类型,而type
的声明方式除了对象之外还可以定义交叉、联合、原始类型等,类型声明的方式适用范围显然更加广泛
4、类型索引:
keyof
类似于Object.keys
,用于获取一个接口中 Key 的联合类型。
interface Button {
type: string
text: string
}
type ButtonKeys = keyof Button
// 等效于
type ButtonKeys = "type" | "text"
5、类型约束:
- 通过关键字
extend
进行约束,不同于在class
后使用extends
的继承作用,泛型内使用的主要作用是对泛型加以约束
type BaseType = string | number | boolean
// 这里表示 copy 的参数
// 只能是字符串、数字、布尔这几种基础类型
function copy<T extends BaseType>(arg: T): T {
return arg
}
- 类型约束通常和类型索引一起使用,例如我们有一个方法专门用来获取对象的值,但是这个对象并不确定,我们就可以使用
extends
和keyof
进行约束
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
const obj = { a: 1 }
const a = getValue(obj, 'a')
6、映射类型:
- 通过
in
关键字做类型的映射,遍历已有接口的key
或者是遍历联合类型,如下例子
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Obj {
a: string
b: string
}
type ReadOnlyObj = Readonly<Obj>
- keyof T:通过类型索引 keyof 的得到联合类型 'a' | 'b'
- P in keyof T 等同于 p in 'a' | 'b',相当于执行了一次 forEach 的逻辑,遍历 'a' | 'b'
- 所以最终
ReadOnlyObj
的接口为下述
interface ReadOnlyObj {
readonly a: string;
readonly b: string;
}
7、条件类型:
- 条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况
T extends U ? X : Y
- 如果 T 是 U 的子集,就是类型 X,否则为类型 Y
9.装饰器
一、定义
1、装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上
- 是一种在不改变原类和使用继承的情况下,动态地扩展对象功能
- 同样的,本质也不是什么高大上的结构,就是一个普通的函数,
@expression
的形式其实是Object.defineProperty
的语法糖
2、expression
求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入
二、使用方式
1、类装饰:
- 例如声明一个函数
addAge
去给 Class 的属性age
添加年龄
function addAge(constructor: Function) {
constructor.prototype.age = 18;
}
@addAge
class Person{
name: string;
age!: number;
constructor() {
this.name = 'huihui';
}
}
let person = new Person();
console.log(person.age); // 18
- 上述代码,实际等同于以下形式
Person = addAge(function Person() { ... });
- 当装饰器作为修饰类的时候,会把构造器传递进去。
constructor.prototype.age
就是在每一个实例化对象上面添加一个age
属性
2、方法/属性装饰:
-
同样,装饰器可以用于修饰类的方法,这时候装饰器函数接收的参数变成了
- target:对象的原型
- propertyKey:方法的名称
- descriptor:方法的属性描述符
-
可以看到,这三个属性实际就是
Object.defineProperty
的三个参数,如果是类的属性,则没有传递第三个参数
// 声明装饰器修饰方法/属性
function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log("prop " + propertyKey);
console.log("desc " + JSON.stringify(descriptor) + "\n\n");
descriptor.writable = false;
};
function property(target: any, propertyKey: string) {
console.log("target", target)
console.log("propertyKey", propertyKey)
}
class Person{
@property
name: string;
constructor() {
this.name = 'huihui';
}
@method
say(){
return 'instance method';
}
@method
static run(){
return 'static method';
}
}
const xmz = new Person();
// 修改实例方法say
xmz.say = function() {
return 'edit'
}
3、参数装饰:
-
接收3个参数
- target :当前对象的原型
- propertyKey :参数的名称
- index:参数数组中的位置
function logParameter(target: Object, propertyName: string, index: number) {
console.log(target);
console.log(propertyName);
console.log(index);
}
class Employee {
greet(@logParameter message: string): string {
return `hello ${message}`;
}
}
const emp = new Employee();
emp.greet('hello');
4、访问器装饰:
- 使用起来方式与方法装饰一致,如下
function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target);
console.log("prop " + propertyKey);
console.log("desc " + JSON.stringify(descriptor) + "\n\n");
};
class Person{
_name: string;
constructor() {
this._name = 'huihui';
}
@modification
get name() {
return this._name
}
}
5、装饰器工厂:
- 如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再函数一个函数即可,如下
function addAge(age: number) {
return function(constructor: Function) {
constructor.prototype.age = age
}
}
@addAge(10)
class Person{
name: string;
age!: number;
constructor() {
this.name = 'huihui';
}
}
let person = new Person();
6、执行顺序:
- 当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用,例如如下
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
// 输出
f(): evaluated
g(): evaluated
g(): called
f(): called
三、应用场景
1、使用装饰器存在两个显著的优点
- 代码可读性变强了,装饰器命名相当于一个注释
- 在不改变原有代码情况下,对原来功能进行扩展
2、借助装饰器的特性,除了提高可读性之后,针对已经存在的类,通过装饰器的特性,在不改变原有代码情况下,对原来功能进行扩展
10.命名空间与模块
一、定义
1、TypeScript
与ECMAScript
2015 一样,任何包含顶级 import
或者 export
的文件都被当成一个模块
- 相反地,如果一个文件不带有顶级的
import
或者export
声明,那么它的内容被视为全局可见的
二、命名空间
1、命名空间一个最明确的目的就是解决重名问题
- 命名空间定义了标识符的可见范围,一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的
- 这样,在一个新的名字空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其他名字空间中
2、TypeScript
中命名空间使用 namespace
来定义,语法格式如下
namespace SomeNameSpaceName {
export interface ISomeInterfaceName { }
export class SomeClassName { }
}
- 以上定义了一个命名空间
SomeNameSpaceName
,如果我们需要在外部可以调用SomeNameSpaceName
中的类和接口,则需要在类和接口添加export
关键字
3、使用方式如下
SomeNameSpaceName.SomeClassName
4、命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性,如下
namespace Letter {
export let a = 1;
export let b = 2;
export let c = 3;
// ...
export let z = 26;
}
5、编译成js
如下
var Letter;
(function (Letter) {
Letter.a = 1;
Letter.b = 2;
Letter.c = 3;
// ...
Letter.z = 26;
})(Letter || (Letter = {}));
三、区别
- 命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中
- 像命名空间一样,模块可以包含代码和声明。 不同的是模块可以声明它的依赖
- 在正常的TS项目开发过程中并不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用
结尾
- 如果只是掌握了
typeScript
的一些基础类型,可能很难游刃有余的去使用typeScript
,需要了解一些typescript
的高阶用法 - 并且
typescript
在版本的迭代中新增了很多功能,需要不断学习与掌握