TypeScript基础知识梳理

之前一直打算看看ts,但一直沉迷摸🐟无法自拔,公司目前项目中也没有ts项目。今年手上正好有两个小程序项目,于是打算用ts写一下,感受一下ts的“魅力”。顺便整理了一下学习笔记,希望能帮到有需要的人。同时也用XMind做了知识图谱,有需要源文件的可以联系我。

安装与编译

安装

    npm install -g typescript //mac下安装前面需要加sudo

编译

  • 可以执行下面命令进行编译,会在当前目录下产生一个test.js文件
    tsc test.ts
  • 这样可能还是比较麻烦,我们可以借助插件方便我们提高效率:
    npm install -g ts-node
  • 安装完后只需执行ts-node test.ts就可以在控制台查看输出结果。

原始数据类型

  • js中数据类型分两种,原始数据类型和对象类型,原始类型包括:布尔值、数字、字符串、null、undefined以及Symbol。

布尔值

  • 在TypeScript中,使用boolean定义布尔值类型
    let isStatus:boolean=true;

数字

  • 使用number定义数值类型
    let isNumber:number=1;

    //16进制(编译后显示10进制数字)
    let isNumber16:number:=0b1010;

字符串

  • 使用string定义字符串类型:
    let isString:string='hello word';

    let year:number=2020;

    let add:string=`${isString},${year}!`;

空值

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

        alert("my name is tom")

    }
  • 声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null。
    let unusable: void = undefined;

Null 和 Undefined

  • 在TypeScript中可以使用nullundefined来定义这两个原始数据类型
    let u:undefined=undefined;

    let n:null=null;
  • 与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量,而void类型的变量不能赋值给number类型的变量:
    let num:number=undefined;//不会报错

    let u:undefined;

    let num: number = u;//也不会报错

    let u:void;

    let num:number=u;// Type 'void' is not assignable to type 'number'.

任意值

  • 任意值(Any)用来表示允许赋值为任意类型。

什么是任意值类型

  • 如果是一个普通类型,在赋值过程中改变类型是不被允许的:
    let renyiString:string='string';

    renyiString=8;

    //Type 'number' is not assignable to type 'string'.
  • 但如果类型是Any,类型,则允许被赋值为任意类型:
    let renyiString:any='string';

    renyiString=8;
  • 在任意值上访问任何属性都是允许的:
    let anyThis:any='hello';

    console.log(anyThis.myName)

    console.log(anyThis.myName.firstName)
  • 也允许调用任何方法:
    let anyThing: any = 'Tom';

    anyThing.setName('Jerry');

    anyThing.setName('Jerry').sayHello();

    anyThing.myName.setFirstName('Cat');
  • 可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

未声明类型的变量

  • 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
    let something;

    something = 'seven';

    something = 7;

    something.setName('Tom');
  • 等于
    let something: any;

    something = 'seven';

    something = 7;

    something.setName('Tom');

数组的类型

基础表示

  • 「类型 + 方括号」表示法
    let numbers:number[]=[1,2,3,4,5]
  • 此时不允许出现其他类型,而且如果使用数组中push等方法,添加元素也得符合相应类型。

数组泛型

  • 我们也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组
    let fibonacci:Array<number>=[1,2,3,4,]
  • 如果数组中又有number类型又有string类型,则可以用符号来区别定义:
    let arr:(number|string)[]=['tom',1];

数组中对象类型的定义

  • 项目中经常遇到数组中有对象的存在,对于这种就比较麻烦了,比如:
let arr:{name:string,age:number}[]=[
    {name:'tom',age:18}
]
  • 这样就比较麻烦了,我们可以使用ts中的类型别名来解决这个问题:
    type PeopleType={name:String,age:Number};

    let arr:PeopleType[]=[
        {name:'bob',age:19}
    ]
  • 也可以用类型定义也可以解决:
    class PeopleType{
        name:string;
        age:number
    }

    let arr:PeopleType[]=[
        {name:'bob',age:19}
    ]

元组的使用和类型约束

  • 在数组中如果里面又有stringnumber,可以使用来进行定义,但一定程度上并不严格。比如改成下面这种格式:
    let arr:(number|string)[]=[111,'222',111];
  • ts并没有报错,如果想要严格限制,则可以这样进行约束:
    let arr:[number,string,number]=[111,'222',111];

Interface接口

  • 比如我们要做一个筛选,吧不符合条件的过滤出去,我们可能会这样写:
const types=(name:string,age:number,height:number)=>{
    age>=20 && height>=180 && console.log('符合条件');

    age<=20 && height <180 &&console.log('不符合')
};

types('tom',20,180) //符合条件
  • 但如果又修改了一些需求,可能还会去大量变更代码,在开发中,代码能复用肯定是最好的,所以可以把一些重复的代码抽离出来:
    interface People{
        name:string;
        age:number;
        height:number;
    }

    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const choose=(people:People)=>{
        console.log(people.name+'----'+people.age+'---'+people.height)
    }

    const people={
        name:'tom',
        age:18,
        height:178
    }

    types(people);

    choose(people);

接口与类型别名的区别

  • 看起来两者没有什么区别,但有个小细节:类型别名可以直接给类型,接口必须代表对象
    type People=string;

    interface People{
        name:string;
        age:number;
        height:number;
    }
  • 如果传入的参数中有不确定项,我们可以使用 ?: 来进行处理:
    interface People{
        name:string;
        age:number;
        height:number;
        say?:string
    };

    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178
    }
    const people2={
        name:'tom',
        age:18,
        height:178,
        say:'hello'
    }

    types(people);

    types(people2);
  • 这时候又有新需求了,如何在后面加入任意多的字段?这时候我们就可以这么写:
    interface People{
        name:string;
        age:number;
        height:number;
        say?:string;
        [propname:string]:any;
    }


    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178,
        say:'hello',
        add:'new add',
        addNumber:123
    }

    types(people);

接口里的方法

  • 接口不仅仅可以存属性,也可以存方法:
    interface People{
        name:string;
        age:number;
        height:number;
        goto():string;
    }

    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178,
        goto(){
            return 'hello'
        }
    }

    types(people)

