本週一篇指摘 Vue 抄襲 Angular 的文章一石激起千層浪。爲此,筆者做爲中立吃瓜的 React 用戶,分析了 13 個主流前端框架版本上萬個變量的命名風格,應用天然語言處理中的文本類似度算法進行了分析,以對這一論點的有效性作出客觀的評價。html
在分析書籍抄襲、論文查重等場景下,使用算法比較文本類似度的方法是一種有效的技術手段。那麼,咱們如何經過這一手段,來分析源碼層面的抄襲呢?前端
在對比形如 我喜歡寫代碼,不喜歡撕逼
和 我不喜歡撕逼,喜歡寫代碼
的兩個句子類似度時,大體的思路是首先分詞,然後計算出詞頻,再將詞頻向量化,最後比較兩個高維向量的夾角,夾角越小則越類似。vue
在【Vue 是否抄襲了 Angular】這一場景下,咱們分析的對象從句子變成了代碼。這時主要的區別是這兩點:node
var
和 function
的關鍵字,這些關鍵字的重複與類似度無關。而對因而否抄襲與類似度的關係,咱們給出這幾個假設:react
給定這幾個前提後,咱們能夠肯定出這樣的分析策略:jquery
在經過 Webpack 引用框架依賴時,一般導入的都是打包成單一文件且未經混淆的框架源碼。這是一個很是好的特性。筆者編寫了一個簡單的 Webpack Loader 以在這個過程當中實現變量名的提取:git
// loader/index.js
// 爲 loader 傳入的 content 即爲 JS 源碼
module.exports = function (content) {
return demo(content)
}複製代碼
在 demo
函數中得到框架源碼後,解析語法樹也不是一個困難的問題了。經過 acorn 這一 Parser 咱們就能作到:github
function demo (content) {
const ast = acorn.parse(content, { sourceType: 'module' })
walk.simple(ast, {
// 在 walk 遍歷時,抽取所有變量聲明語句中的變量名
VariableDeclaration (node) {
const name = node.declarations[0].id.name + '\n'
fs.appendFileSync(resolve('./result.txt'), name)
}
})
return content
}複製代碼
這時候咱們就能在 result.txt
內得到一個前端框架中的所有變量名了,形如:面試
p
i
resolved
c
segs
i
...複製代碼
這都是什麼亂七八糟的…這時候咱們得到的文本並無通過初步的處理,咱們真正感興趣的是各個框架變量名的詞頻。詞頻的計算是一道不錯的面試題,不過在這裏咱們直接經過 Wordclouds 的服務來實現這一步。這一步中還包括基本的清洗,以去除 i
/ a
/ b
這些無心義的變量名。咱們的結果是形如這樣的格式:算法
29 value
19 arg
18 result
16 key
14 index
...複製代碼
以上就是 React / Vue / Angular 三大框架中某一個的 Top 5 變量名,猜猜是哪個?好吧這幾個變量名都十分爛大街…暫時看不出什麼端倪。讓咱們繼續作類似度比較吧,答案在後面揭曉。
咱們在上文中,實際上已經得到了這樣的對象:
const a = {
'foo': 5,
'bar': 4,
'baz': 3
}
const b = {
'foo': 4,
'bar': 6,
'baz': 0
}複製代碼
咱們能夠認爲,每一個變量名是一個獨立的維度,每一個框架中存在的全部類型變量名組成一個高維空間的向量。從而,咱們的問題就簡化爲了如何比較 a 與 b 這兩個向量的類似程度。在此引用阮一峯老師的介紹:
咱們能夠把它們想象成空間中的兩條線段,都是從原點([0, 0, ...])出發,指向不一樣的方向。兩條線段之間造成一個夾角,若是夾角爲0度,意味着方向相同、線段重合;若是夾角爲90度,意味着造成直角,方向徹底不類似;若是夾角爲180度,意味着方向正好相反。所以,咱們能夠經過夾角的大小,來判斷向量的類似程度。夾角越小,就表明越類似。
假定 a 向量是 [x1, y1]
,b向量是 [x2, y2]
,那麼能夠將餘弦定理改寫成下面的形式,計算出的 cosθ
表明類似度:
推廣到高維向量的通常情形:
根據算法編寫出簡化的示例代碼:
function getTheta () {
let x = 0
Object.keys(dictAll).forEach(key => {
if (dictA[key] && dictB[key]) x += dictA[key] * dictB[key]
})
let yA = getY(dictA)
let yB = getY(dictB)
const result = x / (yA * yB)
console.log(result)
}複製代碼
最後運行咱們的分析算法處理上一步的變量名便可:
➜ node analyse vue@2.4.1 vue@2.4.2
0.9436438155995188複製代碼
一系列鋪墊之後,終於到了檢驗真理的時候了。咱們首先基於【類似版本類似度高】的假設,驗證 Vue 是否符合這一假設:
➜ node analyse vue@2.4.1 vue@2.4.2
0.9436438155995188複製代碼
能夠看到,目前最新的 vue 2.4.2 與 2.4.1 之間,確實存在着很高的類似度。接下來比較 vue 最新版與 2.0.0 同一個 Major 版本之間的類似度:
➜ node analyse vue@2.0.0 vue@2.4.2
0.8838059164881868複製代碼
類似度有所下降,說明最新版比起去年的 V2,已經有了不小的改動了。再來比較 V2 與 V1 系列的類似度:
➜ node analyse vue@2.0.0 vue@1.0.28
0.5883193867742227複製代碼
類似度明顯下降,顯然重構之言非虛。最後比較 Vue 的最新版與第一個版本:
➜ node analyse vue@2.4.2 vue@0.6.0
0.4590386014371645複製代碼
這是 Vue 家族中最低的類似度,也達到了 0.45 的水平。接下來是正戲,比較 Vue 和 Angular 的最新版:
➜ node analyse vue@2.4.2 angular@4.3.3
0.19322280449484375複製代碼
區區 0.19 的類似度!好吧,Angular 最新版也是重構過的,咱們不妨直接比較最先【照着 Angular 抄的】的 Vue 和 Angular 1.x 系列:
➜ node analyse vue@0.6.0 angular@1.2.32
0.294527560626686複製代碼
這個類似度也大幅低於 Vue 全系列縱向對比的類似度!爲了更有效地對比,咱們讓隔壁 React 躺個槍(未加版本號表明最新版):
➜ node analyse vue@2.4.2 react
0.27592736925848194複製代碼
0.27 與 0.29 的對比,說明即使是最先階段(與 Angular 類似度最高)的 Vue,類似度也僅僅至關於如今的 Vue 和 React 而已!爲了保證公平,咱們讓 jQuery 也來湊個熱鬧:
➜ node analyse jquery angular@1.2.32
0.2508302720623658複製代碼
這也是不到 0.3 的類似度,據此咱們甚至能夠得出一個大膽的結論:Vue 和 Angular 的類似度,和 Angular 與 jQuery 之間的類似度接近!沒有人會認爲 jQuery 與 Angular 之間存在抄襲吧?
固然,Vue 和 Angular 的類似度是客觀存在的。咱們在前端領域,能夠找到另外一對這樣的例子:jQuery VS Zepto,它們之間的類似度如何呢?
➜ node analyse jquery zepto
0.25994377334635854複製代碼
這個類似度和 Angular VS jQuery 幾乎相同,這說明即使設計理念相近,具體實現不一樣的原創框架之間,類似度也是很低的。Vue VS Angular 也徹底符合這一結論。
hmmm 目前咱們的論據已經比較充分了。最後,咱們比較一種情形:設計理念徹底不一樣的原創框架之間,類似度如何?咱們拉出 jQuery 和 React:
➜ node analyse jquery react
0.1007248324385447複製代碼
全場最低類似度…因此咱們能夠理解 jQuery 時代的前端轉向 React 時有多麼不習慣了吧😂
到此爲止,咱們的結論有:
據此,筆者有理由認爲【Vue 抄襲了 Angular】的論點是站不住腳的。
本文的實驗數據託管在 Github 上,歡迎感興趣的同窗驗證並改進這些結論。最後,框架畢竟只是工具,相互撕逼併不利於社區的發展。引用我司 Boss 的觀點:【一流的人作事,二流的人去評論,三流的人去評論別人的評論】,但願你們能把口水戰的時間放在更務實的事情上,推進技術水平、社區氛圍和平均工資的上升……