寫文章不容易,點個讚唄兄弟
專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】node
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧數組
【Vue原理】Diff - 源碼版 之 相關輔助函數 bash
在開始咱們的 Diff 主要內容以前,咱們先來了解下其中會用的的一些輔助函數app
原本能夠放到 Diff 那裏寫,可是所有合起來內容又太多dom
並且這些函數比較具備公用性,就是抽出來用函數
因此打算獨立一篇文章,先預熱一下,內容也很少,也挺簡單,光看下也會對咱們的思惟有所幫助工具
下面是 Diff 在比較節點時,更新DOM 會使用到的一些函數學習
原本會有更多,爲了看得方便,我把一些合併了ui
下面會介紹三個函數url
insert,createElm,createChildren
insert 做用是把節點插入到某個位置
createElm 做用是建立DOM 節點
createChildren 做用也是建立DOM 節點,可是處理的是一個數組,而且會建立 DOM 節點 和 文本節點
下面就來仔細說說這三個方法
這個函數的做用就是 插入節點
可是插入也會分兩種狀況
一、沒有參考兄弟節點,直接插入父節點的子節點末尾
二、有參考兄弟節點,則插在兄弟節點前面
能夠說這個函數是 Diff 的基石哈哈
function insert(parent, elm, ref) {
if (parent) {
if (ref) {
if (ref.parentNode === parent) {
parent.insertBefore(elm, ref);
}
} else {
parent.appendChild(elm);
}
}
}
複製代碼
這個函數的做用跟它的名字同樣,就是建立節點的意思
建立完節點以後,會調用 insert 去插入節點
你能夠看一下,不難
function createElm(vnode, parentElm, refElm) {
var children = vnode.children;
var tag = vnode.tag;
if (tag) {
vnode.elm = document.createElement(tag)
// 先把 子節點插入 vnode.elm,而後再把 vnode.elm 插入parent
createChildren(vnode, children);
// 插入DOM 節點
insert(parentElm, vnode.elm, refElm);
}
else {
vnode.elm = document.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}
複製代碼
createElm 須要根據 Vnode 來判斷須要建立什麼節點
一、文本節點
二、普通節點
當 vnode.tag 爲 undefined 的時候,建立文本節點,看下 真實文本vnode
而且,文本節點是沒有子節點的
vnode.tag 有值,那就建立相應的 DOM
可是 該 DOM 可能存在子節點,因此子節點甚至子孫節點,也都是要建立的
因此會調用一個 createChildren 去完成全部子孫節點的建立
這個方法處理子節點,必然是用遍歷遞歸的方法逐個處理的
1若是子節點是數組,則遍歷執行 createElm 逐個處理
2若是子節點的 text 屬性有數據,則表示這個 vnode 是個文本節點,直接建立文本節點,而後插入到父節點中
function createChildren(vnode, children) {
if (Array.isArray(children)) {
for (var i = 0; i < children.length; ++i) {
createElm(children[i], vnode.elm, null);
}
}
else if (
typeof vnode.text=== 'string' ||
typeof vnode.text=== 'number' ||
typeof vnode.text=== 'boolean'
) {
vnode.elm.appendChild(
document.createTextNode(vnode.text)
)
}
}
複製代碼
下面的函數是 Vue 專門用來服務 Diff 的,介紹兩個
createKeyToOldIdx,sameVnode
接收一個 children 數組,生成 key 與 index 索引對應的一個 map 表
function createKeyToOldIdx(
children, beginIdx, endIdx
) {
var i, key;
var map = {};
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key;
if (key) {
map[key] = i;
}
}
return map
}
複製代碼
好比你的舊子節點數組是
[{
tag:"div", key: "key_1"
},{
tag:"strong", key:"key_2"
},{
tag:"span", key:"key_4"
}]
複製代碼
通過 createKeyToOldIdx 生成一個 map 表 oldKeyToIdx,是下面這樣
{
"key_1":0,
"key_2":1,
"key_4":2
}
複製代碼
把 vnode 的 key 做爲屬性名,而該 vnode 在children 的位置 做爲 屬性值
這個函數在 Diff 中的做用是
判斷某個新 vnode 是否在 這個舊的 Vnode 數組中,而且拿到它的位置。就是拿到 新 Vnode 的 key,而後去這個 map 表中去匹配,是否有相應的節點,有的話,就返回這個節點的位置
好比
如今我有一個 新子節點數組,一個 舊子節點數組
我拿到 新子節點數組 中的某一個 newVnode,我要判斷他是否 和 舊子節點數組 中某個vnode相同
要怎麼判斷???難道是雙重遍歷數組,逐個判斷 newVnode.key==vnode.key ??
Vue 用了更聰明的辦法,使用 舊 Vnode 數組生成一個 map 對象 obj
當 obj[ newVnode.key ] 存在的時候,說明 新舊子節點數組都存在這個節點
而且我能拿到該節點在 舊子節點數組 中的位置(屬性值)
反之,則不存在
這個方法也給咱們提供了在項目中類似場景的一個解決思路,以對象索引查找替代數組遍歷
但願你們記住哦
這個函數在 Diff 中也起到很是大的做用,你們務必要記住啊
它的做用是判斷兩個節點是否相同
這裏說的相同,並非徹底一毛同樣,而是關鍵屬性同樣,能夠先看下源碼
function sameVnode(a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
!!a.data === !!b.data &&
sameInputType(a, b)
)
}
function sameInputType(a, b) {
if (a.tag !== 'input') return true
var i;
var types = [
'text','number','password',
'search','email','tel','url'
]
var typeA = (i = a.data) && (i = i.attrs) && i.type;
var typeB = (i = b.data) && (i = i.attrs) && i.type;
// input 的類型同樣,或者都屬於基本input類型
return (
typeA === typeB ||
types.indexOf(typeA)>-1 &&
types.indexOf(typeB)>-1
)
}
複製代碼
判斷的依據主要是 三點,key,tag,是否存在 data
這裏判斷的節點是隻是相對於 節點自己,並不包括 children 在內
也就是說,就算data不同,children 不同,兩個節點仍是可能同樣
好比下面這兩個
有一種特殊狀況,就是 input 節點
input 須要額外判斷, 兩個節點的 type 是否相同
或者
兩個節點的類型能夠不一樣,可是必須屬於那些 input 類型
sameVnode 的內容就到這裏了,可是我不由又開始思考一個問題
下面純屬我的意淫想法,僅供參考
sameVnode 應用在 Diff ,做用是爲了判斷節點是否須要新建
當兩個 新舊vnode 進行 sameVnode 獲得 false 的時候,說明兩個vnode 不同,會新建DOM插入
也就是兩個節點從根本上不同時纔會建立
其中會比較 惟一標識符 key 和 標籤名 tag,從而獲得 vnode 是否同樣 ,這些是毫無疑問的了
可是這裏不須要判斷 data 是否同樣,我開始不太明白
後面想到 data 是包含有一些 dom 上的屬性的,因此 data 不同沒有關係
由於就算不同,他們仍是基於同一個 DOM
由於DOM屬性的值是多是動態綁定動態更新變化的,因此變化先後的 兩個 vnode,相應的 data 確定不同,可是其實他們是同一個 Vnode,因此 data 不在判斷範疇
可是 data 在新舊節點中,必須都定義,或者都不定義
不存在 一個定義,而一個沒定義, 可是會相同的 Vnode
好比,下面這個就會存在data
這個就不會存在data
他們在模板中,確定是不屬於同一個節點
涉及的函數主要分爲兩類
一類是專門負責操做 DOM 的,insert,createElm,createChildren
這類函數比較通用,就算在咱們本身的項目中也能夠用得上
一類是專門特殊服務 Diff 的,createKeyToOldIdx,sameVnode
其中會包含一些項目的解決思路
你們務必先記住一下這幾個函數,在下篇內容的源碼中會頻繁出現
到時不會仔細介紹
鑑於本人能力有限,不免會有疏漏錯誤的地方,請你們多多包涵,若是有任何描述不當的地方,歡迎後臺聯繫本人,有重謝