TypeScript浅入浅出

环境搭建

  • npm i typescript -g
    • 只需要安装一次
  • tsc --init
    • 会生成tsconfig.json文件
  • 打开配置文件中"outDir": "./",的注释
    • 根据需求修改
  • tsc 文件名
    • 执行编译生成js文件

VS半自动化环境

  • 终端
  • 运行任务
  • tsc 监视...
    • 即可实现变化保存自动编译为js文件

编译指令

tsc 1.ts --outDir ./dist
如果不加--outDir则默认会编译到ts同一目录
ts-node

tsconfig:


"./src":只编译src目录下文件,内部子文件夹不编译

"./src//":代表递归编译文件夹内部所有子文件夹 后面代表所有文件

ts-node:其他类型编译器 ts-node直接编译


ts数据类型


数字,字符串,布尔值

null underfined

数组 元组 枚举

void any Never


类型系统


string number boolean 基本类型

String Number Boolean 对象类型


基本类型可以赋值给包装类型,但是反之不可

数组(必须存储同一类型)

//基本语法
//数组的声明,此时push是没用的,因为未定义
// let arr:number[];
//数组的定义
let arr:number[]=[];
//泛型方式
// let arr1:Array;
arr.push(...[1,2,4])
console.log(arr)

元组(类型不必相同)

let data:[number,string,boolean];
data=[1,"a",true];
说明:元组再3.1之后,不能越界使用联合类型了,而且赋值和定义的类型要一一对应

联合类型

//多个类型中的一个  或的关系
let a:string|number|boolean=20;
a="a";
console.log(a)

交叉类型


//多个类型的叠加,并且的关系
let b:string&number=

枚举

enum Color{
    RED,
    YELLOW
}
console.log(Color.RED);//0
console.log(Color.YELLOW);//1

enum Color{
    RED=1,
    YELLOW
}
console.log(Color.RED);//1
console.log(Color.YELLOW);//2

Never

说明:代表那些永远不存在的值的类型,ts也可以自动推断,never此时也可以省略
function err():never{
    throw new Error("error")
}

Any

说明:任意类型,在不确定数据类型的情况下使用
let a:any="a";
a=10;
console.log(a)

类型综合

/**
 * 数据类型:
 * 布尔类型(boolean)
 * 数字类型(number)
 * 字符串类型(string)
 * 元组类型(tuple)
 * 枚举类型(enum)
 * 任意类型(any)
 * null和underfined
 * void类型
 * never类型:从不会出现的值
 */

 //不赋值,也不会有默认值
let flag:boolean=true
//第一种定义数组的方式
let arrs:number[]=[1,2,3]
//第二种定义数组的方式
let arrs1:Array=[1,2,3]
//第三种定义数组的方式
let arrs4:Array=[1,'3',true] //不会报错

//元组类型:属于数组的一种,此时数据类型和后面赋值要一一对应
let arrs2:[number,string]=[123,'this is ts']

//枚举类型
enum WEEK{
    success=1, //指定枚举从1开始,不指定则默认0起始
    error,//此时不指定,则为2,如果多个值中间指定赋值,则后面的依此加一
    'underfined'=-1,
    'null'=-2
}
let w:WEEK=WEEK.error;
console.log(WEEK.underfined); //-1

//任意类型
let num:any=123;
num='asdas';

//任意类型使用场景
let item:any=document.getElementById('test');

//null和underfined是其他(never)数据类型的子类型
let num1:undefined;
// console.log(num1) //不报错,如果定义为number类型则报错了

let num2:undefined|number;
// console.log(num2);//兼具两者的优势


function run():void{

}
//声明never的变量只能被never类型赋值:代表从不会出现的值
let a:never;
a=(()=>{
    throw new Error('错误')
})()

函数

//函数表达式
let f:()=>string=function():string{
    return "a"
 }
  let f:()=>void=function(){
 }
//函数声明
function fn(x:number,y:number):number{
    return x+y
}

可选参数和参数默认值