接口和类的约束

  • 在ES6中是有类的概念,类可以和接口相结合:
    interface People{
        name:string;
        age:number;
        height:number;
        goto():string;
    }
    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178,
        say:'hello',
        add:'new add',
        addNumber:123,
        goto(){
            return 'hello'
        }
    };

    class newPeople implements People{
        name='bob';
        age=19;
        height:190;
        say:'hello';
        add:'new add2';
        addNumber:123;
        goto(){
            return 'hi'
        }
    };

    let a=new newPeople();
    console.log(a.goto())
    types(people)

接口的继承

  • 接口与接口也是可以继承的:
    interface People{
        name:string;
        age:number;
        height:number;
        say?:string;
        [propname:string]:any;
        goto():string;
    }


    const types=(people:newPeople)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178,
        say:'hello',
        add:'new add',
        addNumber:123,
        goto(){
            return 'hello'
        },
        back(){
            console.log(1)
        }
    }

    interface newPeople extends People{
        back():void
    }
    types(people)

类的基本使用

  • 首先,我们先创建一个类:
    class Test{
        name='say'
        say(){
            return this.name
        }
    };
  • 这就是平时所写的类,在ts中的继承和ES6的继承是一样的,关键字也是extends,比如我们这里新建个类,继承Text;
    class Test{
        name='say'
        say(){
            return this.name
        }
    };
    class NewTest extends Test{
        back(){
            return 'hello'
        }
    }

    const tom=new NewTest();

    console.log(tom.say());
    console.log(tom.back());

super关键字的使用

  • 如果想在say方法中后面加点东西,可以这么操作:
    class Test{
        name='say'
        say(){
            return this.name
        }
    };

    class NewTest extends Test{
        back(){
            return 'hello'
        }
        say(){
            return super.say()+'----hello'
        }
    }

    const tom=new NewTest();

    console.log(tom.say());
    console.log(tom.back());

ts中类的访问类型

  • ts中的访问类型就是基于三个关键字:privateprotected以及pubilc这三种访问类型。看例子,先定义一个类,然后用这个类的对象,进行赋值:
    class Person{
         name:string
    }

    const person=new Person();

    person.name='tom';

    console.log(person.name)

pubilc

  • 运行可以看到正常的输出内容,这是因为如果不对name的访问属性进行定义,那么他的默认属性就是pubilc,从字面意思来看,它的意思就是公用的,允许在类的内部和外部被调用。
    class Person{
    public name:string
    }

    const person=new Person();

    person.name='tom';

    console.log(person.name)

private

  • private的属性意思就是只允许在类的内部调用,不能在外部调用
    class Person{
        private name:string;
        public say(){
            console.log(this.name+'-----hello');
        }
    }

    const person=new Person();

    person.name='tom';//报错

    console.log(person.name)//报错

protected

  • protected允许在类内以及继承的子类中使用,把刚刚的name改成protected属性,这时候在外部就会报错,这时候再写一个继承,代码如下:
    class Person{
        protected name:string;
        public say(){
            console.log(this.name+'-----hello');
        }

    }

    const person=new Person();

    person.name='tom';//报错

    console.log(person.name)//报错


    class NewPerson extends Person{
        public back(){
            console.log(this.name)
        }
    }

    const newperson=new NewPerson();

    newperson.back()//正常

类的构造函数

  • 首先新建一个类:Person,定义一个name,并在new的时候进行参数传递,然后打印出来,这时候我们就可以使用构造函数constructor :
    class Person{
        pubilc name:string;
        constructor(name){
            this.name=name
        }
    };

    const person=new Person('tom');
    console.log(person.name);

  • 可以看到可以打印出来,但是上面写法有点麻烦,还可以再进行简化:
    class Person{
        constructor(pubilc name:string){}
    };
    const person=new Person('tom');
    console.log(person.name);

类继承中的构造器写法

  • 普通的书写方法上面已经演示了,在子类中使用构造函数需要用**super()**调用父类的构造函数,直接看代码:
    class Person{
        constructor(pubilc name:string){}
    };

    class Teacher extends Person{
        constructor(pubilc age:number){
            super()
        }
    };

    const teacher=new Teacher(18);
    console.log(teacher.age+'---'+teacher.name);

ts中类的Getter、Setter、static以及readonly

  • 在上面中提到了访问类型private,它的最大用处就是封装一个书写,然后通过GetterSetter去访问和修改:
    class Person{
        constructor(private _age:number){

        }
    }
  • 如果想让别人知道,就可以使用Getter来实现,他并不是一个方法,只是一个属性:
    class Person{
        constructor(private _age:number){

        };
        get age(){
            return this._age
        }
    };

    const person=new Person(30);
    console.log(person.age)
  • 这时候你可能会觉得这不是多此一举吗?但在Getter中可以对 _age进行处理:
    class Person{
        constructor(private _age:number){

        };
        get age(){
            return this._age-10
        }
    };

    const person=new Person(30);
    console.log(person.age)
  • 既然 _age是私有的,我们无法进行改变,这时候就可以用Setter进行改变:
    class Person{
        constructor(private _age:number){

        };
        get age(){
            return this._age
        }
        set age(age:number){
            this._age=age
        }
    };

    const person=new Person(30);

    person.age=20;

    console.log(person.age)
  • 在类中,如果想用这个类的实例,就必须先进行new操作,但有没有一种方法不需要new就可以?
    //常规方法 
    class Person{
        say(){
            return 'say hello'
        }
    };

    const person=new Person();
    console.log(person.say());
  • 在ts中,我们不想new的话可以这么写:
    class Person{
    static  say(){
            return 'say hello'
        }
    };

    console.log(Person.say())
  • readonly在初始化后赋值,以后就不能进行修改
    class Person{
        constructor(readonly name:string){}
    }
    let p=new Person('tom');

    p.name='bob'//报错

    console.log(p.name);

类的抽象类

  • abstract 用于定义抽象类和其中的抽象方法。它有几个特点:

  • 抽象类是不允许实例化的

    //错误演示
    abstract class Person {
        public abstract say()
    }
    const tom=new Person()//无法创建抽象类的实例
  • 抽象类中的抽象方法必须被子类实现:
    //错误演示
    abstract class Person {
         abstract say(){
             console.log('hello')
         }
    }
    class Tom extends Person{
        say(){
            console.log('say hello')
        }
    };
    //正确方法
    abstract class Person {
         abstract say()
    }
    class Tom extends Person{
        say(){
            console.log('say hello')
        }
    };

