一、简答题
1. 请简述Vue首次渲染的过程
- 定义Vue的构造函数
- 初始化Vue的实例成员
- 注册 vm 的 _init()方法,初始化 vm
- 注册 vm 的 $data/ $props/ $set/ $delete / $watch
- 初始化事件相关方法$on / $once / $off / $emit
- 初始化生命周期相关的混入方法_update/ $forceUpdate/ $destroy
- 混入 render $nextTick / _render
- 初始化Vue的静态成员
- 初始化Vue.config对象
- 静态方法 set/delete/nextTick
- 设置keep-alive组件
- 注册Vue.use()用来注册插件
- 注册Vue.mixin()实现混入
- 注册Vue.extend()基于传入的options返回一个组件的构造函数
- 注册Vue.directive()、Vue.component()、Vue.filter()
- 初始化完毕后调用 new Vue()
- 调用 this._init()
- 合并options / 初始化操作
- 设置代理对象 initProxy
- 调用$mount将模板编译成render函数
- 获得options选项判断是否有render函数,没有获取选项中的template,则调用getOuterHTML(el)
- 调用compileToFUnctions 将模板编译成render函数
- 执行mount方法
- 重新获取el
- 调用mountComponent
- 判断是否有render选项,如果没有但是传入了模板,当前是发开环境则发送警告
- 通过callHook触发生命周期钩子 beforeMount
- 定义updateComponent调用_render方法调用用户传入的render或编译器生产的虚拟DOM传给_update将虚拟DOM转换成真实DOM更新到界面
- 创建Watchwer对象将updateComponent传入
- 通过callHook触发mounted生命周期钩子函数
- Watcher
- 判断watcher的类型,lazy是否延迟执行更新视图,立即执行get方法
- pushTarget将当前的watcher对象存入当前栈保存父组件
- this.getter.call(vm, vm)
- vm.update(vm_render(), hydrating)调用_patch(vm.$el.vnode)挂载真实DOM
- 记录vm.$el
2. 请简述Vue响应式原理
在生成vue实例时,对传入的data进行遍历,使用Object.defineProperty把这些属性转为getter/setter,每个vue实例都有一个watcher实例,它会在实例渲染时记录这些属性,并在setter触发时重新渲染
总共分为三个阶段:
(1)、init阶段:Vue的data被reactive化,也就是加上set/get
- initState vm 状态的初始化
- 初始化了_data、_props、methods等
- initData(vm) vm数据的初始化
- 初始化_data,组件中data是函数,调用函数返回结果,否则直接返回data
- 获取data中的所有属性
- 获取props / methods
- 判断data上的成员是否和props/methods重名
- 数据的响应式处理observe(data,true)
- observe(value,asRootData)负责为每一个Object类型的value创建一个observer实例
- 判断calue是否是对象
- 如果value有_ob_(observer对象)属性结束
- 创建一个Observer对象
- Obsercer对 对象、数组做响应式化处理
- 初始化实例的vmCount为0,this.vmCount = 0
- 将实例挂载到观测对象的ob属性,设置为不可枚举
- walk(obj) 遍历obj所有属性,为每一个属性调用defineReactive()方法,设置getter/setter
- defineReactive()
- 为一个对象定义一个响应式的属性,每一个属性对应一个dep对象
- 如果该属性的值是对象,继续调用observe
- 如果该属性的值是对象,继续调用observe
- 如果给属性赋新值,继续调用observe
- 如果数据更新发送通知
- 数组的响应式处理
- 调用Object.defineProperty() 重新定义修改数组的方法
- 对插入的新元素,重新遍历数组元素设置为响应式数据
- Dep类
- 依赖对象
- 记录 watcher 对象
- depend() - watcher记录对应的 dep
- 发布通知
(2)、mount阶段
mount阶段的时候,会创建一个Watcher类的对象。这个Watcher实际上是链接Vue组件与Dep的桥梁。每一个Watcher对应一个vue component
(3)、更新阶段
当数据发生改变的时候,就去调用Dep的notify函数,然后通知所有的Watcher调用数据更新
- Watcher类
- Watcher分为三种,Computed Watcher、用户Watcher(侦听器)、渲染Watcher
- Watcher的构造函数初始化、处理expOrFn(渲染watcher和侦听器处理不同)
- 调用this.get(),它里面调用pushTarget()然后this.getter.call(vm,vm)(对于渲染wacher调用updateComponent),如果是用户watcher会获取属性的值(触发get操作)
- 当数据跟新的时候,dep中调用notify()方法,notify()中调用watcher的update()方法
- update()中调用queueWatcher()
- queueWatcher()是一个核心方法,去除重复操作,调用flushSchedulerQueue()刷新队列并执行watcher
- flushScheduleQueue()中对watcher排序,遍历所有watcher,如果有before,触发生命周期的构造函数beforeUpdate,执行watcher.run(),它内部调用this.get(),然后调用this.cb()
- 整个流程结束
3. 请简述虚拟DOM中key的作用和好处
-
在 patch 函数中,调用 patchVnode 之前,会首先调用 sameVnode()判 断当前的新老 VNode是否是相同节点,sameVnode() 中会首先判断 key 是否相同。
-
如果没有设置key会认为是相同节点
- 然后调用patchVnode ++oldStartIdx ++newStartIdx会把所有节点当作相同节点处理内容不一样会更新处理的更新会变多上边会做3次更新一次插入
-
设置key后key变化后会认为是不同节点
-
第一次调用sameVnode(oldStartVnode, newStartVnode) 对比开始节点认为是相同节点调用patchVnode同一节点不做操作++oldStartIdx ++newStartIdx
-
对比第二个节点是不同节点对比最后节点对比最后节点是相同节点调用oatchVode --oldEndIdx --newEndIdx
-
如果开始和结束节点都不同sameVnode(oldStartVnode, newEndVnode)进行 patchVnode,把 oldStartVnode 移动到最后移动游标,获取下一组节点
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[–newEndIdx] -
sameVnode(oldEndVnode, newStartVnode)进行 patchVnode,把 oldEndVnode 移动到最前面
oldEndVnode = oldCh[–oldEndIdx]
newStartVnode = newCh[++newStartIdx] -
以上四种情况都不满足newStartNode 依次和旧的节点比较从新的节点开头获取一个,去老节点中查找相同节点先找新开始节点的key和老节点相同的索引,如果没找到再通过sameVnode找。如果没有找到,创建节点并插入到最前面,获取要移动的老节点,如果使用 newStartNode 找到相同的老节点,执行 patchVnode,并且将找到的旧节点移动到最前面。 如果key相同,但是是不同的元素,创建新元素。
-
当结束时 oldStartIdx > oldEndIdx,旧节点遍历完,但是新节点还没有,说明新节点比老节点多,把剩下的新节点插入到老的节点后面。 当结束时 newStartIdx > newEndIdx,新节点遍历完,但是旧节点还没有,移除旧节点。
-
-
当设置 key 的时候
- 在 updateChildren 中比较子节点的时候,因为 oldVnode 的子节点的 b,c,d 和 newVnode 的 x,b,c的key 相同,所以只做比较,没有更新 DOM 的操作,当遍历完毕后,会再把 x 插入到 DOM 上DOM 操作只有一次插入操作。
4. 请简述VUe中模板编译的过程
- 重写$mount方法
- 如果用户没有传入render函数获取用户传入的template如果没有 template,获取el的 outerHTML 作为模板
- 调用 compileToFunctions 把 template 转换成 render 函数,compileToFunctions是通过createCompiler(baseOptions)创建的,createCompiler通过来返回createCompilerCreator
- 把模板转换成 ast 抽象语法树 – 抽象语法树,用来以树形的方式描述代码结构
- 优化抽象语法树 optimize(ast, options)通过标记静态节点优化抽象语法树
- 把抽象语法树生成字符串形式的 js 代码