说明:通过?来定义可选参数
    function fn(x:Type,y?:Type):Type
    可选参数默认为undefined
    可选参数必须在必传参数之后
//可选参数
function fn(x:number,y?:number):number{
   return x+y
}
//参数默认值,其实因为类型推导,可直接写成y=1
function fn1(x:number,y:number=1):number{
   return x+y
}
console.log(fn(1));//NaN
console.log(fn1(1));//2
补充:可选参数和默认值不要用在一个参数上

剩余参数

//剩余参数
function fn2(...arg:any[]){
   console.log(arg) //[ 1, 2, 3 ]
}
fn2(1,2,3)

函数重载

//定义函数的重载格式
function fn(x:number,y:string);
function fn(x:number,y:number);
//定义函数具体的实现
function fn(x:any,y:any){
   console.log(x+y)
}
fn(1,2) //3
fn(1,"2")//12

函数综合

//函数声明
function run1():void{

}
//匿名函数
let run2=function():number{
    return 123;
}

let fun3=function(name:string,age:number):string{
    return name+'---'+age;
}
// console.log(fun3('zq',12));
//可选参数:必须是再最后面,不能再前面
function fun4(name:string,age?:number):void{}
fun4('zq') 

//默认参数:可选参数可以再默认参数之前
function fun5(name?:string,age:number=20):void{}
fun5('zq') 

//剩余参数
function fun6(...res:number[]):number{
    let sum=0;
    for (let index = 0; index < res.length; index++) {
        sum+=res[index];
    }
    return sum;
}
// console.log(fun6(1,2,3,4,5,6));//21

//剩余参数形式二
function fun7(a:number,...res:number[]):number{
    let sum=0;
    for (let index = 0; index < res.length; index++) {
        sum+=res[index];
    }
    return sum;
}
// console.log(fun7(1,2,3,4,5,6));//20

//es5中出现同名函数,下面会替换上面的,即使参数不同

//函数重载:必须要有一个any的实现
function func(name:string):string;
function func(age:number):number;
function func(str:any):any{
    if(typeof str==='string'){
        return '我叫: '+str
    }else{
        return '我的年龄是: '+str
    }
}

// console.log(func('a'),func(12));我叫: a 我的年龄是: 12

ts中的this

案例一:
let obj={
   a:10,
   fn(){
       //函数中默认this指向是any,通过下面再配置文件中解决,而且如果是类似于
       //document中事件的this,ts会自动推导出类型,this指向不需要下面配置也是事件对象
      //  "noImplicitThis": true
      console.log(this.a)
        //注意:此时this的指向只是是否有提示的问题,真的执行代码配置文件设置不设置值都是10
   }
}
案例二:
let obj={
   a:20,
   fn(this:Document){
      console.log(this.querySelector)
   }
}
document.onclick=obj.fn
说明:如果配置配置了this指向"noImplicitThis": true,
则fn中的this指向就是obj对象,但是此时obj.fn指向了
点击事件,为了有提示信息,需要手动指定this指向this:Document
这个this参数其实是一个假参数,ts编译时候会被去掉,纯粹是
为了代码提示而存在,this指向配置不配置,修改不修改都不影响最后的结果

修饰符

public protected(该类和子类能访问) private(类,对象内部) readonly(类,对象内部可用,其他只读)

案例一:
class Person{
   readonly n:number;
   constructor(num:number){
      this.n=num;
   }
}
let p=new Person(20)
案例二:简写方式
class Person{
   constructor(public num:number){
      this.n=num;
   }
}
let p=new Person(20)
说明:因为ts不同于js,构造函数中属性需要先声明才能
使用,此时public num:number此种方式就是相当于提前再
class中先声明了一份

存取器

class Person{
   //私有属性,大家默认的规则是下划线
   private _num:number;
   //存取器
   //存取器再ts中不是当做方法使用的,而是被当做属性
   get num():number{
      return this._num
   }
   set num(num:number){
      if(num>0){
         this._num=num;
      }
   }
}
let p=new Person()
p.num=-10
console.log(p.num);//undefined

静态

