技术标签: TypeScript 面试 TypeScript知识总结 面试文档 前端 typescript javascript 开发语言
目录
将体量大的程序拆分成多个小的,功能独立的模块是开发中不可或缺的一环,开发复杂程序的核心之一就是让其变得不复杂。模块化开发可以提高代码的可维护性、可重用性、可扩展性和可测试性,从而提高了开发效率和代码质量,TypeScript沿用了JS的模块概念,在之前文章中我介绍过Node环境下的两种类型兼容,顺带提了一下目前常用的模块导入导出方式:Commonjs和ES Module,这两种方式在TS中被称为是外部模块,除此之外TS还包含了内部模块和全局模块,本文将逐一介绍
在编译后的JS文件的同一级常能看到.d.ts后缀的声明文件,其作用是描述代码中已经存在的类型信息或为其提供类型声明。举个例子,使用第三方库时可能会找不到对应的类型信息,于是TS提供了声明文件这个概念,它使开发者拥有对库进行描述的能力,达到静态类型提示或者TS检查的目的,声明文件编译后不会生成任何js代码。
一般声明文件内部是不包含可执行语句的,只有类型或者变量的声明。在开发时通常会在项目根目录中新建一个像global.d.ts(名字自取)的文件用于描述全局的类型,变量,函数,类等等
declare是描述TS文件之外信息的一种机制,它的作用是告诉TS某个类型或变量已经存在,我们可以使用它声明全局变量、函数、类、接口、类型别名、类的属性或方法以及后面会介绍的模块与命名空间
通常在global.d.ts文件中使用declare关键字进行全局声明,以便目录下所有文件都能直接访问
举个例子,在项目根目录新建global.d.ts用于变量类型的全局声明,接着修改tsconfig中配置include为["global.d.ts", "src"],在项目任意目录新建index.ts
// global.d.ts
declare interface IAnimal {
name: string
age?: number
}
declare let animal: IAnimal
// src/index.ts
animal = {
name: "阿黄"
}
可以看到index.ts文件中animal的类型是global.d.ts中声明的变量,其二者产生了关联
tips:声明文件(d.ts)中的所有类型(类型别名和接口除外)及变量都要使用declare定义,或者使用export将其导出,否则会抛出以下错误:.d.ts 文件中的顶级声明必须以 "declare" 或 "export" 修饰符开头。
此外,使用类型别名和接口定义的类型可以不需要声明,直接在全局访问
declare type str = string
// 相当于type str = string
参照上面的定义方式,我们可以在声明文件中声明一个函数,然后再使用前对函数进行实现或重载
// global.d.ts
declare function add(a: number, b: number): number;
// src/index.ts
function add(a: number, b: number) {
return a + b
}
console.log(add(1, 2));// 3
// src/main.ts
function add(a: number, b: number, c: string) {
return a + b + c
}
console.log(add(1, 2, "3"));// 33
对函数声明进行重载
我们在介绍属性装饰器的时候曾用到了declare关键字,当时并没有具体说明使用它的原因,这里咱们详细分析一下,首先贴出一段类似的代码
class Animal {
name?: string;
}
在ES2022及以后类中定义的属性会在编译后保留在类中,就像
class Animal {
name;
}
而在.ts文件中使用declare只会被当成是类型或者变量的定义,最后编译在声明文件.d.ts中,不会编译在.js文件中,就像下面这个类
// index.ts
declare class Animal {
name?: string;
}
// index.js
// 空文件
// index.d.ts
declare class Animal {
name?: string;
}
通过declare这个特点,我们可以在类中属性或者方法定义时使用declare关键字将其指定为声明类型的变量,不会出现在.js中,有效的解决之前的问题
// index.ts
class Animal {
declare name?: string;
}
// index.js
class Animal {}
在TS中模块既可以以单个文件的形式存在,这与JS相同,通过export和import两个关键字进行导出导入,对应的介绍可以参照这篇文章的ESM部分,也可以使用module关键字定义模块。
与JS稍有不同,TS中包含了接口和类型别名,我们同样可以通过export type 类型名 导出对应类型别名,如
// src/main.ts
export type IAnimal = {
name: string
color?: string
}
// src/index.ts
import { IAnimal } from './main'
const animal: IAnimal = {
name: "阿黄",
};
tips:在一个.d.ts文件中使用export关键字会使这个文件成为一个模块(这点很重要,一个声明文件(d.ts)不是全局声明文件(只使用declare声明类型)就得是文件模块(使用export等关键字导出)),比如我们把上面的global文件和index改成下面代码
// global.d.ts
type IAnimal = {
name: string
color?: string
}
export {}
// index.ts
const animal: IAnimal = {
name: "阿黄",
};
此时直接使用全局的IAnimal就会抛错
必须使用export将IAnimal导出并使用import导入该模块中的类型
除了上面的使用方式外,我们可以使用module关键字在一个文件中定义多个模块,如
// global.d.ts
declare module 'global_type' {
export type IAnimal = {
name: string
}
export type ICat = {
name: string
}
}
declare module 'global_type1' {
export type IDog = {
name: string
}
}
// index.ts
import type { IAnimal, ICat } from "global_type"
import type { IDog } from 'global_type1'
const animal: IAnimal = {
name: "阿黄",
};
const dog: IDog = animal
const cat: ICat = animal
每一个使用module定义的内容是一个模块
TS支持CommonJS和ESM两种模块系统,使得声明模块有两种写法,分别是使用字符串和变量名
CommonJS的写法遵循匹配文件的相对或绝对路径,通常模块名作为字符串字面量,该方法不支持导出模块,只允许使用declare定义全局模块,并且使用时需要使用import导入
// global.d.ts
declare module "global_type" {
export type IAnimal = {
name: string
}
}
// src/index.ts
import * as global_type from "global_type"
const myObject: global_type.IAnimal = {}
ESM的写法和定义变量一样,使用变量名匹配标识符进行模块的导入,这种方式与定义命名空间(namespace)的效果一样,使用ESM定义的全局模块可以直接使用,不需要导入
// global.d.ts
declare module global_type {
export type IAnimal = {
name?: string
}
}
// src/index.ts
const myObject: global_type.IAnimal = {}
我们在webpack或者vite等工具中可能会看到类似下面的代码,这种写法是CommonJS模块系统独有的
declare module '*.type' {
export type IDog = {
name: string
}
}
这段代码中使用了*.type通配符,匹配了所有.type结尾的模块,导入.type类型的文件就会有IDog这个类型
import type { IDog } from 'global_type.type'
const animal: IDog = {
name: "阿黄",
};
const dog: IDog = animal
使用module定义的模块遵循全局声明,同样可以使用export导出并在其他文件使用,这种方式是ESM模块系统独有的
// global.d.ts
export module global_type {
export type IAnimal = {
name: string
}
export class Animal implements IAnimal {
name: string
}
}
// index.ts
import { global_type } from '../global'
const myObject: global_type.IAnimal = new global_type.Animal();
模块嵌套可以应对更复杂的结构,避免命名冲突,全局污染,在模块中写模块不需要declare和export关键字,模块默认自带导出
// global.d.ts
declare module global_type {
export module IAnimalModule {
export let animal: IAnimal
export type IAnimal = {
name: string
}
}
module IDogModule {
let dog: IDog
type IDog = {
name?: string
}
}
}
// src/index.ts
let animal: global_type.IAnimalModule.IAnimal = global_type.IAnimalModule.animal
let dog: global_type.IDogModule.IDog = global_type.IDogModule.dog
在模块嵌套时,我们可以把 module { } 或者 namespace { } 的大括号中的作用域称为模块的作用域,作用域中的模块类型可以访问,此时如果在模块中使用 export {} 导出空对象的话,当前模块就会被视为文件模块(这和我们上面说到的全局文件转模块文件的tips类似),需要使用export导出局部类型、变量、模块,否则会默认不做导出操作,成为一个私有的模块:
// global.d.ts
declare module global_type {
module IAnimalModule { // 局部模块,只能在global_type中使用
let animal: IAnimal
type IAnimal = {
name?: string
}
}
export { }
}
// src/index.ts
let animal: global_type.IAnimalModule.IAnimal// “global_type”没有已导出的成员“IAnimalModule”
此时需要将模块中的模块手动导出,或加入到导出对象中
export module IAnimalModule {
let animal: IAnimal
type IAnimal = {
name?: string
}
}
// 或者
export { IAnimalModule }
模块的别名是一种用来简化其访问的方式,可以使用import关键字来定义一个别名,然后用这个别名来代替原来的名称,比如
// global.d.ts
declare module global_type {
export type IAnimal = {
name?: string
}
export class Animal implements IAnimal { }
}
// src/index.ts
import Ani = global_type.Animal
import IAni = global_type.IAnimal
const ami: IAni = new Ani()
因为1.5版本前的命名空间(namespace)是TS提出的模块理念,而上面说到的模块是JS中的ES标准,TypeScript对二者做了区分,所以它被称为内部模块。它同样是模块化机制的一员,它的作用是将全局的变量,函数,类等等封装在一个空间内,防止命名污染,冲突。官方比较推荐使用namespace来代替module的ESM模块系统写法,所以在使用模块的变量写法时,TS会将其转换成namespace
还记得上面说的ESM模块系统的导出方式吗?我们把代码中的module关键字换成namespace,就大功告成了,命名空间拥有模块的变量写法的特性。
namespace global_type {
export type IAnimal = {
name: string
}
export class Animal implements IAnimal {
name: string
}
}
const myObject: global_type.IAnimal = new global_type.Animal();
我们把代码放到一个文件中解析一下编译后的JS文件
var global_type;
(function (global_type) {
class Animal {
name;
}
global_type.Animal = Animal;
})(global_type || (global_type = {}));
const myObject = new global_type.Animal();
可以看到,代码中使用iife产生了一个私有的作用域并且定义了一个空对象,将命名空间导出的变量放至对象中。
思考一个问题,一个命名空间必须通过一处代码块定义吗?
答案是否定的,类似函数重载,命名空间的定义允许声明合并,将同名的命名空间对象进行合并(后面的文章会说到,留个悬念)
模块适用于需要动态加载、封装和复用代码的场景,比如Node.js应用、Web应用、npm包等。模块可以利用模块加载器(如CommonJS/Require.js)或支持ES模块的运行时来管理依赖和导入导出。模块是ES6标准的一部分,是现代代码的推荐组织方式。
命名空间适用于需要在全局范围内定义变量、函数、类、接口等的场景,比如Web应用中使用<script>标签引入所有依赖的HTML页面。命名空间可以避免全局变量的命名冲突,但也会增加组件依赖的难度,尤其是在大型应用中。
在TS中declare global关键字用于向全局作用域中添加类型或变量的声明
以我的理解,global的使用方式应该和module以及namespace类似,照葫芦画瓢,使用global { ... }作为一个代码块表示全局的作用域,在里面定义的类型以及变量应该是能够在任意地方取到的
// global.d.ts
declare global {
type IDog = {
name: string
}
let animal: IDog
}
// src/index.ts
animal = {
name: "阿黄"
}
然而事情并没有这么简单,它会提示:全局范围的扩大仅可直接嵌套在外部模块中或环境模块声明中。
在官方给出的d.ts模板中,代码最后一行做了一个导出的操作
这是为何?
实际上这是为了解决TS编译器的一个问题,上面我们说到,如果声明文件d.ts没有使用export导出或者没有使用declare定义类型、变量,那么编译器就会报错,而使用declare global和使用declare module、declare namespace、declare type、declare interface都不一样,这些类型或者模块的定义就是导出声明,不需要进行额外的export。所以在定义(说是拓展比较贴切)全局的global时会在代码底部导出空对象,来声明这个文件是有导出的
上面我们提到:使用export关键字会使声明文件成为一个模块;如果使用了declare global和export { },那么我们的全局变量和类型就不需要使用declare声明,可以直接写在declare global代码块中
下面是一个完整的示例
// global.d.ts
declare global {
type IDog = {
name: string
}
let animal: IDog
}
export { }
// src/index.ts
animal = {
name: "阿黄"
}
文章写到这里也就结束了,本文简述了TS中模块的使用,针对声明文件,declare关键字,文件模块,命名空间以及全局global关键字这几个方面进行了介绍,同时提出了自己的看法及遇到的问题,希望能对你有帮助。
感谢你看到最后,如果觉得文章还不错的话,还请点赞收藏关注支持一下博主,对文章内容有任何问题还望在评论区留言或私信,感谢!
TypeScript: Documentation - Global .d.ts
文章浏览阅读1.1k次。随着计算机的飞速发展,人工智能技术的逐渐成熟,越来越多的人开始关注这个新兴的领域,开始开发出新的产品和服务。在这个信息爆炸的时代,数据量的呈几何级增长,需要人们对海量数据的分析、处理和决策,而机器学习就是人工智能的一个重要组成部分。从传统的统计学习到深度学习(如卷积神经网络CNN),人工智能技术不断的进步,已经引起了很大的社会影响。在这个过程中,生成模型是一个非常重要的工具,它可以用来帮助理解复杂的数据集。通过训练一个生成模型,可以从父亲的基因中产生出一个系列可能的孩子的基因序列,_父母基因怎么组合
文章浏览阅读470次。解决办法:在model library添加dio_tt的model。原因:model library 没设置二极管的model。ADE后仿时出现error。_后仿真 referencing an undefined model or subcircuit
文章浏览阅读1k次,点赞12次,收藏16次。参考视频。_双向gru
文章浏览阅读926次,点赞12次,收藏8次。欢迎大家能看到我的文章,这篇文章收录了东方博宜OJ 1011~1020所有题目的答案,后续还会往后出,敬请关注!如遇不足,欢迎指出!(不要喷我┭┮﹏┭┮)_东方博宜oj答案1062
文章浏览阅读6.9k次,点赞12次,收藏60次。 我尝试通过移位寄存器级联+三八译码器,实现用3跟控制线,驱动16*16LED点阵屏的效果。这是第一篇博客,讲述74HC595芯片的工作原理 一般情况下,使用单片机来控制LED。一个引脚,控制一个LED,是最直观的方法。但也是最笨的方法。引脚对于单片机来说是珍贵的资源,同样性能的芯片引脚越多,价格就越贵。然而有些外设会占用很多引脚,例如LED屏幕。理论上来讲,一个LED需要一个引脚来操作,64个LED组成8×8屏幕,就需要多达64个引脚。但聪明的工程师会节省引脚,把LED按照行列连接,形成矩阵,只需要_16*16点阵引脚
文章浏览阅读1.3k次,点赞2次,收藏8次。docker小白的学习笔记,将自己之前做的Springboot项目做成容器进行试验,新建的PG数据库并没有导入数据,但是此过程可用。一:部署 postgresql镜像。1:搜索postgresql镜像。docker search postgres;2:拉取postgres镜像docker pull postgres3:创建本地目录映射到容..._docker spring boot nacos连接postgresql数据库
文章浏览阅读8.9k次,点赞12次,收藏121次。在Buck开关中,常使用N-MOS管作为功率开关管。相比于P-MOS,N-MOS具有导通电阻低价格便宜且流过电流较大等优势。在同步结构中对于开关管的使用一般有两种方式:上管为P-MOS,下管为N-MOS;无需外部自举电路上下管均为N-MOS;需要外部自举电路从上图可知,由于N-MOS导通条件是栅极电压比源极电压高。对于上管而言必须增加自举电路才能保证上管完全导通。下面就介绍下自举电路..._自举驱动的buck电路
文章浏览阅读2.2k次。YOLOv4 介绍及其模型优化方法一、YOLOv4 介绍2020 年 4 月,YOLOv4 在悄无声息中重磅发布,在目标检测领域引起广泛的讨论。在 YOLO 系列的原作者 Joseph R..._yolo模型剪枝和蒸馏
文章浏览阅读617次,点赞27次,收藏21次。感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。
文章浏览阅读103次。动态人像抓拍比对系统由前端人脸抓拍采集子系统、网络传输子系统和后端解析管理子系统组成,实现对通行人脸信息的采集、传输、处理、分析与集中管理。系统中,前端人脸采集设备负责人脸图像的采集,接入服务器主要实现图片及信息的接收和转发功能,可为多种型号、多个厂家的抓拍机提供统一接入服务,接收到的抓拍图片存入云存储单元,并由人脸结构化分析服务器对抓拍的视频及图像进行建模以及黑名单实时比对报警,建模得到的人脸信..._bootstrap fileinput request.form.files获取不到
文章浏览阅读115次。抖音SEO矩阵系统是基于抖音平台的搜索引擎优化技术的一种系统,其主要作用是通过一系列的技术手段,提高抖音视频的曝光和排名,使其获得更多的流量和粉丝。在本文中,我们将介绍抖音SEO矩阵系统的开发技术,包括系统设计、代码实现等方面。将源码上传到服务器上,并修改相关配置参数,包括数据库连接参数、域名配置、后台管理员账号等。从公开的代码托管网站上下载抖音SEO账号矩阵系统的源码,例如Github、码云等。将配置好的源码部署到Nginx服务器上,并设置网站的根目录指向源码的入口文件。并在网站上购买一个域名。_云索seo矩阵系统
文章浏览阅读813次。本届大赛赛题分为算法治理、深度伪造和网络安全三大方向的七大赛题,分别是:过滤算法鲁棒性、深度伪造视频检测、深度伪造视频生成方法识别、基于人工智能的音视频合成比赛、说话人无关的音频深度伪造检测识别、说话人相关的音频深度伪造检测识别、Webshell检测识别。编辑 | 宋慧出品 | CSDN近日,由国家互联网信息办公室、工业和信息化部、公安部、国家广播电视总局、厦门市人民政府联合主办,以“融新汇智,竞促发展”为主题的第三届中国人工智能大赛,在北京正式启动。大赛旨在推进国内人工智能技术创.._第三届人工智能大赛网络安全赛道