2023不摆烂,一篇文章快速上手TypeScript基础

547 阅读21分钟

新人第一篇TypeScript文章,陆陆续续写了快半个月。也是我看B站大学小满zs的TS视频后,自己总结的学习笔记;若我有理解错误还请大佬们指出吖。如果是刚接触TS的同学强烈建议动手把代码走一遍,不要只浏览一遍就过了呐。

2023如约而至,即使是前端小白,TypeScript也已经成为必须学习并且能实际应用的一个技术了
当然,技术不是学术,并不是为了考试,而是为了实际应用。所以先了解基础并尽快上手,在实践中再去不断完善和熟练,也许是一种比较节约时间的做法。
正如标题所言,这是一篇快速了解TS的博客,基础却也不失详细;学习后,足够日常的大多需求了

学习的思路:为什么TS要这么做,解决的是JS的什么问题? 带着这样的心理去学习和了解,事半功倍

一、基础类型

首先对TypeScript做一些简单的了解:TS是JS的超集,始于JS终于JS,TypeScript 在编译阶段需要编译器编译成纯 JavaScript 来运行。

安装:npm install typescript -g;使用tsc -v命令查看版本,是否安装成功

拓展:命令tsc xxx.ts会编译ts脚本,生成xxx.js,然后js脚本的运行命令就是node xxx.js

利用这些命令可以得到TS编译后的JS代码,从而了解TS的原理。

TS的基础类型:TS是JS的超集,所以JS基础的类型都包含在内
Boolean、Number、String、null、undefined 以及 ES6 的 Symbol 和 ES10 的 BigInt。

这些基本类型就是在定义变量的时候,变量名:数据类型,就可以了;代码示例以及其注意点

// 1、 Boolean类型
let bol:boolean = false;
// 注意点:使用构造函数 Boolean 通过new创造的对象不是布尔值
// let bol: boolean = new Boolean(1)  //报错

// 2、Number类型和BigInt类型
let num:number = 123;
num = Number.MAX_SAFE_INTEGER;
let bigNum:bigint = BigInt(123);
// 注意点:number和BigInt不是一个类型,不可以兼容互相赋值。
// bigNum = 666;  //报错,不能将类型“number”分配给类型“bigint”

// 3、String类型
let str:string = 'abc';
// 注意点:可以使用es6字符串模板
str = `${num}`

// 4、null和undefined
let un: undefined = undefined;//定义undefined
let nu: null = null;//定义null

// 5、Symbol类型
let sym:symbol = Symbol();

二、空值类型

JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数

// void 类型的用法,主要是用在我们不希望调用者关心函数返回值的情况下,比如通常的异步回调函数
function voidFn():void {
    console.log('test void')
    // 不关心函数返回值,不需要return的时候,void
}

// 其声明类型不为 "void" 或 "any" 的函数必须按类型返回值。
function fn():number {
    return 123;
}

// void也可以定义undefined 和 null类型
let u:void = undefined
// let n: void = null;

// 注意点:如果配置了tsconfig.json 开启了严格模式,null 不能 赋予 void 类型
// ts严格模式的配置:
// tsconfig.json文件:
// {
//     "compilerOptions":{
//         "strict": true
//     }
// }

一般的面试题会问:Typescript中never 和 void 的区别? void 和 undefined 和 null 的区别?

never类型在文章后面会学习,现在先不管;这里先了解既然void也可以定义undefined 和 null类型,那void 和 undefined 和 null 的区别:

注意:tsconfig.json配置文件中,开启strictNullChecks模式,null 和 undefined 赋值给基础类型是会报错的;所以如果只是为了开发,这部分不管就好,除了面试的时候其他的地方不需要知道区别是什么的...

// undefined 和 null 是所有类型的子类型,这样写没问题
let u:undefined = undefined;
let str:string = 'a';
str = u

//这样写会报错 void类型不可以分给其他类型
let test: void = undefined
let num2: string = "1"
num2 = test

三、任意类型(any与unknown)

any 类型实际上就是 失去了TS类型检测的作用,和原生js定义的变量一样了 。这也是any的弊端(不过有时候真的好用哈哈哈)

所以TypeScript 3.0中引入的 unknown 类型,即未知类型。相比any更加严格。

