ES6中的map和set

Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set本身是一个构造函数,用来生成 Set 数据结构。

以下代码

const s = new Set();[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));for (let i of s) {console.log(i);
}
// 2 3 5 4

结果表明 Set 结构不会添加重复的值

向Set中加入值不会发生类型转换,所以5"5"是两个不同的值。

前面说到set结构不会添加重复的值意味着set内部会对值进行判断,使用的判断算法叫“Same-value-zero equality”,类似于精确相等符(===),但主要的区别是向Set加入值时认为NaN等于自身,而精确相等符认为NaN不等于自身

Object.is(NaN, NaN) 返回 true 表明使用 Object.is 或者说 Set 内部使用的 “same-value-zero” 算法时,NaN 是等于自身的。

所以下面例子中Set里面只有一个NaN

let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}

另外两个对象总是不相等的

Array.from()方法可以将Set结构转为数组

所以去重(数组或者字符串)成员可以使用set

[...new Set(array)]
[...new Set('ababbc')].join('')
Array.from(new Set(array));

Set的方法

Set方法说明
add添加某个值,但会Set结构本身
delete删除某个值,返回一个布尔值,表示是否成功
has返回一个布尔值,表示该值是否为Set的成员。
clear清除所有成员,没有返回值。

WeakSet

虽然也是不重复值的集合,但是跟Set有两个区别

  1. WeakSet的成员只能是对象和symbol值,而不能是其他类型的值
  2. WeakSet中的对象都是弱引用,也就是说垃圾回收机制不考虑WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中
    • 因此WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在WeakSet里面的引用就会自动消失
  3. 不能获取 WeakSet 的大小(即包含多少个元素)。

由于 WeakSet 内部有多少个成员,取决于垃圾回收机制有没有运行,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行是不可预测的,因此 ES6 规定 WeakSet 不可遍历。

WeakSet结构的方法

  1. add()
  2. delete()
  3. has()

WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

如果不使用 WeakSet 来存储 DOM 节点,而使用普通的集合(如 ArraySet)或者其他对象来保存对这些节点的引用,可能会在比如以下几种情况下引发内存泄漏:

  1. 未移除的事件监听器

当为一个 DOM 节点添加了事件监听器,并且该节点从文档中移除后,如果你没有显式地移除这些事件监听器,那么即使该节点已经不在文档中,浏览器仍然会保持对该节点及其事件监听器的引用。这会导致该节点无法被垃圾回收,从而占用不必要的内存。

示例:

const elements = [];
const button = document.createElement('button');// 添加事件监听器
button.addEventListener('click', () => {console.log('Button clicked');
});// 将节点存储在一个普通数组中
elements.push(button);// 从文档中移除节点
document.body.removeChild(button);
// 如果不移除事件监听器,按钮不会被垃圾回收
  1. 长时间存在的缓存

如果你在应用中使用了某种形式的缓存来存储 DOM 节点或与它们相关联的数据,并且这些缓存没有适当的清理机制,那么当 DOM 节点从文档中移除时,这些缓存仍然会持有对节点的引用,导致它们无法被垃圾回收。

示例:

const cache = new Map();function cacheElement(element) {cache.set(element, { data: 'some data' });
}const div = document.createElement('div');
cacheElement(div);// 从文档中移除节点
document.body.removeChild(div);// 缓存仍然持有对 div 的引用
console.log(cache.has(div)); // true
  1. 全局变量或闭包中的引用

如果你将 DOM 节点存储在全局变量中,或者通过闭包等方式间接持有对节点的引用,那么即使这些节点已经从文档中移除,它们也不会被垃圾回收,因为它们仍然可以通过这些全局变量或闭包访问到。

示例:

javascript深色版本

let globalNode;function createAndCacheNode() {const node = document.createElement('div');globalNode = node; // 全局变量持有对节点的引用return node;
}const node = createAndCacheNode();
document.body.appendChild(node);// 从文档中移除节点
document.body.removeChild(node);// 全局变量仍然持有对节点的引用
console.log(globalNode === node); // true

Map

JS的数据对象(Obejct),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键(一定程度上对其的使用有限制)

比如下面代码

const data = {}
const element = document.getElementById('myDiv')data[element] = 'metaData'
data['[object HTMLDivElement]'] // "metadata"

上面代码原意是将一个 DOM 节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动转为字符串[object HTMLDivElement]

“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。(Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。)

如果需要“键值对”的数据结构,Map 构造函数比 Object 更合适。

具有极快的查找速度

