JavaScript的diff库详解(示例:vue项目实现两段字符串比对标黄功能)

diff库介绍

diff 库是基于 Myers 差分算法 实现的 JavaScript 文本差异库。
Myers 差分算法 是由 Eugene Myers 在 1986 年发表的一篇经典算法论文 "An O(ND) Difference Algorithm and its Variations" 中描述的一种高效算法,用于计算两个序列(通常是字符串)之间的差异。

该算法的时间复杂度为 O(ND),其中:

  • N 是两个序列中较短序列的长度。
  • D 是两个序列之间的最小编辑距离,即从一个序列变换为另一个序列所需的最少操作次数(插入、删除或替换)。

通过该算法,diff 库可以高效地分析文本之间的差异,广泛应用于文本比对、版本管理和实时内容编辑等场景。


在线演示文档

diff 库提供了一个在线演示网站,方便用户了解其功能:https://kpdecker.github.io/jsdiff

基于在线演示网站,我们可以看到diff 库支持字符级词级行级unified diff等等的差异比较。

前面三个都好理解,unified diff则可能需要了解下相应概念。

Unified Diff 和 Patch 的概念

Unified Diff

Unified Diff 是一种标准化的差异格式,用于描述两个文本文件之间的变化,广泛使用于版本控制系统(如 Git)。它通过行号和上下文信息展示新增、删除或修改的内容,是 diff 工具生成的输出格式之一。

一个 Unified Diff 的典型结构如下:

diff 复制代码
--- oldFile.txt
+++ newFile.txt
@@ -1,4 +1,4 @@
 Line 1
-Line 2
+Line 2 updated
 Line 3
 Line 4

解释:

clike 复制代码
--- oldFile.txt 和 +++ newFile.txt:分别表示旧文件和新文件的文件名。
@@ -1,4 +1,4 @@:上下文范围的描述。
-1,4 表示旧文件从第 1 行开始的 4 行。
+1,4 表示新文件从第 1 行开始的 4 行。
- 表示从旧文件中移除的内容。
+ 表示添加到新文件中的内容。

Patch

Patch 是应用这些差异的一种工具,通常配合 Unified Diff 使用。patch 工具可以读取 Unified Diff 格式的文件,并将其应用到目标文件上,以实现对文件的更新。

diff 库中的 createPatch 方法生成的就是一个 Unified Diff 格式的输出。可以用这个输出作为输入,再使用 applyPatch 方法将这些差异应用到目标文本中。


diff库比对的基本流程

diff 库的所有diff函数都用于比较两个文本,并执行以下三个步骤:

1. 将文本分割为 "tokens"

  • Token 的定义 :Token 是文本中的最小单位,其定义根据所使用的 diff 方法而变化:
    • diffChars 方法中,每个字符是一个token。
    • diffWords 方法中,每个单词是一个token。
    • diffLines 方法中,每一行是一个token。

通过这种分割方式,diff 库能够灵活地比较文本的不同层次(如字符、单词或行)。


2. 找到最小的操作集合

  • 目标:通过最少的插入和删除操作,将第一个 token 数组转换为第二个 token 数组。
  • 相等的定义
    • 默认情况下,两个 token 是否相等由 === 运算符决定。
    • 某些 diff 方法支持自定义"相等"定义。例如:
      • 默认比较中,diffChars("Foo", "FOOD") 会认为 oO 不相等:
        • 结果:删除两个 o,插入两个 O 和一个 D
      • 设置选项 { ignoreCase: true } 后,oO 会被视为相等:
        • 结果:仅需要插入一个 D

3. 返回变换结果

  • 返回值 :一个数组,表示从旧文本到新文本的转换过程。
    • 数组结构 :包含一系列 change objects
    • 顺序:从输入的起始位置到结束位置按顺序排列。
    • change objects 的含义
      • 插入 :在新文本中添加一个或多个 token(added: true)。
      • 删除 :从旧文本中删除一个或多个 token(removed: true)。
      • 保留 :保持一个或多个 token 不变(无 addedremoved 标记)。

示例代码

以下是 diffChars 的一个简单示例:

javascript 复制代码
import { diffChars } from 'diff';

const oldText = "Foo";
const newText = "FOOD";

// 默认比较(区分大小写)
const result = diffChars(oldText, newText);
console.log(result);
/* 数据格式
[
  { value: 'F', count: 1 },
  { removed: true, value: 'o' },
  { removed: true, value: 'o' },
  { added: true, value: 'O' },
  { added: true, value: 'O' },
  { added: true, value: 'D' }
]
*/

// 忽略大小写
const resultIgnoreCase = diffChars(oldText, newText, { ignoreCase: true });
console.log(resultIgnoreCase);
/* 数据格式
[
  { value: 'Foo', count: 3 },
  { added: true, value: 'D' }
]
*/

diff 库安装与使用

1. 安装库

通过 npm 安装:

bash 复制代码
npm install --save diff

2. 在项目中导入

在 Vue 项目中,可以通过以下方式引入库中所需的功能:

javascript 复制代码
import { diffWords } from 'diff';

3. 在 Vue 项目中使用

在 Vue 项目中,可以将比对函数与 v-html 指令结合,动态渲染高亮比对的结果。

组件模板
html 复制代码
<template>
  <div v-html="getYellowDiffText(tableName1, tableName2)"></div>
</template>
组件逻辑
js 复制代码
<script>
import { diffWords } from 'diff';

export default {
  data() {
    return {
      tableName1: 'Hello world!',
      tableName2: 'Hello my friend!',
    };
  },
  methods: {
    /** 比较两个字符串,标记差异部分为黄色,diff库比对结果状态只有added和removed,新增部分即存在差异部分 */
    getYellowDiffText(tableName1, tableName2) {
      let htmltext = '';
      let diffs = [];

      if (!tableName2) {
        diffs = [{ value: tableName1 }];
      } else {
        diffs = diffWords(tableName1, tableName2);
      }

      diffs.forEach((item) => {
        htmltext += item.added
          ? `<span style="background-color: yellow;">${item.value}</span>`
          : item.removed
          ? '' // 个人项目需求不需要比对删除情况,有需求的可以自行处理
          : item.value;
      });

      return htmltext;
    },
  },
};
</script>

diff库配置

  • 可配置忽略空白字符、大小写等比较选项。
相关推荐
该用户已不存在2 分钟前
这6个网站一旦知道就离不开了
前端·后端·github
Ai行者心易6 分钟前
10天!前端用coze,后端用Trae IDE+Claude Code从0开始构建到平台上线
前端·后端
东东23314 分钟前
前端开发中如何取消Promise操作
前端·javascript·promise
掘金安东尼19 分钟前
官方:什么是 Vite+?
前端·javascript·vue.js
柒崽21 分钟前
ios移动端浏览器,vh高度和页面实际高度不匹配的解决方案
前端
渣哥37 分钟前
你以为 Bean 只是 new 出来?Spring BeanFactory 背后的秘密让人惊讶
javascript·后端·面试
烛阴1 小时前
为什么游戏开发者都爱 Lua?零基础快速上手指南
前端·lua
大猫会长1 小时前
tailwindcss出现could not determine executable to run
前端·tailwindcss
Moonbit1 小时前
MoonBit Pearls Vol.10:prettyprinter:使用函数组合解决结构化数据打印问题
前端·后端·程序员
533_1 小时前
[css] border 渐变
前端·css