any与unknown的相同点:

// unknown 也可以定义任何类型的值
let value: unknown;
 
value = true;             // OK
value = 666;               // OK
value = "Hello World";    // OK

any与unknown的区别:
(1)、unknown类型只能赋给unknown/any,即使值是同一类型也不行。any只要值的类型正确,可以任意赋值给其他类型:

let val:unknown = '666';
let str:string = val   //即使val的值是'',也不允许。报错:不能将类型“unknown”分配给类型“string”。

// unknown类型只能赋给unknown/any
let any: any = val;
let unk: unknown = val;

将第一句的let val:unknown = '666';改用any就不会报错。

(2)、值为对象时的区别:

// any类型在对象没有这个属性的时,也是可以获取的(这原本也就是JS的特点)
let obj:any = {b:1}
obj.a
  
// 如果是unknow 是不能调用属性和方法(TS是使用接口来规范对象类型)
let obj2:unknown = {b:1,ccc:():number=>213}
// obj2.b  //不允许
// obj2.ccc()  //不允许

四、Object,object,{}类型

(1)、Object类型

js中原型链所有的数据类型构造函数都最后指向Object,所以可以是任意类型

let obj1:Object = {a:1,b:2};
obj1 = '';
obj1 = 666;

// Object和any不一样,不能调用属性。
let o:Object = {a:1};
// o.a = 6; // 报错:类型“Object”上不存在属性“a”
(2)、object类型

object则是非基础类型(第一节内容)不能赋值基础类型;一般用于泛型约束

let o1:object = {a:1}
// o1.a  // 也报错,类型“object”上不存在属性“a”。
// let o2:object = ''  //报错:不能将类型“string”分配给类型“object”。
(3)、{}类型

{}赋值方式同Object

let o1:{} = {};
let o2:{} = '';

五、接口和对象类型

TypeScript 中用接口来定义对象类型

前面的知识可以看出,基本类型的类型约束并不复杂,但是对象类型好像有点麻烦了:写为any类型,就失去了TS的作用,但是不管是unknown、Object、object、{},可以定义对象,但都限制了使用属性和方法(这也是为了规范引用类型)。

所以,TS是使用了接口来规范对象类型

我们通常这样使用JS定义对象:

let obj = {
  name:'xxx'
}

在TS中,定义对象首先要定义接口来规范对象,然后再定义对象:

注意点:
1、使用interface关键字定义接口
2、多个接口同名,不是后定义覆盖前,而是合并
3、接口中定义好属性,后面定义对象的时候,即有了类型、数量的约束,多写/少写/类型不一致 都不允许

interface A{
  name:string
}
interface A{
  num:number,
  fn():void
}
let obj:A = {
  name:'xxx',
  num:123,
  fn:()=>{}
  // age:1  // 两个A接口合并后都没有age属性,报错
}
obj.name = '';

interface 也可以用来描述函数类型,代码如下:

interface ISum {
    (x:number,y:number):number
}

const add:ISum = (num1, num2) => {
    return num1 + num2
}


拓展:上述TS对象已经正常使用了,但是开发中,往往数据是动态的,数据是后台获取的。
多选/少写/类型不一致 都报错有点过分严格了。

TS提供了两个操作来解决这个问题:
(1)、可选操作符:?
(2)、当属性名不知,类型也不知,个数也不确定 那怎么提前定义ts接口?可以定义[propName: string]: any; 允许添加新的任意属性
注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

interface Person {
    b?:string,
    a:string,
    [propName: string]: any;
}
// 接口描述:a属性必有,b属性可选,允许添加新的任意属性
const person:Person  = {
    a:"213",
    c:"123",
    d:'',
    e:''
}

接口的readonly 只读属性;在初始化后,是不允许被赋值的只能读取
interface Person2 {
    readonly a: string
}
const person2: Person2 = {
    a: "123"
}
person2.a
// person2.a = '666'  //报错:无法为“a”赋值,因为它是只读属性。

interface 还有一个响亮的名称: duck typing(鸭子类型)。

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

只要数据满足了 interface 定义的类型,TS 就可以编译通过。

interface FunctionWithProps {
    (x: number): number
    fnName: string
}
const fn: FunctionWithProps = (x) => { return x } 
fn.fnName = 'hello world'