class Person {
   private static instance;
   private constructor() { }
   public static getInstance() {
      if (!Person.instance) {
         Person.instance = new Person();
      }
      return Person.instance;
   }
}
let p=Person.getInstance();//相等
let p1=Person.getInstance();

抽象类

abstract class Person {
   constructor() { }
   abstract study():void;
}
class Student extends Person{
   study(): void {
      console.log("学习");
   }
}
let s=new Student();
s.study()

类综合

class Person{
    private name:string;
    constructor(n:string){
        this.name=n;
    }
    run():void{
        console.log(this.name);
    }

}
/**
 * 类中属性修饰符
 * public: 公有 都可以访问,默认值
 * protected: 保护类型  在该类和子类能访问
 * private: 在该类能访问
 */
class Student extends Person{
    //实际上在新版本ts,构造函数在此时可省略,方法执行依然正常
    // constructor(n:string){
    //     super(n)
    // }
    static sex='男';//静态属性

    //静态方法里面只能使用静态属性
    static print(){
        console.log("静态方法",this.sex);
    }

    //重写父类方法
    run():void{
        console.log("重写方法");
    }
}
let s=new Student('zq');
// s.run() //zq ,不重写父类方法的情况下
// Student.print()

// s.run() //输出: 重写方法       重写父类的方法

/**
 * 抽象类:
 *      提供其他类继承的基类,不能实例化
 * abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
 * 
 * 抽象类中可以有属性,构造函数,和非抽象方法
 */

 abstract class Animal {
     name:string;
     constructor(name:string){
        this.name=name;
     }
     abstract eat():any;
     run(){
         console.log(this.name+"   跑步");
     }
 }

 class Dog extends Animal{
     eat() {
         console.log("狗吃饭");
     }
     
 }

 let d=new Dog('pf');
 d.eat();
 d.run();//pf   跑步

接口

//接口不能有任何属性和方法实现,只能有抽象描述
interface Options{
   num:number;
   //可选的
   name?:string;
   say();
}
class optImpl implements Options{
   num: number;
   constructor(num:number){
      this.num=num;
   }
   say() {
      console.log(this.num+"说话");
   }
}
function fn(opts:Options) {
   opts.say()
}
fn(new optImpl(20))

断言

interface Options{
   num:number;
   name:string;
}

function fn(opts:Options) {
}
//断言
//按理说必须传入{num:20,name:"呵呵"}类似的才能通过
//但是通过断言可强制判定传入参数是什么类型
fn({} as Options)

补充:
let obj={
   num:10,
   name:"saa",
   a:1
}
fn(obj)
说明:如果把传入的参数先赋值好在传入,可以避免规则检测
不会报错,但是此种情况只能在传入的obj覆盖全部所需参数
情况下,也就是说只能多不能少

索引签名

/**
 * 索引签名:
 *    希望规则是:一组由数字进行key命名的对象
 * 补充:索引签名的key类型只能是string或者number
 * 索引签名在下面传参时候,是可有可没有的,而且不限制个数
 */
interface Options{
   //key是number,value是any类型的数据
   [atrr:number]:any;
}

function fn(opts:Options) {
}
fn({
   0:1,
   2:20
})

函数类型接口

/**
 * 函数类型接口
 * 是一个包含由fn并且值的类型为函数的结构体
 * 并不是描述函数结构而是一个包含函数的对象结构
 */
interface Options{
   fn:Function
}
let o:Options={
   fn:function(){

   }
}

/**
 * 下面约定就是函数结构,而不是包含有函数的对象结构了
 */
interface IFn{
   (x:number):number
}
let fn:IFn=function(x:number){return x}


/**
 * 下面是函数结构的实践
 *    因为interface的约定,保证了传参的正确性
 *    在编译阶段避免了出错
 */
interface MouseEventCallBack{
   (e:MouseEvent):any;
}
let fn1:MouseEventCallBack=function(e:MouseEvent){}
document.onclick=fn1;

补充案例

