前言
最近在開發(fā)過程中遇到了需要diff文件內(nèi)容或者大json的業(yè)務(wù)場景,發(fā)現(xiàn)了一個比較好用且經(jīng)典的js庫diff。這個庫功能十分強(qiáng)大,不僅能夠簡潔地輸出字符串結(jié)果,也能夠輸出規(guī)范化的數(shù)據(jù)結(jié)構(gòu)方便二次開發(fā)。這里筆者針對這個庫的文檔進(jìn)行翻譯和簡單的講解,同時也會展示自己的測試demo。
庫簡介
diff是一個基于javascript實(shí)現(xiàn)的文本內(nèi)容diff的庫。它基于已發(fā)表論文中的算法An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
安裝
npm install diff --save
引用
// 不支持import 語法,也就是module引入
const jsDiff = require('diff');
API
-
JsDiff.diffChars(oldStr, newStr[, options])這個方法將比較兩段文字,比較的維度是基于單個字符
返回一個由描述改變的對象組成的列表。大致如下:
image
added表示是否是添加內(nèi)容,removed表示是否為刪除內(nèi)容。共有的內(nèi)容這兩個屬性都沒有,value表示內(nèi)容,count表示字符的個數(shù)(在某些用法中表示內(nèi)容的行數(shù))
可選的配置屬性ignoreCase: 標(biāo)記為true時忽略字符的大小寫,默認(rèn)為false,這里給出一個測試?yán)樱?br>image
文中例子的線上演示地址演示地址 -
JsDiff.diffWords(oldStr, newStr[, options])該方法比較兩段文字,比較的維度是單詞,忽略空格,返回一個由描述改變對象組成的列表,可選的配置屬性ignoreCase: 同diffChars中一樣,這里給出一個使用例子:
image -
JsDiff.diffWordsWithSpace(oldStr, newStr[, options])該方法比較兩段文字,比較的維度是單詞,同上一個方法不同的是,它將比較空格的差異,返回一個由描述改變的對象組成的列表。這里給出一個例子:
image -
JsDiff.diffLines(oldStr, newStr[, options])比較兩段文字,比較的維度是行??蛇x的配置項(xiàng):
ignoreWhitespace:設(shè)置為true時,將忽略開頭和結(jié)尾處的空格,在diffTrimmedLines中也有這個配置。
newlineIsToken: 設(shè)置為true時,將換行符看作是分隔符。這樣就可以獨(dú)立于行內(nèi)容對換行結(jié)構(gòu)進(jìn)行更改,并將其視為獨(dú)立的(原文:This allows for changes to the newline structure to occur independently of the line content and to be treated as such, 這一句是機(jī)翻的,感覺不大準(zhǔn)確)??偟脕碚f,這樣使得diffLines的輸出對人類閱讀(相較于其他對計(jì)算機(jī)更為友好的輸出方式)更為友好,更加方便于比較差異。返回一個由描述改變的對象組成的列表。(這里返回的obj列表中,count表示這段內(nèi)容的行數(shù),下面的方法類似),接下來展示一個例子:
image -
sDiff.diffTrimmedLines(oldStr, newStr[, options])比較兩段文字,比較的維度是行,忽略開頭和結(jié)尾處的空格,返回一個由描述改變的對象組成的列表。實(shí)例截圖:
image -
JsDiff.diffSentences(oldStr, newStr[, options])比較兩段文字,比較的維度是句子。返回一個由描述改變的對象組成的列表。實(shí)例截圖:
image JsDiff.diffCss(oldStr, newStr[, options])比較兩段內(nèi)容,比較基于css中的相關(guān)符號和語法。返回一個由描述改變的對象組成的列表。-
JsDiff.diffJson(oldObj, newObj[, options])比較兩個JSON對象,比較基于對象內(nèi)部的key。這些key在json對象內(nèi)的順序,在比較時將不會影響結(jié)果。返回一個由描述改變的對象組成的列表。展示一個例子:
image JsDiff.diffArrays(oldArr, newArr[, options])比較兩個數(shù)組,每一個元素使用嚴(yán)格等于來判定(===)??蛇x參數(shù):comparator: function(left, right)用來進(jìn)行相等性的比較,返回一個由描述改變的對象組成的列表。-
JsDiff.createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader)-創(chuàng)造一個統(tǒng)一的diff補(bǔ)丁輸出。參數(shù):-
oldFileName: 移除內(nèi)容在文件名部分輸出的字符串 -
newFileName: 增添內(nèi)容在文件名部分輸出的字符串 -
oldStr: 原始的字符串(作為基準(zhǔn)) -
newStr: 比較內(nèi)容的字符串 -
oldHeader: 在老文件頭部新增的信息 -
newHeader: 在新文件頭部新增的信息 -
options: 一個描述配置的對象,目前僅支持context,用來描述應(yīng)該展示context的多少行
這里展示一個例子:
image
這里可以看到,該方法返回的是已經(jīng)格式化的可直接輸出的字符串,方便直接展示。
-
JsDiff.createPatch(fileName, oldStr, newStr, oldHeader, newHeader)-創(chuàng)造一個統(tǒng)一的diff補(bǔ)丁輸出,該方法的使用和JsDiff.createTwoFilesPatch幾乎一致,唯一的區(qū)別是oldFileName等于newFileName-
JsDiff.structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options)返回一個由描述具體變化的對象構(gòu)成的數(shù)組。這個方法類似于createTwoFilesPatch,但是返回了一個適合于開發(fā)者后續(xù)處理的數(shù)據(jù)結(jié)構(gòu)。其參數(shù)跟createTwoFilesPatch保持一致,返回的數(shù)據(jù)類似于如下:
image
與之對應(yīng)的應(yīng)用實(shí)例如下:
image -
JsDiff.applyPatch(source, patch[, options])使用一個統(tǒng)一的diff補(bǔ)丁。該方法會返回一個應(yīng)用了補(bǔ)丁的新版本字符串。這里的補(bǔ)?。╬atch)可能是字符串形式的diff或者parsePatch和structuredPatch方法返回的輸出??蛇x的配置項(xiàng)有如下:-
fuzzFactor: 拒絕應(yīng)用補(bǔ)丁之前允許比較的內(nèi)容的行數(shù)。默認(rèn)是0 -
compareLine(lineNumber, line, operation, patchContent)用來比較給定的行內(nèi)容在應(yīng)用補(bǔ)丁時是否應(yīng)該被認(rèn)定為相等。默認(rèn)是使用嚴(yán)格相等來比較的,但是這容易與fuzzier比較相沖突。當(dāng)內(nèi)容應(yīng)該被拒絕時返回false。
-
-
JsDiff.applyPatches(patch, options)應(yīng)用一個或者多個補(bǔ)丁。這個方法將會迭代補(bǔ)丁的內(nèi)容并且將其應(yīng)用在回調(diào)中傳入的內(nèi)容上。每個補(bǔ)丁被使用的整體工作流程是:-
options.loadFile(index, callback)調(diào)用者應(yīng)該加載文件的內(nèi)容并且將其傳遞給回調(diào)(callback(err, data))。傳入一個err將會中斷未來補(bǔ)丁的執(zhí)行 -
options.patched(index, content, callback)該方法在每個補(bǔ)丁被使用時調(diào)用。傳入一個err將會中斷未來補(bǔ)丁的執(zhí)行
-
JsDiff.parsePatch(diffStr)將一個補(bǔ)丁解析為結(jié)構(gòu)化數(shù)據(jù)。返回一個由補(bǔ)丁解析而來的JSON對象,該方法適合同applyPatch配合使用。該方法返回的內(nèi)容同JsDiff.structuredPatch返回的內(nèi)容結(jié)構(gòu)上一致。convertChangesToXML(changes)轉(zhuǎn)換一個changes的列表到序列化的XML格式
以上的所有可以接受一個可選的回調(diào)的方法,在該參數(shù)(callback)被省略時該方法工作在同步模式,當(dāng)這個參數(shù)被傳入時工作在異步模式。這使得能夠處理更大的范圍diff而不會使得事件流被長期掛起。callback要么作為最后一個參數(shù)被直接傳入要么作為options中的一個屬性被傳入。
Change Objects
上面的許多方法都會返回change對象(前文翻譯成描述改變的對象),這些對象通常包含以下的屬性:
-
value: 文本內(nèi)容 -
added: 如果是文本被插入新內(nèi)容的話,該值為true -
removed: 如果是文本被移除內(nèi)容的話,該值為true
使用小結(jié)
上述的內(nèi)容主要是基于官方的文檔。這里結(jié)合筆者的實(shí)戰(zhàn)經(jīng)驗(yàn)來說說使用的細(xì)節(jié)。JsDiff的方法絕大多數(shù)的入?yún)⒍际亲址ǔ?code>JsDiff.diffJson,JsDiff.diffArrays等少數(shù)幾個api)。用于比較字符,單詞,句子或者文本文件時,需要將以上內(nèi)容都轉(zhuǎn)換成字符串,句子或者文本文件默認(rèn)使用\n作為分隔符。輸出通常是描述變化的對象組成的Array,方便二次開發(fā),如果只是想簡單輸出文件之間的diff,可以直接使用JsDiff.createTwoFilesPatch支持輸出格式化的內(nèi)容,不用額外處理。關(guān)于二次開發(fā)輸出滿足需求的樣式,這里給一個簡單的例子:
import React from 'react';
const jsDiff = require('diff');
import s from './index.css';
import cx from 'classnames';
const str1 = 'guanlanluditie';
const str2 = 'smartguanlanluditie';
const diffArr = jsDiff.diffChars(str1, str2);
const charColorMap = {
'add': s.charAdd,
'removed': s.charRemoved,
}
export default class Text extends React.Component {
render() {
return <div className={s.result}>
比較結(jié)果:
{diffArr.map((item, index) => {
const { value, added, removed } = item;
const type = added ? 'add' : (removed ? 'removed' : '')
return <span key={index} className={cx(charColorMap[type], s.charPreWrap)}>{value}</span>
})
}
</div>
}
}
關(guān)于使用diff庫實(shí)現(xiàn)類似于github的文件diff效果,可以參考筆者的一個倉庫,也就是上文中的演示代碼,倉庫地址,具體的實(shí)現(xiàn)思路后續(xù)會出一篇文詳述,稍候。