带你搞懂typescript基础知识

一、js开发中的问题

·使用了不存在的变量、函数或成员,不会报错,往往导致查错困难

·把一个不确定的类型当作一个确定的类型处理

·不小心使用了null或者undefined的成员,报错

二、js本身的语言缺点

·js本身的语言特性,决定了他无法适应大型的复杂项目

·弱类型语言:某个变量可以随时更换类型

·js语言要等到运行的时候才知道错误,因为js语言属于是解释型语言

·以上种种原因就导致前端开发大部分时间都在排错

三、TypeScript(简称ts)

ts是js的超集,是一个可选的,静态的类型系统

-类型系统

对代码中所有的标识符(变量,函数,参数,返回值)进行类型检查

-可选的

对于新增加的类型检查方法是可选的,编译时就会显示一些js无法显示的报错

-静态的

无论是浏览器环境还是node环境,无法直接识别ts代码

>babel:es6 ->es5

>tsc :ts->js

类型检查的时间在编译时,而非运行时

四、在node环境中搭建TS开发环境

一、ts默认的情况下做出的假设

1.假设当前执行环境是dom,可以使用浏览器的属性,包括windows、document

2.没有使用模块化,自动视为全局变量

3.编译的目标代码是ES3

二、有两种方式可以更改以上假设

1.使用tsc命令行时,加上选项参数

2.使用ts配置文件,改变编译选项

三、创建配置文件进行配置

有两种方式,一种是直接在文件资源管理器中创建tsconfig.json一种是直接在命令行中输入tsc --init

有几个属性可以选择

常见的有“compilerOptions”编译选项

格式如下:

{
  "compilerOptions":{
    "target":"es2016",//配置编译目标代码的版本标准,这里使用最高标准es7
      "module":"commonjs",//配置编译目标使用的,模块化标准
      "lib":[es2016]//配置库,使其在node环境中使用
      "outDir":"./dist"//将编译的文件存储在dist目录中,和vue,react一样
      },
  //"include":["./src"]//默认在src文件夹中寻找ts文件编译,否则会将整个工程的ts全部编译
   "files":["./src/index.ts"]//执行这行代码,则只会编译这个文件以及他的依赖文件 
}

一旦使用了配置文件就不能在tsc后面跟上文件名,否则会忽略配置文件,导致使用默认假设。

需要额外安装@types/node,@types是一个官方的类型库,其中包含了很多对js代码的的类型描述

五、使用第三方库来简化编译

上面提到编译的文件存储到了dist目录中,那么要进行运行就显得比较繁琐,node ./dist/index.js

ts-node:将ts代码在内存中完成编译,然后放在dist目录中,同时完成运行

nodemon:命令行为:nodemon --exec ts-node src/index.ts,可以实时监控文件的变化并运行

可以将命令行写成脚本,放入package.json中

可以将命令行修改成nodemon -e ts --exec ts-node src/index.ts表示要监测的文件扩展名为ts,否则一旦代码有一点改动就会监测到并从新执行

还可以更改成nodemon --watch src -e ts --exec ts-node src/index.ts这样就只会监控src目录中的ts文件

六、基本的类型检查

一、基本的类型约束

TS是一个可选的静态的类型系统

在进行类型约束时,只需要在变量、函数的参数、函数的返回值位置加上:类型即可

ts在很多场景中可以完成类型推导

any:表示任意类型,对该类型,ts不进行类型检查

小tips:在命名函数时,函数有严格的类型检查,所以类型不能混淆,另外可以对函数重新命名,只需点击函数名然后按F2即可重命名,会自动修改其他用到该函数的地方。

小技巧:如何区分是数字字符串还是数字:如果按照数字的方式朗读就为数字,否则为字符串

常见于电话号码

二、源代码和编译结果差异

编译结果中没有类型约束信息,所以说TS是静态的类型系统,一旦编译完成就与TS无关了

三、基本类型

数字:number

字符串:string

布尔:boolean

数组:number[]或者Array<number>

对象:object(用的较少,因为无法约束到对象里面的属性)

null和undefined其他所有类型的子类型,它们可以赋值给其他类型