tsconfig.json配置

生成

  • 我们可以通过tsc --init去生成ts配置文件:
   tsc --init //终端执行

编译

  • 会生成一个tsconfig.json文件,我们在ts文件中可以随便写点什么:
    //测试tsconfig.json
    const test:string='tsconfig.json';
  • 然后打开tsconfig.json文件,找到complilerOptions属性下的removeComments:true(这个配置是编译后不输出注释),取消掉注释,然后执行命令:
    tsc
  • 这时候打开生成的js文件,发现没有注释,说明成功。

include 、exclude 和 files

  • 如果有多个ts文件,只想编译一个可以在ts配置项中加入include
    "include":["text.ts"],
  • 如果想除了某个文件不编译,剩下的都编译,可以使用exclude
     "exclude":["text.ts"],
  • filesinclude没有什么区别:
     "files":["text.ts"],

compilerOptions配置

removeComments

  • 这个配置意思就是编译后不输出注释

strict

  • 这个设置为true,就代表严格执行ts语法,要严格按照ts语法来编写。

noImplicitAny

  • 允许你的注解类型 any 不用特意表明,如果此时设置了true,看例子:
    //这时候编译会报错
    function test(name) {
         return name;
    }
   
    //正确
     function test(name:any) {
         return name;
    }

strictNullChecks

  • 意思就是,不强制检查null类型,此时如果配置为true看例子:
    //此时就不会报错
    const test: string = null;

outDir和rootDir

  • 此项配置是来指定文件目录和打包后存放目录,rootDir为文件目录,outDir为打包后保存的目录
    {
        "outDir": "./build" ,
        "rootDir": "./src" ,
    }

编译ES6语法

  • 可以使用targetallowJs,target默认为true
    "target":'es5' ,  // 这一项默认是开启的,你必须要保证它的开启,才能转换成功
    "allowJs":true,   // 这个配置项的意思是联通

sourceMap

  • sourceMap 简单说,Source map 就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。

noUnusedLocals

  • 设置noUnusedLocals为true,编译代码:
    const name:tring='111';
    export const age = "text";
  • 这时候就会报错,因为有name变量没有使用。

更多

联合类型和类型保护

联合类型

  • 联合类型的意思就是允许一个类型有两种或者两种以上的类型:
    interface Test{
        name:string;
        say:()=>{

        }
    }

    interface Test2{
        name:string;
        call:()=>{};
    }

    function Tom(fun: Test | Test2){
        console.log(fun.name)
    }

  • 如果此时修改一下方法:
    function Tom(fun: Test | Test2){
        fun.say()
    }
    //报错
  • 这是因为只能访问两个类型的共有方法。

类型保护-类型断言

  • 上面的方法,如果修改完报错,这时候我们可以用as来判断:
    interface Test {
        text: boolean;
        say(): void
    }
    
    interface Test2 {
        text: boolean;
        skill():string
    }
    function judgeWho(val: Waiter | Teacher) {
        if (val.text) {
        (val as Teacher).skill();
        }else{
        (val as Waiter).say();
        }
    }

    const a={
        text:true,
        skill:function():void{
            console.log(1)
        },
        say:function():void{
            console.log(2)
        }
    }
    judgeWho(a) //1

类型保护-in语法

  • in方法与断言比较类似,使用方法如下:
    interface Person{
        name:string;
        say:()=>{

        }
    }

    interface Person2{
        name:string;
        todo:()=>{

        }
    };

    function tom(val:Person|Person2){
        if('todo' in val){
            val.todo()
        }else{
            val.say()
        }
    }

类型保护-typeof语法

  • 可以用typeof方法来判断:
    function add(name:string|number,name2:string|number){
        if(typeof name==='string'||typeof name2=='string'){
            return name+'---'+name2
        }else{
            return name+name2
        }
    }

    add(1,2)

类型保护-instanecof语法

  • 如果要保护类型是一个对象,就可以使用instanceof
    class NumObject{
        num:number
    }

    function numbers(num1:object|NumObject,num2:object|NumObject){
        if(num1 instanceof NumObject && num2 instanceof NumObject){
            return num1.num+num2.num
        }
    }

Enum枚举类型

  • 我们平时会有这种写法:
    function getName(status:any){
        if(status===0){
            return 'one'
        }else if(status===1){
            return 'two'
        }else{
            return 'three'
        }
    }

    console.log(getName(0))
  • 这么写可能有点麻烦,阅读起来还是有点麻烦,这时候可以这么写:
    const Status={
        ONE:0,
        TWO:1,
        THREE:3
    }

    function getName(status:any){
        if(status===Status.ONE){
            return 'one'
        }else if(status===Status.TWO){
            return 'two'
        }else{
            return 'three'
        }
    }

    console.log(getName(0));
  • 这时候我们的枚举就要上场了:
    enum Status{
        ONE,
        TWO,
        THREE
    }

    function getName(status:any){
        if(status===Status.ONE){
            return 'one'
        }else if(status===Status.TWO){
            return 'two'
        }else{
            return 'three'
        }
    }

    console.log(getName(1));
  • 一样也可以输出,因为枚举是有对应数字值的,默认从0开始,当然也可以改变:
    enum Status{
        ONE=1,
        TWO,//2
        THREE//3
    }
  • 也可以进行返查操作:
    enum Status{
        ONE,
        TWO,
        THREE
    }

    function getName(status:any){
        if(status===Status.ONE){
            return 'one'
        }else if(status===Status.TWO){
            return 'two'
        }else{
            return 'three'
        }
    }

    console.log(Status.ONE,Status[1]);

泛型

  • 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