FunctionWithProps 接口描述了一个函数类型,还向这个函数类型添加了 name 属性,这看上去完全是四不像,但是这个定义是完全可以工作的。

这就是 duck typing 和 interface,非常的灵活。


六、数组类型

基本类型讲了,接口+对象类型也讲了,但是数组是特殊的对象

三种方式定义:

// 1、类型[ ] 
let arr0:number[] = [1,2,3] //里面元素只能是number
let arr1:any[] = [1,'a',{}] //任意类型的数组
// 2、数组泛型
let arr2:Array<number> = [1,2,3]  //效果同 arr:number[]

// 3、还可以用接口
interface NumberArray {
    [index: number]: number;
}
let arr3: NumberArray = [1];

多维数组:

let data:number[][] = [[1,2], [3,4]];
arguments类数组:

接口:IArguments
IArguments 是 TypeScript 中定义好了的类型,内置的接口

function Arr(...args:any): void {
  console.log(arguments);
  // let arr:number[] = arguments  //arguments 是类数组,不能赋值给数组类型
  let arr:IArguments = arguments;
}
Arr(111, 222, 333);

IArguments关键字的源码就是一个接口:

interface IArguments {
   [index: number]: any;
   length: number;
   callee: Function;
}

七、TS的函数

参数的个数、类型(不能多或少,除非两种情况: 可选操作符/有参数默认值 则可以少。这点和接口的逻辑一致,用ts的?操作符);以及返回值的类型(没有返回值就是void)

let fn1 = (str1:string, str2?:string, str3:string='参数默认值'):string=>{
  return '666';
}
fn1('');
fn1('','','');
// fn1();//少了,不允许
// fn1('','','','');//多了,不允许

注意:由于ts限制的参数个数,于是ts也拥有了 函数重载 即函数名字相同,而参数不同,从而调用了不同的逻辑。js是没有函数重载的


八、联合类型 | 交叉类型 | 类型断言

(1)、联合类型

TS的 | 操作符

在开发中,有时候无法提前固定好数据的类型
eg:的手机号通常是11位数字,为number类型 这时候产品说需要支持座机,有 "-" 连接,这个时候必须为string

//联合类型,同时支持number | string
let myPhone: number|string  = '010-820'
myPhone = 18080808080;
TS数组的联合类型

只有一种,好办:const array:number[] = [1,2,3]

多种:

不能用元组来初始化

元组只是push有用是联合类型,初始化的话不行,是一一对应的:

只是push的时候是联合类型:

但是这还是没有实现初始化的时候想要用联合类型

用小括号:
const array:(number|string)[] = [1,'',2,3,'abc']  //正确写法
(2)、交叉类型

交叉类型即为合并,ts内置的 & 操作符

interface People {
  age: number,
}
interface Man{
  sex: string
}
const xiaoman = (man: People & Man) => {
  console.log(man.age)
  console.log(man.sex)
}
// xiaoman({age: 18});  //  报错:缺少属性 "sex",但类型 "Man" 中需要该属性。
xiaoman({age: 18,sex: 'male'});
(3)、类型断言

TS的关键字 as 只是用来解决一些特殊场景下TS不报错,并不具有改变能力

例子:(配合联合类型)

let fn = (phone: number | string)=>{
  console.log(phone.length);
}

上述代码的函数中使用了参数的length属性,但是number类型是没有这个属性的。
number无length而string有,无法确定,不定时的错误。ts报错:

image.png

所以:

let fn = (phone: number | string)=>{
  console.log((phone as string).length);  //利用类型断言避免报错
}
fn('123')  // 3
fn(123)  // undefined

这样程序不会报错,而是返回undefined

从结果可以看到,参数为number的时候,结果为undefined,也证明了“类型断言”只是用来解决一些特殊场景下TS不报错,并不具有改变能力(没有因为phone as string而转化了参数的类型)

类型断言是不具影响力的,即不会改变数据。只是通过「欺骗」TypeScript 编译器从而不报错;在编译过程中会删除类型断言

image.png


九、内置对象

// 七、内置对象
// 其他的看他博客去,有点水
// 重点在于Promise
// 语法规则:Promise<T> 类型

