当前位置: 首页 > news >正文

阳谷做网站推广/网站seo课程

阳谷做网站推广,网站seo课程,自学做视频网站,动易网站安装vue实例生命周期与生命周期钩子‍每个 Vue 实例在被创建时都会经过一系列的初始化过程。例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。为了让开发者在Vue实例生命周期的不同阶段有机会插入自己的代码逻辑,vue提供了一…

89fda33fab2f7bb8ef5ab3d7bb15dce6.png

vue实例生命周期与生命周期钩子

每个 Vue 实例在被创建时都会经过一系列的初始化过程。例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。

为了让开发者在Vue实例生命周期的不同阶段有机会插入自己的代码逻辑,vue提供了一种叫做生命周期钩子的函数。主要的生命周期钩子如下:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • activated
  • deactivated
  • beforeDestroy
  • destroyed

官方提供的vue实例生命周期示意图很好地说明了整个过程。大家可以参考这张图来阅读本文。

d65c0ae9b88d8e8ae235369e842a6111.png

初始化

执行new Vue()后,将进入实例初始化阶段。这个阶段会触发两个钩子:beforeCreatecreated。看下主要代码:

 // Vue构造函数
function Vue (options) {// 实例初始化this._init(options)
}Vue.prototype._init = function (options?: Object) {const vm: Component = this// a uid 为每个vue实例分配一个唯一的idvm._uid = uid++vm._isVue = true//...省略部分代码if (options && options._isComponent) {// 合并组件选项对象initInternalComponent(vm, options)} else {// 合并vue选项对象vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)}vm._self = vminitLifecycle(vm)initEvents(vm)initRender(vm)callHook(vm, 'beforeCreate')initInjections(vm) // resolve injections before data/propsinitState(vm)initProvide(vm) // resolve provide after data/props// 初始化完成后,调用created钩子函数callHook(vm, 'created')//...省略部分代码// 如果指定了挂载元素,则执行挂载逻辑if (vm.$options.el) {vm.$mount(vm.$options.el)}}

beforeCreate

_init方法中可以看到,vue会将用户所定义的选项对象与vue构造函数上所预先定义的静态选项对象进行合并。

调用initLifecycle函数为实例初始化生命周期有关的属性。例如初始化vue实例_isMounted_isDestroyed_inactive等属性的值。

调用 initEvents 函数为实例初始化事件相关的属性。例如初始化vue实例的_events_hasHookEvent等属性的值。

调用initRender函数为实例初始化渲染相关的属性。例如初始化vue实例的 _vnode_c()$slots等属性的值。

由此可知,beforeCreate钩子被调用时,已经初始化完成了lifecycle、render、event;但尚未处理data、props、methods、provide/inject、watch、computed等。

created

从代码中能看到,created 钩子被调用时,先后执行了initInjectionsinitStateinitProvide。这几个函数主要是将data、props、inject、methods、computed、watch中所定义的属性使用defineProperty代理到vue实例上。同时会把data、props、inject、computed、watch中的属性全部转换成响应式的。经过这些处理,后面当我们在实例中修改属性值的时候,就会自动触发页面的重绘了。

由此可知,created钩子被调用时,完成了实例的初始化,实例属性也具备了响应式的能力。但尚未开始DOM元素的挂载。一般在钩子里常见的操作是异步向后端获取数据。

DOM 元素挂载

实例初始化完成后,会调用$mount开始DOM元素挂载。这个阶段会触发两个钩子函数:beforeMountmounted

// src/platforms/web/entry-runtime-with-compiler.jsVue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {el = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating)
}// src/platforms/web/runtime/index.js
// 覆写$mount方法
const mount = Vue.prototype.$mountVue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {el = el && query(el)const options = this.$optionsif (!options.render) {let template = options.template// 选项中指定了templateif (template) {if (typeof template === 'string') {// 如果值以 # 开始,则它将被用作选择符,并使用匹配元素的 innerHTML 作为模板if (template.charAt(0) === '#') {template = idToTemplate(template)if (process.env.NODE_ENV !== 'production' && !template) {warn(`Template element not found or is empty: ${options.template}`,this)}}} else if (template.nodeType) {// 虽然在文档中并未说明,但template还可以指定一个DOM元素作为模板template = template.innerHTML} else {if (process.env.NODE_ENV !== 'production') {warn('invalid template option:' + template, this)}return this}} else if (el) {// 选项中指定了eltemplate = getOuterHTML(el)}// 将模板解析成render函数if (template) {const { render, staticRenderFns } = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.staticRenderFns = staticRenderFns}}// ...省略部分代码return mount.call(this, el, hydrating)
}// src/core/instance/lifecycle.js
export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean
): Component {// 将对DOM元素的引用保存到$elvm.$el = el// ...省略部分代码// 调用beforeMount前需要执行模板编译逻辑callHook(vm, 'beforeMount')let updateComponent = () => {vm._update(vm._render(), hydrating)}new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate')}}}, true /* isRenderWatcher */)hydrating = falseif (vm.$vnode == null) {// 标记为已挂载vm._isMounted = true// 触发mounted事件callHook(vm, 'mounted')}return vm
}