在函数中使用

  • 我们先编写一个函数:
    function add(one:string,two:string){
        return `${one}${two}`
    }

    console.log(add('a','b'))
  • 这时候我们希望参数更加灵活一些,两个参数为number或者string:
    function add(one:string|number,two:string|number){
        return `${one}${two}`
    }

    console.log(add('a','b'))
  • 这么书写有些麻烦,这时候就可以使用泛型:
    function add<T>(one:T,two:T){
        return `${one}${two}`
    }

    console.log(add<string>('a','b'))
  • 在使用中,泛型通常用 <T> 来进行表示。泛型可以有多个吗?当然可以:
   function add<T,P>(one:T,two:P){
        return `${one}${two}`
    }

    console.log(add<string,number>('a',2))
  • 同时泛型也支持类型推断:
    function add<T,P>(one:T,two:P){
        return `${one}${two}`
    }

    console.log(add<string,number>('a',2))

在类中使用

  • 首先先写一个类,且接受参数为一个数组,数组里面存放string类型的数据:
    class List {
        constructor(private list:string[]) {};
        getItem(index:number):string{
            return this.list[index]
        }
    };

    const list=new List(['girl','boy','wom']);
    console.log(list.getItem(1))
  • 这时候如果我们传递数组时候里面又想放数字怎么办:
    class List {
        constructor(private list:string[]|number[]) {};
        getItem(index:number):string|number{
            return this.list[index]
        }
    };

    const list=new List(['girl','boy','wom']);
    console.log(list.getItem(1))
  • 这么写就比较复杂了,这时候就可以使用泛型来简化我们的代码:
    class List<T>{
        constructor(private list:T[]) {};
        getItem(index:number):T{
            return this.list[index]
        }
    };

    const list=new List(['girl','boy','wom']);
    console.log(list.getItem(1))
  • 发现上面的代码没有报错?因为类型推论,所以不会报错,严格意义上应该new的时候加上类型:
    const list=new List<string>(['girl','boy','wom']);
  • 还有一种场景,传递过来是一个数组对象,这时候可以通过继承来解决:
    interface People{
        name:string
    }

    class List<T extends People>{
        constructor(private list:T[]) {};
        getItem(index:number):string{
            return this.list[index].name
        }
    };

    const list=new List(
        [
            {name:'boy'},
            {name:'girl'}
        ]
    );
    console.log(list.getItem(1))

泛型约束

  • 上面例子中,泛型可以为任意值,但有时候我们希望还是能稍微约束一下:
    function list<T extends number|string>(name:T){
        return `${name}`
    }

    console.log(list<number>(1))
    class  List<T extends number|string> {
        constructor(private list:T[]) {
            
        };
        getItem(index:number):T{
            return this.list[index]
        }
    }

    const list=new List([1,2])

    console.log(list.getItem(0))

Namespace命名空间

新建一个ts项目

  • 首先,我们建立一个项目文件,然后npm init -y生成package.json文件,然后再tsc -init生成ts配置文件。

  • 在根目录下新建index.html文件,再建立一个srcbulid目录,在src目录下新建一个index.js

  • 配置tsconfig.json文件,设置入口和输出目录(outDir和rootDir)。

  • 打开index.html,引入js文件:

    <script src="./build/index.js"></script>
  • 在新建的ts文件中随便写点什么:
    console.log('hello');
  • 然后tsc编译一下,打开控制台,我们就可以看到了

编写一个小组件

  • 在我们刚刚建立的index.ts文件,写一个headercontentfooter组件:
    class Header {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Header";
            document.body.appendChild(elem);
        }
    }

    class Content {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Content";
            document.body.appendChild(elem);
        }
    }

    class Footer {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Footer";
            document.body.appendChild(elem);
        }
    }

    class Page {
        constructor() {
            new Header();
            new Content();
            new Footer();
        }
    }
  • 然后在index.html中加一行js代码:
    <script>new Page();</script>
  • 这时候我们可以看到内容正常输出,但有一个问题,我们的Headercontentfooter都暴露了出来,并不是只暴露一个page,这时候我们的命名空间就派上了用场:

命名空间

  • 命名空间声明的关键词是namespace 比如声明一个namespace Home,需要暴露出去的类,可以使用export关键词,这样只有暴漏出去的类是全局的,其他的不会再生成全局污染了。
    namespace Home {
    class Header {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Header";
            document.body.appendChild(elem);
        }
    }

    class Content {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Content";
            document.body.appendChild(elem);
        }
    }

    class Footer {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Footer";
            document.body.appendChild(elem);
        }
    }

    export class Page {
        constructor() {
            new Header();
            new Content();
            new Footer();
            }
        }
    }
  • 这么写也是比较麻烦,因为我们需要引入两个文件,我们可以通过配置来让他成为一个文件,打开tsconfig.json,找到这一行:
    "module":"commonjs"
    //修改为:
     "module":"amd
  • 然后找到这一行:
    {
      "outFile": "./build/index.js"
    }

子命名空间

  • 如果在刚刚的组件中再写一个会怎么样?
    namespace Components {
        export namespace SubComponents {
            export class Test {}
        }

        //someting ...
    }

    //读取
    // Components.SubComponents.Test

ts中使用import

  • 首先建立一个文件compontent.ts,随便写点东西,然后使用export导出:
    export class Header {
        constructor() {
        const elem = document.createElement("div");
        elem.innerText = "This is Header";
        document.body.appendChild(elem);
        }
    }

    export class Content {
        constructor() {
        const elem = document.createElement("div");
        elem.innerText = "This is Content";
        document.body.appendChild(elem);
        }
    }

    export class Footer {
        constructor() {
        const elem = document.createElement("div");
        elem.innerText = "This is Footer";
        document.body.appendChild(elem);
        }
    }
  • 然后在index.ts中导入一下:
    import { Header, Content, Footer } from "./compontent";

    export class Page{
        constructor(){
            new Header();
            new Content();
            new Footer();
        }
    }
  • 运行tsc编译,打开build中的index.js,可以看到代码是define开头的,这是 amd 规范的代码,不能直接在浏览器中运行,可以在 Node 中直接运行,所以我们还需要借助require.js的支持:
    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>
  • 然后在index.html中使用require.js写法:
    <script>
       require(["page"], function (page) {
          new page.default();
        });
    </script>