function promise():Promise<string>{
   return new Promise<string>((resolve,reject)=>{
      //  resolve(1)   //Promise<T>,T的类型就是规定了resolve()参数的数据类型
       resolve('1')
   })
}
promise().then(res=>{
    console.log(res)
})

十、Class类

(1)、TS类的基本写法

JS的类是这样定义的,使用class关键字

class Person {
  constructor(num, msg){     //constructor构造函数
    this.num = num;
    this.msg = msg;
  }
}
let per = new Person(1,'message')

TS的类也是class关键字,只是加上类型

// TS的写法:
class Person {
  num:number
  msg:string
  constructor(num, msg){
    this.num = num;
    this.msg = msg;
  }
}
let per = new Person(1,'message')
(2)、TS类的三个修饰符:public、private、protected

用来修饰TS的类的内部的变量;类中的变量的访问权限分内部,子类,外部

  • public:内部,子类,外部
  • protected:内部,子类
  • private:内部
class Person {
  public num:number
  private msg:string
  protected val:string
  constructor(num, msg, val){
    this.num = num;
    this.msg = msg;
    this.val = val;
  }
}
// 子类:
class Student extends Person {
  constructor(){
    super(1,'','');     //super会调用父类的constructor构造函数
    this.val;            //protected,可在内部,子类
    // this.msg;       private,只有内部
  }
}
let per = new Person(1,'message','val');
per.num  //public,可在内部,外部,子类
// per.msg  报错
// per.val  报错
(3)、再补充一个修饰符,static,静态属性和方法

实例属性、方法 与 静态属性、方法 之间都不可以互相调用。

class Person {
  //静态属性:
  static sta:string
  //静态方法:
  static fn(){
    // this.num   //静态函数中也不能拿到给实例的构造函数的方法
    this.sta
  }
  constructor(){
    // this.fn    //构造函数中不能访问静态方法和属性
    this.fn2
  }
  //实例方法:
  fn2(){}
}
let per = new Person();
Person.sta;
// per.sta;   //报错
(4)、用接口去约束类

关键字 implements 可使用多个接口来约束,用逗号隔开。继承还是用extends

interface PersonClass {
    get(type: boolean): boolean
}
class A {
    constructor() {}
}
class B implements PersonClass{
    constructor() {}
    // 不定义这个函数则ts报错,因为接口中规定了名称、个数、和类型。这就是TS的“约束”
    get(type:boolean) {
        return type
    }
}
class C extends A implements PersonClass {
    constructor() {
      super()
    }
    get(type:boolean) {
        return type
    }
}
(5)、抽象类

除了使用接口来约束类,还可以使用抽象类来约束。关键字abstract

抽象类不能直接用来new实例化,只能被继承,然后由派生类去实例化
抽象类里面的抽象方法只写约束,不实现;继承的子类中需要去实现这个抽象方法(定义的抽象方法必须在派生类实现)

// 抽象类:
abstract class Person{
  name:string
  constructor(name){
    this.name = name
  }
  // abstract getName(){}      //只写约束,不实现;继承的子类中
  //需要去实现这个抽象方法(定义的抽象方法必须在派生类实现)
  abstract getName():string

  setName(newName){        //普通实例方法
    this.name = newName
  }
}
// 派生类
class A extends Person{
  constructor(){
    super('名字')
  }
  getName(){
    return this.name
  }
}
// 由派生类提供实例化
let a = new A();
console.log(a.getName())     //名字
a.setName('新名字')
console.log(a.getName())     //新名字

使用抽象类来约束类明显比使用接口来约束更直观。

抽象类的作用:作为类的模板,规范了其子类定义的标准,让代码更加规范。
类比于之前学的接口,接口和抽象类的出现主要是为了添加参数、类型等的限制,来规范代码
类,既可以用接口来规范,也可以用抽象类来规范


十一、元组类型

前面学习的数组类型的每个元素只能是同一种类型:

let array:number[] = [1,2]

而元组可以设置不同类型:

let array2:[string, number] = ['',2]
//注意:当元组越界时,越界的元素他的类型被限制为 联合类型
array2.push('')  //可以
array2.push(1)  //可以
array2.push(true)  //不行,array2的联合类型是 string|number

