TypeScript基本类型、类、封装、继承、泛型、接口、命名空间

简介:前几篇介绍了Vue3的Composition API,Vue-Router,pinia。Vue3能更好支持TS,因此,本节将介绍TS,详细描述了TS的优缺点,安装,如何配置自动编译、tsconfig.json的常用配置,使用webpack打包时的webpack.config.js的配置,还会详细说明TS基本数据类型、对象、类、抽象类、构造方法、接口、继承、封装、泛型。

文章目录

        • 一、初识TypeScript
          • 1、TS介绍
          • 2、TS 的优缺点
          • 3、TS预JS区别
        • 二、安装和环境搭建
          • 1、全局安装
          • 2、tsconfig.json常用配置
          • 3、使用webpack打包编译ts代码
            • 1)新建空文件夹
            • 2)webpack.config.js
            • 3)package.json中添加
            • 4)打包
        • 三、TypeScript 常用语法
          • 1、boolean/number/string
          • 2、null/undefined
          • 3、字面量
          • 4、any
          • 5、unknown
          • 6、void
          • 7、never
          • 8、object
          • 9、array
          • 10、tuple
          • 11、enum枚举
          • 12、类型断言
          • 13、其它用法
            • 摘取一部分类型Pick
            • 剔除一部分类型Qmit
        • 四、面向对象
          • 1、类、构造函数与抽象类:
          • 2、继承与super
          • 3、接口
          • 4、封装属性
          • 5、泛型:
            • 1):函数的泛型,可同时指定多个泛型
            • 2):类的泛型
            • 3):接口的泛型
        • 五、namespace
        • 六、ts使用import语法
        • 七、TS中三个奇怪的符号:!、?? 、?.
          • 7.1、!非空断言符号
          • 7.2、?.运算符(Optional Chaining)
          • 7.3、?? 空值合并运算符

一、初识TypeScript
1、TS介绍

​ TypeScript微软开发、跨平台的编程语言。它是JavaScript的超集,最终会被编译为JavaScript代码。

​ 2012年10月,微软发布了首个公开版本的TypeScript,2013年6月19日,在经历了一个预览版之后微软正式发布了正式版, TypeScript的作者是安德斯海尔斯伯格,C#的首席架构师。

​ TypeScript扩展了JavaScript的语法,所以任何现有的JavaScript程序可以运行在TypeScript环境中。

2、TS 的优缺点

优点:

​ TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可

​ 即使没有显式的定义类型,也能够自动做出类型推论

​ 可以定义从简单到复杂的几乎一切类型

​ 即使 TypeScript 编译报错,也可以生成 JavaScript 文件

​ 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取

​ 类型系统增加了代码的可读性和可维护性

​ 拥有活跃的社区,并且支持ES6规范

不足:

​ 需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等概念

​ 短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本

​ 集成到构建流程需要一些工作量

​ 可能和一些库结合的不是很完美

3、TS预JS区别
TS JS
解决大型项目的代码复杂性 一种脚本语言,用于创建动态网页
可以在编译期间发现并纠正错误 作为一种解释型语言,只能在运行时发现错误
强类型,支持静态和动态类型 弱类型,没有静态类型选项
最终被编译成 JavaScript 代码,使浏览器可以理解 可以直接在浏览器中使用
支持模块、泛型和接口 不支持模块,泛型或接口
社区的支持仍在增长,而且还不是很大 大量的社区支持以及大量文档和解决问题的支持
二、安装和环境搭建
1、全局安装
npm install -g typescript   或者npm i -g typescript
pnpm i typescript 
tsc-V        //检查安装是否成功,Version 4.5.5
2、tsconfig.json常用配置

添加tsconfig.json文件,执行命令tsc -w可编译并监视所有文件,tsconfig.json是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译。
生成ts.config.js文件还可以用命令

tsc --init

tsconfig.json文件,是ts编译器的配置文件,根据其信息,对代码进行编译
最外层的配置

