目录
第14章 生命周期
每个Vue.js
实例在创建时都要经历一系列初始化,例如设置数据监听,编译模板,将实例挂载到DOM并在数据变化时更新DOM等。同时,也会有运行一些叫做生命周期钩子的函数,这给了我们在不同阶段添加自定义代码的机会。
14.1 生命周期图示
Vue.js
可以分为4个阶段:初始化阶段、模板编译阶段、挂载阶段、卸载阶段。
14.1.1 初始化阶段
new Vue()
到created
之间的阶段叫做初始化阶段,这个阶段主要目的就是在Vue.js
实例上初始化一些属性,事件以及响应式数据,如props
、methods
、data
、computed
、watch
、provide
和inject
等。
在
Vue.js
的整个生命周期中,初始化实例属性是第一步,需要实例化的属性既有Vue.js
内部需要用到的属性,也有提供给外部使用的属性。以$
开头的属性是提供给用户使用的外部属性,以_
开头的属性是提供给内部使用的内部属性。
初始化 provie/inject
inject
和provide
选项需要一起使用,它们运行祖先组件向其所有子孙后代注入依赖,并在其上下游关系成立的时间里始终生效(不论组件层次有多深)
初始化 props
props
的实现原理大体上是这样的:父组件提供数据,子组件通过props
字段选择自己需要哪些内容,Vue.js
内部通过子组件的props
选项将需要的数据筛选出来之后添加到子组件的上下文中。
初始化 methods
初始化methods
时,只需要循环选项中的methods
对象,并将每个属性依次挂载到vm
上即可。
初始化 data
简单来说,data
中的数据最终会保存到vm. data
中。然后在vm
上设置一个代理,使得通过vm.x
可以访问到vm._data
中的×
属性。最后由于这些数据并不是响应式数据,所以需要调用observe
函数将data
转换成响应式数据。于是,data
就完成了初始化。
初始化 computed
我们知道计算属性的结果会被缓存,且只有在计算属性所依赖的响应式属性或者说计算属性的返回值发生变化时才会重新计算。那么,如何知道计算属性的返回值是否发生了变化?
这其实是结合watcher
的dirty
属性来分辨的:当dirty
属性为true
时,说明需要重新计算“计算属性”的返回值;当dirty
属性为false
时,说明计算属性的值并没有变,不需要重新计算。
当计算属性中的内容发生变化后,计算属性的watcher
与组件的watcher
都会得到通知。计算属性的watcher
会将自己的dirty
属性设置为true
,当下一次读取计算属性时,就会重新计算一次值。然后组件的watcher
也会得到通知,从而执行render
函数进行重新渲染的操作。由于要重新执行render
函数,所以会重新读取计算属性的值,这时候计算属性的watcher
已经把自己的dirty
属性设置为true
,所以会重新计算一次计算属性的值,用于本次渲染。
这导致一个问题:如果计算属性中用到的状态发生了变化,但最终计算属性的返回值并没有变,这是计算属性依然会认为自己的返回值变了,组件也会重新走一遍渲染流程,只不过最终由于虚拟DOM
的Diff
中发现没有变化,所有在视觉上并不会发现UI
有变化,其实渲染函数会被执行。
解决方法:组件的Watcher
不再观察数据的变化,只是观察计算属性的Watcher
,然后计算属性主动通知组件是否需要进行渲染操作。
初始化 watch
初始化的最后一步是初始化watch
。
只需要循环watch
选项,将对象中的每一项依次调用vm.$watch
方法来观察表达式或computed
在Vue.js
实例上的变化即可。
14.1.2 模板编译阶段
created
钩子函数与beforeMount
钩子函数之间的阶段是模板编译阶段,这个阶段主要目的是将模板编译为渲染函数。
14.1.3 挂载阶段
beforeMount
钩子函数到mounted
钩子函数之间是挂载阶段。
这个阶段,Vue.js
会将其实例挂载到DOM元素上,通俗地讲,就是将模板渲染到指定DOM元素中。在挂载的过程中,Vue.js
会开启Watcher
来持续追踪依赖的变化,当数据(状态)发生变化时,Watcher
会通知虚拟DOM重新渲染视图,并且会在渲染视图前触发beforeUpdate
钩子函数,渲染完毕后触发updated
钩子函数。
14.1.4 卸载阶段
调用vm.$destroy
方法后,Vue.js的生命周期会进入卸载阶段。在这个阶段,Vue.js
会将自身从父组件中删除,取消实例上所有依赖的追踪并且移除所有的事件监听器。
14.2 从源码角度了解生命周期
new Vue() 被调用时发生了什么
在new Vue()
之前,首先进行安全检测,在非生产环境下,如果没有使用new
来调用Vue
,则会在控制台抛出错误警告我们:Vue
是构造函数,应该使用new
关键字来调用,然后调用this._init(options)
来执行生命周期的初始化流程(在生命周期钩子beforeCreate
被触发之前执行了initLifecycle
、initEvents
和 initRender
。在初始化的过程中,首先初始化事件与属性.然后触发生命周期钩子beforeCreate
。随后初始化 provide/inject
和状态,这里的状态指的是props、methods、data、computed 以及 watcha
接着触发生命周期钩子created
。最后,判断用户是否在参数中提供了el
选项,如果是,则调用vm .$mount
方法,进入后面的生命周期阶段。)
14.3 errorCaptured 与错误处理
errorCaptured
钩子函数的作用是捕获来自子孙组件的错误,此钩子函数会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串然后此钩子函数可以返回false
,阻止该错误继续向上传播。
其传播规则如下:
1、默认情况下,如果全局的 config.errorHandler
被定义,那么所有的错误都会发送给它,这样这些错误可以在单个位置报告给分析服务。
2、如果一个组件继承的链路或其父级从属链路中存在多个errorCaptured
钩子,则它们将会被相同的错误逐个唤起。
3、如果errorCaptured
钩子函数自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的config.errorHandler
。
4、一个errorCaptured
钩于函数能够返回false
来阻止错误继续向上传播。
errorCaptured
钩子函数触发:
Vue.js
在调用这些函数时,会使用try……catch
语句来捕获有可能发生的错误。当错误发生并且被try……catch
语句捕获后,Vue.js
会使用handleError
函数来处理错误,该函数会依次触发父组件链路上的每一个父组件中定义的errorCaptured
钩子函数。也就是说errorCaptured
的错误传播是在handleError
函数中实现的。
如有写的不对的地方,欢迎来讨论。