当需要数组的元素类型不一样时,使用TS的元组来规范该数组。


十二、枚举类型

javaScript中是没有枚举的概念
TS帮我们定义了枚举这个类型,通过enum这个关键字

使用枚举类型可以允许我们定义一些带名字的常量,也可以清晰地表达意图或创建一组有区别的用例。

(1)、数字枚举

数字枚举也是增长枚举,会自动+1;也可以定义初始值

enum Types {
  Red,
  SkyBlue
}
// 默认都是从0开始
console.log(Types.Red)  // 0
console.log(Types.SkyBlue)  // 1

enum Type2 {
  Red = 100,
  SkyBlue
}
console.log(Type2.Red)  // 100
console.log(Type2.SkyBlue)  // 101
(2)、字符串枚举
//花括号里面用=号也挺少见的
enum Types{
  Red = 'red',
  Green = 'green',
  BLue = 'blue'
}
console.log(Types.Red)  // red
console.log(Types.Green)  // green

由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。
换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,
这个值通常是很难读的 - 它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。

(3)、异构枚举

枚举可以混合字符串和数字成员:

enum Types{
   No = "No",
   Yes = 1,
   A    //在数字枚举之后,所以A是递增
}
console.log(Types.No)  // No
console.log(Types.Yes)  // 1
console.log(Types.A)  // 2
枚举的应用场景

应用场景:后端返回的字段使用 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

但有时候后端给你返回的数据状态是乱的,就需要我们手动赋值。

比如后端说 Buy 是 100,Send 是 20,Receive 是 1,就可以这么写

enum ItemStatus {
    Buy = 100,
    Send = 20,
    Receive = 1
}

console.log(ItemStatus['Buy'])      // 100
console.log(ItemStatus['Send'])     // 20
console.log(ItemStatus['Receive'])  // 1


十三、类型推论|类型别名

(1)、类型推论:
let str = '';
str = 1    //报错

虽然声明的时候没有定义类型,但是初始化赋值'',所以类型推论为字符串,不能赋值其他类型

没有初始化的话,则类型推论为any:

let a;
a = '';
a = 1;
(2)、类型别名

type 关键字,就算给类型取个名字

type str1 = string
type str2 = string | number
let a1:str1 = ''
let a2:str2 = 1

十四、never类型

TypeScript 使用 never 类型来表示不应该存在的状态

例如 交叉类型 常用于两个interface接口的交叉,来规范对象。如果用于变量就不合适:

type aaa = string;
type bbb = string | number;
// 鼠标移上,ccc被推断为了never,因为一个变量不可能既是string又是number
type ccc = string & number;

记个面试题:Typescript中never 和 void 的区别?
void 表示没有任何类型(可以被赋值为 null 和 undefined)。never 表示一个不包含值的类型,即表示永远不存在的值。拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。


十五、泛型

泛型在TypeScript 是很重要
因为TS代码在定义的时候就需要确定类型,有些场景就不太合适
所以使用泛型就是定义的时候不写死类型,调用/使用的时候再去固定数据的类型;程序使用泛型更灵活,提高了复用性

所谓约束,即改掉JS的松散规则,使用TS严格规则,不符合TS的风格需要报错,这就是约束

用函数举个例子:

function num(a:number, b:number) :Array <number>{
  return [a, b]
}
function str(a:string, b:string) :Array <string>{
  return [a, b]
}
num(1,2);
str('a','b');

上述TS代码中两个函数功能完全一致,只因为类型不一致,就需要定义两个函数,这明显不是优秀的代码
所以可以用泛型来优化:

(1)、函数泛型

上述函数的问题,用函数泛型可以解决:

//例一(参数同一类型):
function fn<T>(a:T, b:T) :Array<T> {
  return [a, b];
}
fn<string>('a','b')
fn<number>(1,2)

//例二(参数不同类型):
function fn2<T,U>(a:T,b:U) :Array<T|U> {
  return [a,b];
}
fn2<string,number>('a',1);

泛型的语法为函数名字后面跟一个<参数名> 参数名可以随便写 例如我这儿写了T
当我们使用这个函数的时候把参数的类型传进去就可以了 (也就是动态类型)

多敲几遍就嘎嘎熟练

(2)、泛型约束