在n中有很长的数据,但是利用Map则查找十分迅速:

const m=new map(['Kris',21],['Bob',19],['Lily',25],['Jack',27]);
m.get('Kris');   //  21
m.get('Lily');   //  25

初始化Map需要一个二维数组,或者直接初始化一个空Map,

let m=new Map();
//-----------------------
const m=new map(['Kris',21],['Bob',19],['Lily',25],['Jack',27]);
m.get('Kris');   //  21
m.get('Lily');   //  25

Map构造函数接受数组作为参数,实际执行的是以下算法

const items = [['name', '张三'], ['title', 'Author']]
const map = new Map()items.forEach(([key, value]) => map.set(key, value)
)

在ES6中有使用map对象和set对象,当作Map构造函数的参数,结果都生成了新的Map对象

任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)都可以当作Map构造函数的参数。这就是说,SetMap都可以用来生成新的 Map。

例如

const set = Set([['foo', 1], ['bar', 2]])
const m1 = new Map(set)
m1.get('foo') // 1const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3

这里或许有疑问就是Map不是只接受值吗,为什么这里使用了键值对形式却能接受并生成set

原因:

  • 虽然Set通常用于存储唯一的值,但是也可以存储任何类型的数据结构,包括键值对(即每个成员是一个双元素数组)
  • Map 构造函数会遍历传入的可迭代对象,并假设每个成员是一个 [key, value] 形式的数组。
  • 如果将一个 Set 传递给 Map 构造函数,并且 Set 中的每个成员都是一个双元素数组,那么 Map 会将这些数组解释为键值对,并创建相应的 Map
  • 如果 Set 中的每个成员都是一个双元素数组,那么 Map 构造函数会将这些数组解释为键值对,并创建相应的 Map

Map易错点

const map = new Map();map.set(['a'], 555);
map.get(['a']) // undefined

上面代码的setget方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined

注意这里的[‘a’]是两个不同的数组实例,内存地址不一样

,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。

如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0-0就是一个键,布尔值true和字符串true则是两个不同的键。另外,undefinednull也是两个不同的键。虽然NaN不严格相等于自身,但 Map 将其视为同一个键。

Map的方法

Map方法说明
set(key, val):向Map中添加新元素
get(key):通过键值查找特定的数值并返回
has(key):判断Map对象中是否有Key所对应的值,有返回true,否则返回false
delete(key):通过键值从Map中移除对应的数据
clear():将这个Map中的所有元素删除

一个key只能对应一个value,所以多次对一个key放入value,后面的值会把前面的值冲掉

Map的使用场景

  1. 需要动态键的数据存储

当需要存储的数据键不是静态的字符串或符号时,Map 是一个更好的选择。Map 允许任何类型的值作为键,包括对象、函数、甚至其他 MapSet

示例:

const map = new Map();
const keyObj = { id: 1 };
const keyFunc = () => "hello";map.set(keyObj, "value associated with keyObj");
map.set(keyFunc, "value associated with keyFunc");console.log(map.get(keyObj)); // "value associated with keyObj"
console.log(map.get(keyFunc)); // "value associated with keyFunc"
  1. 频繁的增删操作

Map 在增删操作上的性能通常优于普通对象。对于频繁的插入和删除操作,Map 提供了更高效的方法,如 .set(), .delete(), 和 .clear()

示例:

const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');// 删除一个键值对
map.delete('key1');// 清空整个 Map
map.clear();
  1. 保持插入顺序

Map 会按照插入的顺序迭代其元素,这对于需要维护键值对顺序的场景非常有用。相比之下,普通对象的属性顺序在某些情况下是不确定的。

示例:

const map = new Map([['first', 'value1'],['second', 'value2'],['third', 'value3']
]);for (let [key, value] of map) {console.log(`${key}: ${value}`);
}
// 输出:
// first: value1
// second: value2
// third: value3
  1. 计数器或频率统计

Map 可以用于统计某个值出现的次数,特别适合处理数组中的重复项或其他形式的频率统计。

示例:

const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];const wordCounts = new Map();
for (const word of words) {wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
}console.log(wordCounts); // Map(3) { 'apple' => 3, 'banana' => 2, 'orange' => 1 }
  1. 缓存机制

Map 可以用于实现简单的缓存机制,特别是在需要根据键来快速查找和更新数据的情况下。你可以使用 Map 来存储计算结果,避免重复计算。

示例:

const cache = new Map();function fibonacci(n) {if (cache.has(n)) {return cache.get(n);}let result;if (n <= 1) {result = n;} else {result = fibonacci(n - 1) + fibonacci(n - 2);}cache.set(n, result);return result;
}console.log(fibonacci(10)); // 55
console.log(cache); // Map(11) { 0 => 0, 1 => 1, 2 => 1, 3 => 2, 4 => 3, 5 => 5, 6 => 8, 7 => 13, 8 => 21, 9 => 34, 10 => 55 }
  1. 关联复杂对象

Map 可以用于将复杂对象(如 DOM 节点、函数等)与元数据关联起来,而不会修改这些对象本身。这在处理事件监听器、组件状态等场景中非常有用。

示例:

const elements = document.querySelectorAll('button');
const elementData = new Map();elements.forEach(element => {elementData.set(element, { clicks: 0 });element.addEventListener('click', () => {const data = elementData.get(element);data.clicks++;console.log(`Button clicked ${data.clicks} times`);});
});
  1. 替代普通对象

在某些情况下,Map 可以替代普通对象,尤其是在你需要动态添加或删除属性,或者需要更高效的性能时。Map 提供了更多的内置方法和更好的性能特性。

示例:

const userPreferences = new Map([['theme', 'dark'],['language', 'en']
]);userPreferences.set('timezone', 'UTC+8');console.log(userPreferences.get('theme')); // 'dark'
console.log(userPreferences.size); // 3
  1. 处理大型数据集

Map 在处理大型数据集时表现出色,特别是当数据集包含大量键值对时。Map 的迭代、查找和更新操作都比普通对象更高效。

示例:

const largeDataSet = new Map();
for (let i = 0; i < 1000000; i++) {largeDataSet.set(i, `value${i}`);
}console.log(largeDataSet.get(500000)); // 'value500000'
  1. 多线程环境下的共享数据

在 Web Workers 或 Node.js 的多线程环境中,Map 可以用于在不同线程之间共享数据,因为它提供了更安全和高效的并发访问方式。

示例:

// 主线程
const worker = new Worker('worker.js');
const sharedData = new Map();worker.postMessage(sharedData);// worker.js
self.onmessage = function(e) {const map = e.data;console.log(map.get('key'));
};
  1. 避免原型链污染

普通对象的属性查找会沿着原型链进行,这可能导致意外的行为或冲突。Map 不依赖于原型链,因此可以避免这些问题,特别是在你不确定对象的来源或结构时。

示例:

const obj = {};
obj.__proto__.foo = 'bar'; // 污染了原型链console.log(obj.foo); // 'bar'const map = new Map();
map.set('foo', 'baz');console.log(map.get('foo')); // 'baz'

WeakMap

与Map的却别

  1. weakMap只接受对象(NULL除外)和Symbol值作为键名,不接受其它类型的值作为键名

    • const map = new WeakMap();
      map.set(1, 2) // 报错
      map.set(null, 2) // 报错
      map.set(Symbol(), 2) // 不报错
      
  2. WeakMap的键名所指向的对象,不计入垃圾回收机制。键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内(也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。),注意是键名,而不是键值,键值依然是正常引用

  3. 没有遍历操作,没有size属性,无法清空

设计目的是:有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用,所以设计出WeakMap解决这个问题

第2条例子说明解释

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [[e1, 'foo 元素'],[e2, 'bar 元素'],
];
// 对这两个对象添加一些文字说明,这就形成了对e1和e2的引用//如果不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放e1和e2占用的内存
arr[0] = null
arr[1] = null
// 这样的手动删除的方法很容易忘记写然后造成内存泄漏

键值是正常应用,所以WeakMap外部消除了obj的引用,WeakMap内部的引用依然存在

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}

一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。

WeakMap结构的方法

  1. get()
  2. set()
  3. has()
  4. delete()

用途

WeakMap 应用的典型场合就是 DOM 节点作为键名。下面是一个例子。

let myWeakmap = new WeakMap();myWeakmap.set(document.getElementById('logo'),{timesClicked: 0})
;document.getElementById('logo').addEventListener('click', function() {let logoData = myWeakmap.get(document.getElementById('logo'));logoData.timesClicked++;
}, false);

上面代码中,document.getElementById('logo')是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

WeakMap 的另一个用处是部署私有属性。

const _counter = new WeakMap();
const _action = new WeakMap();class Countdown {constructor(counter, action) {_counter.set(this, counter);_action.set(this, action);}dec() {let counter = _counter.get(this);if (counter < 1) return;counter--;_counter.set(this, counter);if (counter === 0) {_action.get(this)();}}
}const c = new Countdown(2, () => console.log('DONE'));c.dec()
c.dec()
// DONE