例如:

let num1:number=undefined
let a:string=null
//都是合理的写法

可以在配置文件中加上"strictNullChecks":true

使用更加严格的空类型检查,从此null和undefined只能赋值给自身。

四、其他类型

联合类型:多种类型任选其一

let name:string|undefined;
if(typeof(name)==="string"){
  //类型保护
name.//直接显示name作为字符串的可选方法
  }

配合类型保护进行判断

类型保护:当对某个变量进行类型判断之后,在判断的语句块中便可以确定它的确切类型,typeof可以触发。

void类型:通常用于约束函数的返回值,表示该函数没有任何返回

never类型:表示函数永远不会结束,没有返回值

function throwError(msg:string):never{
throw newError(msg);
}

字面量类型:使用一个值进行约束

//类似于设定强制取值范围
let gender="男"|"女";
gender=1//false


let user:{
  name:string,
  age:number
}
user={
  name:"34"
  age:"33"
}//使用一个对象对其进行约束,对象内类型必须一样

元祖类型:一个固定长度的数组,并且数组中的每一项的类型确定

let tu:[string,number]
tu["3",4];

any类型:可以绕过检查,因为any类型的数值可以赋给任意类型

五、类型别名

type Gender="男" |"女“
  type User={
	name:string,
  age:number,
  gender:Gender
}
let u:User
u={
name:"sndia",
  age:12,
gender:"男”
}
function getUser(g:Gender):User[]{
return []
}

 类型别名的好处就是防止我们重复去书写一些类型的代码

六、函数的相关约束

函数重载:在函数实现之前,对函数调用的多种情况进行声明。

function combine(a:number,b:number):number;
function combine(a:string,b:string):string;
function combine(a:number|string,b:number|string):number|string{
if(typeof a==='number'&&typeof b==='number'){
return a * b;
}
if(typeof a==='string'&&typeof b==='string'){
return a + b;
}
  throw new Error('a和b 必须是同种类型');
}
const n= combine(3,5)

可选参数:可以在某些参数后面添加问号,表示某个参数是可选的;需要注意的是默认参数一定是可选参数,并且可选参数必须在参数列表的末尾

function sum(a:number,b:number,c?:number){
}

sum(3,4)

七、拓展类型-枚举

>扩展类型:类型别名,枚举,接口,类

枚举通常用于约束某个变量的取值范围

字面量和联合类型配合也可以达到同样目的,但是会产生一些问题

>字面量类型的问题

会导致在类型约束地方产生重复代码。可以使用类型别名来解决

逻辑名称和真实的值产生了混淆,导致当修改真实的值时,产生了大量的修改工作

- 字面量类型不会进入到编译结果。

枚举

定义枚举

enum 枚举名{
    枚举字段1 = 值1,
    枚举字段2 = 值2,
    ...
}

枚举会出现在编译结果中,编译结果中表现为对象。

enum Level {
    level1,
    level2,
    level3
}

let l: Level = Level.level1;
l = Level.level2;

console.log(l);

function getUsers(lev:Level){

}

枚举的规则:

  • 枚举的字段值可以是字符串或数字
  • 数字枚举的值会自动自增
  • 被数字枚举约束的变量,可以直接赋值为数字
  • 数字枚举的编译结果 和 字符串枚举有差异

最佳实践:

  • 尽量不要在一个枚举中既出现字符串字段,又出现数字字段
  • 使用枚举时,尽量使用枚举字段的名称,而不使用真实的值

扩展知识:位枚举(枚举的位运算)

针对的数字枚举

位运算:两个数字换算成二进制后进行的运算

enum Permission {
    Read = 1,   // 0001
    Write = 2,  // 0010
    Create = 4, // 0100
    Delete = 8  // 1000
}

//1. 如何组合权限
//使用或运算
//0001
//或
//0010
//0011
let p: Permission = Permission.Read | Permission.Write;

//2. 如何判断是否拥有某个权限
//0011
//且
//0010
//0010
function hasPermission(target: Permission, per: Permission) {
    return (target & per) === per;
}
//判断变量p是否拥有可读权限

