Vue2.x 源码 - render 函数生成 VNode

上一篇:Vue2.x 源码 - 编译过程(compile)

Vue 的渲染过程:
在这里插入图片描述

上一篇看了一下编译,编译之后返回了 render 函数,那么 render 又是如何生成 vnode 的呢?本文来看一下。

在 Vue 官方文档中介绍 render 函数 的第一个参数是 createElement 用来创建 VNode;

<div id="app">
	{{ message }} 
</div>

render 函数表示:

render:function(createElement){
	return createElement('div',{
		attrs:{
			id:'app'
		}
	}, this.message)
}

再来看看 Vue 初始化的时候绑定在实例原型上的 _render 方法,在 src/core/instance/render.js 文件里:

 //执行render函数生成vnode,以及异常处理
  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options
    //创建子组件的 vnode 执行normalizeScopedSlots
    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }
    // 设置父vnode
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      currentRenderingInstance = vm
       // 执行 render 函数,生成 vnode
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // render函数出错
      // 开发环境渲染错误信息,生产环境返回之前的 vnode,以防止渲染错误导致组件空白
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }
    // 如果返回的数组只包含一个节点,转化一下
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // 如果渲染函数出错,返回空vnode
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // 设置父节点
    vnode.parent = _parentVnode
    return vnode
  }

这段代码主要是 vnode = render.call(vm._renderProxy, vm.$createElement) 这段代码拿到 vnode,可以看到传入的参数是 vm.$createElement 方法,这个方法是在 initRender 方法里面传入的 createElement 方法;最后的结果:vnode 是由 createElement 方法返回的;

那么 vnode 到底是什么东西呢?

虚拟DOM(Virtual DOM)这个概念的产生是因为浏览器中频繁的对真实 DOM 进行操作是会产生性能问题的;虚拟 DOM 是用一个原生 js 对象去描述一个真实 DOM 节点;

由于直接操作 DOM 性能低,但是 js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过 diff 算法比对差异进行更新 DOM (减少了对真实 DOM 的操作)。虚拟 DOM 不依赖真实平台环境从而也可以实现跨平台;

在 Vue.js 中,虚拟 DOM 是⽤ VNode 这么⼀个 Class 去描述,它是定义在 src/core/vdom/vnode.js 中的:

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void; // real context vm for functional nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  devtoolsMeta: ?Object; // used to store functional render context for devtools
  fnScopeId: ?string; // functional scope id support
  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag //当前标签名
    this.data = data //当前节点数据
    this.children = children //当前节点的子节点
    this.text = text //当前节点文本
    this.elm = elm //当前虚拟节点对应的真实节点
    this.ns = undefined //当前节点的名字空间
    this.context = context //当前节点的编译作用域
    this.fnContext = undefined //用于功能节点的真实上下文vm
    this.fnOptions = undefined //SSR缓存
    this.fnScopeId = undefined //功能范围id支持
    this.key = data && data.key //节点的key属性,被当作节点的标志,用以优化
    this.componentOptions = componentOptions //组件的option选项
    this.componentInstance = undefined //当前节点对应的组件的实例
    this.parent = undefined //当前节点的父节点
    this.raw = false //是否为原生HTML或只是普通文本
    this.isStatic = false //是否是静态节点
    this.isRootInsert = true //是否作为根节点插入
    this.isComment = false //是否是注释节点
    this.isCloned = false //是否是克隆节点
    this.isOnce = false //是否是v-once指令
    this.asyncFactory = asyncFactory //异步组件的工厂方法
    this.asyncMeta = undefined //异步源
    this.isAsyncPlaceholder = false //是否异步的预赋值
  }
  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}

这里有很多的属性,其中最重要的属性只有几个:tag、data、children 和 key。其余很多属性只是在 Vue 中为适用不同的场景,额外添加的。

createElement 方法

src/core/vdom/create-element.js 文件里:

export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array<VNode> {
  //data是数组或者原始类型,说明当前参数没有传递data,所以需要将参数重载
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  //确定参数使用哪一个 normalization,根据render初始化的时候传入的boolean值决定
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}

对于没有传递 data 的情况这里多说一下,因为很容易误解:第三个参数 data 是可以不传的

//没有传递data
creatElement(this, 'div', 'hello', 1, false);
|
|
//重载后
creatElement(this, 'div', undefined, 'hello', 1, false);