beforeMount

我们知道vue需要render函数来生成vnode。但是在实际开发中,基本都是通过templateel来指定模板,很少直接提供一个render函数。因此在触发beforeMount前,vue最重要的一个工作就是将HTML模板编译成render函数。beforeMount钩子函数被调用时,我们尚不能访问DOM元素。

mounted

每个vue实例都会对应一个render watcherrender watcher 会创建vnode(通过_render方法),并对vnode进行diff后,创建或者更新DOM元素(通过_update方法)。对于初次渲染来说,当创建完DOM元素后,把DOM树的根元素插入到body中,然后触发mounted钩子函数。此时,在钩子函数中可以对DOM元素进行操作了。

更新

实例完成初始化和挂载之后,如果由于用户的交互导致实例的状态发生了变化,实例将进入更新阶段。例如在代码中执行 this.msg = 'update msg',vue实例需要更新DOM元素。

实例的更新是异步的。前面提到过,render watcher 会负责调度程序创建vnode、创建更新DOM元素。当数据发生变化后,vue不会立即启动DOM的更新,而是先把实例对应的render watcher添加到一个队列中。然后在下一个事件循环中,统一执行DOM更新,清空队列。也就是调用下面代码中的flushSchedulerQueue函数。

此阶段会触发的钩子是:beforeUpdateupdated

/*** 清空所有的队列并执行watcher的更新逻辑*/
function flushSchedulerQueue () {flushing = truelet watcher, id// 队列按照watcher的id升序排序,目的是确保:// 1. 组件总是从父向子进行更新// 2. 用户创建的watcher先于渲染watcher更新// 3. 如果组件在父组件的watcher运行时被销毁,该组件的watcher可以跳过处理queue.sort((a, b) => a.id - b.id)for (index = 0; index < queue.length; index++) {watcher = queue[index]// 调用watcher.before,触发beforeUpdate钩子if (watcher.before) {watcher.before()}id = watcher.idhas[id] = null// 更新domwatcher.run()}const activatedQueue = activatedChildren.slice()const updatedQueue = queue.slice()resetSchedulerState()// 触发activated钩子callActivatedHooks(activatedQueue)// 触发updated钩子callUpdatedHooks(updatedQueue)
}// 触发updated钩子
function callUpdatedHooks (queue) {let i = queue.lengthwhile (i--) {const watcher = queue[i]const vm = watcher.vmif (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {callHook(vm, 'updated')}}
}

beforeUpdate

在初始化阶段,vue实例中的数据(data/props/computed/watch)已经被处理成响应式的了。任何对数据的访问(getter)都会被watcher添加为依赖,任何对数据的变更,都将触发对数据有依赖的watcher的更新。watcher在更新之前会调用watcher.before方法,该方法是在挂载阶段创建watcher实例的时候定义的。watcher.before中会触发beforeUpdate钩子。此时vue实例只是确定了最终需要更新的数据,尚未真正开始更新。

updated

从代码中可以看到,在触发updated钩子前,vue实例需要对DOM元素进行更新。更新的过程是异步的。具体方式通过实例的render watcher执行run方法。该方法会去调用我们在挂载阶段介绍的updateComponent函数。从而重新创建vnode,并进行vnode的diff操作后更新DOM元素。

我们还注意到,代码中调用了callActivatedHooks函数,该函数用来触发activated钩子。下文我们再做说明,这里不展开。

销毁

当vue实例的$destroy方法时,实例将进入销毁阶段。此时触发的钩子是:beforeDestorydestroyed

  // 销毁Vue实例Vue.prototype.$destroy = function () {const vm: Component = this// 避免重复执行销毁操作if (vm._isBeingDestroyed) {return}// 触发实例的beforeDestroy钩子callHook(vm, 'beforeDestroy')vm._isBeingDestroyed = true// 将实例从其父实例中的$chilren中移除(断开与父实例的联系)const parent = vm.$parentif (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {remove(parent.$children, vm)}//  销毁实例的 watchersif (vm._watcher) {vm._watcher.teardown()}let i = vm._watchers.lengthwhile (i--) {vm._watchers[i].teardown()}if (vm._data.__ob__) {vm._data.__ob__.vmCount--}vm._isDestroyed = true// 销毁指令、ref等vm.__patch__(vm._vnode, null)// 触发destroyed事件callHook(vm, 'destroyed')// 移除实例的所有事件监听器vm.$off()if (vm.$el) {vm.$el.__vue__ = null}// 释放循环引用(#6759)if (vm.$vnode) {vm.$vnode.parent = null}}
}

