[TOC]
*****
## 5 [\core\vdom\] 虚拟DOM目录
### 5-1 目录文件
~~~
\core\vdom\
vnode.js ;虚拟节点
helper.js
patch.js
create-elment.js
create-component.js
modules\index.js
modules\directive.js
~~~
### 5-2 vnode.js(虚拟节点)
>[info] module
~~~
;创建虚拟节点对象
;tag 标签名称
;data
;children
;text
;elm
;ns
;context
;key
export default function VNode (tag, data, children, text, elm, ns, context) {
return {
tag,
data,
children,
text,
elm,
ns,
context,
key: data && data.key
}
}
~~~
>[info] export
~~~
export default function VNode (tag, data, children, text, elm, ns, context) {}
~~~
### 5-3 helper.js(辅助文件)
>[info] import
~~~
;(导入)基础工具,虚拟节点
import { isArray, isPrimitive } from '../util/index'
import VNode from './vnode'
~~~
>[info] module
~~~
;空白节点
const whitespace = VNode(undefined, undefined, undefined, ' ')
;节点扁平化处理
export function flatten (children) {
if (typeof children === 'string') {
;子节点为字符串
return [VNode(undefined, undefined, undefined, children)]
}
if (isArray(children)) {
;子节点为数组
const res = []
for (let i = 0, l = children.length; i < l; i++) {
const c = children[i]
if (isArray(c)) {
;递归扁平化处理
res.push.apply(res, flatten(c))
} else if (isPrimitive(c)) {
if (c === ' ') {
;空白,
res.push(whitespace)
} else {
;数字,字符串
res.push(VNode(undefined, undefined, undefined, c))
}
} else if (c) {
;其他复杂对象
res.push(c)
}
}
;返回扁平化结果
return res
}
}
;事件监听器更新
export function updateListeners (on, oldOn, add) {
let name, cur, old, event, capture
;遍历新的事件监听器数组on
for (name in on) {
cur = on[name]
old = oldOn[name]
;新增事件监听器
if (old === undefined) {
capture = name.charAt(0) === '!'
event = capture ? name.slice(1) : name
if (isArray(cur)) {
;新增事件监听器为数组
add(event, arrInvoker(cur), capture)
} else {
;新增单个事件监听器
cur = { fn: cur }
on[name] = cur
add(event, fnInvoker(cur), capture)
}
} else if (isArray(old)) {
;原有事件监听器为数组
old.length = cur.length
for (let i = 0; i < old.length; i++) old[i] = cur[i]
on[name] = old
} else {
;原有事件监听器为单个
old.fn = cur
on[name] = old
}
}
}
;数组arr函数回调
function arrInvoker (arr) {
return function (ev) {
const single = arguments.length === 1
for (let i = 0; i < arr.length; i++) {
single ? arr[i](ev) : arr[i].apply(null, arguments)
}
}
}
;单个o函数回调
function fnInvoker (o) {
return function (ev) {
const single = arguments.length === 1
single ? o.fn(ev) : o.fn.apply(null, arguments)
}
}
~~~
>[info] export
~~~
;节点扁平化处理
export function flatten (children) {}
~~~
### 5-4 patch.js()
>[info] import
~~~
;(导入)虚拟节点,基础工具
import VNode from './vnode'
import { isPrimitive, renderString, warn } from '../util/index'
~~~
>[info] module
~~~
;空节点
const emptyNode = VNode('', {}, [])
;钩子数组
const hooks = ['create', 'update', 'remove', 'destroy']
;undef检测
function isUndef (s) {
return s === undefined
}
;def检测
function isDef (s) {
return s !== undefined
}
;比较两个节点是否相同
function sameVnode (vnode1, vnode2) {
return vnode1.key === vnode2.key && vnode1.tag === vnode2.tag
}
;索引修正
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
;创建patch函数
export function createPatchFunction (backend) {
let i, j
const cbs = {}
const { modules, nodeOps } = backend
//hooks注册
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (modules[j][hooks[i]] !== undefined) cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
//elm元素的空节点
function emptyNodeAt (elm) {
return VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
;
function createRmCb (childElm, listeners) {
function remove () {
if (--remove.listeners === 0) {
removeElement(childElm)
}
}
remove.listeners = listeners
return remove
}
function removeElement (el) {
const parent = nodeOps.parentNode(el)
nodeOps.removeChild(parent, el)
}
;
function createElm (vnode, insertedVnodeQueue) {
let i, elm
const data = vnode.data
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode)
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(i = vnode.child)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
return vnode.elm
}
}
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
elm = vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag)
if (Array.isArray(children)) {
for (i = 0; i < children.length; ++i) {
nodeOps.appendChild(elm, createElm(children[i], insertedVnodeQueue))
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(elm, nodeOps.createTextNode(vnode.text))
}
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
} else {
elm = vnode.elm = nodeOps.createTextNode(vnode.text)
}
return vnode.elm
}
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (i.create) i.create(emptyNode, vnode)
if (i.insert) insertedVnodeQueue.push(vnode)
}
}
function addVnodes (parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
nodeOps.insertBefore(parentElm, createElm(vnodes[startIdx], insertedVnodeQueue), before)
}
}
function invokeDestroyHook (vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
if (isDef(i = vnode.child)) {
invokeDestroyHook(i._vnode)
}
}
}
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (isDef(ch)) {
if (isDef(ch.tag)) {
invokeDestroyHook(ch)
removeAndInvokeRemoveHook(ch)
} else { // Text node
nodeOps.removeChild(parentElm, ch.elm)
}
}
}
}
function removeAndInvokeRemoveHook (vnode, rm) {
if (rm || isDef(vnode.data)) {
const listeners = cbs.remove.length + 1
if (!rm) {
// directly removing
rm = createRmCb(vnode.elm, listeners)
} else {
// we have a recursively passed down rm callback
// increase the listeners count
rm.listeners += listeners
}
// recursively invoke hooks on child component root node
if (isDef(i = vnode.child) && isDef(i = i._vnode) && isDef(i.data)) {
removeAndInvokeRemoveHook(i, rm)
}
for (i = 0; i < cbs.remove.length; ++i) {
cbs.remove[i](vnode, rm)
}
if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
i(vnode, rm)
} else {
rm()
}
} else {
removeElement(vnode.elm)
}
}
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue) {
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, elmToMove, before
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = oldKeyToIdx[newStartVnode.key]
if (isUndef(idxInOld)) { // New element
nodeOps.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
} else {
elmToMove = oldCh[idxInOld]
if (process.env.NODE_ENV !== 'production' && !elmToMove) {
warn(
'Duplicate track-by key: ' + idxInOld + '. ' +
'Make sure each v-for item has a unique track-by key.'
)
}
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
nodeOps.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
}
}
}
if (oldStartIdx > oldEndIdx) {
before = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
function patchVnode (oldVnode, vnode, insertedVnodeQueue) {
let i, hook
if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
i(oldVnode, vnode)
}
// skip nodes with v-pre
if (isDef(i = vnode.data) && i.pre) {
return
}
let elm = vnode.elm = oldVnode.elm
const oldCh = oldVnode.children
const ch = vnode.children
if (oldVnode === vnode) return
if (!sameVnode(oldVnode, vnode)) {
const parentElm = nodeOps.parentNode(oldVnode.elm)
elm = createElm(vnode, insertedVnodeQueue)
nodeOps.insertBefore(parentElm, elm, oldVnode.elm)
removeVnodes(parentElm, [oldVnode], 0, 0)
return
}
if (isDef(vnode.data)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
i = vnode.data.hook
if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode)
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue)
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) {
nodeOps.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) {
nodeOps.setTextContent(elm, vnode.text)
}
if (isDef(hook) && isDef(i = hook.postpatch)) {
i(oldVnode, vnode)
}
}
function invokeInsertHook (queue) {
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
function hydrate (elm, vnode, insertedVnodeQueue) {
if (process.env.NODE_ENV !== 'production') {
if (!assertNodeMatch(elm, vnode)) {
return false
}
}
vnode.elm = elm
const { tag, data, children } = vnode
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode)
if (isDef(i = vnode.child)) {
// child component. it should have hydrated its own tree.
invokeCreateHooks(vnode, insertedVnodeQueue)
return true
}
}
if (isDef(tag)) {
if (isDef(children)) {
const childNodes = elm.childNodes
for (let i = 0; i < children.length; i++) {
const success = hydrate(childNodes[i], children[i], insertedVnodeQueue)
if (!success) {
return false
}
}
}
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
}
return true
}
function assertNodeMatch (node, vnode) {
if (vnode.tag) {
if (vnode.tag.indexOf('vue-component') === 0) {
return true
} else {
return vnode.tag === node.tagName.toLowerCase() && (
vnode.children
? vnode.children.length === node.childNodes.length
: node.childNodes.length === 0
)
}
} else {
return renderString(vnode.text) === node.data
}
}
return function patch (oldVnode, vnode) {
let elm, parent
const insertedVnodeQueue = []
if (!oldVnode) {
// empty mount, create new root element
createElm(vnode, insertedVnodeQueue)
} else {
if (sameVnode(oldVnode, vnode)) {
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
if (isUndef(oldVnode.tag)) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.hasAttribute('server-rendered')) {
oldVnode.removeAttribute('server-rendered')
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(insertedVnodeQueue)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
elm = oldVnode.elm
parent = nodeOps.parentNode(elm)
createElm(vnode, insertedVnodeQueue)
if (parent !== null) {
nodeOps.insertBefore(parent, vnode.elm, nodeOps.nextSibling(elm))
removeVnodes(parent, [oldVnode], 0, 0)
}
}
}
invokeInsertHook(insertedVnodeQueue)
return vnode.elm
}
}
~~~
>[info] export
~~~
;(导出)创建渲染函数
export function createPatchFunction (backend) {}
~~~
### 5-5 create-element.js(虚拟元素)
### 5-6 create-component.js(虚拟组件)
### 5-7 modules\index.js(入口文件)
### 5-8 modules\directive.js(指令)
![](https://box.kancloud.cn/2016-05-06_572bf6c0146bf.png)
- 概述
- 框架结构
- 编译入口(\entries)
- web-compiler.js(web编译)
- web-runtime.js(web运行时)
- web-runtime-wih-compiler.js(web编译运行)
- web-server-renderer.js(web服务器渲染)
- 核心实现 (\core)
- index.js(核心入口)
- config.js(核心配置)
- core\util(核心工具)
- core\observer(双向绑定)
- core\vdom(虚拟DOM)
- core\global-api(核心api)
- core\instance(核心实例)
- 模板编译(\compiler)
- compiler\parser(模板解析)
- events.js(事件解析)
- helper.js(解析助手)
- directives\ref.js(ref指令)
- optimizer.js(解析优化)
- codegen.js(渲染生成)
- index.js(模板编译入口)
- web渲染(\platforms\web)
- compiler(web编译目录)
- runtime(web运行时目录)
- server(web服务器目录)
- util(web工具目录)
- 服务器渲染(\server)
- render-stream.js(流式渲染)
- render.js(服务器渲染函数)
- create-renderer.js(创建渲染接口)
- 框架流程
- Vue初始化
- Vue视图数据绑定
- Vue数据变化刷新
- Vue视图操作刷新
- 框架工具
- 基础工具(\shared)
- 模板编译助手
- 核心实例工具
- Web渲染工具
- 基础原理
- dom
- string
- array
- function
- object
- es6
- 模块(Module)
- 类(Class)
- 函数(箭头)
- 字符串(扩展)
- 代理接口(Proxy)
- 数据绑定基础
- 数据绑定实现
- mvvm简单实现
- mvvm简单使用
- vdom算法
- vdom实现
- vue源码分析资料