baidumap vue 判断范围_vue 数据渲染
生活随笔
收集整理的這篇文章主要介紹了
baidumap vue 判断范围_vue 数据渲染
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本文轉載于 SegmentFault 社區社區專欄:山外de樓作者:山外de樓
??
?
??
vm._update(vm._render(), hydrating) // 生成虛擬dom,并更新真實dom
}這是在 mountComponent 方法的內部,會定義一個 updateComponent方法,在這個方法中 vue 會通過 vm._render()函數生成虛擬 dom,并將生成的 vnode 作為第一個參數傳入 vm._update()函數中進而完成虛擬 dom 到真實 dom 的渲染。第二個參數 hydrating是跟服務端渲染相關的,在瀏覽器中不需要關心。這個函數最后會作為參數傳入到 vue 的 watch 實例中作為 getter函數,用于在數據更新時觸發依賴收集,完成數據響應式的實現。這個過程不在本文的介紹范圍內,在這里只要明白,當后續 vue 中的 data 數據變化時,都會觸發 updateComponent 方法,完成頁面數據的渲染更新。具體的關鍵代碼如下: new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
// 觸發beforeUpdate鉤子
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
// 觸發mounted鉤子
callHook(vm, 'mounted')
}
return vm
}代碼中還有一點需要注意的是,在代碼結束處,會做一個判斷,當 vm 掛載成功后,會調用 vue 的 mounted 生命周期鉤子函數。這也就是為什么我們在 mounted 鉤子中執行代碼時,vm 已經掛載完成的原因。
?
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) vnode 類vnode 就是用一個原生的 js 對象去描述 dom 節點的類。因為瀏覽器操作 dom 的成本是很高的,所以利用 vnode 生成虛擬 dom 比創建一個真實 dom 的代價要小很多。vnode 類的定義如下:export default class VNode {
tag: string | void; // 當前節點的標簽名
data: VNodeData | void; // 當前節點對應的對象
children: ?Array; // 當前節點的子節點
text: string | void; // 當前節點的文本
elm: Node | void; // 當前虛擬節點對應的真實dom節點
..../*創建一個空VNode節點*/export const createEmptyVNode = (text: string = '') => {const node = new VNode()
node.text = text
node.isComment = truereturn node
}/*創建一個文本節點*/export function createTextVNode (val: string | number) {return new VNode(undefined, undefined, undefined, String(val))
}
....可以看到 vnode 類中仿照真實 dom 定義了很多節點屬性和一系列生成各類節點的方法。通過對這些屬性和方法的操作來達到模仿真實 dom 變化的目的。
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}為什么會有這個過程,是因為傳入的參數中的子節點是 any 類型,而 vue 最終生成的虛擬 dom 實際上是一個樹狀結構,每一個 vnode 可能會有若干個子節點,這些子節點應該也是 vnode 類型。所以需要對子節點處理,將子節點統一處理成一個 vnode 類型的數組。同時還需要根據 render 函數的來源不同,對子節點的數據結構進行相應處理。
if (typeof tag === 'string') {
let Ctor
// 獲取tag的名字空間
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
// 判斷是否是內置的標簽,如果是內置的標簽則創建一個相應節點
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
warn(
`The .native modifier for v-on is only valid on components but it was used on .`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
// 如果是組件,則創建一個組件類型節點
// 從vm實例的option的components中尋找該tag,存在則就是一個組件,創建相應節點,Ctor為組件的構造類
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
//其他情況,在運行時檢查,因為父組件可能在序列化子組件的時候分配一個名字空間
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
// tag不是字符串的時候則是組件的構造類,創建一個組件節點
vnode = createComponent(tag, data, context, children)
}vnode 創建后的處理這部分同樣也是一些 if/else 分情況的處理邏輯:1. 如果 vnode 成功創建,且是一個數組類型,則返回創建好的 vnode 節點2. 如果 vnode 成功創建,且有命名空間,則遞歸所有子節點應用該命名空間3. 如果 vnode 沒有成功創建則創建并返回一個空的 vnode 節點 if (Array.isArray(vnode)) {
// 如果vnode成功創建,且是一個數組類型,則返回創建好的vnode節點
return vnode
} else if (isDef(vnode)) {
// 如果vnode成功創建,且名字空間,則遞歸所有子節點應用該名字空間
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
// 如果vnode沒有成功創建則創建空節點
return createEmptyVNode()
}
?
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
}
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
/*是同一個節點的時候直接修改現有的節點*/
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
}
....
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
/*更新父的占位符節點*/
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor) /*調用destroy回調*/
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor) /*調用create回調*/
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0) /* 刪除舊節點 */
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode) /* 調用destroy鉤子 */
}
}第四步:返回 vnode.elm,即最后生成的虛擬 dom 對應的真實 dom,將 vm.$el 賦值為這個 dom 節點,完成掛載。其中重點的過程在第二步和第三步中,特別是 diff 算法對新舊節點的比較和更新很有意思。
-?END - 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
??
前言
?vue 是如何將編譯器中的代碼轉換為頁面真實元素的?這個過程涉及到模板編譯成 AST 語法樹,AST 語法樹構建渲染函數,渲染函數生成虛擬 dom,虛擬 dom 編譯成真實 dom 這四個過程。本文著重分析后兩個過程。?
整體流程
解讀代碼之前,先看一張 vue 編譯和渲染的整體流程圖:vue 會把用戶寫的代碼中的 標簽中的代碼解析成 AST 語法樹,再將處理后的 AST 生成相應的 render 函數,render 函數執行后會得到與模板代碼對應的虛擬 dom,最后通過虛擬 dom 中新舊 vnode 節點的對比和更新,渲染得到最終的真實 dom。有了這個整體的概念我們再來結合源碼分析具體的數據渲染過程。??
從 vm.$mount 開始
vue 中是通過 mount 實例方法去掛載 vm 的,數據渲染的過程就發生在vm.mount 階段。在這個方法中,最終會調用 mountComponent方法來完成數據的渲染。我們結合源碼看一下其中的幾行關鍵代碼: updateComponent = () => {vm._update(vm._render(), hydrating) // 生成虛擬dom,并更新真實dom
}這是在 mountComponent 方法的內部,會定義一個 updateComponent方法,在這個方法中 vue 會通過 vm._render()函數生成虛擬 dom,并將生成的 vnode 作為第一個參數傳入 vm._update()函數中進而完成虛擬 dom 到真實 dom 的渲染。第二個參數 hydrating是跟服務端渲染相關的,在瀏覽器中不需要關心。這個函數最后會作為參數傳入到 vue 的 watch 實例中作為 getter函數,用于在數據更新時觸發依賴收集,完成數據響應式的實現。這個過程不在本文的介紹范圍內,在這里只要明白,當后續 vue 中的 data 數據變化時,都會觸發 updateComponent 方法,完成頁面數據的渲染更新。具體的關鍵代碼如下: new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
// 觸發beforeUpdate鉤子
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
// 觸發mounted鉤子
callHook(vm, 'mounted')
}
return vm
}代碼中還有一點需要注意的是,在代碼結束處,會做一個判斷,當 vm 掛載成功后,會調用 vue 的 mounted 生命周期鉤子函數。這也就是為什么我們在 mounted 鉤子中執行代碼時,vm 已經掛載完成的原因。
?
vm._render()
接下來具體分析 vue 生成虛擬 dom 的過程。前面說了這一過程是調用 vm._render()方法來完成的,該方法的核心邏輯是調用 vm.$createElement 方法生成 vnode,代碼如下:vnode = render.call(vm._renderProxy, vm.$createElement)其中 vm.renderProxy是個代理,代理 vm,做一些錯誤處理,vm.$createElement是創建 vnode 的真正方法,該方法的定義如下:vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)可見最終調用的是 createElement方法來實現生成 vnode 的邏輯。在進一步介紹 createElement 方法之前,我們先理清楚兩個個關鍵點:1. render的函數來源2. vnode到底是什么render 方法的來源
在 vue 內部其實定義了兩種 render 方法的來源,一種是如果用戶手寫了 render 方法,那么 vue 會調用這個用戶自己寫的 render 方法,即下面代碼中的 vm.$createElement;另外一種是用戶沒有手寫 render 方法,那么vue內部會把 template 編譯成 render 方法,即下面代碼中的 vm._c。不過這兩個 render 方法最終都會調用 createElement 方法來生成虛擬 dom。// bind the createElement fn to this instance// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) vnode 類vnode 就是用一個原生的 js 對象去描述 dom 節點的類。因為瀏覽器操作 dom 的成本是很高的,所以利用 vnode 生成虛擬 dom 比創建一個真實 dom 的代價要小很多。vnode 類的定義如下:export default class VNode {
tag: string | void; // 當前節點的標簽名
data: VNodeData | void; // 當前節點對應的對象
children: ?Array; // 當前節點的子節點
text: string | void; // 當前節點的文本
elm: Node | void; // 當前虛擬節點對應的真實dom節點
..../*創建一個空VNode節點*/export const createEmptyVNode = (text: string = '') => {const node = new VNode()
node.text = text
node.isComment = truereturn node
}/*創建一個文本節點*/export function createTextVNode (val: string | number) {return new VNode(undefined, undefined, undefined, String(val))
}
....可以看到 vnode 類中仿照真實 dom 定義了很多節點屬性和一系列生成各類節點的方法。通過對這些屬性和方法的操作來達到模仿真實 dom 變化的目的。
createElement
有了前面兩點的知識儲備,接下來回到 createElement 生成虛擬 dom 的分析。createElement 方法中的代碼很多,這里只介紹跟生成虛擬 dom 相關的代碼。該方法總體來說就是創建并返回一個 vnode 節點。在這個過程中可以拆分成三件事情:1. 子節點的規范化處理;2. 根據不同的情形創建不同的 vnode 節點類型;3. vnode 創建后的處理。下面開始分析這 3 個步驟:子節點的規范化處理
if (normalizationType === ALWAYS_NORMALIZE) {children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}為什么會有這個過程,是因為傳入的參數中的子節點是 any 類型,而 vue 最終生成的虛擬 dom 實際上是一個樹狀結構,每一個 vnode 可能會有若干個子節點,這些子節點應該也是 vnode 類型。所以需要對子節點處理,將子節點統一處理成一個 vnode 類型的數組。同時還需要根據 render 函數的來源不同,對子節點的數據結構進行相應處理。
創建 vnode 節點
這部分邏輯是對 tag 標簽在不同情況下的處理,梳理一下具體的判斷case如下:1. 如果傳入的 tag 標簽是字符串,則進一步進入下列第 2 點和第 3 點判斷,如果不是字符串則創建一個組件類型 vnode 節點。2. 如果是內置的標簽,則創建一個相應的內置標簽 vnode 節點。3. 如果是一個組件標簽,則創建一個組件類型 vnode 節點。4. 其他情況下,則創建一個命名空間未定義的 vnode 節點。 let vnode, nsif (typeof tag === 'string') {
let Ctor
// 獲取tag的名字空間
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
// 判斷是否是內置的標簽,如果是內置的標簽則創建一個相應節點
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
warn(
`The .native modifier for v-on is only valid on components but it was used on .`,
context
)
}
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
// 如果是組件,則創建一個組件類型節點
// 從vm實例的option的components中尋找該tag,存在則就是一個組件,創建相應節點,Ctor為組件的構造類
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
//其他情況,在運行時檢查,因為父組件可能在序列化子組件的時候分配一個名字空間
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
// tag不是字符串的時候則是組件的構造類,創建一個組件節點
vnode = createComponent(tag, data, context, children)
}vnode 創建后的處理這部分同樣也是一些 if/else 分情況的處理邏輯:1. 如果 vnode 成功創建,且是一個數組類型,則返回創建好的 vnode 節點2. 如果 vnode 成功創建,且有命名空間,則遞歸所有子節點應用該命名空間3. 如果 vnode 沒有成功創建則創建并返回一個空的 vnode 節點 if (Array.isArray(vnode)) {
// 如果vnode成功創建,且是一個數組類型,則返回創建好的vnode節點
return vnode
} else if (isDef(vnode)) {
// 如果vnode成功創建,且名字空間,則遞歸所有子節點應用該名字空間
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
// 如果vnode沒有成功創建則創建空節點
return createEmptyVNode()
}
vm._update()
vm._update()做的事情就是把 vm._render()生成的虛擬 dom 渲染成真實 dom。_update()方法內部會調用 vm.__patch__ 方法來完成視圖更新,最終調用的是 createPatchFunction方法,該方法的代碼量和邏輯都非常多,它定義在 src/core/vdom/patch.js文件中。下面介紹下具體的 patch 流程和流程中用到的重點方法:重點方法
1. createElm:該方法會根據傳入的虛擬 dom 節點創建真實的 dom 并插入到它的父節點中2. sameVnode:判斷新舊節點是否是同一節點。3. patchVnode:當新舊節點是相同節點時,調用該方法直接修改節點,在這個過程中,會利用 diff 算法,循環進行子節點的的比較,進而進行相應的節點復用或者替換。4. updateChildren方法:diff 算法的具體實現過程?
patch 流程
第一步:
判斷舊節點是否存在,如果不存在就調用 createElm() 創建一個新的 dom 節點,否則進入第二步判斷。 if (isUndef(oldVnode)) {// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
}
第二步:
通過 sameVnode() 判斷新舊節點是否是同一節點,如果是同一個節點則調用 patchVnode() 直接修改現有的節點,否則進入第三步判斷。const isRealElement = isDef(oldVnode.nodeType)if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
/*是同一個節點的時候直接修改現有的節點*/
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
}
第三步:
如果新舊節點不是同一節點,則調用 createElm()創建新的 dom,并更新父節點的占位符,同時移除舊節點。else {....
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
/*更新父的占位符節點*/
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor) /*調用destroy回調*/
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor) /*調用create回調*/
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0) /* 刪除舊節點 */
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode) /* 調用destroy鉤子 */
}
}第四步:返回 vnode.elm,即最后生成的虛擬 dom 對應的真實 dom,將 vm.$el 賦值為這個 dom 節點,完成掛載。其中重點的過程在第二步和第三步中,特別是 diff 算法對新舊節點的比較和更新很有意思。
其他注意點
sameVnode 的實際應用
在 patch 的過程中,如果兩個節點被判斷為同一節點,會進行復用。這里的判斷標準是:1. key 相同2. tag(當前節點的標簽名)相同3. isComment(是否為注釋節點)相同4. data 的屬性相同平時寫 vue 時會遇到一個組件中用到了 A 和 B 兩個相同的子組件,可以來回切換。有時候會出現改變了 A 組件中的值,切到 B 組件中,發現 B 組件的值也被改變成和 A 組件一樣了。這就是因為 vue 在 patch 的過程中,判斷出了 A 和 B 是 sameVnode,直接進行復用引起的。根據源碼的解讀,可以很容易地解決這個問題,就是給 A 和 B 組件分別加上不同的 key 值,避免 A 和 B 被判斷為同一組件。虛擬 DOM 如何映射到真實的 DOM 節點
vue 為平臺做了一層適配層,瀏覽器平臺的代碼在 /platforms/web/runtime/node-ops.js。不同平臺之間通過適配層對外提供相同的接口,虛擬 dom 映射轉換真實 dom 節點的時候,只需要調用這些適配層的接口即可,不需要關心內部的實現。最后通過上述的源碼和實例的分析,我們完成了 Vue 中數據渲染的完整解讀。-?END - 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的baidumap vue 判断范围_vue 数据渲染的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python共轭梯度法_Numerica
- 下一篇: C语言程序设计基础篇