beforeDestroy

从代码中可以看到,$destroy被调用时,在执行实际的销毁动作前触发beforeDestroy。此时,由于并未开始执行实际的销毁代码,实例及DOM元素仍可正常访问。

destroyed

从代码中可以看到,销毁操作主要包括以下几点:清空所有的watcher、删除所有的指令、删除DOM元素并关闭DOM上的所有的事件、断开与父实例之间的关系、将实例标记成已销毁状态。此时实例已经被销毁,已经无法访问实例的属性和DOM元素了。

keep-alive包裹下的组件的生命周期钩子

下面的两个钩子只有当组件包裹在keep-alive时才会触发。

activated

HTML标签和组件标签在vue内部实现中都有对应的vnode,组件vnode在设计上与普通的HTML标签的vnode有所不同。例如组件vnode上包含initprepatchinsertdestroy等钩子。这些钩子在组件实例初始化、更新和销毁等不同的阶段进行调用。

// 组件vnode的钩子
const componentVNodeHooks = {// ...省略其他钩子insert (vnode: MountedComponentVNode) {const { context, componentInstance } = vnodeif (!componentInstance._isMounted) {componentInstance._isMounted = true// 触发组件的mounted钩子callHook(componentInstance, 'mounted')}if (vnode.data.keepAlive) {if (context._isMounted) {queueActivatedComponent(componentInstance)} else {activateChildComponent(componentInstance, true /* direct */)}}}
}// 触发activated钩子
export function activateChildComponent (vm: Component, direct?: boolean) {// ...省略部分代码if (vm._inactive || vm._inactive === null) {vm._inactive = falsefor (let i = 0; i < vm.$children.length; i++) {activateChildComponent(vm.$children[i])}// 调用实例的activated钩子callHook(vm, 'activated')}
}

与根实例一样,keep-alive包裹下的组件实例初始化时同样会依次经历初始化阶段、挂载阶段,但在挂载阶段之后会调用组件vnode的insert钩子,insert钩子会触发组件实例的activated钩子。因为insert 是在组件实例挂载完成后调用的,所以mounted的触发早于activated

当组件切换回来的同时,组件的数据发生了变化,此时组件将进入更新阶段,意味着将会依次触发beforeUpdateupdated钩子。

那么问题来了,activatedbeforeUpdateupdated钩子哪个先触发呢?

答案是先触发beforeUpdate,再触发activated,最后触发updated

我们回顾下前面更新阶段的代码:

// 触发activated钩子
callActivatedHooks(activatedQueue)
// 触发updated钩子
callUpdatedHooks(updatedQueue)//...省略部分代码// 触发activated钩子
function callActivatedHooks (queue) {for (let i = 0; i < queue.length; i++) {queue[i]._inactive = trueactivateChildComponent(queue[i], true /* true */)}
}