//3. 如何删除某个权限
//0011
//异或
//0010
//0001
p = p ^ Permission.Write;
console.log(hasPermission(p, Permission.Write));

八、模块化

TS中如何书写模块化语句

TS中,导入和导出模块,统一使用ES6的模块化标准

编译结果中的模块化

TS中的模块化在编译结果中:

  • 如果编译结果的模块化标准是ES6: 没有区别
  • 如果编译结果的模块化标准是commonjs:导出的声明会变成exports的属性,默认的导出会变成exports的default属性;

如何在TS中书写commonjs模块化代码

导出:export = xxx

导入:import xxx = require("xxx")

模块解析

模块解析:应该从什么位置寻找模块

TS中,有两种模块解析策略

  • classic:经典
  • node:node解析策略(唯一的变化,是将js替换为ts)
  • 相对路径require("./xxx")
  • 非相对模块require("xxx")

九、接口和类型兼容性

扩展类型-接口

接口:inteface

扩展类型:类型别名、枚举、接口、类

TypeScript的接口:用于约束类、对象、函数的契约(标准)

契约(标准)的形式:

  • API文档,弱标准
  • 代码约束,强标准

和类型别名一样,接口,不出现在编译结果中

  1. 接口约束对象
  2. 接口约束函数 接口可以继承

可以通过接口之间的继承,实现多种接口的组合

使用类型别名可以实现类似的组合效果,需要通过&,它叫做交叉类型

它们的区别:

  • 子接口不能覆盖父接口的成员
  • 交叉类型会把相同成员的类型进行交叉

readonly

只读修饰符,修饰的目标是只读

只读修饰符不在编译结果中

类型兼容性

B->A,如果能完成赋值,则B和A类型兼容

鸭子辨型法(子结构辨型法):目标类型需要某一些特征,赋值的类型只要能满足该特征即可

  • 基本类型:完全匹配
  • 对象类型:鸭子辨型法

类型断言

当直接使用对象字面量赋值的时候,会进行更加严格的判断

  • 函数类型

一切无比自然

参数:传递给目标函数的参数可以少,但不可以多

返回值:要求返回必须返回;不要求返回,随意;

十、TS中的类

TS中的类

面向对象思想

基础部分,学习类的时候,仅讨论新增的语法部分。

属性

使用属性列表来描述类中的属性

属性的初始化检查

strictPropertyInitialization:true

属性的初始化位置:

  1. 构造函数中
  2. 属性默认值

属性可以修饰为可选的

属性可以修饰为只读的

使用访问修饰符

访问修饰符可以控制类中的某个成员的访问权限

  • public:默认的访问修饰符,公开的,所有的代码均可访问
  • private:私有的,只有在类中可以访问
  • protected:暂时不讲

Symble

属性简写

如果某个属性,通过构造函数的参数传递,并且不做任何处理的赋值给该属性。可以进行简写

访问器

作用:用于控制属性的读取和赋值

class User {
    readonly id: number //不能改变
    gender: "男" | "女" = "男"
    pid?: string
    private _publishNumber: number = 3; //每天一共可以发布多少篇文章
    private _curNumber: number = 0; //当前可以发布的文章数量

    constructor(public name: string, private _age: number) {
        this.id = Math.random();
    }

    set age(value: number) {
        if (value < 0) {
            this._age = 0;
        }
        else if (value > 200) {
            this._age = 200;
        }
        else {
            this._age = value;
        }
    }

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

    publish(title: string) {
        if (this._curNumber < this._publishNumber) {
            console.log("发布一篇文章:" + title);
            this._curNumber++;
        }
        else {
            console.log("你今日发布的文章数量已达到上限");
        }
    }
}

const u = new User("aa", 22);
//c#
u.age = 1.5;
console.log(u.age);


u.publish("文章1")
u.publish("文章2")
u.publish("文章3")
u.publish("文章4")
u.publish("文章5")
u.publish("文章6")

十一、泛型

泛型

有时,书写某个函数时,会丢失一些类型信息(多个位置的类型应该保持一致或有关联的信息)

