Vue.js 源码剖析-响应式原理、虚拟 DOM、模板编译和组件化
简答题
1、请简述 Vue 首次渲染的过程。
- new Vue() 创建 Vue 实例
- 调用 this._init() 方法
- 合并 options 初始化 options 中的属性
- 触发 beforeCreate 和 created 生命周期钩子函数
- 调用 vm.$mount()
- 判断是否有传入 render(h 函数),如果没有传入 render,就调用 complierToFunctions()将传入的 template 模板转为 render(完整版本才有的功能)
- 如果有传入 render,直接调用 mountComponent()
- mountComponent()
- 触发 beforeMounted 钩子
- 定义 updateComponent
(1).vm.update()更新时触发,将虚拟 dom 转为真实 dom
(2).vm._render()渲染虚拟 DOM - 创建 Watcher 实例
- 触发 mounted
- return vm
- 调用 watcher.get()
- 创建完 watcher 会调用一次
- 调用 updateComponent()
- 调用 vm._render()创建 VNode
- 调用 vm._update()挂载真实 DOM
2、请简述 Vue 响应式原理。
Vue2.x 中响应式原理的核心是通过 Object.defineProperty 来挟持 data 中的属性,在属性被访问时 调用 get 方法, 当属性被重新赋值时调用 set 方法来实现的具体实现如下:
- 遍历$data 中的所有属性,将其转化为 getter 和 setter 注入到 Vue 实例中
- 利用 Observer 循环遍历 data 中的每一个属性成员,当属性被访问时,调用 dep 中的 addSub 收集依赖,当属性值发生改变时就会调用 dep 中的 notify 发送通知,通知 watcher 更新视图
(需要判断对象和数组,上面的方法适用于对象,数组只会将数组的值处理为响应式的,不会循环里面的属性) - 调用 Compiler()对象处理插值表达式,在处理差值表达式的过程中会创建 Watcher 实例,添加 target 属性(Observer 中收集依赖只会收集含有 target 属性的成员,如果没有 target 属性,说明页面中没有引用,无需收集)
3、请简述虚拟 DOM 中 Key 的作用和好处。
- 作用:
当虚拟 DOM 中的新老节点后含有子节点且不相同时会利用 diff 算法来比较子节点的差异
如果新老节点的开始节点和结束节点都不相同的话,需要用新节点的开始节点和结束节点去老节点中寻找具有相同 key 的节点,从而减少 dom 的操作,如果此时没有 key 的话就会直接更新老节点 - 好处:
增加节点的重用性,避免节点频繁的重绘,提升性能
避免出现顺序错乱或者老节点中的其他属性值被移动到新节点中的问题
4、请简述 Vue 中模板编译的过程。
- 解析器(parse)
通过 parse 把 template 模板字符串转为 AST 树,它是一种用 JavaScript 队形的形式来描述整个模板,整个 parse 的过程是利用正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数,来达到构造 AST 树的目的 - 优化器(optimize)
通过 optimize 把整个 AST 树中的每一个 AST 元素节点标记了 static 和 staticRoot。optimize 的过程就是深度遍历这个 AST 树,去检测它的每一颗子树是不是静态节点,如果是静态节点则他们声称 DOM 永远不需要改变 - 代码生成器(generate)
把优化后的 AST 树转换成 render 函数的字符串