interface AjaxData{
   code:number;
   data:any;
}
interface AjaxCallBack{
   (rs:AjaxData):any
}
function ajax(callback:AjaxCallBack){
   callback({
      code:200,
      data:{}
   })
}

泛型

/**
 * 泛型:
 *    很多时候,类型写死,不利于复用
 */
//泛型变量
function fn(args:T):T{
   return args;
}
function fn1(args:T,args1:S):[T,S]{
   return [args,args1];
}

//数组形式
function fn2(args:T[]):T[]{
   return args;
}
function fn3(args:Array){}

泛型类

class MyArray{
   private _data:T[]=[];
   public push(v:T):number{
      return this._data.length;
   }
}
let a=new MyArray();
a.push("a")

let b=new MyArray();
b.push(1)

泛型类型

//泛型类型
let fn:(x:T,y:T)=>number=function(x,y){
   return Number(x)+Number(y)
}
let fn1=function(x:T,y:S):number{
   return Number(x)+Number(y)
}
console.log(fn(1,2));//3
console.log(fn1(1,"2"));//3

泛型接口

interface IFN{
   (x:T,y:S):S
}
let fn2:IFN=function(x,y){
   return Number(x)+Number(y);
}

类类型

/**
 * 类类型:
 *    表示这个类型对应的对象
 */

 //错误实例:此时Array代表就是类类型,但是需要的参数是该类
 //对应的构造函数
function getArray1(constructor:Array){
   return new constructor();
}
getArray2(Array)

//补充:p后面的Person就是类类型
let p:Person=new Person();
//下面是构造函数
let fn1:{new ():Person}


//正确写法
function getArray2(constructor:{new():Array}){
   return new constructor();
}
getArray2(Array)

泛型约束

形式一:
function fn(a:T){
   console.log(a);
}

形式二:
interface Len{
   length:number
}
function fn1(a:T){
   console.log(a.length);
}
fn1("a")//此时在fn1(1)则会报错,因为数字类型没有length属性

接口综合

//ts自定义方法传入参数  对json进行约束
function printLabel(labelInfo:{label:string}):void{
    console.log("printlabel");
}

// printLabel('name')//错误
// printLabel({name:'haha'})//错误
printLabel({label:'haha'}) //正确

//一、函数类型接口
/**
 * 以上只是针对单一方法进行约束,那么批量约束呢
 * 1. 属性接口   对json的约束
 */
interface FullName{
    firstName:string;
    secondName:string; 
}

function printName(name:FullName){
    console.log(name.firstName,name.secondName);
}
let obj={
    age:20,
    firstName:'zq',
    secondName:'pf',
}
//注意: 传入对象引用,只要包含必须项即可,但是如果直接传入对象,则不能包含多余属性
//但是建议,不要添加多余属性
printName(obj)


/**
 * 2. 可选属性
 */
interface FullName1{
    firstName:string;
    secondName?:string; //可选属性
}

function printName1(name:FullName1){
    console.log(name.firstName,name.secondName);
}
printName1({firstName:'zq'})//zq undefined

/**
 * 3.函数类型接口
 * 对方法的传入参数和返回值进行约束
 */

 interface encrypt{
     (key:string,value:string):string;
 }
 let md5:encrypt=function(key:string,value:string):string{
    return 'haha';
 }
//1. 可索引接口:数组,对象的约束(不常用)
interface UserArr{
   [index:number]:string;
}
let arr:UserArr=['aaa','bbb']


interface UserObj{
   [index:string]:string;
}
let obj1:UserObj={name:'20'}

//2. 类类型接口: 对类的约束和抽象类有点相似
interface Animal1{
   name:string;
   eat1(str:string):void;
}
class Dog1 implements Animal1{
   name: string;    
   constructor(name:string){
       this.name=name;
   }
   eat1(str: string): void {
       console.log(this.name,str);
   }
}
let d1=new Dog1('zq');
d1.eat1('haha'); //zq haha
//1. 接口扩展:接口继承其他接口
interface A1{
    eat():void;
}
interface A2{
    fly():void;
}
//接口可以多继承
interface P1 extends A1,A2{
    run():void
}
//类只能单继承,但是可以多实现,而且继承和实现可并存
class P2 implements P1{
    run(): void {

    }    
    eat(): void {

    }
    fly(): void {

    }
}