可以看到在函数flushSchedulerQueue中,callActivatedHooks函数用来触发activated。而callActivatedHooks的顺序在callUpdatedHooks前面,所以activated钩子的触发早于updated钩子。

deactivated

const componentVNodeHooks = {// ... 省略其他钩子destroy (vnode: MountedComponentVNode) {const { componentInstance } = vnodeif (!componentInstance._isDestroyed) {// 未被keep-alive包裹销毁组件if (!vnode.data.keepAlive) {componentInstance.$destroy()} else {// 被keep-alive包裹deactivateChildComponent(componentInstance, true /* direct */)}}}
}export function deactivateChildComponent (vm: Component, direct?: boolean) {if (direct) {vm._directInactive = trueif (isInInactiveTree(vm)) {return}}if (!vm._inactive) {vm._inactive = truefor (let i = 0; i < vm.$children.length; i++) {deactivateChildComponent(vm.$children[i])}// 调用实例的deactivated钩子callHook(vm, 'deactivated')}
}

当组件被切换到其他的组件时,会调用组件vnode的destroy钩子,在组件被keep-alive包裹的情况下,只会将组件对应的DOM元素从DOM树删除,但不会销毁组件实例,此时会调用deactivateChildComponent 从而触发deactivated钩子。组件在没有被keep-alvie包裹的情况下,才会调用$destroy销毁组件实例,触发beforeDestroydestroyed钩子。

总结

关于vue实例的生命周期,官网讲解的其实是比较简单易懂的。本文主要还是希望能从源码的角度,进一步让大家理解每个生命周期做了什么处理。更好地理解钩子的触发时机及先后顺序。欢迎大家留言讨论~

推荐阅读

  • Vue执行流程解析
  • vue编译过程分析
  • vue渲染过程解析-VDOM &DOM
  • vue组件实现原理解析
  • keep-alive是如何保持组件状态的
http://www.lbrq.cn/news/1537615.html

相关文章:

  • 中国最大的建站网站/东方网络律师团队
  • 公司网站后台维护怎么做/站长工具网站
  • 移动网站打不开/搜索引擎优化的办法有哪些
  • 婚纱摄影网站建设方案/竞价托管的注意事项
  • 给公司做网站/找关键词的三种方法
  • 网站静态化设计/电商平台链接怎么弄
  • 做网站有什么框架/网络营销策划方案怎么写
  • 深圳设计网站推荐/360优化大师app
  • access做网站数据方法/营销一体化平台
  • 哪个网站做的系统好/杭州云优化信息技术有限公司
  • 山东网站建设哪家公司好/seo培训学院
  • 哪个网站有ae免费模板/百度官网认证多少钱
  • 凡科建设网站安全吗/seo日常工作都做什么的
  • 音视频娱乐网站开发商/营销型网站建设方案
  • 网站代码修改/网络营销整合推广
  • 网站换名称域名/产品推广ppt范例
  • 郑州网站建设九零后/优化大师客服
  • 帮别人做网站犯法吗/网站百度权重
  • 什么样的网站流量容易做/帮我搜一下长沙做网络销售
  • 租外国服务器做的网站要备案吗/广东全网推广
  • 东莞网站设计哪家强/西安seo和网络推广
  • 郑州网站建设喝彩科技/百度手机助手下载安装
  • 电子商务网站设计与管理/网络营销是以什么为中心
  • 建设银行官方招聘网站/百度关键词的费用是多少
  • 版面设计图/seo点击软件手机
  • wordpress 极验/网站seo视频教程
  • 本地做网站顺序/网络seo是什么意思
  • 做网站只解析www的会怎么样/廊坊网站建设优化
  • 东莞网站建设方案推广/广州网站建设
  • 小众电商平台/白杨seo教程
  • Linux Shell 常用操作与脚本示例详解
  • Tomcat 性能优化终极指南
  • MCP(模型上下文协议):是否是 AI 基础设施中缺失的标准?
  • LeetCode 100 -- Day2
  • 计算机大数据毕业设计推荐:基于Spark的气候疾病传播可视化分析系统【Hadoop、python、spark】
  • 数据库:表和索引结构