Vue版本2.7.4
// 在git下载vue仓库,修改package.json
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:full-dev"
运行yarn dev,dist文件会多一个vue.js.map
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id="root"></div>
<script>
new Vue({
el: document.getElementById('root'),
template: `<div class="container"><span>n:{{name}},</span><span>a:{{age}}</span><button @click="fnAdd">+</button></div>`,
data() {
return { name: 'tt', age: 0 }
},
methods: {
fnAdd() {
this.age++
}
}
})
</script>
</body>
</html>
beforeCreate
主要任务是处理数据,initInjections、initState、initProvide都是在这个阶段执行的
initState
- initProps:把props的属性都代理到_props
- initMethods:把方法都挂到vm实例上,并bind
- initData:执行data函数,结果被挂到了_data上,将data的各个属性值代理到_data
- 初始化computed和watch
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
Observer data每个属性,每层对象都添加一个ob实例
export class Observer {
dep: Dep
vmCount: number // number of vms that have this object as root $data
constructor(public value: any, public shallow = false, public mock = false) {
// this.value = value
this.dep = mock ? mockDep : new Dep()
this.vmCount = 0
// 对象的隐式属性__ob__指向Observer实例
def(value, '__ob__', this)
if (isArray(value)) {
...
if (!shallow) {
this.observeArray(value)
}
} else {
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// defineReactive对象的每个属性
defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock)
}
}
}
// defineReactive数组的每个值
observeArray (value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
}
defineReactive作用就是用Object.defineProperty对属性的get/set方法做拦截。由于get和set内部会用到dep和obj,所以他们会被一直存在内存里
created
执行实例的$mount: 这个mount是临时定义的,功能是编译template
- 首先检查是否定义render,如果有不再执行编译过程
- 如果没有会把定义的template作为参数执行compileToFunctions
vue-template-compiler
初始化一个项目,引入vue-template-compiler。就可以查看template中定义的html片段的最终产物
// 插值
const template = `<p>{{message}}</p>`
// with (this) { return _c('p', [_v(_s(message))]) }
// 表达式
const template = `<p>{{flag ? message : 'no message found'}}</p>`
// with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}
// 属性和动态属性
const template = `
<div id="div1" class="container">
<img :src="imgUrl"/>
</div>
`
// with(this){return _c('div',{staticClass:"container",attrs:{"id":"div1"}},[_c('img',{attrs:{"src":imgUrl}})])}
// 条件判断
const template = `
<div>
<p v-if="flag === 'a'">A</p>
<p v-else>B</p>
</div>
`
// with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}
// 循环
const template = `
<ul>
<li v-for="item in list" :key="item.id">{{item.title}}</li>
</ul>
`
// with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}
// 事件
const template = `
<button @click="clickHandler">submit</button>
`
// with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}
// v-model
const template = `<input type="text" v-model="name">`
// with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}
编译过程
// template
<div class="container"><span>n:{{name}},</span><span>a:{{age}}</span><button @click="fnAdd">+</button></div>
- 将template解析成类似抽象语法树的ast对象
- 优化ast树,检不需要更新的节点,比如button的文本节点,会被定义static: true
- 把ast对象转成一段字符串,经过new Function(code),就是直接执行的函数语句。这个函数就是render,会被挂到vm.$options
export const createCompiler = createCompilerCreator(function baseCompile(
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
// 最终产物
with(this){return _c('div',{staticClass:"container"},[_c('span',[_v("n:"+_s(name)+",")]),_c('span',[_v("a:"+_s(age))]),_c('button',{on:{"click":fnAdd}},[_v("+")])])}
with代码块中访问变量,访问的就是this的,this就是vue实例。_c就是createElement
// 下划线小写方法就是对函数的映射
function installRenderHelpers(target) {
target._o = markOnce;
target._n = toNumber;
target._s = toString;
target._l = renderList;
target._t = renderSlot;
target._q = looseEqual;
target._i = looseIndexOf;
target._m = renderStatic;
target._f = resolveFilter;
target._k = checkKeyCodes;
target._b = bindObjectProps;
target._v = createTextVNode;
target._e = createEmptyVNode;
target._u = resolveScopedSlots;
target._g = bindObjectListeners;
target._d = bindDynamicKeys;
target._p = prependModifier;
}
有了render,接着调用mount不是同一个
// public mount method
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
beforeMount
// 定义实例的更新函数
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
// watcher会把更新函数存起来,初始化和更新时会被调用
new Watcher(
vm,
updateComponent,
noop,
watcherOptions,
true /* isRenderWatcher */
)
依赖收集
Watcher实例化后,render函数会被立即执行
(function anonymous () {
with (this) {
return _c('div', { staticClass: "container" }, [_c('span', [_v("n:" + _s(name) + ",")]), _c('span', [_v("a:" + _s(age))]), _c('button', { on: { "click": fnAdd } }, [_v("+")])])
}
})
render执行过程会访问到变量,触发属性的get方法
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// Dep.target指向当前watcher实例
if (Dep.target) {
if (__DEV__) {
// defineReactive时产生的dep,对象每个属性都有一个dep
dep.depend({
target: obj, // 属性所在的对象,和dep一样,都是defineReactive过程产生的
type: TrackOpTypes.GET,
key
})
} else {
dep.depend()
}
...
}
})
depend(info ?: DebuggerEventExtraInfo) {
if (Dep.target) {
// 当前属性的dep会被添加到当前的watcher实例中
Dep.target.addDep(this)
...
}
}
addDep(dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// dep也会保存和自己有联系的watcher
dep.addSub(this)
}
}
}
render的执行过程,就是让watcher和被访问到的属性的dep互相建立联系,产物就是vnode。接着调用_update里的vm.__patch__方法,将vnode转为dom
首次渲染
// 真实节点被转成没有属性和子节点的vnode,elm指向真实节点
oldVnode = emptyNodeAt(oldVnode)
// replacing existing element
const oldElm = oldVnode.elm
// 找到根节点的父节点
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(
vnode, // 新的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) // 根节点端的下一个兄弟节点
)
// vnode -> dom的过程,有children会递归调用,深度遍历
function createElm (
vnode,
insertedVnodeQueue,
parentElm?: any,
refElm?: any,
nested?: any,
ownerArray?: any,
index?: any
) {
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
// 有tag,创建dom
if (isDef(tag)) {
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
// 遍历children,根据子的vode创建真实dom,依次插入父dom
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
// 当前dom,插入父dom
insert(parentElm, vnode.elm, refElm)
if (__DEV__ && data && data.pre) {
creatingElmInVPre--
}
} else if (isTrue(vnode.isComment)) { // 注释
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
// 文本节点
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
// 执行创建时期的钩子函数,主要功能是把data中的属性,追加到dom上
function invokeCreateHooks (vnode, insertedVnodeQueue) {
// updateAttrs、updateClass、updateDOMListeners、updateDOMProps、updateStyle、_enter、create、updateDirectives
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
最终root节点是被替换了,说明root的作用就是找到父节点和下一个兄弟节点,首次渲染过程结束,触发mounted
beforeUpdate
只要修改被页面用到的data的属性值,会触发set
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
...
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
// 比对新旧值是否相等
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__) {
// dep触发通知方法
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
// 就是调用dep.subs里的所有watcher的update方法
dep.notify()
}
}
})
watcher queue
watcher的update并不会被立即执行,而是被queueWatcher(this),queueWatcher函数的功能:
- 去重,已经在queue里的watcher,不在进入队列
- 当前正在处理watcher,也不在进入队列。Dep有个static属性target,指向当前watcher
- flushing为false,表示没有正在处理watcher,就把当前wathcer push到队列
- 将调度方法,放到nextTick里。nextTick(flushSchedulerQueue),等待(宏/微)任务队列
// 执行被nextTick的回调函数
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// 任何使用nextTick的函数,都是先被push到全局的callbacks
export function nextTick (cb?: (...args: any[]) => any, ctx?: object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e: any) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
...
}
// 截取一小部分nextTick代码
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
}
flushSchedulerQueue函数就是遍历watcher queue
- 遍历到哪个watcher就将当前watcher,从队列中删掉,这个情况就对应queueWatcher的第2中情况
- 执行watcher.run(),即vm._update(vm._render(), hydrating),最终产物是新的vnode 更新过程,vm上有旧的vnode,接下来还是执行patch方法
更新渲染
sameVnode
// 对比新旧vnode,主要还是对比key、tag
function sameVnode(a, b) {
return (
a.key === b.key &&
a.asyncFactory === b.asyncFactory &&
((a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)) ||
(isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))
)
}
新旧节点相同,就要patchVnode,主要功能:
- 静态节点,直接返回,比如在编译的优化阶段,被打上标记的静态节点
- vnode的text和children,不是共存的。是text,不相同就修改内容。都有children执行updateChildren,有旧vnode就删除旧dom,否则就创建新dom
更新节点的属性
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
}
updateChildren
function updateChildren (
parentElm,
oldCh,
newCh,
insertedVnodeQueue,
removeOnly
) {
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, vnodeToMove, refElm
// removeOnly is a special flag used only by <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly
if (__DEV__) {
checkDuplicateKeys(newCh)
}
// 新旧节点的头、尾,共四个
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,
newCh,
newStartIdx
)
oldStartVnode = oldCh[++oldStartIdx] // 两个头指针都右移
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
// 新旧尾节点相同
patchVnode(
oldEndVnode,
newEndVnode,
insertedVnodeQueue,
newCh,
newEndIdx
)
oldEndVnode = oldCh[--oldEndIdx] // 两个尾指针都左移
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) {
// 旧的头结点、新尾节点相同,把新的尾节点属性更新到旧的头结点
patchVnode(
oldStartVnode,
newEndVnode,
insertedVnodeQueue,
newCh,
newEndIdx
)
// 把旧的头结点移动到尾节点的下一个节点,就是头结点移动到末尾
canMove &&
nodeOps.insertBefore(
parentElm,
oldStartVnode.elm,
nodeOps.nextSibling(oldEndVnode.elm)
)
// 上一步只是把真实dom做了移动,vnode顺序还是不变,所以旧的头结点右移
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx] // 新的尾节点左移
} else if (sameVnode(oldEndVnode, newStartVnode)) {
// 过程同上,先更新属性,再移动尾dom到头部
patchVnode(
oldEndVnode,
newStartVnode,
insertedVnodeQueue,
newCh,
newStartIdx
)
canMove &&
nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx] // 旧尾指针左移
newStartVnode = newCh[++newStartIdx] // 新头指针右移
} else {
// 没有找到新旧相同的节点
if (isUndef(oldKeyToIdx))
// 把所有有key的旧节点,都收集到一个map里,以key为键,索引是值
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
// 如果新的开始节点有key,去旧节点map按key匹配
// 否则去旧的节点里去匹配(这是新旧节点都没有key的情况,匹配tag等)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) {
// New element,没找到就直接创建新dom,查到旧dom前
createElm(
newStartVnode,
insertedVnodeQueue,
parentElm,
oldStartVnode.elm,
false,
newCh,
newStartIdx
)
} else {
vnodeToMove = oldCh[idxInOld]
// 找到新旧相同节点,同样是更新熟悉,移动到旧dom前
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(
vnodeToMove,
newStartVnode,
insertedVnodeQueue,
newCh,
newStartIdx
)
oldCh[idxInOld] = undefined
canMove &&
nodeOps.insertBefore(
parentElm,
vnodeToMove.elm,
oldStartVnode.elm
)
} else {
// 对应oldKeyToIdx的map集合,key相同但是其他属性不同
// same key but different element. treat as new element
createElm(
newStartVnode,
insertedVnodeQueue,
parentElm,
oldStartVnode.elm,
false,
newCh,
newStartIdx
)
}
}
newStartVnode = newCh[++newStartIdx] // 这个过程的新节点都已创建,所以新头指针右移
}
}
if (oldStartIdx > oldEndIdx) {
// 旧节点遍历完了,剩余的新节点都插入最后一个新节点前面
// newEndIdx如果左移过,它就有真实dom,比如旧头结点和新尾节点相同情况
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(
parentElm,
refElm,
newCh,
newStartIdx,
newEndIdx,
insertedVnodeQueue
)
} else if (newStartIdx > newEndIdx) {
// 新节点遍历完,删掉剩余的旧节点
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
updated结束