泛型综合

// 泛型基本使用
function getData(name:T):T{
    return name;
}
console.log(getData('zq'));
console.log(getData(12));

//泛型类
class MinClass{
    public list:T[]=[]
    add(value:T):void{
        this.list.push(value)
    }
    min():T{
        let min=this.list[0];
        return min;
    }
}
let m=new MinClass();

// 泛型接口
//1. 方式一
interface ConfinFn{
    (v1:T):T;
}
let fn:ConfinFn=function(v1:T):T{
    return v1;
}
fn('haha')

//2. 方式二
interface ConfinFn1{
    (v1:T):T;
}
function fn2(v1:T):T{
    return v1;
}
let fn3:ConfinFn1= fn2;
fn3('sad')

/**
 *泛类 把类当作参数的泛型类
 */

class User{
    //之所以加underfined是因为不初始化报错,再多加一个类型就不报错了
    username:string|undefined; 
    password:string|undefined;
}

//这样就可以把各种外部类传递进来
class MysqlDB{
    add(user:T):boolean{
        return true;
    }
}
let m1=new MysqlDB();
// m1.add(new User())

命名空间和模块

export.ts

export namespace B {
    export let url = 'safas';
    export function getData(): any[] {
        return [
            1, 2, 3
        ]
    }
    // export {url,getData} 一次统一暴露,但是使用此方式,上面的export就不能存在了

    // export default getData 一个模块只能用一次,引入方式也有区别
}

export namespace C{
    export let url1 = 'safas';
    export function getData1(): any[] {
        return [
            1, 2, 3
        ]
    }
}
modules.ts

/**
 * 命令空间: 内部模块,主要用于组指代码,避免命名冲突(其实针对的就是一个文件同名方法的冲突解决方案)
 * 模  块 : ts的外部模块的简称,侧重代码复用,一个模块里可能有多个命令空间
 */
import { B,C } from './export';
// import getData from './export';   export default方式导出的时候的引入方式

//如果导出时候没有命名空间 则可以通过as取别名
import { getData as get } from './export';
B.getData();

装饰器

  • 方式一:配置文件(开启装饰器):"experimentalDecorators": true
  • 方式二:根据vscode错误提示(装饰器错误提示)也可以自动修改
/**
 * 装饰器:
 *  类装饰器   属性装饰器  方法装饰器   参数装饰器
 * 
 * 装饰器写法:
 *      普通装饰器(无法传参)
 *      装饰器工厂(可传参)
 * 
 * 通俗的讲:装饰器就是一个方法
 */

 //1. 类装饰器
namespace A{
    function logclass(params:any){
        //params其实就是被装饰的类
    //    console.log(params);
       //扩展属性
       params.prototype.apiURL='www.zq.com';
       params.prototype.run=function(){
           console.log('run');
       }
    }
   
    @logclass
    class HttpClient{
       getData(){
       }
    }
    let h:any=new HttpClient();
    // console.log(h.apiURL); //www.zq.com
//    h.run()
}



//2. 类装饰器(带参数)
namespace B{
    function logclass(params:string){
        //此时params就是hello
        //target就是被装饰的类
        return function(target:any){
            // console.log(params,target);
        }
    }
    
    //装饰的过程是直接执行的,不需要实例化,不需要调用
    @logclass('hello')
    class HttpClient{
       getData(){
       }
    }
    let h=new HttpClient();
}



/**
 * 重载构造的小例子
 * 类的装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数
 * 如果类的装饰器返回一个值,它会使用提供的构造函数来替换类的声明
 */
namespace C{
    function logclass(target:any){
        return class extends target{
            apiUrl='修改路径';
            getData(){
                console.log('修改前');
                this.apiUrl='修改路径'+'-----';
                console.log(this.apiUrl);
            }
        }
    }
    