```# TypeScript基础知识梳理

> 之前一直打算看看ts,但一直沉迷摸🐟无法自拔,公司目前项目中也没有ts项目。今年手上正好有两个小程序项目,于是打算用ts写一下,感受一下ts的“魅力”。顺便整理了一下学习笔记,希望能帮到有需要的人。同时也用**XMind**做了知识图谱,有需要源文件的可以联系我。

## 安装与编译

### 安装

```js
    npm install -g typescript //mac下安装前面需要加sudo

编译

  • 可以执行下面命令进行编译,会在当前目录下产生一个test.js文件
    tsc test.ts
  • 这样可能还是比较麻烦,我们可以借助插件方便我们提高效率:
    npm install -g ts-node
  • 安装完后只需执行ts-node test.ts就可以在控制台查看输出结果。

原始数据类型

  • js中数据类型分两种,原始数据类型和对象类型,原始类型包括:布尔值、数字、字符串、null、undefined以及Symbol。

布尔值

  • 在TypeScript中,使用boolean定义布尔值类型
    let isStatus:boolean=true;

数字

  • 使用number定义数值类型
    let isNumber:number=1;

    //16进制(编译后显示10进制数字)
    let isNumber16:number:=0b1010;

字符串

  • 使用string定义字符串类型:
    let isString:string='hello word';

    let year:number=2020;

    let add:string=`${isString},${year}!`;

空值

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

        alert("my name is tom")

    }
  • 声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null。
    let unusable: void = undefined;

Null 和 Undefined

  • 在TypeScript中可以使用nullundefined来定义这两个原始数据类型
    let u:undefined=undefined;

    let n:null=null;
  • 与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量,而void类型的变量不能赋值给number类型的变量:
    let num:number=undefined;//不会报错

    let u:undefined;

    let num: number = u;//也不会报错

    let u:void;

    let num:number=u;// Type 'void' is not assignable to type 'number'.

任意值

  • 任意值(Any)用来表示允许赋值为任意类型。

什么是任意值类型

  • 如果是一个普通类型,在赋值过程中改变类型是不被允许的:
    let renyiString:string='string';

    renyiString=8;

    //Type 'number' is not assignable to type 'string'.
  • 但如果类型是Any,类型,则允许被赋值为任意类型:
    let renyiString:any='string';

    renyiString=8;
  • 在任意值上访问任何属性都是允许的:
    let anyThis:any='hello';

    console.log(anyThis.myName)

    console.log(anyThis.myName.firstName)
  • 也允许调用任何方法:
    let anyThing: any = 'Tom';

    anyThing.setName('Jerry');

    anyThing.setName('Jerry').sayHello();

    anyThing.myName.setFirstName('Cat');
  • 可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

未声明类型的变量

  • 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
    let something;

    something = 'seven';

    something = 7;

    something.setName('Tom');
  • 等于
    let something: any;

    something = 'seven';

    something = 7;

    something.setName('Tom');

数组的类型

基础表示

  • 「类型 + 方括号」表示法
    let numbers:number[]=[1,2,3,4,5]
  • 此时不允许出现其他类型,而且如果使用数组中push等方法,添加元素也得符合相应类型。

数组泛型

  • 我们也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组
    let fibonacci:Array<number>=[1,2,3,4,]
  • 如果数组中又有number类型又有string类型,则可以用符号来区别定义:
    let arr:(number|string)[]=['tom',1];

数组中对象类型的定义

  • 项目中经常遇到数组中有对象的存在,对于这种就比较麻烦了,比如:
let arr:{name:string,age:number}[]=[
    {name:'tom',age:18}
]
  • 这样就比较麻烦了,我们可以使用ts中的类型别名来解决这个问题:
    type PeopleType={name:String,age:Number};

    let arr:PeopleType[]=[
        {name:'bob',age:19}
    ]
  • 也可以用类型定义也可以解决:
    class PeopleType{
        name:string;
        age:number
    }

    let arr:PeopleType[]=[
        {name:'bob',age:19}
    ]

元组的使用和类型约束

  • 在数组中如果里面又有stringnumber,可以使用来进行定义,但一定程度上并不严格。比如改成下面这种格式:
    let arr:(number|string)[]=[111,'222',111];
  • ts并没有报错,如果想要严格限制,则可以这样进行约束:
    let arr:[number,string,number]=[111,'222',111];

Interface接口

  • 比如我们要做一个筛选,吧不符合条件的过滤出去,我们可能会这样写:
const types=(name:string,age:number,height:number)=>{
    age>=20 && height>=180 && console.log('符合条件');

    age<=20 && height <180 &&console.log('不符合')
};

types('tom',20,180) //符合条件
  • 但如果又修改了一些需求,可能还会去大量变更代码,在开发中,代码能复用肯定是最好的,所以可以把一些重复的代码抽离出来:
    interface People{
        name:string;
        age:number;
        height:number;
    }

    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const choose=(people:People)=>{
        console.log(people.name+'----'+people.age+'---'+people.height)
    }

    const people={
        name:'tom',
        age:18,
        height:178
    }

    types(people);

    choose(people);

接口与类型别名的区别

  • 看起来两者没有什么区别,但有个小细节:类型别名可以直接给类型,接口必须代表对象
    type People=string;

    interface People{
        name:string;
        age:number;
        height:number;
    }
  • 如果传入的参数中有不确定项,我们可以使用 ?: 来进行处理:
    interface People{
        name:string;
        age:number;
        height:number;
        say?:string
    };

    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178
    }
    const people2={
        name:'tom',
        age:18,
        height:178,
        say:'hello'
    }

    types(people);

    types(people2);
  • 这时候又有新需求了,如何在后面加入任意多的字段?这时候我们就可以这么写:
    interface People{
        name:string;
        age:number;
        height:number;
        say?:string;
        [propname:string]:any;
    }


    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178,
        say:'hello',
        add:'new add',
        addNumber:123
    }

    types(people);

接口里的方法

  • 接口不仅仅可以存属性,也可以存方法:
    interface People{
        name:string;
        age:number;
        height:number;
        goto():string;
    }

    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178,
        goto(){
            return 'hello'
        }
    }

    types(people)

