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 的典型结构如下:
--- oldFile.txt
+++ newFile.txt
@@ -1,4 +1,4 @@Line 1
-Line 2
+Line 2 updatedLine 3Line 4
解释:
--- 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")
会认为o
和O
不相等:- 结果:删除两个
o
,插入两个O
和一个D
。
- 结果:删除两个
- 设置选项
{ ignoreCase: true }
后,o
和O
会被视为相等:- 结果:仅需要插入一个
D
。
- 结果:仅需要插入一个
- 默认比较中,
- 默认情况下,两个 token 是否相等由
3. 返回变换结果
- 返回值:一个数组,表示从旧文本到新文本的转换过程。
- 数组结构:包含一系列 change objects。
- 顺序:从输入的起始位置到结束位置按顺序排列。
- change objects 的含义:
- 插入:在新文本中添加一个或多个 token(
added: true
)。 - 删除:从旧文本中删除一个或多个 token(
removed: true
)。 - 保留:保持一个或多个 token 不变(无
added
或removed
标记)。
- 插入:在新文本中添加一个或多个 token(
示例代码
以下是 diffChars
的一个简单示例:
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 安装:
npm install --save diff
2. 在项目中导入
在 Vue 项目中,可以通过以下方式引入库中所需的功能:
import { diffWords } from 'diff';
3. 在 Vue 项目中使用
在 Vue 项目中,可以将比对函数与 v-html
指令结合,动态渲染高亮比对的结果。
组件模板
<template><div v-html="getYellowDiffText(tableName1, tableName2)"></div>
</template>
组件逻辑
<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
库api
- Diff.diffChars(oldStr, newStr[, options]) - diffs 两个文本块,将每个字符视为一个标记。
- Diff.diffWords(oldStr, newStr[, options]) - diff 两个文本块,将每个单词和每个标点符号视为一个标记。在计算 diff 时,将忽略空格(但在最终的 change 对象中尽可能保留)。
- Diff.diffWordsWithSpace(oldStr, newStr[, options]) - diff 两个文本块,将每个单词、标点符号、换行符或连续(非换行符)空格视为一个标记。
- Diff.diffLines(oldStr, newStr[, options]) - diff 两个文本块,将每一行视为一个标记。
- Diff.diffSentences(oldStr, newStr[, options]) - diff 两个文本块,将每个句子视为一个标记。字符 .、 !和 ?后跟空格时,被视为标记句子的结尾;没有其他内容被视为标记句子结束。
- Diff.diffCss(oldStr, newStr[, options]) - diffs 两个文本块,比较 CSS 令牌。
- Diff.diffJson(oldObj, newObj[, options]) - 通过首先将两个 JSON 可序列化对象序列化为格式精美的 JSON,然后将 JSON 的每一行视为令牌,来区分这两个对象。对象属性在序列化的 JSON 中按字母顺序排序,因此被比较的对象中的属性顺序不会影响结果。
- Diff.diffArrays(oldArr, newArr[, options]) - diff 两个标记数组,比较每个项的严格相等性 (===)。
- Diff.createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr[, oldHeader[, newHeader[, options]]]) - 首先使用 diffLines 计算 diff,然后将其序列化为统一的 diff 格式,从而创建统一的 diff 补丁。
diff
库配置
- 可配置忽略空白字符、大小写等比较选项。