    @logclass
    class HttpClient{
        apiUrl:string|undefined;
        constructor(){
            this.apiUrl='路径初始化'
        }
       getData(){
           console.log(this.apiUrl);
       }
    }
    let h=new HttpClient();
    // h.getData() //修改前  修改路径-----
}

/**
 * 属性装饰器:
 *  属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
 *      1. 对于静态成员来说是类的构造函数,对于实例成员时类的原型对象
 *      2. 成员的名字
 */

namespace D{
    function logurl(params:string){
        return function(target:any,attr:any){
            // console.log(target,attr);
            //虽然这样可修改,但是通过打印发现target[attr]是undefined,很奇怪
            //而且必须这个属性如果在构造函数有赋值,构造函数内部赋值会替换装饰器的赋值,因为构造是后执行的
            target[attr]=params;//相当于用@logurl('xxx')的参数给apiUrl重新赋值
        }
    }
    
    class HttpClient{
        @logurl('xxx')
        apiUrl:string|undefined;
       getData(){
           console.log(this.apiUrl);
       }
    }
    let h=new HttpClient();
    // h.getData()
}


/**
 * 方法装饰器:
 *      会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法定义
 * 方法装饰器参数:
 *  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象:{getData: ƒ, constructor: ƒ}
 *  2. 成员的名字(方法名称): "getData" 
 *  3. 成员的属性描述符: {value: ƒ, writable: true, enumerable: true, configurable: true}
 */


 //方式装饰器一
namespace D{
    function logMethods(params:any){
        return function(target:any,methodsName:any,desc:any){
            // console.log(target,methodsName,desc);
    
            //扩展属性和方法
            target.apiUrl=params;
            target.run=function(){
                console.log(this.apiUrl);
            }
        }
    }
    
    class HttpClient{
        @logMethods('www')
        getData(){
            console.log('执行');
        }
    }
    let h:any=new HttpClient();
    // h.getData();//执行
    // h.run();//www
}

//方法装饰器二
namespace E{
    function logMethods(params:any){
        return function(target:any,methodsName:any,desc:any){
            /**
             * ƒ () {
                    console.log('执行');
                }
             */
            // console.log(desc.value);
    
            //修改装饰器的方法,把装饰器方法里面传入的参数修改为string类型
            
            //1. 保存当前方法
            let om=desc.value;
            desc.value=function(...args:any[]){
                console.log("执行原始方法前");
                args=args.map((value)=>{
                    return String(value)
                })
                //如果需要绑定上下文,甚至传参到原始方法
                om.apply(this,args);
                console.log("执行原始方法后");
            }
            //如果不涉及上下文调用原始的getData方法,则可以把方法调用放在外面
            // om();
        }
    }
    
    class HttpClient{
        @logMethods('www')
        getData(...args:any[]){
            args.forEach(element => {
                console.log(element);
            });
        }
    }
    let h:any=new HttpClient();
    // h.getData(1,2,3,4);
    /**
     * 输出结果:
     * 执行原始方法前
        1
        2
        3
        4
     执行原始方法后
     */
}

/**
 * 方法参数装饰器:用处不大,可能有复杂用法,但是基本可以通过类装饰器替代,不必研究
 *      参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,
 *  参数列表:
 *  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象:{getData: ƒ, constructor: ƒ}
 *  2. 方法的名字: "getData"
 *  3. 参数在函数参数列表中索引 : 0
 */

 //? 代表可选参数:可传可不传
function logParams(params?:any){
    return function(target:any,methodsName:any,index:any){
        // console.log(target,methodsName,index);
        target.apiURL=params;//扩展属性
    }
}

class HttpClient{
    getData(@logParams('xxx') uuid:any){
        console.log(uuid);
    }
}
let h:any=new HttpClient();
h.getData();


/**
 * 装饰器执行顺序:
 *  属性装饰器-》方法装饰器-》方法参数装饰器-》类装饰器
 * 同类装饰器存在多个:从后向前,从内到外的顺序
 */

你可能感兴趣的:(TypeScript浅入浅出)