- 创建 Vue 实例对象
- init过程会初始化生命周期,初始化事件中心,初始化渲染、执行beforeCreate周期函数、初始化 data、props、computed、watch(computed 和 data 会分别建立各自的响应系统)执行created周期函数等。
- 初始化后,调用$mount方法对Vue实例进行挂载,(挂载的核心过程包括模板编译、渲染以及更新三个过程)。
- 如果没有在 Vue 实例上定义render方法而是定义了template,那么需要经历编译阶段。需要先将template 字符串编译成 render function,template 字符串编译步骤如下 :
- parse正则解析template字符串形成 AST(抽象语法树,是源代码的抽象语法结构的树状表现形式)
- optimize标记静态节点跳过 DIFF 算法(DIFF 算法是逐层进行比对,只有同层级的节点进行比对,因此时间的复杂度只有 O(n)。如果对于时间复杂度不是很清晰的,可以查看我写的文章ziyi2/algorithms-javascript/渐进记号)
- generate将 AST 转化成render function字符串
- 编译成render function 后,调用$mount的mountComponent方法,先执行beforeMount钩子函数,然后核心是实例化一个渲染Watcher,在它的回调函数(初始化的时候执行,以及组件实例中监测到数据发生变化时执行)中调用updateComponent方法(此方法调用render方法生成虚拟 Node,最终调用update方法更新 DOM)。
- 调用render方法将render function渲染成虚拟的Node(真正的 DOM 元素是非常庞大的,因为浏览器的标准就把 DOM 设计的非常复杂。如果频繁的去做 DOM 更新,会产生一定的性能问题,而 Virtual DOM 就是用一个原生的 JavaScript 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多,而且修改属性也很轻松,还可以做到跨平台兼容),render方法的第一个参数是createElement(或者说是h函数),这个在官方文档也有说明。
- 生成虚拟 DOM 树后,需要将虚拟 DOM 树转化成真实的 DOM 节点,此时需要调用update方法,update方法又会调用pacth方法把虚拟 DOM 转换成真正的 DOM 节点。需要注意在图中忽略了新建真实 DOM 的情况(如果没有旧的虚拟 Node,那么可以直接通过createElm创建真实 DOM 节点),这里重点分析在已有虚拟 Node 的情况下,会通过sameVnode判断当前需要更新的 Node节点是否和旧的 Node 节点相同(例如我们设置的key属性发生了变化,那么节点显然不同),如果节点不同那么将旧节点采用新节点替换即可,如果相同且存在子节点,需要调用patchVNode方法执行 DIFF 算法更新 DOM,从而提升 DOM 操作的性能。
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
......
vm._self = vm
// 初始化生命周期(找到第一个非抽象父级,将当前实例添加到父实例的 $children 属性里,并设置当前实例的 $parent 为父实例,在当前实例上设置一些属性)
initLifecycle(vm)
// 初始化事件中心(主要调用updateComponentListeners,主要作用就是将绑定在组件上的事件,保存至vm._events属性中)
initEvents(vm)
// 初始化渲染(主要定义了vm的_c和$createElement方法)
initRender(vm)
callHook(vm, 'beforeCreate')
// 初始化注入
initInjections(vm) // resolve injections before data/props
// 初始化状态
// 初始化 props、data、methods、watch、computed 等属性
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
......
}
}
``
响应式流程:
- 在init的时候会利用Object.defineProperty方法(不兼容 IE8)监听Vue实例的响应式数据的变化从而实现数据劫持能力(利用了 JavaScript 对象的访问器属性get和set,在未来的 Vue3 中会使用 ES6 的Proxy来优化响应式原理)。在初始化流程中的编译阶段,当render function被渲染的时候,会读取Vue实例中和视图相关的响应式数据,此时会触发getter函数进行依赖收集(将观察者Watcher对象存放到当前闭包的订阅者Dep的subs中),此时的数据劫持功能和观察者模式就实现了一个 MVVM 模式中的 Binder,之后就是正常的渲染和更新流程。
- 当数据发生变化或者视图导致的数据发生了变化时,会触发数据劫持的setter函数,setter会通知初始化依赖收集中的Dep中的和视图相应的Watcher,告知需要重新渲染视图,Wather就会再次通过update方法来更新视图。
生命周期钩子 | 组件状态 | 组件状态
----|---|---
beforeCreate | 实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据 | 常用于初始化非响应式变量
created | 实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到$el属性,$ref属性内容为空数组 | 常用于简单的ajax请求,页面的初始化
beforeMount | 在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 | –
mounted | 实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问 | 常用于获取VNode信息和操作,ajax请求
beforeupdate | 响应式数据更新时调用,发生在虚拟DOM打补丁之前 | 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器
updated | 虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作 | 避免在这个钩子函数中操作数据,可能陷入死循环
beforeDestroy | 实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作
destroyed | 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | -
注意:
created阶段的ajax请求与mounted请求的区别:
1. 前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态
1. 接口请求一般放在mounted中,但需要注意的是服务端渲染时不支持mounted,需要放到created中。
父子组件生命周期:
1. 仅当子组件完成挂载后,父组件才会挂载
2. 当子组件完成挂载后,父组件会主动执行一次beforeUpdate/updated钩子函数(仅首次)
--father--beforeCreate --father--created --father--beforeMount --child--beforeCreate --child--created --child--beforeMount --child--mounted --father--mounted --father--beforeUpdate --father--update
3. 父子组件在data变化中是分别监控的,但是在更新props中的数据是关联的(可实践)
4. 销毁父组件时,先将子组件销毁后才会销毁父组件
--father--beforeDestory --child--beforeCreate --child--destoryed --father--destoryed
- 组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
- 组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
###### 加载渲染过程
> 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
###### 子组件更新过程
> 父beforeUpdate->子beforeUpdate->子updated->父updated
###### 父组件更新过程
> 父 beforeUpdate -> 父 updated
###### 销毁过程
> 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
何时需要使用beforeDestroy
1. 清除自己定义的定时器
2. 解除事件绑定 scroll
3. 当前页面使用了$on 需要销毁前解绑
转载至:[阿里前端面试](https://juejin.im/post/5e6ebfa86fb9a07ca714d0ec#heading-47)