include // 指定哪些ts文件需要被编译,注:/**:任意目录   ; /*:任意文件
exclude // 不需要被编译的文件,
// 默认值:["node_modules","bower_components","jspm_packages"]
extends // 定义被继承的配置文件
files // 指定被编译文件的列表,只有需要编译的文件少时才会用到 ,用的少
compilerOptions // 编译器的选项
	"target":指定编译的es版本
	"module":指定使用的模块化的规范,一版用ES2015
	"lib":一般使用默认值; strict (一版情况下,不用动): 开启所有严格的类型检查,会同时配置:alwaysStrict、noImplicitAny、noImplicitThis、strictNullChecks
	"outDir":打包后文件的位置
	"outFile":./dist/app.js” -->所有全局作用域中的代码都会合并到一个文件中,注意:要合并的话,module必须是amd和system
	"allowJs":是否对js文件进行编译,默认是false
	"checkJs":是否检察js代码
	"removeComments":false, //编译后是否删除注释
	"noEmit": false, // 编译后不会生成任何js文件,当只想使用语法检测功能时用设置为true
	"noEmitOnError": true, // 发生错误时不输出任何文件
	// 所有严格检察的总开关
	"strict":true 
	// 语法检察
	"alwaysStrict": // 设置编译后的文件是否使用严格模式,默认false
	"noImplicitAny":true //不允许隐式any
	"noImplicThis":true // 不允许不明确类型的this
	"strictNullChecks":false // 严格的检察空值

例子

{
  "include": [ "./src/**/*"],
  "exclude": [ "./src/hello/**/*"],
  "compilerOptions": {
    "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
    "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
    "diagnostics": true, // 打印诊断信息
    "target": "es2017", // 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'esnext:最新的版本'
    "module": "amd", //  'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'
    "outFile": "./dist/app.js", // 所有全局作用域中的代码都会合并到一个文件中,注意:要合并的话,module必须是amd和system
     "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
    "allowJs": true, // 是否允许编译JS文件,默认是false
    "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
    "outDir": "./dist", // 指定编译后的文件存放的目录,源码与编译后区分开了
    "rootDir": "./src",  //要编译的
    "declarationMap": true, // 为声明文件生成sourceMap
    "removeComments":false, //编译后是否删除注释
    "noEmit": false, // 编译后不会生成任何js文件,当只想使用语法检测功能时用设置为true
    "noEmitOnError": true, // 发生错误时不输出任何文件
    "strict": true, // 开启所有严格的类型检查,如:alwaysStrict、noImplicitAny、noImplicitThis、strictNullChecks
    ...
  }
}
3、使用webpack打包编译ts代码
1)新建空文件夹
npm init -Y  //在空文件夹下生成package.json
npm install -D webpack webpack-cli typescript ts-loader   //安装依赖
npm install -D html-webpack-plugin   //webpack的插件,作用是自动生成index.html文件
npm install -D webpack-dev-server     //webpack的启动服务
npm install -D clean-webpack-plugin   //webpack插件,作用清除dist内容,保证每次Build都是最新文件
npm install -D @babel/core @babel/preset-env babel-loader core-js    //安装bable,兼容不同浏览器 
2)webpack.config.js
const path = require('path')    //引入一个包
const HTMLWebpackPlugin = require('html-webpack-plugin')   //引入HTML插件
const {CleanWebpackPlugin} = require('clean-webpack-plugin')  //引入clear-webpack-plugin 插件
module.exports = {  //webpack的所有配置信息都写在module.exports
   mode:'development',// development为开发者环境,production为生产环境变量 ,还有一个为none
 	entry:"./src/index.ts" , //指定入口文件
   output: {    //指定打包文件所在目录
    path:path.resolve(__dirname,'dist'),   //指定打包文件的路径
    filename: "bundle.js"  //打包后文件的名称
  },
  module: {       //指定webpack打包时使用的模块
    rules: [{
      test:/\.ts/,   //test指定的是规则生成的文件
      use:[    //要使用的loader
            {
              loader: "babel-loader",   //配置bable,指定加载器
              options: {
                presets:[   //设置预定义的环境
                  "@babel/preset-env",  //指定环境的插件
                  {   //配置信息
                    targets:{   //要兼容的目标浏览器
                      "chrome":"88"
                    },
                    "corejs":"3",  //指定corejs的版本
                     "useBuiltIns":"usage",//使用corejs的方式"usage" 表示按需加载
                  }
                ]
              }
            },
          'ts-loader'
      ],
      exclude: /node_modules/,  //要排除被编译的文件
    }]
  },
  plugins: [    //配置webpack插件
     new CleanWebpackPlugin(),
     new HTMLWebpackPlugin({      //自动生成html文件
       template: "./src/index.html"    //提供的模板
    }),
  ],
  resolve: {    //用来设置引用模块
    extensions: ['.ts','.js']   //可以引用ts、js文件
  }
}
3)package.json中添加
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack",
    "start": "webpack server"
  },
