$mount()
方法
工程源码:
src/platforms/web/entry-runtime-with-compiler.js
mount()
方法并返回Vue.prototype.$mount = function (el) {// 根据用户传入的 el 属性获取节点el = el && query(el)let vm = this;//把节点放在 vm.$el 上方便后面使用vm.$el = el;let options = vm.$options;let template/*** 编译权重:* 优先看有没有 render 函数, 如果有就直接用* 如果没有 render 函数就看有没有 template 模板* 如果都没有就直接获取 el 的 outerHTML 作为渲染模板*/if (!options.render) {if (!options.template) {template = el.outerHTML} else {template = vm.$options.template}}if (template) {//用 template 生成 render 函数let render = compileToFunctions(template)options.render = render}//调用 mount 方法开始渲染页面。return mount(this, el)
}
query
方法
function query (el) { // 传入了 `#box` 字符串if (typeof el === 'string') {var selected = document.querySelector(el);if (!selected) {warn('Cannot find element: ' + el);return document.createElement('div')}return selected} else {return el}
}
mountComponent()
工程源码:
src/core/instance/lifecycle.js
beforeMount
钩子updateComponent
方法mounted
钩子function mountComponent (vm, el) {// 渲染之前调用 beforeMount 生命周期callHook(vm, 'beforeMount')// 定义一个更新渲染函数 (用来获取虚拟 DOM 后渲染真实 DOM)let updateComponent = () => {// 整个渲染周期最关键的一行vm._update(vm._render())}// 生成一个渲染 Watcher 每次页面依赖的数据更新后会调用 updateComponent 进行渲染new Watcher(vm, updateComponent, () => {},{before () {callHook(vm, 'beforeUpdate')}}, true)// 渲染真实 dom 结束后调用 mounted 生命周期callHook(vm, 'mounted')
}
Watcher()
工程源码:
src/core/observer/watcher.js
Watcher
内部调用了 updateComponent
方法 (数据添加依赖我们后面说)export class Watcher {constructor(vm,expOrFn,cb,options) {if (typeof expOrFn === 'function') {// 将 updateComponent 方法赋值给 getterthis.getter = expOrFn}this.get();}get() {pushTarget(this)let value// 使用 call 调用 updateComponent 方法value = this.getter.call(this.vm, this.vm);popTarget()return value}
}
触发 updateComponent()
方法, 内部的 vm.update(vm._render())
就会执行
工程源码:
src/core/instance/render.js
先看 _render()
方法
render()
函数render()
函数, 获取虚拟 DOM 后返回Vue.prototype._render = function () {let vm = this// 取出 render 函数let render = vm.$options.render;// 调用 render 函数得到虚拟 DOMreturn render.call(vm)
}
vm.update()
工程源码:
src/core/instance/lifecycle.js
patch()
传入根元素 #box
渲染patch()
传入旧的虚拟 DOM 和新的虚拟 DOM 进行 diff 对比更新Vue.prototype._update = function (vnode) {let vm = this// 获取到上一次的虚拟 DOM 用于 diff 对比const prevVnode = vm._vnodeif (!prevVnode) {// 没有上次的虚拟 DOM, 说明是首次渲染vm.$el = patch(vm.$el, vnode)} else {// 有虚拟 DOM 说明是数据更新驱动视图更新vm.$el = patch(prevVnode, vnode)}// 保留虚拟 DOMvm._vnode = vnode
}
patch()
工程源码:
src/core/vdom/patch.js
return function patch(el, vnode, hydrating, removeOnly) {// 判断有没有旧的虚拟 DOM 如果没有就进入 ifif (isUndef(oldVnode)) {isInitialPatch = truecreateElm(vnode, insertedVnodeQueue)}// ... 省略其他不重要的代码 ...return vnode.elm
}function createElm (vnode, // 虚拟dominsertedVnodeQueue,parentElm, // 父节点
) {// 查看元素 tag 是不是组件, 如果是组件就 return 不走这里, 去创建组件if (createComponent(vnode, insertedVnodeQueue, parentElm)) {return}const data = vnode.data // 获取 data 数据const children = vnode.children // 获取子元素const tag = vnode.tag // 获取标签名if (isDef(tag)) {// 创建真实 DOMvnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode)// 如果有子节点就递归创建子节点createChildren(vnode, children, insertedVnodeQueue)// 给父元素插入子元素insert(parentElm, vnode.elm, refElm)} 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)}
}function createChildren (vnode, children, insertedVnodeQueue) {if (Array.isArray(children)) {for (let i = 0; i < children.length; ++i) {// 创建子节点createElm(children[i], insertedVnodeQueue, vnode.elm)}}
}
render
函数比在 template
中写代码编译效率更高render()
函数是用来创建虚拟 DOM 的_update
中调用的 patch()
函数才是真正将虚拟 DOM 转成真实 DOM 的方法
上一篇:分布式智能家居项目雏形