接口和类的约束

  • 在ES6中是有类的概念,类可以和接口相结合:
    interface People{
        name:string;
        age:number;
        height:number;
        goto():string;
    }
    const types=(people:People)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178,
        say:'hello',
        add:'new add',
        addNumber:123,
        goto(){
            return 'hello'
        }
    };

    class newPeople implements People{
        name='bob';
        age=19;
        height:190;
        say:'hello';
        add:'new add2';
        addNumber:123;
        goto(){
            return 'hi'
        }
    };

    let a=new newPeople();
    console.log(a.goto())
    types(people)

接口的继承

  • 接口与接口也是可以继承的:
    interface People{
        name:string;
        age:number;
        height:number;
        say?:string;
        [propname:string]:any;
        goto():string;
    }


    const types=(people:newPeople)=>{
        people.age>=20 && people.height>=180 && console.log('符合条件');

        people.age<=20 && people.height <180 &&console.log('不符合')
    };

    const people={
        name:'tom',
        age:18,
        height:178,
        say:'hello',
        add:'new add',
        addNumber:123,
        goto(){
            return 'hello'
        },
        back(){
            console.log(1)
        }
    }

    interface newPeople extends People{
        back():void
    }
    types(people)

类的基本使用

  • 首先,我们先创建一个类:
    class Test{
        name='say'
        say(){
            return this.name
        }
    };
  • 这就是平时所写的类,在ts中的继承和ES6的继承是一样的,关键字也是extends,比如我们这里新建个类,继承Text;
    class Test{
        name='say'
        say(){
            return this.name
        }
    };
    class NewTest extends Test{
        back(){
            return 'hello'
        }
    }

    const tom=new NewTest();

    console.log(tom.say());
    console.log(tom.back());

super关键字的使用

  • 如果想在say方法中后面加点东西,可以这么操作:
    class Test{
        name='say'
        say(){
            return this.name
        }
    };

    class NewTest extends Test{
        back(){
            return 'hello'
        }
        say(){
            return super.say()+'----hello'
        }
    }

    const tom=new NewTest();

    console.log(tom.say());
    console.log(tom.back());

ts中类的访问类型

  • ts中的访问类型就是基于三个关键字:privateprotected以及pubilc这三种访问类型。看例子,先定义一个类,然后用这个类的对象,进行赋值:
    class Person{
         name:string
    }

    const person=new Person();

    person.name='tom';

    console.log(person.name)

pubilc

  • 运行可以看到正常的输出内容,这是因为如果不对name的访问属性进行定义,那么他的默认属性就是pubilc,从字面意思来看,它的意思就是公用的,允许在类的内部和外部被调用。
    class Person{
    public name:string
    }

    const person=new Person();

    person.name='tom';

    console.log(person.name)

private

  • private的属性意思就是只允许在类的内部调用,不能在外部调用
    class Person{
        private name:string;
        public say(){
            console.log(this.name+'-----hello');
        }
    }

    const person=new Person();

    person.name='tom';//报错

    console.log(person.name)//报错

protected

  • protected允许在类内以及继承的子类中使用,把刚刚的name改成protected属性,这时候在外部就会报错,这时候再写一个继承,代码如下:
    class Person{
        protected name:string;
        public say(){
            console.log(this.name+'-----hello');
        }

    }

    const person=new Person();

    person.name='tom';//报错

    console.log(person.name)//报错


    class NewPerson extends Person{
        public back(){
            console.log(this.name)
        }
    }

    const newperson=new NewPerson();

    newperson.back()//正常

类的构造函数

  • 首先新建一个类:Person,定义一个name,并在new的时候进行参数传递,然后打印出来,这时候我们就可以使用构造函数constructor :
    class Person{
        pubilc name:string;
        constructor(name){
            this.name=name
        }
    };

    const person=new Person('tom');
    console.log(person.name);

  • 可以看到可以打印出来,但是上面写法有点麻烦,还可以再进行简化:
    class Person{
        constructor(pubilc name:string){}
    };
    const person=new Person('tom');
    console.log(person.name);

类继承中的构造器写法

  • 普通的书写方法上面已经演示了,在子类中使用构造函数需要用**super()**调用父类的构造函数,直接看代码:
    class Person{
        constructor(pubilc name:string){}
    };

    class Teacher extends Person{
        constructor(pubilc age:number){
            super()
        }
    };

    const teacher=new Teacher(18);
    console.log(teacher.age+'---'+teacher.name);

ts中类的Getter、Setter、static以及readonly

  • 在上面中提到了访问类型private,它的最大用处就是封装一个书写,然后通过GetterSetter去访问和修改:
    class Person{
        constructor(private _age:number){

        }
    }
  • 如果想让别人知道,就可以使用Getter来实现,他并不是一个方法,只是一个属性:
    class Person{
        constructor(private _age:number){

        };
        get age(){
            return this._age
        }
    };

    const person=new Person(30);
    console.log(person.age)
  • 这时候你可能会觉得这不是多此一举吗?但在Getter中可以对 _age进行处理:
    class Person{
        constructor(private _age:number){

        };
        get age(){
            return this._age-10
        }
    };

    const person=new Person(30);
    console.log(person.age)
  • 既然 _age是私有的,我们无法进行改变,这时候就可以用Setter进行改变:
    class Person{
        constructor(private _age:number){

        };
        get age(){
            return this._age
        }
        set age(age:number){
            this._age=age
        }
    };

    const person=new Person(30);

    person.age=20;

    console.log(person.age)
  • 在类中,如果想用这个类的实例,就必须先进行new操作,但有没有一种方法不需要new就可以?
    //常规方法 
    class Person{
        say(){
            return 'say hello'
        }
    };

    const person=new Person();
    console.log(person.say());
  • 在ts中,我们不想new的话可以这么写:
    class Person{
    static  say(){
            return 'say hello'
        }
    };

    console.log(Person.say())
  • readonly在初始化后赋值,以后就不能进行修改
    class Person{
        constructor(readonly name:string){}
    }
    let p=new Person('tom');

    p.name='bob'//报错

    console.log(p.name);