4)打包
npm run build   //在dist下得到打包文件bundle.js及index.html
三、TypeScript 常用语法

​ javascript有七种原始数据类型,boolean、number、string、null、undefined 、Symbol (ES6)和 BigInt (ES10)

​ typescript的基本类型有:boolean、number、string、null、undefined 、字面量(限定变量的值就是该字面量的值)、any、unknown(类型安全的any)、void(空值)、never(没有值)、object、array、tuple(元组)、enum(枚举)

类型可以约束变量

1、boolean/number/string
let a:boolean = true
let b:string = 'wl'
let c:number = 12
2、null/undefined
const onlyNull: null = null;
const onlyUndefined: undefined = undefined;
3、字面量
let a:25    //a永远是25
//使用联合类型
let b :"male"| "female"
let c:string|number|boolean    //使用广泛
4、any

任意类型,一个变量设置为any,相当于对该变量关闭了TS的类型检测;使用ts时不建议使用any;

注:声明变量不指定类型,TS解析器自动判断变量是any类型(隐式any)

let d:any  //显式any
let a      //隐式any,自动判定a类型为any
5、unknown

表示未加类型的值

注:any与unknown区别:unknown是一个类型安全的any,unknown类型的变量不能直接赋值给其它变量;使用unknown类型给其它赋值,需要先判断类型

let e:unknown
let s:string
if(typeof e === "string"){    //使用unknown类型给其它赋值,需要先判断类型
    s = e
}
//类型断言,告诉解析器实际类型
s = e as string   //变量 as 类型
s = <string>e     //<类型>变量
6、void

表示为空,以函数为例,表示没有返回值

function fn():void {
    return
}
7、never

表示没有值,不会有返回结果,使用率较少

function fn2():never {
    throw new Error('出错了')
}
8、object

表示一个js对象

注:{}用来指定对象中可以包含哪些属性, 语法:{属性名:属性值,属性名:属性值};在属性名后面加上?表示此属性是可选的

定义函数,设置函数结构的类型声明,语法:(形参:类型,形参:类型,…)=>返回值类型

let a:object   //不常用
a = {}/[]/function () {}
let b:{name:string,age:number,height?:number}  //?表示此属性是可选的
b = {name:'wl',age:123}
let c:{name:string,[propName:string]:any}    //定义对象,[propName:string]:any 表示任意类型的属性
c={name:'dsk',a:12,b:'fd'}
let d:(a:number,b:number)=>number         //定义函数,设置函数结构的类型声明 语法:(形参:类型,形参:类型,...)=>返回值类型
d = function (a:12,b:23):number {
    return a+b
}
//如果函数的参数是对象,对对象类型参数的注解
function getNumber({one}:{one:number}){
	return one
}
const one = getNumber({one:1})

ts提示元素具有隐式any
TypeScript基本类型、类、封装、继承、泛型、接口、命名空间_第1张图片

9、array

两种写法:类型[ ] (如:string[ ],number[ ]) ;Array<类型>(如:Array,Array)

let e:string[]= ['1','2']
let f:number[]=[1,2]
let g: Array<string> = ['a','b']

TypeScript基本类型、类、封装、继承、泛型、接口、命名空间_第2张图片

10、tuple

元组,固定长度的数组,语法:[类型,类型,类型,…]

let h :[string,stringnumber] =  ['1','2',3]

在这里插入图片描述

11、enum枚举
enum Gender{
    Male,
    Female = 1
}
let i:{gender:Gender}
i = {
    gender:Gender.Female
}
12、类型断言

TypeScript基本类型、类、封装、继承、泛型、接口、命名空间_第3张图片
高级类型,自定义保护,就不需要每个都进行类型断言
react 使用jss

13、其它用法
//使用&表示同时
let hh :{name:string} & {age:number}
hh={name:'wl',age:2}
//类型别名
type  myType = 1|2|3|4
let k :myType
摘取一部分类型Pick
剔除一部分类型Qmit

TypeScript基本类型、类、封装、继承、泛型、接口、命名空间_第4张图片

四、面向对象

要想面向对象,操作对象,首先便要拥有对象,那么下一个问题就是如何创建对象,要创建对象,必须要先定义类,所讲的类可以理解为对象的模型,程序中可以根据创建指定类型的对象,举例来说:可以通过Person类来制建人的对象,不同的类可以用来创建不同的对象。