例如需要返回长度length,但不是所有的类型都有length,所以要做泛型约束避免错误,使得函数更健全:

function fn<T>(a:T){
  return a.length;
}

上面TS代码中函数报错,T类型没有length;

所以需要用接口约束泛型T(extends还能这样用):

interface Len {
  length:number
}
function fn<T extends Len>(a:T){
 return a.length;
}
fn<string>('abc');  //OK
// fn<number>(1);  // 报错,number被Len接口约束了;使得程序更健全
(3)、使用keyof 约束对象的属性

因为JS是动态语言可以直接增加属性,但是TS就不允许(其实其他语言也不允许,JS太松散灵活)
所以导致:

function fn<T>(obj:T, key){
  return obj[key]
}
let o = {a:1, b:2}
fn(o, 'a');
fn(o, 'c')  //并未报错

这就出问题了,o这个对象没有c属性,但是没提示报错

所以需要约束,来避免这种不确定性,TS定义了keyof这个关键字来约束:

function fn2<T,K extends keyof T>(obj:T, key:K){
  return obj[key]
}
let o2 = {a:1, b:2}
fn2(o, 'a');
// fn2(o, 'c') //报错:Argument of type '"c"' is not assignable to parameter of type '"a" | "b"'   (这个报错也能看出上述的原理,联合类型)

原理:<T,K extends keyof T>,K通过keyof操作符会获取T类型的所有键,即keyof T返回了一个联合类型;然后再用extends关键字约束K类型必须为keyof T返回的联合类型的子类型

(4)、泛型类

和泛型函数差不多,就是定义的时候不定死类型,实例化对象的时候再去写类型

class Sub<T>{
  a:T[] = [];
}
// 实例化number:
let obj = new Sub<number>();
obj.a = [1,2]
obj.a = [3,4,5]
// obj.a = ['a','b']  //报错

// 实例化string:
let obj1 = new Sub<string>();
// obj1.a = [1,2]  //报错
obj1.a = ['a']
(5)、type 和 interface 都可以定义函数类型

type 这么写:

type Print = <T>(arg: T) => T
const printFn:Print = function print(arg) {
    console.log(arg)
    return arg
}

interface 这么写:

interface Iprint<T> {
    (arg: T): T
}

function print<T>(arg:T) {
    console.log(arg)
    return arg
}


const myPrint: Iprint<number> = print


十六、配置文件:tsconfig.json

在项目中,TS的配置文件是tsconfig.json
配置项很多,这里了解常用的配置即可,其他的用到的时候再去搜索

生成tsconfig.json 文件(注意:json文件里面只能用"",不能'') 这个文件是通过tsc --init命令生成的

常用的几个配置:

include

不写这个配置则是默认编译当前目录下所有的ts文件,写的话就是一个数组,将哪些TS文件编译为JS文件

"include": ["./index1.ts","./index2.ts"]
exclude

即include反过来,格式一样

target

指定编译后的js 的版本,例如es5 or es6

allowJS

是否允许编译js文件;默认当然允许,为true

removeComments

是否在编译过程中删除文件中的注释

strict

是否开启严格模式


十七、命名空间

namespace

TypeScript 与ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块
相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见
所以,不同文件定义了同名变量,也会报错,因为这是全局的

例如我们在在一个 TypeScript 工程项目下建立一个文件 1.ts,声明一个变量a,如下:

const a = 1

然后在另一个文件同样声明一个变量a,这时候会出现错误信息:

image.png

为了避免命名冲突,有两种解决办法:

  • import或者export引入模块系统即可;这里typescript语法与ES6一致,不赘述
  • 命名空间

只允许在命名空间或模块的顶部声明命名空间。即在ts文件中用namespace

namespace A{
  export const a = 1
}
console.log(A.a)

原理是包了一层function,上述TS代码编译后的JS代码是这样:

var A;
(function(A){
  A.a = 1;
})(A || (A = {}))

命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。
但就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中

在正常的TS项目开发过程中并不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用(所以就是,了解即可,用到再说)


十八、装饰器

Decorator 装饰器是一项实验性特性,在未来的版本中可能会发生改变
所以不学以后再说 没错我就是懒

TS入门搞完啦;以后要进阶再去学习新的,目前这些够用。