将 data 设置为 undefined,其他的参数向后移动一位,这样形参和实参就能一 一对应起来了;

createElement ⽅法实际上是对 _createElement ⽅法的封装,它允许传⼊的参数更加灵活,在处理这些参数后,调⽤真正创建 VNode 的函数 _createElement

_createElement 函数的入参:

context:VNode 当前上下文环境;
tag:标签,可以是正常的HTML元素标签,也可以是Component组件;
data:VNode的数据,其类型为VNodeData;
children:VNode的子节点;
normalizationType:children子节点规范化类型;

具体代码:

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  //data是响应式数据,创建空vnode
  if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
  //检测data中是否有is属性,是的话tag替换为is指向的内容,处理动态组件
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  // tag如果为空,创建空虚拟节点
  if (!tag) {
    return createEmptyVNode()
  }
  // data 中的key如果定义了必须是string、number、boolean以及 symbol
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    if (!__WEEX__ || !('@binding' in data.key)) {
      warn(
        'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
        context
      )
    }
  }
  // 支持单函数子函数作为默认作用域槽
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  //处理children的两种模式
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  //tag是string
  if (typeof tag === 'string') {
    let Ctor
    //获取tag的名字空间
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    //判断是否是保留的标签
    if (config.isReservedTag(tag)) {
      // tag 上有.native修饰符,发出警告 :v-on的.native修饰符只在组件上有效,但在<${tag}>上使用过
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      //如果是保留的标签则创建一个相应节点
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      //如果tag对应的是组件名,创建组件
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      //未知或未列出的名称空间元素在运行时进行检查,因为当其父元素将子元素规范化时,可能会给它分配一个名称空间
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    //当 render: h => h(App) 时传入的是组件则tag为对象 走此逻辑
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  //vnode是数组直接返回;
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    //不是数组但是不为空,如果有名字空间,则递归所有子节点应用该名字空间,
    if (isDef(ns)) applyNS(vnode, ns)
    //当在槽节点上使用像:style和:class这样的深度绑定时,必须确保父节点重新呈现
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    //为空则创建一个空vnode;
    return createEmptyVNode()
  }
}

通过检测 data 中是否有 is 属性,是的话 tag 替换为 is 指向的内容,处理动态组件;

_createElement主要做两件事情:规范化子节点和创建 VNode 节点,接下来我们围绕这两个方面来详细介绍。

1、规范化子节点

虚拟 DOM 是一个树形结构,每一个节点都应该是 VNode 类型,但是 children 参数又是任意类型的,所以如果有子节点,我们需要把它进行规范化成 VNode 类型,如果没有子节点,那么 children 就是 undefined。下面就看看子节点的规范,在 src/core/vdom/helpers/normalize-children.js文件中:

1、simpleNormalizeChildren

export function simpleNormalizeChildren (children: any) {
  for (let i = 0; i < children.length; i++) {
    if (Array.isArray(children[i])) {
      return Array.prototype.concat.apply([], children)
    }
  }
  return children
}

simpleNormalizeChildren ⽅法调⽤场景是 render 函数当函数是编译⽣成的。理论上编译⽣成的 children 都已经是 VNode 类型的,但这⾥有⼀个例外,就是 functional component 函数式组件 返回的是⼀个数组⽽不是⼀个根节点,所以会通过 Array.prototype.concat ⽅法把整个 children 数组降维,让它的深度只有⼀层。

2、normalizeChildren

export function normalizeChildren (children: any): ?Array<VNode> {
  return isPrimitive(children)
    ? [createTextVNode(children)]
    : Array.isArray(children)
      ? normalizeArrayChildren(children)
      : undefined
}

normalizeChildren ⽅法的调⽤场景有 2 种,⼀个场景是 render 函数是⽤户⼿写的,当 children 只有⼀个节点的时候,Vue.js 从接⼝层⾯允许⽤户把 children 写成基础类型⽤来创建单 个简单的⽂本节点,这种情况会调⽤ createTextVNode 创建⼀个⽂本节点的 VNode;另⼀个场景是 当编译 template、slot 、 v-for 的时候会产⽣嵌套数组的情况,会调⽤ normalizeArrayChildren ⽅法, 接下来看⼀下它的实现:

