封装一个JS递归模板

封装一个JS递归模板

在 JavaScript 的森林中,递归是那条弯曲蜿蜒的小径,引领着旅人深入数据结构的密林之中。递归的脚步轻盈,踏过一条条分支,每个节点都记录了一次探索的记忆。本文将以DOM元素的深度遍历为例------这是一个普遍而又具体的应用场景------带您一起撰写一个封装良好的递归模板。这个模板不仅会在遍历中标记每一个DOM元素,还将设法搜集所有的 <a> 标签,在深邃蔚蓝的编程海洋中,这份模板将成为一颗闪耀的星辰。

递归模板的设计理念

递归模板的设计需要考虑几个重要的维度:

  1. 类型约束:确保只处理期望的数据类型。
  2. 私有域:创建独立的作用域,防止变量冲突。
  3. 出口条件:设定一个或多个递归结束的条件。
  4. 递归方向:确定下一步递归的具体方向。
  5. 操作执行:在每次递归中执行特定操作。

通过精心设计这些部分,可以确保递归不仅稳定可靠,还足够灵活,能应对不同的数据处理需求。

递归模板的构建

将目光投至一段暗藏玄机的代码------这里展现了如何通过递归沿着DOM树悄然前行,从根节点深入到每一个子节点,并在每一步中执行有意义的操作。

类型约束

起始之前,必须约束递归所处理的数据类型。在本例中,递归模板专门处理 HTMLElement 数组。

typescript 复制代码
// 唯一入口参数类型
type IIput = Array<HTMLElement>;
// 最终返回的数据结构
type IData = { max: number, aArray: IIput };

如同树木需脚踏坚实的土地,类型的明确为递归的执行提供了坚实基础。

创建递归私有域

在JavaScript的世界中,私有域允许我们在一个封闭的空间进行变量的声明和操作,把外界的喧嚣隔绝在外。

typescript 复制代码
function recurContainer(nodeArray: IIput): IData {
  // 这里初始化返回值,出口条件设置函数,递归方向确定函数及既定操作函数...
}

在函数 recurContainer 中,创建了一个递归的容器,其中包含了所有相关的私有方法和初始化数据。域中,Symbol 是遍历路径上的神秘符号,保护着函数名不被外部世界所知晓。

出口条件设置函数

一个明智的递归设计者,总是确立明确的归途。

typescript 复制代码
// 出口条件设置函数
const exit = function (): boolean {
  return this?.children.length === 0;
};

每一次递归的展开一定有着清晰的结束之路。在这里,如果当前遍历的DOM元素没有子元素,则意味着到达了递归的出口。

递归方向确定函数

递归的旅人需要方向,这便是由 next 函数指引的。

typescript 复制代码
// 递归方向确定函数
const next = function (): IIput {
  Reflect.deleteProperty(this, snext);
  return [...this?.children];
};

在这段代码中,next 函数确定了下一步的递归路径------子元素的集合。在每一步中,它以连贯的逻辑指出了向下深入的方向。

完成既定操作函数

递归的魅力,在于它访问每个节点时如同艺术家的细心,细致地在每个元素上留下痕迹。

typescript 复制代码
// 完成既定操作函数
const work = function (): void {
  this.setAttribute('ok', 'ok');
  Reflect.set(context, 'max', Math.max(this.childElementCount, context.max));
  if (this?.tagName === 'A') {
    Reflect.get(context, 'aArray').push(this);
  }
};

work 函数在DOM树中的每一个节点上执行操作,既留下了 'ok' 属性的印记,也收集了 <a> 标签,并记录了子元素的最大数量。

递归函数的精心雕琢

处心积虑,将所有零散的思考和构想织成一个完整的递归函数 recursion,将捕获每一个DOM元素。

typescript 复制代码
// 递归函数
function recursion(currents: IIput): void {
  // 此处为具体的递归算法...
}

在迷人的语言世界中,递归就像是叙述故事的叙事者,在逻辑的藤蔓上攀爬,不放过一个分支,不错过一个元素,直至故事走向终结,然后递归的美梦随着返回值醒来。

递归精髓的展示:既定操作

递归的真正魅力,在于它如何执行既定操作。这些操作就像艺术家的笔触,一次次涂抹在DOM的画布上。

typescript 复制代码
current[swork]();

每当递归访问到一个DOM元素,work 函数便被调用。它像是熟悉的老朋友,拜访每一道风景,留下足迹,也寻找宝藏。