上面代码中,Countdown类的两个内部属性_counter_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。

WeakRef

是基于弱引用的数据结构

ES2021提供了WeakRef对象用于直接创建对象的弱引用

let target = {}
let wr = new WeakRef(target)
// wr属于对target的弱引用。垃圾回收机制不会计入这个引用,wr的引用不会妨碍原始对象target被垃圾回收机制清除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/64521.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# 用语句初始化数据库,创建库和表 MySQL示例

目录 简要说明 代码实现 简要说明 有时候项目中&#xff0c;在部署过程中&#xff0c;单独用工具去创建数据库和表&#xff0c;会消耗很多人力和时间&#xff0c;也不利于后期程序迭代去增加数据表&#xff0c; 那可以在程序启动的时候&#xff0c;去判断数据库和表是否存在…

基于SpringBoot的疫苗在线预约功能实现十二

一、前言介绍&#xff1a; 1.1 项目摘要 随着全球公共卫生事件的频发&#xff0c;如新冠疫情的爆发&#xff0c;疫苗成为了预防和控制传染病的重要手段。传统的疫苗预约方式&#xff0c;如人工挂号或电话预约&#xff0c;存在效率低、易出错、手续繁琐等问题&#xff0c;无法…

java技术点

1 mysql的索引下推: 就是从减少服务层的的回表操作&#xff0c;在引擎层实现联表查询 2 可重入锁: 就是当前:z线程可重复获取锁&#xff0c;比如递归函数里有锁&#xff0c;防止死锁 3 解决redis脑裂: 参数 持久化 优化网络和硬件 4 加密&#xff1a; MD5 不可解 对称算法…

MySQL基础 -----MySQL数据类型

目录 INT类型 tinyint类型 类型大小范围 测试tinyint类型数据 float类型 测试&#xff1a; 测试正常数据范围的数据 测试插入范围超过临界值的数据&#xff1a; 测试float类型的四舍五入 ​编辑 decimal类型 同样测试&#xff1a; 字符串类型 char类型 测试&…

代码开发相关操作

使用Vue项目管理器创建项目&#xff1a;&#xff08;vue脚手架安装一次就可以全局使用&#xff09; windowR打开命令窗口&#xff0c;输入vue ui&#xff0c;进入GUI页面&#xff0c;点击创建-> 设置项目名称&#xff0c;在初始化git下面输入&#xff1a;init project&…

Pandas系列|第一期:列值的前N码模糊匹配

背景&#xff1a;物料清单&#xff08;BOM&#xff09;在做关键器件筛选时&#xff0c;需要筛选出编码的前N码模糊匹配给定的前缀list的所有bom行 关键点&#xff1a;前N码模糊匹配 df[col].str.startswith(tuple(item_prefix_list)&#xff09; 解决方法&#xff1a; impor…

如何在 Ubuntu 22.04 上安装和使用 Rust 编程语言环境

简介 Rust 是一门由 Mozilla 开发的系统编程语言&#xff0c;专注于性能、可靠性和内存安全。它在没有垃圾收集的情况下实现了内存安全&#xff0c;这使其成为构建对性能要求苛刻的应用程序&#xff08;如操作系统、游戏引擎和嵌入式系统&#xff09;的理想选择。 接下来&…

MybatisPlus-配置加密

配置加密 目前配置文件中的很多参数都是明文&#xff0c;如果开发人员发生流动&#xff0c;很容易导致敏感信息的泄露。所以MybatisPlus支持配置文件的加密和解密功能。 我们以数据库的用户名和密码为例。 生成秘钥 首先&#xff0c;我们利用AES工具生成一个随机秘钥&#…

记录:virt-manager配置Ubuntu arm虚拟机

virt-manager&#xff08;Virtual Machine Manager&#xff09;是一个图形用户界面应用程序&#xff0c;通过libvirt管理虚拟机&#xff08;即作为libvirt的图形前端&#xff09; 因为要在Linux arm环境做测试&#xff0c;记录下virt-manager配置arm虚拟机的过程 先在VMWare中…

艾体宝案例丨CircleCI 助力 ANA Systems 打造高效 CI/CD 模型

在现代软件开发领域&#xff0c;效率和可靠性是企业在竞争中取胜的关键。本文将深入探讨 ANA Systems 如何通过引入业界领先的 CI/CD 平台——CircleCI&#xff0c;克服传统开发流程的瓶颈&#xff0c;实现开发运营效率的全面提升。同时&#xff0c;本文还将详细解析 CircleCI …