function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
  const res = []
  let i, c, lastIndex, last
  //遍历children 获取单个节点判断是不是数组是则递归
  for (i = 0; i < children.length; i++) {
    c = children[i]
    if (isUndef(c) || typeof c === 'boolean') continue
    lastIndex = res.length - 1
    last = res[lastIndex]
    //  nested
    if (Array.isArray(c)) {
      if (c.length > 0) {
        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
        // 合并相邻文本节点
        if (isTextNode(c[0]) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
          c.shift()
        }
        res.push.apply(res, c)
      }
    } else if (isPrimitive(c)) {
      if (isTextNode(last)) {
        // 合并相邻的两个text
        res[lastIndex] = createTextVNode(last.text + c)
      } else if (c !== '') {
        // 将primitive转换为vnode
        res.push(createTextVNode(c))
      }
    } else {
      if (isTextNode(c) && isTextNode(last)) {
        //合并相邻文本节点
        res[lastIndex] = createTextVNode(last.text + c.text)
      } else {
        // 嵌套数组子数组的默认键(可能是由v-for生成的)
        if (isTrue(children._isVList) &&
          isDef(c.tag) &&
          isUndef(c.key) &&
          isDef(nestedIndex)) {
          c.key = `__vlist${nestedIndex}_${i}__`
        }
        res.push(c)
      }
    }
  }
  return res
}

nestedIndex 表示嵌套索引,循环 children 节点,判断每一个子节点是不是数组类型,是则递归调用 normalizeArrayChildren 方法;如果是基础类型调用 createTextVNode 方法转换成 VNode 类型,然后推到数组中去;如果已经是 VNode 类型了直接推进数组即可;

在遍历过程中,如果存在连续的两个 text 节点,则会把他们合并成一个 text 节点;

2、创建 VNode 节点

回到 createElement 函数,规范化 children 后,接下来会去创建⼀个 VNode 的实例:

if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }

这⾥先对 tag 做判断,如果是 string 类型,则接着判断如果是内置的⼀些节点,则直接创建⼀个 普通 VNode,如果是为已注册的组件名,则通过 createComponent 创建⼀个组件类型的 VNode,否 则创建⼀个未知的标签的 VNode。 如果是 tag ⼀个 Component 类型,则直接调⽤ createComponent 创建⼀个组件类型的 VNode 节点;

3、创建组件节点

这里创建组件类型的 VNode 节点 在 src/core/vdom/create-component.js 文件中:

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  //判断节点tag不存在直接返回
  if (isUndef(Ctor)) {
    return
  }
  //创建子类构造器函数
  const baseCtor = context.$options._base
  // 普通选项对象:将其转换为构造函数
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }
  // 如果在此阶段它不是构造函数或异步组件工厂,则拒绝。
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }
  // 异步组件
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // 返回async组件的占位符节点,该节点呈现为注释节点,但保留该节点的所有原始信息。这些信息将用于异步服务器渲染和水合。
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
  data = data || {}
  // 解析构造函数选项,以防在创建组件构造函数后应用全局mixin
  resolveConstructorOptions(Ctor)
  // 将组件v-model 数据转换为props和events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }
  //提取props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  // 功能组件
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }
  // 提取侦听器,因为需要将它们视为子组件侦听器而不是DOM侦听器
  const listeners = data.on
  // 用带有.native修饰符的监听器替换,以便在父组件补丁期间处理它。
  data.on = data.nativeOn
  if (isTrue(Ctor.options.abstract)) {
    // 抽象组件只保留 props & listeners & slot
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }
  // 将组件管理钩子安装到占位符节点上
  installComponentHooks(data)
  // return a placeholder vnode
  const name = Ctor.options.name || tag
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,
    { Ctor, propsData, listeners, tag, children },
    asyncFactory
  )
  // Weex specific: invoke recycle-list optimized @render function for
  // extracting cell-slot template.
  // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }
  return vnode
}

总共分为三个步骤:构建子类构造器函数、安装组件钩子函数、实例化 vnode;

1、构建子类构造器函数

 const baseCtor = context.$options._base
  // 普通选项对象:将其转换为构造函数
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }

这里的 context 从入参追溯,是 Vue 实例;之前的 initGlobalAPI 方法中有这样一段代码 Vue.options._base = Vue,在参数合并的时候用 mergeOptions 方法把 options 扩展到了 vm.$options 上,这样 baseCtor 就是 Vue 实例本身;baseCtor.extend(Ctor) 实际上是 Vue.extend(Ctor);关于 Vue.extend 可以参考:(Vue2.x 源码 - 初始化:全局API);将 Ctor 构建成子类构造器函数;

