前端代码复用学习笔记:整洁架构与清晰架构
基础代码的复用往往比较简单,但是业务代码的复用通常是困难的,如果没有特殊的手段去治理项目会逐渐发展为难以维护的巨石应用,按照维基百科记载,代码的复用形式主要有三种,程序库,应用框架,设计模式
程序库
前端业务代码在程序库的体现主要是通过业务组件,稍微大点的团队都有自己的业务组件库,但是我去过的很多团队都有落地难的问题,其中有些是技术层面的,但是更多的是出现在跨职责协作上,其中,我认为影响最大的是 UI 设计师和前端开发之间的协作关系
UI 组件资产
UI 设计师作为前端的直接上游,生产前端工程师所需的设计资源,如果生产的设计资源本身就是不规范的,必然会极大影响前端团队组件化的发展
UI 设计师作为设计人员,和工程师的思维方式不同,对组件设计规范的意识并不强烈,大部分设计人员对规范的定义仅仅在主题色和主观意识强烈的 风格 上,UI 设计师的最终产出由少部分 视觉要素约定 +设计师个人 主观设计倾向 决定,也就是说,设计师的心情好坏和人员更替会直接影响每次产出设计稿的呈现,这对设计师来说很正常,但对前端工程师来说是致命的
基于此,我们需要在工具上对 UI 设计师的产出加以约束,使其在前端工程师可接受的范围内自由发挥,例如 figma,figma 的 component 概念与前端组件化概念高度吻合,我们可以要求设计稿中可复用的独立单元必须是一个 figma component,从而倒逼设计师建立业务的组件资产,进而确保组件设计的一致性
组件代码设计
按照软件设计原则,找到程序中的变化内容并将其与不变的内容区分开,而在前端,按照代码改动频率依次排列,视图代码>视图逻辑>业务逻辑,相比业务逻辑,UI 代码属于高频变动的部分,尤其是是由 html+css 组成的视图代码
例如电商的商品卡片,不同节日下的商品卡片很可能有不同的 UI 样式,但是他们的业务逻辑是一样的,他们使用同样的业务数据和业务行为
在这种情况下大部分开发会选择两条路
-
要么为每个节日商品卡片单独封装一个组件,
-
要么封装一个包含所有节日样式的巨石组件,然后由外部的一个 prop 参数控制组件的渲染
前者会导致代码的大量冗余,后者违背了单一职责的设计原则,组件内部根据外部传入的节日类型写了一堆 if else,时间久了没人看得懂一堆 if else 里写的是什么
为了减少代码冗余和提升可维护性,我们可以按照职责将组件划为四部分
-
业务上下文组件
-
交互上下文组件
-
无状态视图组件
-
入口组件
可以看到和 Flux 架构很像,但是又有些区别,更像是对 Flux 架构的补充完善
-
多了一层交互状态组件,在 Flux 架构里只是简单区分了无状态组件和有状态组件,但是对 UI 的状态管理很模糊,究竟是由视图组件自己生产自己消费,还是由状态组件统一管理。不过 Flux 架构都是五年前的东西了,当时对组件的状态管理还处于探索阶段,也没有 hooks 这种能够最小化抽取状态和逻辑的利器,对代码的粒度按照职责做了进一步的细分,自然就有了更多复用的可能
-
使用 context + hook 代替 props 传参,这个其实很好理解,在父组的 render 函数里声明子组件并对子组件传参,从而达到控制子组件渲染的目的,这在耦合定义里叫控制耦合,而使用 context 可以做到无需在父组件内声明子组件也依旧可以传递数据,这种基于消费数据结构而进行耦合的方式叫标记耦合,在对各种耦合的定义中,标记耦合就是要比控制耦合的耦合度更低,耦合度低复用率也就上去了
-
减少全局依赖,把全局依赖下沉到某种上下文里,之前做过几次老代码到新项目的迁移,迁移过去的组件有大量直接依赖全局环境的代码,request,localstorage,甚至是挂在 window 下的全局变量,为了不影响新项目的运行,迁移过去的老代码做了大量修改,如果一开始就把这些全局依赖封装在 context 里,平移过去的代码几乎不需要修改,或者只是修改 context 这一小块就够了
应用框架
一条业务线往往会存在多个项目,不同项目之间通用业务代码的复用一直是个问题,在若干个项目上往往存在着多次复制黏贴的代码,经过项目的多次迭代,他们可能还会在各自的项目上衍生出不同的版本,长此以往维护成本会让开发人员痛苦不堪,我们当然知道抽离到 npm 私库是最佳实践,但是抽离模块的繁琐过程很大程度上提高了业务线上开发人员的心智成本,从而降低了将代码抽离到 npm 的意愿,在这种情况下,Monorepo 也许是更好的解决方案
lerna 提供了一个简易的 Monorepo 方案,利用 lerna 软链接项目作为依赖的特性,使得项目间的代码共享变得更加容易
上面我们用 lerna 管理了三个项目,其中 shared 属于其他两个项目的共享代码,wap-app 和 web-app 可以通过依赖的方式直接消费 shared 项目中的组件,工具函数和业务服务,极大优化我们的工作流程
设计模式
很多时候我们提到代码复用第一时间想到的组件,但是组件有组件的局限性,它是和 UI 框架 强耦合 的,而有一些场景需要需要实现 ==跨平台跨框架的代码复用== ,例如一个产品它既有 PC 网页,也有 RN 开发的 App,还有小程序
他们存在完全迥异的视图层或者框架体系,与之强耦合的组件没办法实现复用,但它们背后的业务逻辑都是一样的,所以为了复用我们的业务逻辑,需要实现业务逻辑的 ==框架无关,环境无关==
设计本质上就是以一种可以将它们重新组合在一起的方式将事物拆开…… 将事物拆分成可以重新组合的事物,这就是设计。 — Rich Hickey《设计、重构和性能》
系统设计其实就是系统的拆分,最重要的是我们可以在不耗费太多时间的情况下重新把它们组起来。
我同意上面这个观点,但我认为系统架构的另一个主要目标是系统的可扩展性。我们应用的需求是不断变化的。我们希望我们的程序可以非常易于更新和修改以满足持续变化的新需求。干净的架构就可以帮助我们实现这一目标。
了解干净架构,可以阅读《前端领域的 “干净架构” 前端领域的 “干净架构” - 掘金》
整洁架构Clean Architecture
《架构整洁之道》中提出的整洁架构就是解决这个问题的,腾讯文档团队也有对整洁架构的相关实践 让 JS 摆脱框架的束缚
我们可以将 react 的代码快速的迁移到一个类 react 框架,但是我们很难将他迁移到 Vue 框架和 Angular 框架。可能在代码迁移合并升级的时候,我们能做的可能只有重构。
我们可能会想到使用 Web Components 或者是微服务,但是带来的可能会是,更多的浏览器限定,要看更多的框架文档去遵循更多的规范。
无论框架如何的迭代,我们 JS 是永远不会太大的变化的,所有框架都是基于 JS 去写的,如何让我们通用 JS 代码被不同的框架去应用,让我们的通用代码去框架化,是我们应该思考的问题。
既然我们要去框架化,那架构就不去用了,直接用 JS 就好了呀,那就是一种技术的倒退,我们要做的不是不用框架,而是用一种更巧妙的方式去可以适配更多的框架。
例如,目前前端的类 MVVM 架构,ViewModel 层可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑。我们要去用我们不变的 JS 代码去适配更多框架的 VM。
Clean Architecture 是由 Robert C. Martin 在 2012 年提出的,最早只是在 Android,Android 应用有很重的 View 层。今天,前端应用走向了 MV* 的架构方案,也有了一层很重的 View 层。
《DDD中常提到的应用架构总结(六边形、洋葱、整洁、清晰) DDD系列 - 第0讲 DDD中常提到的应用架构总结(六边形、洋葱、整洁、清晰)_整洁架构 六边形架构_罗小爬EX的博客-CSDN博客》
架构随时间的演进可参见下图:
Robert C. Martin 总结了六边形架构(即端口与适配器架构):DCI (Data-Context-Interactions,数据-场景-交互)架构BCI(Boundary Control Entity,Boundary Control Entity)架构等多种架构,归纳出了这些架构的基本特点:
-
框架无关性。系统不依赖于框架中的某个函数,框架只是一个工具,系统不能适应于框架。
-
可被测试。业务逻辑脱离于 UI、数据库等外部元素进行测试。
-
UI 无关性。不需要修改系统的其它部分,就可以变更 UI,诸如由 Web 界面替换成 CLI。
-
数据库无关性。业务逻辑与数据库之间需要进行解耦,我们可以随意切换 LocalStroage、IndexedDB、Web SQL。
-
外部机构(agency)无关性。系统的业务逻辑,不需要知道其它外部接口,诸如安全、调度、代理等。
如你所见,作为一个普通(不分前后端)的开发人员,我们关注于业务逻辑的抽离,让业务逻辑独立于框架。而在前端的实化,则是让前端的业务逻辑,可以独立于框架,只让 UI(即表现层)与框架绑定。一旦,我们更换框架的时候,只需要替换这部分的业务逻辑即可。

