vue created 调用方法_浅浅谈vue.js
1、Vue的生命周期在Vue官方文档中生命周期函数有如下定义每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。简单的说,生命周期就是Vue实例在某个时间点下会自动执行的函数。为了加深对Vue生命周期函数的理解,
1、Vue的生命周期
在Vue官方文档中生命周期函数有如下定义
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做 生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。
简单的说,生命周期就是Vue实例在某个时间点下会自动执行的函数。为了加深对Vue生命周期函数的理解,接下来讲从源码的角度对Vue生命周期进行分析。
Vue实例化的入口为/src/core/instance/index.js,可以看到vue实际上是一个利用ES5语法定义得到类。在new vue时还会执行一系列的初始化函数,他们的主要作用是往vue的原型链上挂载一系列的方法。
1、beforeCreate和created(/src/core/instance/state.js)
beforeCreated和created都是在实例化Vue的阶段在_init方法中执行的,由于beforeCreated和created的调用是在initState前后,而initState的作用是初始化props、data、methods、watch和compute等属性的。所以在initState之前调用的beforeCreated是取不到这些属性的,而在initState之后调用的created则可以。(虽然一般用不到这种极限操作,不过实际在initState中这些属性的初始化也有先后顺序的,props->methods->data->computed->watch)
2、beforeMount和mounted(/src/core/instance/lifecycle.js)
beforeMount和mounted的调用发生在DOM挂载的前后,在执行vm.render函数渲染Vnode之前执行了beforeMount,在执行完vm.update把VNode patch到真实的DOM上之后mounted钩子函数。也就是说在beforeMount之前Vue就会找到对应的template并将其编译成render函数,在mounted执行前将其挂载到DOM上。所以再mounted钩子函数中可以通过DOM API获取DOM节点,同时可以利用$ref属性访问到。
3、beforeUpdate和updated(/src/core/instance/lifecycle.js和/src/core/observer/scheduler.js)
beforeUpdate和updated的钩子函数执行时机都应该是在数据更新的时候,如下图所示在执行beforeUpdate和updated前会先判断DOM是否挂载,也就是说在第一次数据变化(mounted前)是不会执行befoUpdate和updated函数的。然后在flushSchedulerQueue 函数中对DOM进行重新渲染,在渲染完成后调用updated钩子函数。
4、beforeDestory和destoryed(/src/core/instance/lifecycle.js)
beforeDestory和destoryed两者的执行时机都是在组件销毁的阶段。在组件销毁时都会调用$destory方法,beforeDestory的执行时机是$destory最开始的地方,之后执行了一系列的销毁动作,包括删除掉所有的self,_watch,数据引用等,执行完后调用了destoryed。所以beforeDestory是在实例销毁前调用的,在这一步Vue实例还完全可用。
5、activated和deactivated(/src/core/instance/lifecycle.js)
activated和deactivated是专门为keep-alive组件定制的钩子函数,由于组件一旦被keep-alive缓存,那么再次渲染时就不会执行created和mounted函数,但有时又需要在再次渲染时做一些事,所以就有了这个钩子函数。
5、总结各钩子函数的使用场景
1)beforecreate
完成实例初始化,初始化非响应式变量
this指向创建的实例
可以在这加个loading事件
data computed watch methods上的方法和数据均不能访问
2)created
实例创建完成
完成数据(data props computed)的初始化 导入依赖项
可访问data computed watch methods上的方法和数据
未挂载DOM,不能访问$el,$ref为空数组
可在这结束loading,还做一些初始化,实现函数自执行
可以对data数据进行操作,可进行一些请求,请求不易过多,避免白屏时间太长
若在此阶段进行的 DOM 操作一定要放在 Vue.nextTick() 的回调函数中
3)berofeMount
有了el,编译了template|/outerHTML
能找到对应的template,并编译成render函数
4)mounted
完成创建vm.$el,和双向绑定
完成挂载DOM 和渲染;可在mounted钩子对挂载的dom进行操作
即有了DOM 且完成了双向绑定 可访问DOM节点,$ref
可在这发起后端请求,拿回数据,配合路由钩子做一些事情
可对DOM 进行操作
5)beforeUpdate
数据更新之前
可在更新前访问现有的DOM,如手动移除添加的事件监听器
6)updated
完成虚拟DOM的重新渲染和打补丁
组件DOM 已完成更新
可执行依赖的dom 操作
注意:不要在此函数中操作数据,会陷入死循环的
7)activated
在使用vue-router时有时需要使用<keep-alive></keep-alive>来缓存组件状态,这个时候created钩子就不会被重复调用了,
如果我们的子组件需要在每次加载的时候进行某些操作,可以使用activated钩子触发
8)deactivated
keep-alive 组件被移除时使用
9)beforeDestroy
在执行app.$destroy()之前
可做一些删除提示,如:你确认删除XX吗?
可用于销毁定时器,解绑全局时间 销毁插件对象
10)destroyed
当前组件已被删除,监听事件、组件、事件、子实例也被销毁,这时组件已经没有了,无法操作里面的任何东西了。
2、Vue响应式
在Vue官方文档中是这么描述响应式的
Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题。
其实简单点说Vue的响应式就是当数据发生变化后,会重新对页面渲染。但是这又是怎么实现的呢。接下来就会根据vue源码对,响应式做一下简单的分析。
响应式的研究对象是数据,而在前面Vue生命周期的描述中我们知道,数据的初始化是在initState方法中进行的,所以我们再来看看initState方法。在initState方法中会依次调用initProps,initMethods,initData,initComputed,initWatch等方法,完成数据和方法的初始化工作。
响应式中我们更关心数据的变化,所以先来看看initData方法。在initData方法中首先判断data是否是对象,之后用遍历methods和props,把不同名的data代理到vm上(明白同名为啥会报错了吧)。最后利用observe方法来监听data的变化。
来看看observe相关的逻辑,Observer函数将在传入的value对象中添加一个”__ob__”属性,将自己赋给这个属性。如果value是数组调用observeArray函数,来对每个元素单独执行observe。不然对value执行walk,在walk中执行defineReactive函数。
在definereactivity中会调用defineProperty方法,利用defineProperty中的get和set属性,实现在取值和赋值时添加一些操作。给value设置了getter和setter,在getter中进行依赖收集,在setter中派发更新。收集依赖的目的是为了当这些响应式数据发送变化,触发它们的 setter 的时候,能知道应该通知哪些订阅者去做相应的逻辑处理,我们把这个过程叫派发更新,这是一个非常经典的观察者设计模式的实现。不过这里就不对依赖收集和派发更新的具体实现进行叙述了,不然又是一个很大的篇幅。
通过defineProperty实现响应式,但这其中还是存在一些问题无法检测到对象属性的添加或删除,这是因为 Vue 通过Object.defineProperty来将对象的key转换成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性。如果是删除属性,我们可以用vm.$delete实现,那如果是新增属性,Vue则提供了set (/src/core/observer/index.js)方法供我们使用。
对于其他的一些数组的操作Vue则重写了这些方法,所以并非这些函数碰巧可以在vue中能用,而是vue对此做了一些操作。能够直接使用的数组函数也只有这些了。
3、Vue模板挂载与渲染
要分析Vue模板的挂载与渲染就要知道模板是什么,其实从本质上来说模板的本质就是字符串(早期没有模板引擎时,对后端返回的数据就是利用字符串拼接的方式来产生HTML标签的,个人觉得这应该就是模板的前身了)。但是模板这种字符串又和我们传统意义上的字符串有些不同,他有for循环有if判断,所以可以说模板是一种有逻辑的字符串。但是字符串就是一种数据类型,怎么会有逻辑呢?所以这里就需要对字符串进行一些转化了,由于前端三剑客中只有JS是图灵完备的语言,所以这里就需要把模板转化成js函数。也就是生成Vue的render函数。
在vue-cli生成的项目中,在main.js函数里有如下代码,其中$mount函数就是生成render函数的关。
由于Vue值有很多平台的兼容性代码,所以也定义了很多套的$mount函数,这里我们研究平时较为常用的compiler版本入口为src/platforms/web/entry-runtime-withcompiler.js 。
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
$mount方法支持传入 2 个参数,第一个是el,它表示挂载的元素,可以是字符串,也可以是 DOM 对象,如果是字符串在浏览器环境下会调用query方法转换成 DOM 对象的。第二个参数是和服务端渲染相关,在浏览器环境下我们不需要传第二个参数。$mount之后又对el做了限制,Vue 不能挂载在body、html这样的根节点上。接下来如果没有定义render方法,则会把el或者template字符串转换成render方法。在 Vue 2.0 中,所有 Vue 的组件的渲染最终都需要render方法,这个过程相当于一个在线编译的过程是利用compileToFunctions实现的。compileToFunctions实现有点复杂,这里就不再多说。
这里的$mount方法实际上是对原型链上的$mount方法进行了复用和改写,原型链上的$mount方法定义在src/platform/web/runtime/index.js 中,可以看到这里的$mount方法应该是一个简化版,最后相当于执行了mountComponent 方法。
mountComponent 方法定义在src/core/instance/lifecycle.js中
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
mountComponent 核心就是先调用 vm._render 方法先生成虚拟 Node,再实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法,最终调用 vm._update 更新 DOM。Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数。函数最后判断为根节点的时候设置vm._isMounted为true, 表示这个实例已经挂载了,同时执行mounted钩子函数。至此模板的挂载和渲染完成。
最后总结一下整个模板挂载和渲染的过程
1、确定模板的挂载点,并确保这个确保这个模板挂载点不为html,body这类跟节点(因为模板会把这个挂载点替换掉,把html或body替换了可能会出现问题)。
2、vue中有两种渲染方式,一种是通过template模板字符串,另一个是通过手写render函数。对于template模板则需要先调用compileToFunctions方法,将模板字符串转化为抽象语法树(AST),最后转化为render渲染函数,参与实例的挂载。而对于手写的render函数则可以绕过编译阶段,直接调用$mount方法。
3、无论是template模板还是手写render函数,最终都将进入mountComponent过程,这个阶段会实例化一个渲染watcher,渲染watcher的回调函数有两个执行时机,一个是在初始化时执行,另一个是当vm实例检测到数据发生变化时会再次执行回调函数。
4、回调函数是执行updateComponent的过程,这个方法有两个阶段,一个是vm._render,另一个是vm._update。 vm._render会执行前面生成的render渲染函数,并生成一个Virtual Dom tree,而vm._update会将这个Virtual Dom tree转化为真实的DOM节点。
更多推荐
所有评论(0)