类的抽象类

  • abstract 用于定义抽象类和其中的抽象方法。它有几个特点:

  • 抽象类是不允许实例化的

    //错误演示
    abstract class Person {
        public abstract say()
    }
    const tom=new Person()//无法创建抽象类的实例
  • 抽象类中的抽象方法必须被子类实现:
    //错误演示
    abstract class Person {
         abstract say(){
             console.log('hello')
         }
    }
    class Tom extends Person{
        say(){
            console.log('say hello')
        }
    };
    //正确方法
    abstract class Person {
         abstract say()
    }
    class Tom extends Person{
        say(){
            console.log('say hello')
        }
    };

tsconfig.json配置

生成

  • 我们可以通过tsc --init去生成ts配置文件:
   tsc --init //终端执行

编译

  • 会生成一个tsconfig.json文件,我们在ts文件中可以随便写点什么:
    //测试tsconfig.json
    const test:string='tsconfig.json';
  • 然后打开tsconfig.json文件,找到complilerOptions属性下的removeComments:true(这个配置是编译后不输出注释),取消掉注释,然后执行命令:
    tsc
  • 这时候打开生成的js文件,发现没有注释,说明成功。

include 、exclude 和 files

  • 如果有多个ts文件,只想编译一个可以在ts配置项中加入include
    "include":["text.ts"],
  • 如果想除了某个文件不编译,剩下的都编译,可以使用exclude
     "exclude":["text.ts"],
  • filesinclude没有什么区别:
     "files":["text.ts"],

compilerOptions配置

removeComments

  • 这个配置意思就是编译后不输出注释

strict

  • 这个设置为true,就代表严格执行ts语法,要严格按照ts语法来编写。

noImplicitAny

  • 允许你的注解类型 any 不用特意表明,如果此时设置了true,看例子:
    //这时候编译会报错
    function test(name) {
         return name;
    }
   
    //正确
     function test(name:any) {
         return name;
    }

strictNullChecks

  • 意思就是,不强制检查null类型,此时如果配置为true看例子:
    //此时就不会报错
    const test: string = null;

outDir和rootDir

  • 此项配置是来指定文件目录和打包后存放目录,rootDir为文件目录,outDir为打包后保存的目录
    {
        "outDir": "./build" ,
        "rootDir": "./src" ,
    }

编译ES6语法

  • 可以使用targetallowJs,target默认为true
    "target":'es5' ,  // 这一项默认是开启的,你必须要保证它的开启,才能转换成功
    "allowJs":true,   // 这个配置项的意思是联通

sourceMap

  • sourceMap 简单说,Source map 就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。

noUnusedLocals

  • 设置noUnusedLocals为true,编译代码:
    const name:tring='111';
    export const age = "text";
  • 这时候就会报错,因为有name变量没有使用。

更多

联合类型和类型保护

联合类型

  • 联合类型的意思就是允许一个类型有两种或者两种以上的类型:
    interface Test{
        name:string;
        say:()=>{

        }
    }

    interface Test2{
        name:string;
        call:()=>{};
    }

    function Tom(fun: Test | Test2){
        console.log(fun.name)
    }

  • 如果此时修改一下方法:
    function Tom(fun: Test | Test2){
        fun.say()
    }
    //报错
  • 这是因为只能访问两个类型的共有方法。

类型保护-类型断言

  • 上面的方法,如果修改完报错,这时候我们可以用as来判断:
    interface Test {
        text: boolean;
        say(): void
    }
    
    interface Test2 {
        text: boolean;
        skill():string
    }
    function judgeWho(val: Waiter | Teacher) {
        if (val.text) {
        (val as Teacher).skill();
        }else{
        (val as Waiter).say();
        }
    }

    const a={
        text:true,
        skill:function():void{
            console.log(1)
        },
        say:function():void{
            console.log(2)
        }
    }
    judgeWho(a) //1

类型保护-in语法

  • in方法与断言比较类似,使用方法如下:
    interface Person{
        name:string;
        say:()=>{

        }
    }

    interface Person2{
        name:string;
        todo:()=>{

        }
    };

    function tom(val:Person|Person2){
        if('todo' in val){
            val.todo()
        }else{
            val.say()
        }
    }

类型保护-typeof语法

  • 可以用typeof方法来判断:
    function add(name:string|number,name2:string|number){
        if(typeof name==='string'||typeof name2=='string'){
            return name+'---'+name2
        }else{
            return name+name2
        }
    }

    add(1,2)

类型保护-instanecof语法

  • 如果要保护类型是一个对象,就可以使用instanceof
    class NumObject{
        num:number
    }

    function numbers(num1:object|NumObject,num2:object|NumObject){
        if(num1 instanceof NumObject && num2 instanceof NumObject){
            return num1.num+num2.num
        }
    }

Enum枚举类型

  • 我们平时会有这种写法:
    function getName(status:any){
        if(status===0){
            return 'one'
        }else if(status===1){
            return 'two'
        }else{
            return 'three'
        }
    }

    console.log(getName(0))
  • 这么写可能有点麻烦,阅读起来还是有点麻烦,这时候可以这么写:
    const Status={
        ONE:0,
        TWO:1,
        THREE:3
    }

    function getName(status:any){
        if(status===Status.ONE){
            return 'one'
        }else if(status===Status.TWO){
            return 'two'
        }else{
            return 'three'
        }
    }

    console.log(getName(0));
  • 这时候我们的枚举就要上场了:
    enum Status{
        ONE,
        TWO,
        THREE
    }

    function getName(status:any){
        if(status===Status.ONE){
            return 'one'
        }else if(status===Status.TWO){
            return 'two'
        }else{
            return 'three'
        }
    }

    console.log(getName(1));
  • 一样也可以输出,因为枚举是有对应数字值的,默认从0开始,当然也可以改变:
    enum Status{
        ONE=1,
        TWO,//2
        THREE//3
    }
  • 也可以进行返查操作:
    enum Status{
        ONE,
        TWO,
        THREE
    }

    function getName(status:any){
        if(status===Status.ONE){
            return 'one'
        }else if(status===Status.TWO){
            return 'two'
        }else{
            return 'three'
        }
    }

    console.log(Status.ONE,Status[1]);

泛型

  • 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