这种架构使得,框架和一切外部相关的实现细节被隔离在框架与驱动层,业务逻辑层只负责业务本身,与外部的联系由接口适配器层控制,假设我们需要从 Vue 迁移到 React,或者从 Vue2 升级到 Vue3,我们只需要变动接口适配器层和框架与驱动层,业务逻辑层可以继续复用
如图所示 Clean Architecture 一共分为四个环,四个层级。环与环之间,存在一个依赖关系原则:源代码中的依赖关系,必须只指向同心圆的内层,即由低层机制指向高级策略。其类似于 SOLID 中的依赖倒置原则:
高层模块不应该依赖低层模块,两者都应该依赖其抽象
抽象不应该依赖细节,细节应该依赖抽象
与此同时,四个环都存在各自核心的概念:
实体 Entities (又称领域对象或业务对象,实体用于封装企业范围的业务规则)
用例 Use Cases(交互器,用例是特定于应用的业务逻辑)
接口适配器 Interface Adapters (接口适配器层的主要作用是转换数据)
框架和驱动(Frameworks and Drivers),最外层由各种框架和工具组成,比如 Web 框架、数据库访问工具等
这个介绍可能有些简单,让我复制/粘贴一下更详细的解释:
实体(Entities),实体用于封装企业范围的业务规则。实体可以是拥有方法的对象,也可以是数据结构和函数的集合。如果没有企业,只是单个应用,那么实体就是应用里的业务对象。这些对象封装了最通用和高层的业务规则,极少会受到外部变化的影响。任何操作层面的改动都不会影响到这一层。
用例(Use Cases),用例是特定于应用的业务逻辑,一般用来完成用户的某个操作。用例协调数据流向或者流出实体层,并且在此过程中通过执行实体的业务规则来达成用例的目标。用例层的改动不会影响到内部的实体层,同时也不会受外层的改动影响,比如数据库、UI 和框架的变动。只有而且应当应用的操作发生变化的时候,用例层的代码才随之修改。
接口适配器(Interface Adapters)。接口适配器层的主要作用是转换数据,数据从最适合内部用例层和实体层的结构转换成适合外层(比如数据持久化框架)的结构。反之,来自于外部服务的数据也会在这层转换为内层需要的结构。
框架和驱动(Frameworks and Drivers)。最外层由各种框架和工具组成,比如 Web 框架、数据库访问工具等。通常在这层不需要写太多代码,大多是一些用来跟内层通信的胶水代码。这一层包含了所有实现细节,把实现细节锁定在这一层能够减少它们的改动对整个系统造成的伤害。
从某种意义上来说,Clean Architectute 是一种规范化和模板化的实施方案。与此同时,它在数据层的三层机制,使得它存在两层防腐层,usecase 可以作为业务的缓冲层,repository 层可以隔离后端服务与模型。
清晰架构Explicit Architecture
2017年Herberto Graca在其《软件架构编年》史系列文章中提出清晰架构Explicit Architecture,即将DDD, Hexagonal, Onion, Clean, CQRS等进行融合后的架构。