2、安装组件钩子函数

installComponentHooks(data)
function installComponentHooks (data: VNodeData) {
  const hooks = data.hook || (data.hook = {})
  //遍历自定义的hooks
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    const existing = hooks[key]
    const toMerge = componentVNodeHooks[key]
    if (existing !== toMerge && !(existing && existing._merged)) {
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
    }
  }
}
const componentVNodeHooks = {
  //初始化时触发
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
  //patch之前触发
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    const options = vnode.componentOptions
    const child = vnode.componentInstance = oldVnode.componentInstance
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },
  //插入到DOM时触发
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },
  //节点移除之前触发
  destroy (vnode: MountedComponentVNode) {
    const { componentInstance } = vnode
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy()
      } else {
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  }
}
const hooksToMerge = Object.keys(componentVNodeHooks)

installComponentHooks方法执行的时候,遍历componentVNodeHooks方法里定义的 hooks 对象的属性,然后会把这些 hooks 合并到 data.hook 上面,方便后面使用;如果有相同的 hook,则会执行mergeHook方法来合并,mergeHook方法的定义如下:

function mergeHook (f1: any, f2: any): Function {
  const merged = (a, b) => {
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}

合并就是把 data.hook里面的钩子函数在componentVNodeHooks 里面定义的钩子函数后面执行;

3、实例化 VNode

const name = Ctor.options.name || tag
const vnode = new VNode(
  `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  data, undefined, undefined, undefined, context,
  { Ctor, propsData, listeners, tag, children },
  asyncFactory
)

通过 new VNode 实例化⼀个 vnode 并返回。

总结:render 函数主要是调用 createElement 方法包装的 _createElement 方法,该对子节点进行规范化之后创建生成 vnode, 然后会调用 vm._update ⽅法,执⾏ patch 函数,将 vnode 渲染到真实 DOM 节点上去;

下一篇:Vue2.x 源码 - VNode渲染过程(update、patch)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue.js 2.x中,回调函数的作用机制主要是通过组件的生命周期钩子函数来实现的。Vue.js的组件生命周期是指一个组件从创建到销毁的整个过程,而钩子函数则是在不同的生命周期阶段执行的回调函数Vue.js 2.x中常用的生命周期钩子函数包括:beforeCreatecreated、beforeMount、mounted、beforeUpdate、updated、beforeDestroy和destroyed。 具体的回调函数作用机制如下: 1. beforeCreate:在实例初始化之后、数据观测和事件/watcher事件配置之前被调用。此时,组件实例已经创建,但还没有进行数据的初始化和DOM的挂载。 2. created:在实例创建完成后被立即调用。此时,实例已完成数据观测、属性和方法的运算,以及watch/event事件回调的配置。但是,此时还未开始挂载阶段,组件的$el属性还不可见。 3. beforeMount:在挂载开始之前被调用。相关的render函数首次被调用,虚拟DOM开始生成。 4. mounted:el被新创建的vm.$el替换,并挂载到实例上之后调用该钩子函数。此时,组件已经完成模板编译,并将数据和模板生成了最终的HTML。注意,此时组件已经挂载到页面上。 5. beforeUpdate:在数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子函数中对更新之前的状态进行修改。 6. updated:在虚拟DOM重新渲染和打补丁之后调用,表示组件已经更新。此时,组件的数据已经更新,DOM也已经重新渲染。 7. beforeDestroy:在实例销毁之前调用。在这一步,实例仍然完全可用,可以进行一些清理工作和收尾工作。 8. destroyed:在实例销毁之后调用。此时,所有的事件监听器已被移除,所有的子实例也被销毁。 这些生命周期钩子函数提供了许多机会来控制组件的行为和交互,可以在特定的阶段执行一些初始化、更新或清理的操作。通过在不同的钩子函数中定义特定的回调函数,可以实现组件在不同生命周期阶段执行相应的逻辑。 需要注意的是,在Vue.js 2.x中,钩子函数是同步执行的,并且父组件的钩子函数会先于子组件的钩子函数执行。这样可以确保组件按照正确的顺序进行初始化、更新和销毁。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值