封装一个JS递归模板
在 JavaScript 的森林中,递归是那条弯曲蜿蜒的小径,引领着旅人深入数据结构的密林之中。递归的脚步轻盈,踏过一条条分支,每个节点都记录了一次探索的记忆。本文将以DOM元素的深度遍历为例——这是一个普遍而又具体的应用场景——带您一起撰写一个封装良好的递归模板。这个模板不仅会在遍历中标记每一个DOM元素,还将设法搜集所有的 <a>
标签,在深邃蔚蓝的编程海洋中,这份模板将成为一颗闪耀的星辰。
递归模板的设计理念
递归模板的设计需要考虑几个重要的维度:
- 类型约束:确保只处理期望的数据类型。
- 私有域:创建独立的作用域,防止变量冲突。
- 出口条件:设定一个或多个递归结束的条件。
- 递归方向:确定下一步递归的具体方向。
- 操作执行:在每次递归中执行特定操作。
通过精心设计这些部分,可以确保递归不仅稳定可靠,还足够灵活,能应对不同的数据处理需求。
递归模板的构建
将目光投至一段暗藏玄机的代码——这里展现了如何通过递归沿着DOM树悄然前行,从根节点深入到每一个子节点,并在每一步中执行有意义的操作。
类型约束
起始之前,必须约束递归所处理的数据类型。在本例中,递归模板专门处理 HTMLElement
数组。
1// 唯一入口参数类型
2type IIput = Array<HTMLElement>;
3// 最终返回的数据结构
4type IData = { max: number, aArray: IIput };
5
如同树木需脚踏坚实的土地,类型的明确为递归的执行提供了坚实基础。
创建递归私有域
在JavaScript的世界中,私有域允许我们在一个封闭的空间进行变量的声明和操作,把外界的喧嚣隔绝在外。
1function recurContainer(nodeArray: IIput): IData {
2 // 这里初始化返回值,出口条件设置函数,递归方向确定函数及既定操作函数...
3}
4
在函数 recurContainer
中,创建了一个递归的容器,其中包含了所有相关的私有方法和初始化数据。域中, Symbol
是遍历路径上的神秘符号,保护着函数名不被外部世界所知晓。
出口条件设置函数
一个明智的递归设计者,总是确立明确的归途。
1// 出口条件设置函数
2const exit = function (): boolean {
3 return this?.children.length === 0;
4};
5
每一次递归的展开一定有着清晰的结束之路。在这里,如果当前遍历的DOM元素没有子元素,则意味着到达了递归的出口。
递归方向确定函数
递归的旅人需要方向,这便是由 next
函数指引的。
1// 递归方向确定函数
2const next = function (): IIput {
3 Reflect.deleteProperty(this, snext);
4 return [...this?.children];
5};
6
在这段代码中, next
函数确定了下一步的递归路径——子元素的集合。在每一步中,它以连贯的逻辑指出了向下深入的方向。
完成既定操作函数
递归的魅力,在于它访问每个节点时如同艺术家的细心,细致地在每个元素上留下痕迹。
1// 完成既定操作函数
2const work = function (): void {
3 this.setAttribute('ok', 'ok');
4 Reflect.set(context, 'max', Math.max(this.childElementCount, context.max));
5 if (this?.tagName === 'A') {
6 Reflect.get(context, 'aArray').push(this);
7 }
8};
9
work
函数在DOM树中的每一个节点上执行操作,既留下了 'ok'
属性的印记,也收集了 <a>
标签,并记录了子元素的最大数量。
递归函数的精心雕琢
处心积虑,将所有零散的思考和构想织成一个完整的递归函数 recursion
,将捕获每一个DOM元素。
1// 递归函数
2function recursion(currents: IIput): void {
3 // 此处为具体的递归算法...
4}
5
在迷人的语言世界中,递归就像是叙述故事的叙事者,在逻辑的藤蔓上攀爬,不放过一个分支,不错过一个元素,直至故事走向终结,然后递归的美梦随着返回值醒来。
递归精髓的展示:既定操作
递归的真正魅力,在于它如何执行既定操作。这些操作就像艺术家的笔触,一次次涂抹在DOM的画布上。
1current[swork]();
2
每当递归访问到一个DOM元素, work
函数便被调用。它像是熟悉的老朋友,拜访每一道风景,留下足迹,也寻找宝藏。
结语:递归模板的应用
终于,这一段递归的旅程归于平静。设置适当的出口条件,确定递归的方向,执行既定的操作,融会贯通了这段复杂而优雅的代码。递归不再是始终莫测的秘境,而是一种可靠而高效的解决方法。
1recurContainer(Array.of(document.body));
2
在长篇叙述的终结,无需悬念的显示结果清晰在前:每一个DOM元素被赋予了新的属性,宛若恒星闪烁在浩瀚的文档之河,宝贵的 <a>
标签也一一被收入囊中。递归的魔力无处不在,回旋腾挪之间,展示出JavaScript深度遍历的无限可能。
附录:全部代码
1// 类型约束
2
3// 唯一入口参数类型
4type IIput = Array<HTMLElement>;
5// 最终返回的数据结构
6type IData = { max: number, aArray: IIput };
7
8// 创建递归私有域
9function recurContainer(nodeArray: IIput): IData {
10
11 // 唯一值句柄
12 const sexit = Symbol('exit');
13 const snext = Symbol('next');
14 const swork = Symbol('work');
15
16 // 初始化返回值
17 const context: IData = { max: 0, aArray: [] };
18
19 // 出口条件设置函数
20 const exit = function (): boolean {
21 return this?.children.length === 0;
22 };
23
24 // 递归方向确定函数
25 const next = function (): IIput {
26 // 删除增强
27 Reflect.deleteProperty(this, snext);
28 return [...this?.children];
29 };
30
31 // 完成既定操作函数
32 const work = function (): void {
33
34 // 操作前数据存储
35
36 // 遍历对象本身操作
37 this.setAttribute('ok', 'ok');
38
39 // 操作后数据存储
40 Reflect.set(context, 'max', Math.max(this.childElementCount, context.max));
41 if (this?.tagName === 'A') {
42 Reflect.get(context, 'aArray').push(this);
43 }
44
45 };
46
47 // 递归函数
48 function recursion(currents: IIput): void {
49
50 // 入参合法性判断
51 if (Reflect.getPrototypeOf(currents) !== Array.prototype) { return; }
52
53 // 开始遍历
54 for (let i: number = 0; i < currents.length; i++) {
55
56 // 递归+遍历
57 let current = currents.at(i) as any;
58
59 // 对象增强
60 Reflect.set(current, sexit, exit);
61 Reflect.set(current, snext, next);
62 Reflect.set(current, swork, work);
63
64 // 既定操作
65 // Reflect.get(current,swork).call(current);
66 current[swork]();
67
68 // 出口判定
69 // if (Reflect.get(current,sexit).call(current)) {
70 if (current[sexit]()) {
71 continue;
72 } else {
73
74 // 删除增强
75 Reflect.deleteProperty(current, sexit);
76 Reflect.deleteProperty(current, swork);
77
78 // 向下递归
79 // recursion(Reflect.get(current,snext).call(current));
80 recursion(current[snext]());
81 }
82 }
83 }
84
85 // 开始递归
86 recursion(nodeArray);
87
88 // 返回结果
89 return context;
90}
91
92// 获取结果
93recurContainer(Array.of(document.body)); // 使用Array.of()保证传递的是一个数组!
94
95/*上例是给每一个DOM增加了一个ok属性,并得到了所有的a标签。*/
96