和前面的watcher关联起来,数据变化了,怎么就watcher变化,然后patch,然后就虚拟dom了
1. 细节知识点
1. $mount什么时候挂到vue原型上
mountComponent函数定义在src\core\instance\lifecycle.js
$mount挂载是在src\platforms\web\runtime\index.js文件中,运行时文件初次挂载,编译版本文件再次覆盖挂载
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
2. mount挂载在什么时候执行
初始化数据执行完,执行完created之后,立刻执行挂载
3. dep怎么和响应式的数据一一对应的
答:每一此把数据变成响应式(执行defineReactive)时候,new 一个Dep,这个dep被getter方法使用到,形成闭包被保存下来(也可以理解为getter方法是唯一的,每一个响应式数据生成一个唯一的getter函数,dep是写在getter里面的代码)
4. watcher怎么和dep对应的
1) watcher的初始化在挂载阶段,此时数据响应式初始化已经执行完毕,所有数据一一对应的dep已经创建,但是dep里面保存的watcher还是空的
2) watcher初始化的时候,执行一次渲染,触发数据响应式的get方法,Dep.target有值,触发dep收集,整个watcher实例被收集到dep里面去(依赖收集过程)。最终在派发更新执行的时候,是执行watcher里面update方法,update方法最终又执行渲染render,此时会再次访问到响应式的get方法,触发dep.depend(依赖收集),但是收集过程中检测到了当前watcher已经被当前dep收集了,于是不再收集
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)) { // 第二次访问值开始,此处就为false,不触发dep.addSub方法
dep.addSub(this)
}
}
}
5. 组件怎么和watcher一一对应的,代码体现在
组件初次渲染的时候会生成一个watcher,这个watch传了参数有组件的渲染函数,dep通知这个watcher更新的时候,watcher只会执行这个组件的渲染函数(只传了一个渲染函数)
6. watcher是用来做什么的
答:就是用来做渲染的(里面藏了render函数),以及和dep做对应的(里面有和dep关联的方法addDep)
2. 代码步骤汇总
new Vue =》
_init() =>》
各种初始化,执行created =》
vm.options.el) (编译版本重写了多了个编译步骤) =》
mountComponent =》
new Watcher =》
vm._update(vm._render(), hydrating) =》
vm._render() =》
vm.update =》
patch =》
patch =》
createPatchFunction(这个函数七百多行,diff算法,渲染在此执行) =>
return function patch(node, vnode, inVPre) { ... } (详情看diff算法部分)
3. 详细代码步骤
mountComponent
1) 执行beforecreated。
2) 定义updateComponent函数
3) new Watcher,在new Watcher的初始化时候,会执行传入的updateComponent函数,执行渲染render
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
}
}
// 执行beforeMount钩子函数
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
// TODO
} else {
updateComponent = () => {
// 这个render函数都干了什么:拿到了vnode,最终生成dom
// _render最后返回的是vnode
// 最终渲染的地方是在_update里面的patch方法里面做的
// _render最终会调用$createElement函数生成vnode
vm._update(vm._render(), hydrating)
}
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// 如果是根节点,就在此执行mounted钩子函数
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
new Watcher
new Watcher时候,传入的expOrFn参数就是渲染函数updateComponent,这个函数会在new的时候执行一次,
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// 写那种以字符串命名的函数解析方式
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
vm._render()
1. _render(在src\core\instance\render.js文件中的renderMixin函数中被挂载到prototype上,renderMixin在new Vue之前就执行了。
2. _render()的目的是返回vnode,vnode详细资料到vnode文档中去看
Vue.prototype._render = function (): VNode { // 最终返回的是vnode
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
// vnode在此创建
// vm._renderProxy是当前上下文,在生产环境就是this
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
// TODO..
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
vnode = render.call(vm._renderProxy, vm.$createElement)
render函数编译产生的,在src\platforms\web\entry-runtime-with-compiler.js文件中$mount函数内
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
根据不同的模板产生不同的render函数,例如长的如下的样子
模板
<div id="demo" >
{{currentBranch}}
</div>
render函数
(function anonymous() {
with(this) {
return _c('div',{attrs:{"id":"demo"}},[_v("\n "+_s(currentBranch)+"\n")])
}
})
_update
_update传两个参数,一个是vnode即_render,另一个是是否混合开发.
_render是拿到vnode,_update是真的把vnode渲染程真是的dom,diff算法在此内部执行
有旧的vnode,则走diff算法的patch,没有旧的vnode,则走直接渲染的vnode
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode // 当组件更新的时候就会有
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// TODO..
}
patch
patch
createPatchFunction
createPatchFunction,这个函数七百多行,diff算法,渲染在此执行。详情查看diff算法部分