在函数中使用

  • 我们先编写一个函数:
    function add(one:string,two:string){
        return `${one}${two}`
    }

    console.log(add('a','b'))
  • 这时候我们希望参数更加灵活一些,两个参数为number或者string:
    function add(one:string|number,two:string|number){
        return `${one}${two}`
    }

    console.log(add('a','b'))
  • 这么书写有些麻烦,这时候就可以使用泛型:
    function add<T>(one:T,two:T){
        return `${one}${two}`
    }

    console.log(add<string>('a','b'))
  • 在使用中,泛型通常用 <T> 来进行表示。泛型可以有多个吗?当然可以:
   function add<T,P>(one:T,two:P){
        return `${one}${two}`
    }

    console.log(add<string,number>('a',2))
  • 同时泛型也支持类型推断:
    function add<T,P>(one:T,two:P){
        return `${one}${two}`
    }

    console.log(add<string,number>('a',2))

在类中使用

  • 首先先写一个类,且接受参数为一个数组,数组里面存放string类型的数据:
    class List {
        constructor(private list:string[]) {};
        getItem(index:number):string{
            return this.list[index]
        }
    };

    const list=new List(['girl','boy','wom']);
    console.log(list.getItem(1))
  • 这时候如果我们传递数组时候里面又想放数字怎么办:
    class List {
        constructor(private list:string[]|number[]) {};
        getItem(index:number):string|number{
            return this.list[index]
        }
    };

    const list=new List(['girl','boy','wom']);
    console.log(list.getItem(1))
  • 这么写就比较复杂了,这时候就可以使用泛型来简化我们的代码:
    class List<T>{
        constructor(private list:T[]) {};
        getItem(index:number):T{
            return this.list[index]
        }
    };

    const list=new List(['girl','boy','wom']);
    console.log(list.getItem(1))
  • 发现上面的代码没有报错?因为类型推论,所以不会报错,严格意义上应该new的时候加上类型:
    const list=new List<string>(['girl','boy','wom']);
  • 还有一种场景,传递过来是一个数组对象,这时候可以通过继承来解决:
    interface People{
        name:string
    }

    class List<T extends People>{
        constructor(private list:T[]) {};
        getItem(index:number):string{
            return this.list[index].name
        }
    };

    const list=new List(
        [
            {name:'boy'},
            {name:'girl'}
        ]
    );
    console.log(list.getItem(1))

泛型约束

  • 上面例子中,泛型可以为任意值,但有时候我们希望还是能稍微约束一下:
    function list<T extends number|string>(name:T){
        return `${name}`
    }

    console.log(list<number>(1))
    class  List<T extends number|string> {
        constructor(private list:T[]) {
            
        };
        getItem(index:number):T{
            return this.list[index]
        }
    }

    const list=new List([1,2])

    console.log(list.getItem(0))

Namespace命名空间

新建一个ts项目

  • 首先,我们建立一个项目文件,然后npm init -y生成package.json文件,然后再tsc -init生成ts配置文件。

  • 在根目录下新建index.html文件,再建立一个srcbulid目录,在src目录下新建一个index.js

  • 配置tsconfig.json文件,设置入口和输出目录(outDir和rootDir)。

  • 打开index.html,引入js文件:

    <script src="./build/index.js"></script>
  • 在新建的ts文件中随便写点什么:
    console.log('hello');
  • 然后tsc编译一下,打开控制台,我们就可以看到了

编写一个小组件

  • 在我们刚刚建立的index.ts文件,写一个headercontentfooter组件:
    class Header {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Header";
            document.body.appendChild(elem);
        }
    }

    class Content {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Content";
            document.body.appendChild(elem);
        }
    }

    class Footer {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Footer";
            document.body.appendChild(elem);
        }
    }

    class Page {
        constructor() {
            new Header();
            new Content();
            new Footer();
        }
    }
  • 然后在index.html中加一行js代码:
    <script>new Page();</script>
  • 这时候我们可以看到内容正常输出,但有一个问题,我们的Headercontentfooter都暴露了出来,并不是只暴露一个page,这时候我们的命名空间就派上了用场:

命名空间

  • 命名空间声明的关键词是namespace 比如声明一个namespace Home,需要暴露出去的类,可以使用export关键词,这样只有暴漏出去的类是全局的,其他的不会再生成全局污染了。
    namespace Home {
    class Header {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Header";
            document.body.appendChild(elem);
        }
    }

    class Content {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Content";
            document.body.appendChild(elem);
        }
    }

    class Footer {
        constructor() {
            const elem = document.createElement("div");
            elem.innerText = "This is Footer";
            document.body.appendChild(elem);
        }
    }

    export class Page {
        constructor() {
            new Header();
            new Content();
            new Footer();
            }
        }
    }
  • 这么写也是比较麻烦,因为我们需要引入两个文件,我们可以通过配置来让他成为一个文件,打开tsconfig.json,找到这一行:
    "module":"commonjs"
    //修改为:
     "module":"amd
  • 然后找到这一行:
    {
      "outFile": "./build/index.js"
    }

子命名空间

  • 如果在刚刚的组件中再写一个会怎么样?
    namespace Components {
        export namespace SubComponents {
            export class Test {}
        }

        //someting ...
    }

    //读取
    // Components.SubComponents.Test

ts中使用import

  • 首先建立一个文件compontent.ts,随便写点东西,然后使用export导出:
    export class Header {
        constructor() {
        const elem = document.createElement("div");
        elem.innerText = "This is Header";
        document.body.appendChild(elem);
        }
    }

    export class Content {
        constructor() {
        const elem = document.createElement("div");
        elem.innerText = "This is Content";
        document.body.appendChild(elem);
        }
    }

    export class Footer {
        constructor() {
        const elem = document.createElement("div");
        elem.innerText = "This is Footer";
        document.body.appendChild(elem);
        }
    }
  • 然后在index.ts中导入一下:
    import { Header, Content, Footer } from "./compontent";

    export class Page{
        constructor(){
            new Header();
            new Content();
            new Footer();
        }
    }
  • 运行tsc编译,打开build中的index.js,可以看到代码是define开头的,这是 amd 规范的代码,不能直接在浏览器中运行,可以在 Node 中直接运行,所以我们还需要借助require.js的支持:
    <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>
  • 然后在index.html中使用require.js写法:
    <script>
       require(["page"], function (page) {
          new page.default();
        });
    </script>