C++20 四大特性之一:Module 特性详解
今天小编给各位分享module的知识,文中也会对其通过C++20 四大特性之一:Module 特性详解和物理层协议有哪四大特性等多篇文章进行知识讲解,如果文章内容对您有帮助,别忘了关注本站,现在进入正文!
内容导航:
一、C++20 四大特性之一:Module 特性详解
C++20 最大的特性是什么?
最大的特性是迄今为止没有哪一款编译器完全实现了所有特性。
文章来源:网易云信
有人认为 C++20 是 C++11 以来最大的一次改动,甚至比 C++11 还要大。本文仅介绍 C++20 四大特性当中的 Module 部分,分为三部分:
探究 C++ 编译链接模型的由来以及利弊
介绍 C++20 Module 机制的使用姿势
总结 Module 背后的机制、利弊、以及各大编译器的支持情况
C++ 是兼容 C 的,不但兼容了 C 的语法,也兼容了 C 的编译链接模型。1973年初,C 语言基本定型:有了预处理、支持结构体;编译模型也基本定型为:预处理、编译、汇编、链接四个步骤并沿用至今;1973年,K&R 二人使用 C 语言重写了 Unix 内核。
为何要有预处理?为何要有头文件?在 C 诞生的年代,用来跑 C 编译器的计算机 PDP-11 的硬件配置是这样的:内存:64 KiB 硬盘:512 KiB。编译器无法把较大的源码文件放入狭小的内存,故当时 C 编译器的设计目标是能够支持模块化编译,即将源码分成多个源码文件、挨个编译,以生成多个目标文件,最后整合(链接)成一个可执行文件。
C 编译器分别编译多个源码文件的过程,实际上是一个 One pass compile 的过程,即:从头到尾扫描一遍源码、边扫描边生成目标文件、过眼即忘(以源码文件为单位)、后面的代码不会影响编译器前面的决策,该特性导致了 C 语言的以下特征:
结构体必须先定义再使用,否则无法知道成员的类型以及偏移,就无法生成目标代码。
局部变量先定义再使用,否则无法知道变量的类型以及在栈中的位置,且为了方便编译器管理栈空间,局部变量必须定义在语句块的开始处。
外部变量只需要知道类型、名字(二者合起来便是声明)即可使用(生成目标代码),外部变量的实际地址由连接器填写。
外部函数只需知道函数名、返回值、参数类型列表(函数声明)即可生成调用函数的目标代码,函数的实际地址由连接器填写。
头文件和预处理恰好满足了上述要求,头文件只需用少量的代码,声明好函数原型、结构体等信息,编译时将头文件展开到实现文件中,编译器即可完美执行 One pass comlile 过程了。
至此,我们看到的都是头文件的必要性和益处,当然,头文件也有很多负面影响:
低效:头文件的本职工作是提供前置声明,而提供前置声明的方式采用了文本拷贝,文本拷贝过程不带有语法分析,会一股脑将需要的、不需要的声明全部拷贝到源文件中。
传递性:最底层的头文件中宏、变量等实体的可见性,可以通过中间头文件“透传”给最上层的头文件,这种透传会带来很多麻烦。
降低编译速度:加入 a.h 被三个模块包含,则 a 会被展开三次、编译三次。
顺序相关:程序的行为受头文件的包含顺影响,也受是否包含某一个头文件影响,在 C++ 中尤为严重(重载)。
不确定性:同一个头文件在不同的源文件中可能表现出不同的行为,导致这些不同的原因,可能源自源文件(比如该源文件包含的其他头文件、该源文件中定义的宏等),也可能源自编译选项。
C++20 中加入了 Module,我们先看 Module 的基本使用姿势,最后再总结 Module 比 头文件的优势。
Module(即模块)避免了传统头文件机制的诸多缺点,一个 Module 是一个独立的翻译单元,包含一个到多个 module interface file(即模块接口文件),包含 0 个到多个 module implementation file(即模块实现文件),使用 Import 关键字即可导入一个模块、使用这个模块暴露的方法。
实现一个最简单的 Modulemodule_hello.cppm:定义一个完整的hello模块,并导出一个 say_hello_to 方法给外部使用。当前各编译器并未规定模块接口文件的后缀,本文统一使用 ".cppm" 后缀名。".cppm" 文件有一个专用名称"模块接口文件",值得注意的是,该文件不光可以声明实体,也可定义实体。
main 函数中可以直接使用 hello 模块:
编译脚本如下,需要先编译 module_hello.cppm 生成一个 pcm 文件(Module 缓存文件),该文件包含了 hello 模块导出的符号。
以上代码有以下细节需要注意:
module hello:声明了一个模块,前面加一个 export,则意味着当前文件是一个模块接口文件(module interface file),只有在模块接口文件中可以导出实体(变量、函数、类、namespace等)。一个模块至少有一个模块接口文件、模块接口文件可以只放实体声明,也可以放实体定义。
import hello:不需加尖括号,且不同于 include,import 后跟的不是文件名,而是模块名(文件名为 module_hello.cpp),编译器并未强制模块名必须与文件名一致。
想要导出一个函数,在函数定义/声明前加一个 export 关键字即可。
Import 的模块不具有传递性。hello 模块包含了 string_view,但是 main 函数在使用 hello 模块前,依然需要再 import ; 。
模块中的 Import 声明需要放在模块声明之后、模块内部其他实体声明之前,即:import <iostream>; 必须放在 export module hello; 之后,void internal_helper() 之前。
编译时需要先编译基础的模块,再编译上层模块,buildfile.sh 中先将 module_hello 编译生成 pcm,再编译 main。
接口与实现分离上个示例中,接口的声明与实现都在同一个文件中(.cppm中,准确地说,该文件中只有函数的实现,声明是由编译器自动生成、放到缓存文件pcm中),当模块的规模变大、接口变多之后,将所有的实体定义都放在模块接口文件中会非常不利于代码的维护,C++20 的模块机制还支持接口与实现分离。下面我们将接口的声明与实现分别放到 .cppm 和 .cpp 文件中。
module_hello.cppm:我们假设 say_hello_to、func_a、func_b 等接口十分复杂,.cppm 文件中只包含接口的声明(square 方法是个例外,它是函数模板,只能定义在 .cppm 中,不能分离式编译)。
module_hello.cpp:给出 hello 模块的各个接口声明对应的实现。
代码有几个细节需要注意:
整个 hello 模块分成了 module_hello.cppm 和 module_hello.cpp 两个文件,前者是模块接口文件(module 声明前有 export 关键字),后者是模块实现文件(module implementation file)。当前各大编译器并未规定模块接口文件的后缀必须是 cppm。
模块实现文件中不能 export 任何实体。
函数模板,比如代码中的 square 函数,定义必须放在模块接口文件中,使用 auto 返回值的函数,定义也必须放在模块接口文件。
可见性控制
在模块最开始的例子中,我们就提到了模块的 Import 不具有传递性:main 函数使用 hello 模块的时候必须 import <string_view>,如果想让 hello 模块中的 string_view 模块暴露给使用者,需使用 export import 显式声明:
hello 模块显式导出 string_view 后,main 文件中便无需再包含 string_view 了。
子模块(Submodule)当模块变得再大一些,仅仅是将模块的接口与实现拆分到两个文件也有点力不从心,模块实现文件会变得非常大,不便于代码的维护。C++20 的模块机制支持子模块。
这次 module_hello.cppm 文件不再定义、声明任何函数,而是仅仅显式导出 hello.sub_a、hello.sub_b 两个子模块,外部需要的方法都由上述两个子模块定义,module_hello.cppm 充当一个“汇总”的角色。
子模块 module hello.sub_a 采用了接口与实现分离的定义方式:“.cppm” 中给出定义,“.cpp” 中给出实现。
module hello.sub_b 同上,不再赘述。
这样,hello 模块的接口和实现文件被拆分到了两个子模块中,每个子模块又有自己的接口文件、实现文件。
值得注意的是,C++20 的子模块是一种“模拟机制”,模块 hello.sub_b 是一个完整的模块,中间的点并不代表语法上的从属关系,不同于函数名、变量名等标识符的命名规则,模块的命名规则中允许点存在于模块名字当中,点只是从逻辑语义上帮助程序员理解模块间的逻辑关系。
Module Partition除了子模块之外,处理复杂模块的机制还有 Module Partition。Module Partition 一直没想到一个贴切的中文翻译,或者可以翻译为模块分区,下文直接使用 Module Partition。Module Partition 分为两种:
module implementation partition
module interface partition
module implementation partition 可以通俗的理解为:将模块的实现文件拆分成多个。module_hello.cppm 文件:给出模块的声明、导出函数的声明。
模块的一部分实现代码拆分到 module_hello_partition_internal.cpp 文件,该文件实现了一个内部方法 internal_helper。
模块的另一部分实现拆分到 module_hello.cpp 文件,该文件实现了 func_a、func_b,同时引用了内部方法 internal_helper(func_a、func_b 当然也可以拆分到两个 cpp 文件中)。
值得注意的是, 模块内部 Import 一个 module partition 时,不能 import hello:internal;而是直接import :internal; 。
module interface partition 可以理解为模块声明拆分到多个文件中。module implementation partition 的例子中,函数声明只集中在一个文件中,module interface partition 可以将这些声明拆分到多个接口文件。
首先定义一个内部 helper:internal_helper:
hello 模块的 a 部分采用声明+定义合一的方式,定义在 module_hello_partition_a.cppm 中:
hello 模块的 b 部分采用声明+定义分离的方式,module_hello_partition_b.cppm 只做声明:
module_hello_partition_b.cpp 给出 hello 模块的 b 部分对应的实现:
module_hello.cppm 再次充当了”汇总“的角色,将模块的 a 部分+ b 部分导出给外部使用:
module implementation partition 的使用方式较为直观,相当于我们平时编程中“一个头文件声明多个 cpp 实现”这种情况。module interface partition 有点类似于 submodule 机制,但语法上有较多差异:
module_hello_partition_b.cpp 第一行不能使用 import hello:partition_b;虽然这样看上去更符合直觉,但是不允许。
每个 module partition interface 最终必须被 primary module interface file 导出,不能遗漏。
primary module interface file 不能导出 module implementation file,只能导出 module interface file,故在 module_hello.cppm 中 export :internal; 是错误的。
同样作为处理大模块的机制,Module Partition 与子模块最本质的区别在于:子模块可以独立的被外部使用者 Import,而 Module Partition 只在模块内部可见。
全局模块片段(Global module fragments)
C++20 之前有大量的不支持模块的代码、头文件,这些代码实际被隐式的当作全局模块片段处理,模块代码与这些片段交互方式如下:
事实上,由于标准库的大多数头文件尚未模块化(VS 模块化了部分头文件),整个第二章的代码在当前编译器环境下(Clang12)是不能直接编译通过的——当前尚不能直接 import < iostream > 等模块,通全局模块段则可以进行方便的过渡(在全局模块片段直接 #include <iostream>),另一个过渡方案便是下一节所介绍的 Module Map——该机制可以使我们能够将旧的 iostream编译成一个 Module。
Module Map
Module Map 机制可以将普通的头文件映射成 Module,进而可以使旧的代码吃到 Module 机制的红利。下面便以 Clang13 中的 Module Map 机制为例:
假设有一个 a.h 头文件,该头文件历史较久,不支持 Module:
通过给 Clang 编译器定义一个 module.modulemap 文件,在该文件中可以将头文件映射成模块:
编译脚本需要依次编译 A、ctype、iostream 三个模块,然后再编译 main 文件:
首先使用 -fmodule-map-file 参数,指定一个 module map file,然后通过 -fmodule 指定 map file 中定义的 module,就可以将头文件编译成 pcm。main 文件使用 A、iostream 等模块时,同样需要使用 fmodule-map-file 参数指定 mdule map 文件,同时使用 -fmodule 指定依赖的模块名称。
注:关于 Module Map 机制能够查到的资料较少,有些细节笔者也未能一一查明,例如:
通过 Module Map 将一个头文件模块化之后,头文件中暴露的宏会如何处理?
假如头文件声明的实体的实现分散在多个 cpp 中,该如何组织编译?
Module 与 Namespace
Module 与 Namespace 是两个维度的概念,在 Module 中同样可以导出 Namespace:
总结最后,对比最开始提到的头文件的缺点,模块机制有以下几点优势:
无需重复编译:一个模块的所有接口文件、实现文件,作为一个翻译单元,一次编译后生成 pcm,之后遇到 Import 该模块的代码,编译器会从 pcm 中寻找函数声明等信息,该特性会极大加快 C++ 代码的编译速度。
隔离性更好:模块内 Import 的内容,不会泄漏到模块外部,除非显式使用 export Import 声明。
顺序无关:Import 多个模块,无需关心这些模块间的顺序。
减少冗余与不一致:小的模块可以直接在单个 cppm 文件中完成实体的导出、定义,但大的模块依然会把声明、实现拆分到不同文件。
子模块、Module Partition 等机制让大模块、超大模块的组织方式更加灵活。
全局模块段、Module Map 制使得 Module 与老旧的头文件交互成为可能。
缺点也有:
编译器支持不稳定:尚未有编译器完全支持 Module 的所有特性、Clang13 支持的 Module Map 特性不一定保留到主干版本。
编译时需要分析依赖关系、先编译最基础的模块。
现有的 C++ 工程需要重新组织 pipline,且尚未出现自动化的构建系统,需要人工根据依赖关系组构建脚本,实施难度巨大。
Module 不能做什么?
Module 不能实现代码的二进制分发,依然需要通过源码分发 Module。
pcm 文件不能通用,不同编译器的 pcm 文件不能通用,同一编译器不同参数的 pcm 不能通用。
无法自动构建,现阶段需要人工组织构建脚本。
编译器如何实现对外隐藏 Module 内部符号的?
在 Module 机制出现之前,符号的链接性分为外部连接性(external linkage,符号可在文件之间共享)、内部链接性(internal linkage,符号只能在文件内部使用),可以通过 extern、static 等关键字控制一个符号的链接性。
Module 机制引入了模块链接性(module linkage),符号可在整个模块内部共享(一个模块可能存在多个 partition 文件)。
对于模块 export 的符号,编译器根据现有规则(外部连接性)对符号进行名称修饰(name mangling)。
对于 Module 内部的符号,统一在符号名称前面添加 “_Zw” 名称修饰,这样链接器链接时便不会链接到内部符号。
截至2020.7,三大编译器对 Module 机制的支持情况:
以上就是本文的全部内容,关于 C++20 的四大特性我们介绍了其一
写在最后:其实每个人都有自己的选择,学编程,每一种编程语言的存在都有其应用的方向,选择你想从事的方向,去进行合适的选择就对了!对于准备学习编程的小伙伴,如果你想更好的提升你的编程核心能力(内功)不妨从现在开始!
编程学习书籍分享:
编程学习视频分享:
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)
欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!
对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!
一、物理层协议有哪四大特性
物理层协议的四大特性:1、机械特性:
指明接口所用接线器的形状和尺寸、引线数目和排列、固定和锁定装置等等。
2、 电气特性:
指明在接口电缆的各条线上出现的电压的范围。
3、功能特性:
指明某条线上出现的某一电平的电压表示何种意义。
4、过程特性:
指明对于不同功能的各种可能事件的出现顺序。
二、ES6、ES7、ES8、ES9、ES10新特性一览
ECMA规范最终由TC39敲定。TC39由包括浏览器厂商在内的各方组成,他们开会推动JavaScript提案沿着一条严格的发展道路前进。 从提案到入选ECMA规范主要有以下几个阶段:
ES6的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度很大,所以ES6中的特性比较多。 在这里列举几个常用的:
1.类(class)
对熟悉Java,object-c,c#等纯面向对象语言的开发者来说,都会对class有一种特殊的情怀。ES6 引入了class(类),让JavaScript的面向对象编程变得更加简单和易于理解。
2.模块化(Module)
ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。
导出(export)
ES6允许在一个模块中使用export来导出多个变量或函数。
导出变量
ES6将一个文件视为一个模块,上面的模块通过 export 向外输出了一个变量。一个模块也可以同时往外面输出多个变量。
导出函数
导入(import)
定义好模块的输出以后就可以在另外一个模块通过import引用。
3.箭头(Arrow)函数
这是ES6中最令人激动的特性之一。=>不只是关键字function的简写,它还带来了其它好处。箭头函数与包围它的代码共享同一个this,能帮你很好的解决this的指向问题。有经验的JavaScript开发者都熟悉诸如var self = this;或var that = this这种引用外围this的模式。但借助=>,就不需要这种模式了。
箭头函数的结构
箭头函数的箭头=>之前是一个空括号、单个的参数名、或用括号括起的多个参数名,而箭头之后可以是一个表达式(作为函数的返回值),或者是用花括号括起的函数体(需要自行通过return来返回值,否则返回的是undefined)。
卸载监听器时的陷阱
除上述的做法外,我们还可以这样做:
4.函数参数默认值
ES6支持在定义函数的时候为其设置默认值:
这样写一般没问题,但当参数的布尔值为false时,就会有问题了。比如,我们这样调用foo函数:
foo(0, "")
因为0的布尔值为false,这样height的取值将是50。同理color的取值为‘red’。
所以说,函数参数默认值不仅能是代码变得更加简洁而且能规避一些问题。
5.模板字符串
ES6支持模板字符串,使得字符串的拼接更加的简洁、直观。
在ES6中通过${}就可以完成字符串的拼接,只需要将变量放在大括号之中。
6.解构赋值
解构赋值语法是JavaScript的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。
获取数组中的值
从数组中获取值并赋值到变量中,变量的顺序与数组中对象顺序对应。
如果没有从数组中的获取到值,你可以为变量设置一个默认值。
通过解构赋值可以方便的交换两个变量的值。
获取对象中的值
7.延展操作符(Spread operator)
延展操作符...可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造对象时, 将对象表达式按key-value的方式展开。
语法
应用场景
没有展开语法的时候,只能组合使用 push,splice,concat 等方法,来将已有数组元素变成新数组的一部分。有了展开语法, 构造新数组会变得更简单、更优雅:
和参数列表的展开类似, ... 在构造字数组时, 可以在任意位置多次使用。
展开语法和 Object.assign() 行为一致, 执行的都是浅拷贝(只遍历一层)。
在ECMAScript 2018中延展操作符增加了对对象的支持
8.对象属性简写
在ES6中允许我们在设置一个对象的属性的时候不指定属性名。
对象中必须包含属性和值,显得非常冗余。
对象中直接写变量,非常简洁。
9.Promise
Promise 是异步编程的一种解决方案,比传统的解决方案callback更加的优雅。它最早由社区提出和实现的,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
嵌套两个setTimeout回调函数:
上面的的代码使用两个then来进行异步编程串行化,避免了回调地狱:
10.支持let与const
在之前JS是没有块级作用域的,const与let填补了这方便的空白,const与let都是块级作用域。
ES2016添加了两个小的特性来说明标准化过程:
1.Array.prototype.includes()
includes() 函数用来判断一个数组是否包含一个指定的值,如果包含则返回 true,否则返回false。
includes 函数与 indexOf 函数很相似,下面两个表达式是等价的:
接下来我们来判断数字中是否包含某个元素:
使用indexOf()验证数组中是否存在某个元素,这时需要根据返回值是否为-1来判断:
使用includes()验证数组中是否存在某个元素,这样更加直观简单:
2.指数操作符
在ES7中引入了指数运算符**,**具有与Math.pow(..)等效的计算结果。
使用自定义的递归函数calculateExponent或者Math.pow()进行指数运算:
使用指数运算符**,就像+、-等操作符一样:
1.async/await
ES2018引入异步迭代器(asynchronous iterators),这就像常规迭代器,除了next()方法返回一个Promise。因此await可以和for...of循环一起使用,以串行的方式运行异步操作。例如:
2.Object.values()
Object.values()是一个与Object.keys()类似的新函数,但返回的是Object自身属性的所有值,不包括继承的值。
假设我们要遍历如下对象obj的所有值:
从上述代码中可以看出Object.values()为我们省去了遍历key,并根据这些key获取value的步骤。
3.Object.entries()
Object.entries()函数返回一个给定对象自身可枚举属性的键值对的数组。
接下来我们来遍历上文中的obj对象的所有属性的key和value:
4.String padding
在ES8中String新增了两个实例函数String.prototype.padStart和String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
5.函数参数列表结尾允许逗号
主要作用是方便使用git进行多人协作开发时修改同一个函数减少不必要的行变更。
6.Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors()函数用来获取一个对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象。
返回obj对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象。
7.SharedArrayBuffer对象
SharedArrayBuffer 对象用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。
8.Atomics对象
Atomics 对象提供了一组静态方法用来对 SharedArrayBuffer 对象进行原子操作。
这些原子操作属于 Atomics 模块。与一般的全局对象不同,Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象一样)。
多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。
wait() 和 wake() 方法采用的是 Linux 上的 futexes 模型(fast user-space mutex,快速用户空间互斥量),可以让进程一直等待直到某个特定的条件为真,主要用于实现阻塞。
1.异步迭代
在async/await的某些时刻,你可能尝试在同步循环中调用异步函数。例如:
这段代码不会正常运行,下面这段同样也不会:
这段代码中,循环本身依旧保持同步,并在在内部异步函数之前全部调用完成。
ES2018引入异步迭代器(asynchronous iterators),这就像常规迭代器,除了next()方法返回一个Promise。因此await可以和for...of循环一起使用,以串行的方式运行异步操作。例如:
2.Promise.finally()
一个Promise调用链要么成功到达最后一个.then(),要么失败触发.catch()。在某些情况下,你想要在无论Promise运行成功还是失败,运行相同的代码,例如清除,删除对话,关闭数据库连接等。
.finally()允许你指定最终的逻辑:
3.Rest/Spread 属性
ES2015引入了Rest参数和扩展运算符。三个点(...)仅用于数组。Rest参数语法允许我们将一个不定数量的参数表示为一个数组。
展开操作符以相反的方式工作,将数组转换成可传递给函数的单独参数。例如Math.max()返回给定数字中的最大值:
ES2018为对象解构提供了和数组一样的Rest参数()和展开操作符,一个简单的例子:
或者你可以使用它给函数传递参数:
扩展运算符可以在其他对象内使用,例如:
可以使用扩展运算符拷贝一个对象,像是这样obj2 = {...obj1},但是 这只是一个对象的浅拷贝 。另外,如果一个对象A的属性是对象B,那么在克隆后的对象cloneB中,该属性指向对象B。
4.正则表达式命名捕获组
JavaScript正则表达式可以返回一个匹配的对象——一个包含匹配字符串的类数组,例如:以YYYY-MM-DD的格式解析日期:
这样的代码很难读懂,并且改变正则表达式的结构有可能改变匹配对象的索引。
ES2018允许命名捕获组使用符号?,在打开捕获括号(后立即命名,示例如下:
任何匹配失败的命名组都将返回undefined。
命名捕获也可以使用在replace()方法中。例如将日期转换为美国的 MM-DD-YYYY 格式:
5.正则表达式反向断言
目前JavaScript在正则表达式中支持先行断言(lookahead)。这意味着匹配会发生,但不会有任何捕获,并且断言没有包含在整个匹配字段中。例如从价格中捕获货币符号:
ES2018引入以相同方式工作但是匹配前面的反向断言(lookbehind),这样我就可以忽略货币符号,单纯的捕获价格的数字:
以上是 肯定反向断言 ,非数字\D必须存在。同样的,还存在 否定反向断言 ,表示一个值必须不存在,例如:
6.正则表达式dotAll模式
正则表达式中点.匹配除回车外的任何单字符,标记s改变这种行为,允许行终止符的出现,例如:
7.正则表达式 Unicode 转义
到目前为止,在正则表达式中本地访问 Unicode 字符属性是不被允许的。ES2018添加了 Unicode 属性转义——形式为\p{...}和\P{...},在正则表达式中使用标记 u (unicode) 设置,在\p块儿内,可以以键值对的方式设置需要匹配的属性而非具体内容。例如:
此特性可以避免使用特定 Unicode 区间来进行内容类型判断,提升可读性和可维护性。
8.非转义序列的模板字符串
之前,\\u开始一个 unicode 转义,\\x开始一个十六进制转义,\后跟一个数字开始一个八进制转义。这使得创建特定的字符串变得不可能,例如Windows文件路径 C:\\uuu\\xxx\111。更多细节参考模板字符串。
1.行分隔符(U + 2028)和段分隔符(U + 2029)符号现在允许在字符串文字中,与JSON匹配
以前,这些符号在字符串文字中被视为行终止符,因此使用它们会导致SyntaxError异常。
2.更加友好的 JSON.stringify
如果输入 Unicode 格式但是超出范围的字符,在原先JSON.stringify返回格式错误的Unicode字符串。现在实现了一个改变JSON.stringify的第3阶段提案,因此它为其输出转义序列,使其成为有效Unicode(并以UTF-8表示)
3.新增了Array的flat()方法和flatMap()方法
flat()和flatMap()本质上就是是归纳(reduce) 与 合并(concat)的操作。
Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 和 深度值1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。 这里我们拿map方法与flatMap方法做一个比较。
4.新增了String的trimStart()方法和trimEnd()方法
5.Object.fromEntries()
Object.entries()方法的作用是返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。
而Object.fromEntries() 则是 Object.entries() 的反转。
Object.fromEntries() 函数传入一个键值对的列表,并返回一个带有这些键值对的新对象。这个迭代参数应该是一个能够实现@iterator方法的的对象,返回一个迭代器对象。它生成一个具有两个元素的类似数组的对象,第一个元素是将用作属性键的值,第二个元素是与该属性键关联的值。
6.Symbol.prototype.description
通过工厂函数Symbol()创建符号时,您可以选择通过参数提供字符串作为描述:
以前,访问描述的唯一方法是将符号转换为字符串:
现在引入了getter Symbol.prototype.description以直接访问描述:
7.String.prototype.matchAll
matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器。 在 matchAll 出现之前,通过在循环中调用regexp.exec来获取所有匹配项信息(regexp需使用/g标志:
如果使用matchAll ,就可以不必使用while循环加exec方式(且正则表达式需使用/g标志)。使用matchAll 会得到一个迭代器的返回值,配合 for...of, array spread, or Array.from() 可以更方便实现功能:
matchAll可以更好的用于分组
8.Function.prototype.toString()现在返回精确字符,包括空格和注释
9.修改 catch 绑定
在 ES10 之前,我们必须通过语法为 catch 子句绑定异常变量,无论是否有必要。很多时候 catch 块是多余的。 ES10 提案使我们能够简单的把变量省略掉。
不算大的改动。
之前是
现在是
10.新的基本数据类型BigInt
现在的基本数据类型(值类型)不止5种(ES6之后是六种)了哦!加上BigInt一共有七种基本数据类型,分别是: String、Number、Boolean、Null、Undefined、Symbol、BigInt
三、电容器的四大特性是什么
说电容的四个主要特性:充电、放电、隔直、通交;
当电容器的两个极板之间加上电压时,电容器就会储存电荷。电容器的电容量在数值上等于一个导电极板上的电荷量与两个极板之间的电压之比。电容器的电容量的基本单位是法拉(F)。在电路图中通常用字母C表示电容元件。
电容器在调谐、旁路、耦合、滤波等电路中起着重要的作用。晶体管收音机的调谐电路要用到它,彩色电视机的耦合电路、旁路电路等也要用到它。
电容器的作用:
耦合:用在耦合电路中的电容称为耦合电容,在阻容耦合放大器和其他电容耦合电路中大量使用这种电容电路,起隔直流通交流作用。
滤波:用在滤波电路中的电容器称为滤波电容,在电源滤波和各种滤波器电路中使用这种电容电路,滤波电容将一定频段内的信号从总信号中去除。
退耦:用在退耦电路中的电容器称为退耦电容,在多级放大器的直流电压供给电路中使用这种电容电路,退耦电容消除每级放大器之间的有害低频交连。
关于module的问题,通过《ES6、ES7、ES8、ES9、ES10新特性一览》、《电容器的四大特性是什么》等文章的解答希望已经帮助到您了!如您想了解更多关于module的相关信息,请到本站进行查找!
爱资源吧版权声明:以上文中内容来自网络,如有侵权请联系删除,谢谢。