结语:递归模板的应用

终于,这一段递归的旅程归于平静。设置适当的出口条件,确定递归的方向,执行既定的操作,融会贯通了这段复杂而优雅的代码。递归不再是始终莫测的秘境,而是一种可靠而高效的解决方法。

typescript 复制代码
recurContainer(Array.of(document.body));

在长篇叙述的终结,无需悬念的显示结果清晰在前:每一个DOM元素被赋予了新的属性,宛若恒星闪烁在浩瀚的文档之河,宝贵的<a>标签也一一被收入囊中。递归的魔力无处不在,回旋腾挪之间,展示出JavaScript深度遍历的无限可能。

附录:全部代码

typescript 复制代码
// 类型约束

// 唯一入口参数类型
type IIput = Array<HTMLElement>;
// 最终返回的数据结构
type IData = { max: number, aArray: IIput };

// 创建递归私有域
function recurContainer(nodeArray: IIput): IData {

  // 唯一值句柄
  const sexit = Symbol('exit');
  const snext = Symbol('next');
  const swork = Symbol('work');

  // 初始化返回值
  const context: IData = { max: 0, aArray: [] };

  // 出口条件设置函数
  const exit = function (): boolean {
    return this?.children.length === 0;
  };

  // 递归方向确定函数
  const next = function (): IIput {
    // 删除增强
    Reflect.deleteProperty(this, snext);
    return [...this?.children];
  };

  // 完成既定操作函数
  const work = function (): void {

    // 操作前数据存储

    // 遍历对象本身操作
    this.setAttribute('ok', 'ok');

    // 操作后数据存储
    Reflect.set(context, 'max', Math.max(this.childElementCount, context.max));
    if (this?.tagName === 'A') {
      Reflect.get(context, 'aArray').push(this);
    }

  };

  // 递归函数
  function recursion(currents: IIput): void {

    // 入参合法性判断
    if (Reflect.getPrototypeOf(currents) !== Array.prototype) { return; }

    // 开始遍历
    for (let i: number = 0; i < currents.length; i++) {

      // 递归+遍历
      let current = currents.at(i) as any;

      // 对象增强
      Reflect.set(current, sexit, exit);
      Reflect.set(current, snext, next);
      Reflect.set(current, swork, work);

      // 既定操作
      // Reflect.get(current,swork).call(current);
      current[swork]();

      // 出口判定
      // if (Reflect.get(current,sexit).call(current)) {
      if (current[sexit]()) {
        continue;
      } else {

        // 删除增强
        Reflect.deleteProperty(current, sexit);
        Reflect.deleteProperty(current, swork);

        // 向下递归
        // recursion(Reflect.get(current,snext).call(current));
        recursion(current[snext]());
      }
    }
  }

  // 开始递归
  recursion(nodeArray);

  // 返回结果
  return context;
}

// 获取结果
recurContainer(Array.of(document.body)); // 使用Array.of()保证传递的是一个数组!

/*上例是给每一个DOM增加了一个ok属性,并得到了所有的a标签。*/
相关推荐
qq_544329174 分钟前
下载一个项目到跑通的大致过程是什么?
javascript·学习·bug
计算机-秋大田12 分钟前
基于微信小程序的校园失物招领系统设计与实现(LW+源码+讲解)
java·前端·后端·微信小程序·小程序·课程设计
林涧泣22 分钟前
【Uniapp-Vue3】下拉刷新
前端·vue.js·uni-app
浪遏29 分钟前
Langchain.js | Memory | LLM 也有记忆😋😋😋
前端·llm·aigc
luoganttcc1 小时前
华为升腾算子开发(一) helloword
java·前端·华为
九月十九2 小时前
AviatorScript用法
java·服务器·前端
Jane - UTS 数据传输系统2 小时前
VUE+ Element-plus , el-tree 修改默认左侧三角图标,并使没有子级的那一项不展示图标
javascript·vue.js·elementui
_.Switch3 小时前
Python Web开发:使用FastAPI构建视频流媒体平台
开发语言·前端·python·微服务·架构·fastapi·媒体
菜鸟阿康学习编程3 小时前
JavaWeb 学习笔记 XML 和 Json 篇 | 020
xml·java·前端
索然无味io4 小时前
XML外部实体注入--漏洞利用
xml·前端·笔记·学习·web安全·网络安全·php