Linux dd 命令详解:工作原理与实用指南(C/C++代码实现)

这段代码是一个模仿 Linux dd 命令的工具&#xff0c;它用于在不同文件之间复制数据。dd 是一个非常强大的命令行工具&#xff0c;可以用于数据备份、转换和复制。下面我将详细解释这段代码的原理、实现方式以及如何运行和测试。 Linux dd 命令的工作原理 dd 命令是 Unix 和 …

ChatGPT客户端安装教程(附下载链接)

用惯了各类AI的我们发现每天打开网页还挺不习惯和麻烦&#xff0c;突然发现客户端上架了&#xff0c;懂摸鱼的人都知道这里面的道行有多深&#xff0c;话不多说&#xff0c;开整&#xff01; 以下是ChatGPT客户端的详细安装教程&#xff0c;适用于Windows和Mac系统&#xff1a…

《C 语言携手 PaddlePaddle C++ API:开启深度学习开发新征程》

在深度学习领域&#xff0c;PaddlePaddle 作为一款强大的深度学习框架&#xff0c;为开发者提供了丰富的功能和高效的计算能力。而 C 语言&#xff0c;凭借其高效性和广泛的应用场景&#xff0c;与 PaddlePaddle 的 C API 相结合&#xff0c;能够为深度学习开发带来独特的优势。…

ARM CCA机密计算安全模型之固件启动

安全之安全(security)博客目录导读 目录 1、安全启动(Verified boot) 2、镜像格式和签名方案 3、防回滚 4、离线启动(Off-line boot) 5、CCA HES固件启动流程 6、CCA系统安全域启动过程 7、应用程序PE启动过程 8、稳健性 本节定义了将CCA固件引导至可证明状态的要…

Element@2.15.14-tree checkStrictly 状态实现父项联动子项,实现节点自定义编辑、新增、删除功能

背景&#xff1a;现在有一个新需求&#xff0c;需要借助树结构来实现词库的分类管理&#xff0c;树的节点是不同的分类&#xff0c;不同的分类可以有自己的词库&#xff0c;所以父子节点是互不影响的&#xff1b;同样为了选择的方便性&#xff0c;提出了新需求&#xff0c;选择…

【ALSA】snd_pcm_avail 接口

目录 简介使用场景注意事项函数签名代码示例 简介 snd_pcm_avail 是 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;库中的一个函数&#xff0c;用于获取 PCM&#xff08;Pulse Code Modulation&#xff09;设备环形缓冲区中可用的音频数据量。这个函数对于音…

计算机网络——期末复习(1)背诵

背诵 交换机与路由器&#xff1a;交换机连接同一子网&#xff0c;利用帧中的目的物理地址转发帧&#xff0c;工作在数据链路层&#xff1b;路由器连接不同子网&#xff0c;利用IP数据报中的目的IP地址转发IP数据报&#xff0c;工作在网络层。五层的任务&#xff1a;&#xff0…

概率论得学习和整理27:关于离散的数组 随机变量数组的均值,方差的求法3种公式,思考和细节。

目录 1 例子1&#xff1a;最典型的&#xff0c;最简单的数组的均值&#xff0c;方差的求法 2 例子1的问题&#xff1a;例子1只是1个特例&#xff0c;而不是普遍情况。 2.1 例子1各种默认假设&#xff0c;导致了求均值和方差的特殊性&#xff0c;特别简单。 2.2 我觉得 加权…

【HarmonyOS NEXT】Web 组件的基础用法以及 H5 侧与原生侧的双向数据通讯

关键词&#xff1a;鸿蒙、ArkTs、Web组件、通讯、数据 官方文档Web组件用法介绍&#xff1a;文档中心 Web 组件加载沙箱中页面可参考我的另一篇文章&#xff1a;【HarmonyOS NEXT】 如何将rawfile中文件复制到沙箱中_鸿蒙rawfile 复制到沙箱-CSDN博客 目录 如何在鸿蒙应用中加…

ASP.NET Core - 依赖注入 自动批量注入

依赖注入配置变形 随着业务的增长&#xff0c;我们项目工作中的类型、服务越来越多&#xff0c;而每一个服务的依赖注入关系都需要在入口文件通过Service.Add{}方法去进行注册&#xff0c;这将是非常麻烦的&#xff0c;入口文件需要频繁改动&#xff0c;而且代码组织管理也会变…