1、类、构造函数与抽象类:

使用class定义,对象中包含属性和方法

注:

1)、直接定义的属性是实例属性/方法,通过对象的实例去访问;
2)、readonly 开头的属性表示一个只读属性,不能被修改;
3)、属性/方法以static开头,定义的是静态属性/方法,直接通过类访问,无需创建对象;
4)、在实例方法中,this就表示当前实例;
5)、以abstract开头的是抽象类,抽象类可以被继承,但不能创建对象(实例化);
6)、抽象类可以有抽象方法,使用abstract开头定义抽象方法,抽象方法只能定义在抽象类中并且没有方法体,子类必须对方法进行重写

class Person{
    static readonly name:string//定义的实例属性,readonly表示只读不能被修改,static定义静态属性
    age:number
    constructor(name:string,age:number){ //构造函数
        this.name=name;this.age = age
    }
    static sayHello(){}   //定义方法,类的静态方法
}
const per = new Person('ww',1)  //通过实例对象访问属性,如per.name
Person.sayHello  //前有static,直接就能访问到类属性

(function(){
    abstract class Animal{      //定义抽象类
    name:string;
    constructor(name:string){this.name = name}
    abstract sayHello():void;   //使用abstract开头定义抽象方法,抽象方法只能定义在抽象类中并且没有方法体,子类必须对方法进行重写
}
class dog extends Animal{
    sayHello(){console.log('重写父类方法')}
}
})()
2、继承与super

注:

extends继承父类的方法

定义一个类,在子类中添加与父类相同的方法,将覆盖父类的方法;子类覆盖父类叫方法的重写

super继承父类的属性与方法

(function(){
   class Animal{
        name:string;
        constructoe(name:string){this.name = name} 
        sayHello(console.log('叫')) 
   }
   class dog extends Animal{
       gender:string
       constructor(name:string,gender:string){
        // 访问派生类的构造函数中的 "this" 前,必须调用 "super",初始化父类构造函数 --并把参数传给父类
           super(name)  //调用父类构造方法
           this.gender = gender      
       }
       sayHello{   //继承方法
           super.sayHello()       
       }
       run(){}   //新增加方法
   } 
})()
3、接口

作用:面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范。ts中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。

注:
1)、接口用来定义类的结构,定义一个类中包含哪些属性方法,接口也可以当做类型声明,接口可以重复申请
2)、接口在定义类的时候限制类的结构,接口只定义对象的结构,不考虑实际值,在接口中所有的方法都是抽象方法
3)、来?标志传入的变量,这样可以传入定义的接口以外的值,否则如果直接传入对象中无接口定义的值会报错,所以建议接口定义了哪些值就传哪些值。

(function(){
	//类类型接口
    interface myInter{    
        name:string;
        age?: number; //加个问号,接口属性就可以变成可传可不传了,不传默认是undefined。
        sayhello():void;    
    }
    class myclass implements myInter{   //用类实现接口,接口就是对类的限制
        name:string;
        constructor(name:string){this.name = name}  
        sayhello(){}          
    }
})()
4、封装属性

ts在类的属性前添加修饰符:

1)、public:可在任意位置访问,是默认值;

2)、private:私有属性,私有属性只能在类内部访问(修改),在类内添加方法,私有属性可被访问;

3)、peotected:受保护的属性,只能在当前类和当前类的子类中被访问(修改)

注:

  • 由于属性是在对象中设置的,属性可以任意的被修改,将会导致对象中的数据变得非常不安全。因此需要对属性进行封装。

  • js封装的属性存取器使用时需要调用相应的getter和setter方法;而ts封装的属性存取器使用时可直接当作变量来用就行。

  • 加getter和setter方法只是为了对属性的值做判断,如果不需做判断则没必要使用。

    (function(){
      class A {     //定义类时可以直接将属性定义在构造函数中(简化代码)
         constructor(public name:string,private age:number){            
        }
        get age(){ return this.name;}
        set age(value:string){
            if(value>=0){  this.name = value;}  
        }
    }
    const a = new A('ww',11)
    a.name   //可直接访问
    a.age = -13  //不符合判断条件,不被执行
    })()
    
    5、泛型:

    作用:泛型就是解决类、接口、方法的复用性,以及对不特定数据类型的支持。

    1):函数的泛型,可同时指定多个泛型
    function fn1<T,K>(a:T,b:K){
        console.log(b)
        return a
    }
    //泛型实现接口继承
    Interface inter{
        length:number
    }
    function fun2<T extends inter>(a:T){   //T extends inter表示泛型T必须是Interface的实现类(子类)
        return a.length
    }
    

    这里的T可改成其他任意值,但定义的值,和传入的参数以及返回的参数是一样的,一般默认写法是T,也是业内规范的选择。

    2):类的泛型
    class myclass<T>{
        name:T;
        constructor(name:T){this.name = name}
    }
    const mc = new myclass<string>('qq')
    
    3):接口的泛型
