vue2源码-- 挂载阶段

Posted 在厕所喝茶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2源码-- 挂载阶段相关的知识,希望对你有一定的参考价值。

目录

原理分析

1、$mounted函数实际上调用的是mountComponent函数,该函数首先判断是否存在render函数,如果不存在,则给一个默认的渲染函数,该渲染函数会创建一个注释类型的VNode节点,然后给出警告。

2、触发beforeMount声明周期函数

3、定义updateComponent函数,该函数内部首先执行render渲染函数,得到一份最新的VNode节点树,然后执行_update方法对最新的Vnode节点和上一次的VNode节点进行对比,并更新DOM元素(即patch操作),完成一次渲染。这样子完成了挂载操作的一半工作。

4、另一半工作是开启对模板中的数据监控,当数据发生变化时,通知期其依赖进行视图更新。首先创建一个watcher实例,并将定义好的updateComponent函数作为第二个参数传入,watcher 构造函数的第二个参数支持 2 种类型,一种是数据路径,另外一种是函数。如果是数据路径,就会根据路径去读取这个数据,如果是函数,就会执行函数,一旦读取了数据或者执行了函数,就会触发数据的getter方法,getter方法会将watcher实例添加到改数据的依赖列表中,,当数据发生变化时,就会通知依赖列表进行更新,当依赖接收到通知后,就会调用第四个参数回调函数去更新视图。

换句话来说,updateComponent函数所使用到的数据都会被watcher监控,只要这些数据发生了变化,那么watcher都将会得到通知,从而掉用第四个参数回调函数去更新视图

源码

// hydrating是跟服务端渲染相关的
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component 
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating);
;
// $mount实际调用的是这个函数
export function mountComponent(
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component 
  vm.$el = el;
  // $mount最终需要的是render函数
  if (!vm.$options.render) 
    // 没有渲染函数的情况下会默认给一个渲染函数
    vm.$options.render = createEmptyVNode;
    // ...
  
  // 触发beforeMount钩子函数
  callHook(vm, "beforeMount");

  let updateComponent;
  if (process.env.NODE_ENV !== "production" && config.performance && mark) 
    updateComponent = () => 
      // ...
      const vnode = vm._render();
      vm._update(vnode, hydrating);
    ;
   else 
    updateComponent = () => 
      vm._update(vm._render(), hydrating);
    ;
  

  // Watcher2个作用,一是初始化的时候执行回调函数,二是当vm实例中检测的数据发生变化就执行回调函数
  // 渲染watcher
  // 如果是渲染watcher,在初始化的时候会把vm._watcher赋值为watcher的实例
  new Watcher(
    vm,
    updateComponent,
    noop,
    
      before() 
        if (vm._isMounted && !vm._isDestroyed) 
          // 如果数据发生了变化,并且组件在渲染状态并且没有销毁,触发beforeUpdate生命周期函数
          callHook(vm, "beforeUpdate");
        
      ,
    ,
    true /* isRenderWatcher */
  );
  hydrating = false;

  // vm.$vnode:Vue 实例的父虚拟 Node,为null表示为根vue实例
  if (vm.$vnode == null) 
    // 根实例
    vm._isMounted = true; // 标识已经挂载
    callHook(vm, "mounted");
  
  return vm;

// 把vnode渲染成真实的dom
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) 
  const vm: Component = this;
  const prevEl = vm.$el;
  const prevVnode = vm._vnode;
  const restoreActiveInstance = setActiveInstance(vm);
  vm._vnode = vnode;
  // Vue.prototype.__patch__ is injected in entry points
  // based on the rendering backend used.
  if (!prevVnode) 
    // 没有旧prevVnode就是第一次渲染
    vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
   else 
    // 更新
    vm.$el = vm.__patch__(prevVnode, vnode);
  
  restoreActiveInstance();
  // update __vue__ reference
  if (prevEl) 
    prevEl.__vue__ = null;
  
  if (vm.$el) 
    vm.$el.__vue__ = vm;
  
  if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) 
    vm.$parent.$el = vm.$el;
  
;

Vue源码学习- Hook Event

Hook Event (钩子事件),也就是生命周期钩子函数,供开发者在特定的逻辑点添加额外的处理逻辑,比如:在组件挂载阶段提供了beforeMount和mounted两个生命周期钩子,供开发者在组件挂载阶段执行额外的逻辑处理,比如为组件来准备渲染所需的数据。

callHook

/src/core/instance/lifecycle.js

/**
  * callHook(vm,'mounted')
  * 执行实例指定的生命周期钩子函数
  * 如果实例设置对应Hook Event ,比如:<comp @hook:mounted="method"/>,执行完生命周期函数之后,触发该事件的执行
*/
export function callHook(vm:Component, hook:string)
	//在执行生命周期钩子函数期间禁止收集依赖
	pushTarget()
	//从实例配置对象中获取指定钩子函数,比如mounted
	const hnalders = vm.$options[hook]
	//mounted hook
	const info = `$hook hook`
	if(handlers)
		//通过invokewithErrorHandler 执行生命周期钩子
		for(let i = 0,j=handlers.length;i<j;i++)
			invokeWithErrorHandling(hanlders[i],vm,null,vm,info)
		
	
	// HookEvent ,如果设置了Hook Event ,比如<comp @hook:mounted = 'method"/>则通过$emit触发该事件
	//vm._hasHookEvent 标识组件是否有hook event,这是在vm.$on中处理组件自定义事件时设置的
	if(vm._hasHookEvent)
		vm.$emit('hook:'+hook)
	
	//关闭依赖收集
	popTarget()

invokeWithErrorHanding

/src/core/util/error.js

/**
 * 通用函数,执行指定函数handler
 * 传递进来的函数会调用try catch包裹,进行异常捕获处理
*/
export function invokeWithErrorHandling(
	handler:  Function,
	context: any,
	args: null | any[],
	vm: any,
	info: string
	)
	 let res
	 try
		res = args ? handler.apply(context,args) : handler.call(context)
		if(res && !res._isVue && isPromise(res) && !res._handled)
		res.catch(e => handleError(e,vm,info+'(Promise/async)'))
		res._handled = true
	
	catch(e)
		handleError(e,vm,info)

	
	return res
	
  • Hook Event 时如何实现的?
<comp @hook:lifecycleMethod='method'/>
  • 处理组件自定义事件的时候(vm.$on)如果发现组件有hook:xx格式的事件(xx为Vue的生命周期函数),则将vm._hasHookEvent置为true,表示该组件有Hook Event
  • 在组件生命周期方法被触发的时候,内部会通过callHook方法来执行这些生命周期函数,在生命周期函数执行之后,如果发现vm._hasHookEvent为true,则表示当前组件有Hook Event ,通过vm.$emit(‘hook:xx’)触发Hook Event的执行。
    这就是Hook Event的实现原理;

以上是关于vue2源码-- 挂载阶段的主要内容,如果未能解决你的问题,请参考以下文章

vue2源码-- 模板编译阶段

vue2源码-- 初始化阶段

vue2源码-- 初始化阶段

2021-6月面试总结-vue,uniapp,小程序,h5,更新中

vue2.0-vue实例的生命周期

vue源码分析