-
最中心的红色多边形Application Core即表示业务逻辑实现,即应用核心
-
红色多边形的边界即表示端口Port,即应用核心的入口/出口定义
-
Application Layer - 应用层,包括:
-
Application Services,业务用例的编排服务即及其interface定义,应用服务的作用通常如下:
-
使用 Repostitory 查找一个或多个实体;
-
让这些实体执行一些领域逻辑;
-
再次使用 Repostitory 让这些实体持久化,有效地保存数据变化;
-
触发应用事件(如发送邮件、调用第三方API、发送MQ消息等)。
-
-
CQRS命令/查询处理器
-
Event Listener事件监听器
-
Port端口定义,如ORM 接口Repostitory、搜索引擎接口、消息接口等等
-
-
Domain Layer - 领域层,这一层含了数据和操作数据的逻辑,它们只和领域本身有关,独立于调用这些逻辑的业务过程。它们完全独立,对应用层完全无感知。
-
Domain Services - 领域服务,封装涉及多实体(相同或不同实体类型)的领域逻辑,且领域服务间可以相互调用。
-
Domain Models - 领域模型,在架构的正中心,完全不依赖外部任何层次的领域模型。它包含了那些表示领域中某个概念的业务对象,如实体、值对象、枚举以及其它领域模型种用到的任何对象(如领域事件Domain Events,简单理解为MQ消息)。
-
-
红色多边形的外侧左半圆部分即为主/主动适配器(用户界面User Interface实现)
-
如Spring MVC中的Controller实现
-
Command Query Bus 命令查询总线
-
-
红色多边形的外侧右半圆部分即次/被动适配器(基础设置Infrastructure实现)
-
如数据持久化实现Mysql、短信通知实现、MQ通知、搜索引擎ES实现等
-
Event Bus 事件总线
-
-
依赖方向由外到内,且内层不知道外层(参见之前洋葱架构)
采用按组件分包Package By Component,即整合传统按层分包Package By Layer和按特性分包Package By Feature,组件Component可以理解为专属于特定一个领域内的业务逻辑服务和数据访问逻辑的组合,也可以理解为特定领域,如账单、用户、评论或帐号等。
在清晰架构中可以理解为:
-
先按照层次进行分包
-
表现层Presentation
-
业务核心层Application Core
-
基础设施层Infrastructure)
-
-
之后每一层次再按照特性分包
参考文章:
前端业务代码如何复用 前端业务代码如何复用 - 知乎
让 JS 摆脱框架的束缚 让 JS 摆脱框架的束缚
DDD中常提到的应用架构总结(六边形、洋葱、整洁、清晰) DDD系列 - 第0讲 DDD中常提到的应用架构总结(六边形、洋葱、整洁、清晰)_整洁架构 六边形架构_罗小爬EX的博客-CSDN博客
Clean Frontend Architecture:整洁前端架构 Clean Frontend Architecture:整洁前端架构 | clean-frontend
微模块-前端业务模块化探索# 微模块-前端业务模块化探索 | Elux
“整洁架构” 和商家前端的重构之路 “整洁架构”和商家前端的重构之路 - 得物技术的个人空间 - OSCHINA - 中文开源技术交流社区
转载本站文章《前端代码复用学习笔记:整洁架构与清晰架构》,
请注明出处:前端代码复用学习笔记:整洁架构与清晰架构 - 前端架构设计 - 周陆军的个人网站
相关文章:
前端代码复用学习笔记:整洁架构与清晰架构
基础代码的复用往往比较简单,但是业务代码的复用通常是困难的,如果没有特殊的手段去治理项目会逐渐发展为难以维护的巨石应用,按照维基百科记载,代码的复用形式主要有三种,程序库,应用框架,设计…...
【python刷题】leecode官方提示“->“,“:“这些符号是什么意思?什么是Type Hints?
作者:20岁爱吃必胜客(坤制作人),近十年开发经验, 跨域学习者,目前于海外某世界知名高校就读计算机相关专业。荣誉:阿里云博客专家认证、腾讯开发者社区优质创作者,在CTF省赛校赛多次取得好成绩。…...
【华为OD机试真题2023 JAVA】最佳对手
华为OD机试真题,2023年度机试题库全覆盖,刷题指南点这里 最佳对手 知识点排序DFS搜索回溯 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: 游戏里面,队伍通过匹配实力相近的对手进行对战。 但是如果匹配的队伍实例相差太大,对于双方游戏体验都不会太好。 给定n个…...
css实现文字大小自适应
在页面编写中经常会碰到页面自适应的问题,也就是页面内部的元素会随着窗口的放大缩小而放大缩小,box可以通过calc 百分比的形式做到页面自适应,但是box内的字体却无法做到这点,往往box自适应大小了,内部的字体还是原来…...
【Redis】搭建哨兵集群
目录 集群结构 准备实例和配置 启动 测试 集群结构 这里我们搭建一个三节点形成的Sentinel集群,来监管之前的Redis主从集群。如图: 三个sentinel实例信息如下: 节点IPPORTs1192.168.150.10127001s2192.168.150.10127002s3192.168.150.…...
CTFHub | .htaccess
0x00 前言 CTFHub 专注网络安全、信息安全、白帽子技术的在线学习,实训平台。提供优质的赛事及学习服务,拥有完善的题目环境及配套 writeup ,降低 CTF 学习入门门槛,快速帮助选手成长,跟随主流比赛潮流。 0x01 题目描述…...
微机原理 || 8253 芯片 (详细讲解 + 经典例题)
一点点看!一定可以看懂!考试没有问题的!加油💪 前面知识写的详细,看不懂可以先看典例,回头来梳理就明白了【典例就是常考的题】 目录 Part 1: 芯片知识总结 (一)8253 芯片特点 …...
python Django高级操作-分页-定义CVS-发送邮件
分页 分页是指在web页面有大量数据需要显示,为了阅读方便在每个页页中只显示部分数据。优点: 1.方便阅读2.减少数据提取量,减轻服务器压力。Paginator对像 负责分页数据整体的管理对象的构造方法Paginator属性 Paginator方法 Paginator异常exception pag…...
React 用一个简单案例体验一遍 React-dom React-router React-redux 全家桶
一、准备工作 本文略长,建议耐心读完,每一节的内容与上一节的内容存在关联,最好跟着案例过一遍,加深记忆。 1.1 创建项目 第一步,执行下面的命令来创建一个 React 项目。 npx create-react-app react-example cd rea…...
9. C#面向对象基础
一、C# 类 在 C# 中,类是引用类型的。类由成员属性和成员方法构成。我们可以动态创建类的实例(instance),这个实例也被称为对象(object),我们可以通过类和对象来设计程序。 1、类的定义 类的定…...
【MIT 6.S081】Lab2: system calls
本Lab包括两个简单系统调用的实现,进一步熟悉系统调用接口。 笔者用时约1.5h 概述 根据文档说明,当我们添加一个系统调用时,比如第一个任务是添加一个trace,需要进行以下操作: 首先将系统调用的原型添加到user/user…...
设计模式之单例模式~
设计模式包含很多,但与面试相关的设计模式是单例模式,单例模式的写法有好几种,我们主要学习这三种—饿汉式单例,懒汉式单例、登记式单例,这篇文章我们主要学习饿汉式单例 单例模式: 满足要点: 私有构造 …...
top终端详解
1.top命令行使用 2.top每行意义 3.补充 1.top命令行使用 top命令是一个常用的Linux系统命令,用于实时查看系统的运行状态和进程信息。下面是top命令的几个常用参数的含义: -d seconds:设置top命令的更新间隔时间,单位是秒。默认是…...
解决一个偶现的503 bug,花了俺不少时间
概述 在3月2日晚上,大概8点左右,本想打道回府,回家休息,突然被人在bug群了一下,说是管理后台,访问不了,界面上出现了: 503 service temporarily unavailable我赶紧尝试访问了一下,确…...
什么是栈,如何实现?
欢迎来到 Claffic 的博客 💞💞💞 “但有一枝堪比玉,何须九畹始征兰?” 前言: 栈是一种特殊的线性表,就像开盖的桶一样,从底部开始放数据,从顶部开始取数据,那么栈具体是…...
在我的MacBook上捣鼓ESP8266
周三是我们的篮球日,打篮球后总是会有些兴奋,然后就容易睡不着,额,睡不着就拿我的ESP8266开发板出来捣鼓一下。先下载编译工具链https://github.com/espressif/ESP8266_RTOS_SDK下载sdkgit clone https://github.com/espressif/ES…...
【深度强化学习】(8) iPPO 模型解析,附Pytorch完整代码
大家好,今天和各位分享一下多智能体深度强化学习算法 ippo,并基于 gym 环境完成一个小案例。完整代码可以从我的 GitHub 中获得:https://github.com/LiSir-HIT/Reinforcement-Learning/tree/main/Model 1. 算法原理 多智能体的情形相比于单智…...
77.qt qml-QianWindow-V1版本界面讲解
上章介绍: 76.qt qml-QianWindow开源炫酷界面框架简介(支持白色暗黑渐变自定义控件均以适配) 界面如下所示: 代码结构如下所示:...
RHCE学习日记二
1、在 node1 主机上配置 chrony 时间服务器,将该主机作为时间服务器。 命令: vim /etc/chrony.conf 在文件位置添加命令: #Use public servers from the pool.ntp.org project. #Please consider joining the pool (https://www.pool.ntp.org…...
Dubbo原理简介
Dubbo缺省协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。 作为RPC:支持各种传输协议,如dubbo,hession,json,fastjson,底层采用mina,netty长连接…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
云原生周刊:k0s 成为 CNCF 沙箱项目
开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...
Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...
背包问题双雄:01 背包与完全背包详解(Java 实现)
一、背包问题概述 背包问题是动态规划领域的经典问题,其核心在于如何在有限容量的背包中选择物品,使得总价值最大化。根据物品选择规则的不同,主要分为两类: 01 背包:每件物品最多选 1 次(选或不选&#…...
C++中vector类型的介绍和使用
文章目录 一、vector 类型的简介1.1 基本介绍1.2 常见用法示例1.3 常见成员函数简表 二、vector 数据的插入2.1 push_back() —— 在尾部插入一个元素2.2 emplace_back() —— 在尾部“就地”构造对象2.3 insert() —— 在任意位置插入一个或多个元素2.4 emplace() —— 在任意…...

