debug Vue之整体流程

245 阅读2分钟

前言

通过debug源码的方式学习 Vue2.6 的整体构建流程,建议先不要关注具体的实现细节。

start

在vue源码中新建 examoples/demo/index.html 文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../../dist/vue.js"></script>
</head>
<body>
  <div id="app">
    {{name}}
    <input type="text" v-model="name">
  </div>
</body>
</html>
<script>
  new Vue({
    el: '#app',
    data() {
      return {
        name: 'test'
      }
    }
  })
</script>

修改 package.json 中的脚本命令,添加--sourcemap

"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap",

关键节点的断点位置

加载完vuejs资源,根据平台重写$mount

image.png

给Vue的构造函数挂载方法并根据传入的options初始化

image.png

this._init(options)

initLifecycle(vm) // 给实例挂载$parent、$root、$children等属性
initEvents(vm) // 挂载_events
initRender(vm) // 最主要的是挂载$createElement,$createElement可以创建 VNode
callHook(vm, 'beforeCreate')
initInjections(vm) // 处理inject
initState(vm) // 初始化props、methods、data
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

image.png

initData(vm),拦截数据设置get、set,对象递归监听、数组改写原型链

image.png


如果有el参数,就直接执行mount(),否则需要手动调用原型上的mount(),否则需要手动调用原型上的mount()

image.png

调用改写后的$mount(),先处理el获取template,通过compileToFunctions()获取render挂载到options上

将模版template转换成了render函数,接下来看下具体细节

image.png

加载Vue的时候引入 compileToFunctions 方法,依次会触发 createCompiler()、createCompilerCreator(),返回函数真正的createCompiler函数

image.png

image.png

image.png

image.png

此时才开始调用compile()编译模版

image.png

parse() 解析template模版,最终生成ast。继续看这一步的实现

image.png

parseHTML() 正则匹配解析模版字符串,并且对应各种vue指令的逻辑,最终处理完的就是ast image.png

image.png

得到ast,是否需要优化,再转成render

image.png

"with(this){return _c('div',{attrs:{"id":"app"}},[_v("\n "+_s(name)+"\n "),_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}}})])}"

image.png

image.png

将render转换成render函数

image.png

image.png

最终的render函数

image.png


开始mountComponent()

image.png

将组件作为一个watcher实例 image.png

new Watcher()的时候,构造函数中调用了原型方法get,触发劫持数据中的get,收集依赖(也就是watcher实例)

image.png

value = this.getter.call(vm, vm),有两个作用


作用一:先调用 updateComponent 方法,执行vm._render()

image.png

vm._render()最终调用挂载在vm.$options上的render函数,最终生成Vnode(过程见下)

image.png

作用二:触发侦测数据的getter,自动收集依赖(也就是本wathcer实例)

image.png

执行vm.$options上的render函数,render函数内部调用了_c方法

(function anonymous() {
    with (this) {
        return _c('div', {
            attrs: {
                "id": "app"
            }
        }, [_v("\n    " + _s(name) + "\n    "), _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
                }
            }
        })])
    }
}
)

_c在initRender()的时候就挂载在了Vue原型上

image.png

执行_c() 其实就是执行_createElement()

image.png

根据不同的节点,实例花Vnode创建不同类型的虚拟dom

image.png

image.png

然后再执行vm._update(), 进行虚拟Dom的patch比较

image.png

Vue.prototype.__patch__

image.png

image.png

image.png

image.png

createPatchFunction()并在函数内部声明createElm() 函数, 返回path方法

image.png

image.png

patch阶段


开始执行patch(), patch()内部调用createElm()函数

image.png

createElm()函数内部调用createChildren(),也会调用invokeCreateHooks()

image.png

image.png

createChildren()内部再调用createElm()函数,形成递归

image.png

invokeCreateHooks()循环执行对应的更新方法,并通过浏览器的原生方法操作dom完成挂载

image.png

image.png

patch()结束后并将返回真实dom节点返回

image.png

双向绑定


v-model指令别解析识别,会执行updateDirectives(),调用指令的 inserted 方法

image.png

指令的 inserted 方法内监听事件

image.png

修改input的值,触发侦测数据的setter,通知依赖watcher

怎么触发setter的呢?

image.png

执行 watcher 的update()

image.png

queueWatcher() 队列缓存,调用nextTick(flushSchedulerQueue)

image.png

nextTick 用微任务实现,避免频繁更新,修改完成以后统一更新

image.png

执行watcher.run(),内部再次调用this.get(),触发render()

image.png