泛型:是指附属于函数、类、接口、类型别名之上的类型

泛型相当于是一个类型变量,在定义时,无法预先知道具体的类型,可以用该变量来代替,只有到调用时,才能确定它的类型

很多时候,TS会智能的根据传递的参数,推导出泛型的具体类型

如果无法完成推导,并且又没有传递具体的类型,默认为空对象

泛型可以设置默认值

在函数中使用泛型

在函数名之后写上<泛型名称>

如何在类型别名、接口、类中使用泛型

直接在名称后写上<泛型名称>

泛型约束

泛型约束,用于现实泛型的取值

export class ArrayHelper<T> {

    constructor(private arr: T[]) { }

    take(n: number): T[] {
        if (n >= this.arr.length) {
            return this.arr;
        }
        const newArr: T[] = [];
        for (let i = 0; i < n; i++) {
            newArr.push(this.arr[i]);
        }
        return newArr;
    }


    shuffle() {
        for (let i = 0; i < this.arr.length; i++) {
            const targetIndex = this.getRandom(0, this.arr.length);
            const temp = this.arr[i];
            this.arr[i] = this.arr[targetIndex];
            this.arr[targetIndex] = temp;
        }
    }


    private getRandom(min: number, max: number) {
        const dec = max - min;
        return Math.floor(Math.random() * dec + max);
    }
}
// interface hasNameProperty {
//     name: string
// }

// /**
//  * 将某个对象的name属性的每个单词的首字母大小,然后将该对象返回
//  */
// function nameToUpperCase<T extends hasNameProperty>(obj: T): T {
//     obj.name = obj.name
//         .split(" ")
//         .map(s => s[0].toUpperCase() + s.substr(1))
//         .join(" ");
//     return obj;
// }

// const o = {
//     name:"kevin yuan",
//     age:22,
//     gender:"男"
// }

// const newO = nameToUpperCase(o);

// console.log(newO.name); //Kevin Yuan


//将两个数组进行混合
//[1,3,4] + ["a","b","c"] = [1, "a", 3, "b", 4, "c"]
function mixinArray<T, K>(arr1: T[], arr2: K[]): (T | K)[] {
    if (arr1.length != arr2.length) {
        throw new Error("两个数组长度不等");
    }
    let result: (T | K)[] = [];
    for (let i = 0; i < arr1.length; i++) {
        result.push(arr1[i]);
        result.push(arr2[i]);
    }
    return result;
}

const result = mixinArray([1, 3, 4], ["a", "b", "c"]);

result.forEach(r => console.log(r));

一个小demo

开发一个字典类(Dictionary),字典中会保存键值对的数据

键值对数据的特点:

  • 键(key)可以是任何类型,但不允许重复
  • 值(value)可以是任何类型
  • 每个键对应一个值
  • 所有的键类型相同,所有的值类型相同

字典类中对键值对数据的操作:

  • 按照键,删除对应的键值对  
  • 循环每一个键值对       
  • 得到当前键值对的数量    
  • 判断某个键是否存在       
  • 重新设置某个键对应的值,如果不存在,则添加   
export type CallBack<T, U> = (key: T, val: U) => void

export class Dictionary<K, V> {
    private keys: K[] = [];
    private vals: V[] = [];

    get size(){
        return this.keys.length;
    }

    set(key: K, val: V) {
        const i = this.keys.indexOf(key)
        if (i < 0) {
            this.keys.push(key);
            this.vals.push(val);
        }
        else {
            this.vals[i] = val;
        }
    }

    forEach(callback: CallBack<K, V>) {
        this.keys.forEach((k, i) => {
            const v = this.vals[i];
            callback(k, v);
        })
    }

    has(key: K) {
        return this.keys.includes(key);
    }

    delete(key: K) {
        const i = this.keys.indexOf(key);
        if (i === -1) {
            return;
        }
        this.keys.splice(i, 1);
        this.vals.splice(i, 1);
    }
}

以上是typescript的一些浅薄认识,感谢您的阅读

猜你喜欢

转载自blog.csdn.net/Vince_13/article/details/133127589
今日推荐