nterface ConfigFn<T> {
  //参数类型 ,返回值类型
  (value: T): T;
}
//接口方法
function getData<T>(value: T): T {
  return value;
}
//使用接口
let myGetDate: ConfigFn<string> = getData;
console.log(myGetDate("3"));
五、namespace

好处:
①提供类似模块化开发的方式
②尽可能少的声明全局变量参数
③把相关的内容封装到一起去,对外提供统一的暴露接口

命名空间这个语法,很类似编程中常说的模块化思想,比如webpack打包时,每个模块有自己的环境,不会污染其他模块,不会有全局变量产生。命名空间就跟这个很类似,注意这里是类似,而不是相同。

命名空间声明的关键词是namespace, 比如声明一个namespace Home,需要暴露出去的类,可以使用export关键词,这样只有暴漏出去的类是全局的,其他的不会再生成全局污染了。
index.ts文件

namespace Home{
    export class page{
        constructor(){
            new Components.Header()
            new Components.Context()
            new Components.foot()
        }
    }
}

命名空间实现组件化
新建components.ts文件

namespace Components{
    export namespace SubComponents{   //子命名空间
        export class test {}
    }
    export class Header{
        constructor(){
            const elem = document.createElement("div")
            elem.innerText = "this is wl"
            document.body.appendChild(elem)
        }
    }
    export class Context{
        constructor(){
            const elem = document.createElement("div")
            elem.innerText = "content   content"
            document.body.appendChild(elem)
        }
    }
    export class foot{
        constructor(){
            const elem = document.createElement('div')
            elem.innerText = "foot foot"
            document.body.appendChild(elem)
        }
    }
}

注:
1)、每个类都用export导出,使用tsc编译后,生成index.js文件,将此文件引入到index.html中

<script src="./build/index.js"></script>

2)、多个文件编译成一个文件,需要在ts.config.js中进行配置,将所有文件都输入到build/index.js中,注意的问题是,开启outFile后,所有全局作用域中的代码都会合并到一个文件中,module必须是amd或system

{
  "module":"amd",
  "outFile": "./build/page.js"
}

3)、子命名空间
在命名空间里,再写一个命名空间

namespace Components {
  export namespace SubComponents {
    export class Test {}
  }
  //someting ...
}

在浏览器中会查看到Components.SubComponents.Test

六、ts使用import语法

使用es6的export导出所有的类

    export class Header{
        constructor(){
            const elem = document.createElement("div")
            elem.innerText = "this is wl"
            document.body.appendChild(elem)
        }
    }
    export class Context{... }
    export class foot{...}

使用Import导入

import {Header,Context,foot} from "./importComponents"
export default class page{
    constructor(){
        new Header()
        new Context()
        new foot()
    }
}

编译后,发现js文件有define开头,这是amd 规范的代码,不能直接在浏览器中运行,可以在 Node 中直接运行;这种代码在浏览器中是没办法被直接运行的,需要其他库(require.js)的支持。

<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>

在index.html文件中

<script >
    require(["page"],function (page) {
      new page.default();
    })
  </script>
七、TS中三个奇怪的符号:!、?? 、?.

ts断言:有非空断言、类型断言(手动指定一个类型)

7.1、!非空断言符号

1) 忽略null和undefined类型
2) 调用函数时忽略undefined类型
3) 确定赋值断言 :

7.2、?.运算符(Optional Chaining)

核心功能:如果遇到 null 或 undefined 就可以立即停止某些表达式的运行
1)可选元素访问
2)可选链函数的调用

7.3、?? 空值合并运算符

操作数
运作规则:当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。

Js的逻辑或 || 运算符:
运作规则:逻辑或会在左操作数为 falsy 值时返回右侧操作数。

短路
运作规则:当空值合并运算符的左表达式不为 null 或 undefined 时,不会对右表达式进行求值。

你可能感兴趣的:(typescript,javascript,前端)