翼度科技»论坛 编程开发 JavaScript 查看内容

关于Vue实例创建的整体流程

10

主题

10

帖子

30

积分

新手上路

Rank: 1

积分
30
前言

本篇是分析new Vue()过程相关的流程,主要了解如下两点:

  • new Vue()整体处理流程
  • 挂载处理流程

整体逻辑

使用Vue构造函数创建Vue实例,具体的处理逻辑如下图:

从上图中可以看出,Vue实例的具体处理是定义相关的内部属性,处理实例选项props、data、methods、computed、watch,以及beforeCreate、created生命周期函数的执行。
这里每一个选项都有不少实现逻辑,这里先不展开,之后会有专门的文章细究。
实际上面逻辑流程中还存在一些细节点,例如$options的差异处理等,这些需要针对特定的场景来具体聊才能更深的理解,这里先暂时不展开。

el或$mount挂载

对于挂载的处理是在_init即Vue()构造函数中调用的,具体的处理就是调用$mount方法,如下:
  1. // el就是挂载点
  2. if (vm.$options.el) vm.$mount(vm.$options.el);
复制代码
而$mount是定义在Vue原型上的实例方法,这也解释了Vue挂载的两种方式:

  • el形式,例如: new Vue({ el: ‘#app’})
  • $mount形式,例如: new Vue().$mount(‘#app’)
$mount方法中具体处理如下:

从上面可以得出知晓下面几个结论:

  • el和$mount两种方式挂载的缘由,实际上el本质上还是调用$mount方法
  • 若render函数存在,源码中会将template/el 转换为render函数
  • 挂载点应该是普通的html元素,而不应该是html或body这种特殊的
  • 如果template不存在,实际上会获取包含挂载点在内的outerHTML部分作为template
$mount内部是调用mountComponent函数,该函数的具体处理是什么呢?具体处理逻辑如下图:

从上图逻辑处理中可知:

  • mountComponent中实际上处理beforeMount、mounted
  • 每个Vue实例都会有相应的Watcher实例对应,Watcher构造函数中vm._watcher和vm._watchers记录了当前实例和watcher对象集合

全局Watcher的具体逻辑

每一个vue实例都对应一个watcher实例,这个watcher就是在挂载阶段创建的,负责当前实例对应的视图渲染。
其主要逻辑如下:
  1. var updateComponent = function () {
  2.         vm._update(vm._render(), hydrating);
  3. };
  4. new Watcher(vm, updateComponent, noop, {
  5.         before: function before() {
  6.             if (vm._isMounted || !vm._isDestroyed) {
  7.           callHook(vm, 'beforeUpdate');
  8.         }
  9.     }
  10. }, true)
复制代码
下面是Watcher构造函数中涉及到的主要逻辑:
  1.     var Watcher = function Watcher (
  2.       vm,
  3.       expOrFn,
  4.       cb,
  5.       options,
  6.       isRenderWatcher
  7.     ) {
  8.       this.vm = vm;
  9.       if (isRenderWatcher) {
  10.         vm._watcher = this;
  11.       }
  12.       // 收集当前实例所有watcher对象
  13.       vm._watchers.push(this);
  14.       // options主要处理
  15.       if (options) {
  16.         this.computed = !!options.computed;
  17.       } else {
  18.         this.deep = this.user = this.computed = this.sync = false;
  19.       }
  20.       this.dirty = this.computed; // for computed watchers
  21.       this.deps = [];
  22.       this.newDeps = [];
  23.       this.depIds = new _Set();
  24.       this.newDepIds = new _Set();
  25.       if (typeof expOrFn === 'function') {
  26.         this.getter = expOrFn;
  27.       }
  28.       if (this.computed) {
  29.         this.value = undefined;
  30.         this.dep = new Dep();
  31.       } else {
  32.         this.value = this.get();
  33.       }
  34.     };
复制代码
watcher对象创建的过程中,有几点逻辑需要主要关注:

  • 每一个vue都对应一个watcher实例,该watcher实例负责视图渲染,可以通过_watcher属性得知
  1.         // 是否是渲染Watcher对象
  2.    if (isRenderWatcher) {
  3.     vm._watcher = this;
  4.   }
复制代码

  • 计算属性的标识即computed
  • 基于computed的不同操作,即get方法是否立即执行,非计算属性的直接就调用了this.get()方法
Watcher对象中get方法是非常重要的逻辑,其主要逻辑可以归纳如下:
  1. Watcher.prototype.get = function get () {
  2.   pushTarget(this);
  3.   var value;
  4.   var vm = this.vm;
  5.   try {
  6.     value = this.getter.call(vm, vm);
  7.   } catch (e) {
  8.           // 相关代码
  9.   } finally {
  10.           // 支持Watcher对象
  11.     if (this.deep) {
  12.       traverse(value);
  13.     }
  14.     popTarget();
  15.     this.cleanupDeps();
  16.   }
  17.   return value
  18. };
复制代码
实际通过get方法的逻辑可以得到几个主要逻辑:

  • pushTarget和PopTarget的设置
  • 执行对应的getter函数
  • watch deep选项的支持逻辑
对于全局Watcher,这里的getter函数就是执行其对应的render函数渲染出来视图。而watch deep选项则是Vue watch API的使用内容,这里暂不展开。
实际上最主要的就是pushTarget相关的逻辑了,实际上这涉及到一个非常重要的属性Dep.target。

Dep.target

Dep.target,该属性是依赖收集关键点之一。
通过Vue源码知道,Dep.target的改变途径只有两个,即pushTarget和popTarget。
  1.     function pushTarget(target) {
  2.       if (Dep.target) targetStack.push(Dep.target);
  3.       Dep.target = target;
  4.     }
  5.     function popTarget() {
  6.       Dep.target = targetStack.pop();
  7.     }
复制代码
而Vue data中每个属性的获取都会检验Dep.target是否存在。
只有Dep.target存在下才会去调用dep.depend()方法来做相关的处理,而pushTarget和popTarget就是关键了。
Vue源码中调用pushTarget函数实际上就4处:

  • handleError:处理错误情况的方法
  • callHook:生命周期函数调用
  • Watcher.prototype.get:Watcher对象get实例方法
  • getData:初始化data时当data为函数会调用getData方法
从上面的pushTarget函数的逻辑可知会传递target参数,pushTarget会保存当前Dep.target到数组(模拟栈结构)中。
上面4处调用pushTarget只有一处传递的非空的target,即Watcher.prototype.get。
对于Dep.target需要注意的是:

  • Dep.target只有三种值:null、undefined、watcher对象
  • pushTarget和popTarget是改变Dep.target的唯一途径
而通过pushTarget可以知道targetStack只会保存watcher对象。通过Vue源码可知pushTarget和popTarget总是一起搭配出现的,实际上主要作用就是:
在使用pushTarget的地方临时修改Dep.target的值,已满足相关条件
明确下这里的相关条件,实际上通过源码中Dep.target使用位置来判断是可以清晰得到结论的,即:
Dep.target判断操作共有3处:

  • defineReactive的get函数中
  • Watcher.prototype.depend
  • Dep.prototype.depend
从上面可知Dep.target是为watcher对象服务的,实际上通过Vue响应式原理的描述(Dep与Watcher是相关关联的从而构成响应式非常重要的一部分),也可以证明Dep.target指向Watcher对象。
Watcher.prototype.get是唯一传递了非空target了,而Dep.target值也可能为null、undefined。而=非watcher之外的Dep.target的操作都将Dep.target临时设置为undefined,是为了避免执行涉及到watcher相关逻辑。
结合整体代码可知:
当执行get函数时,Dep.target始终是对应的watcher对象

总结

通过对Vue实例创建过程的整体分析,可知其主要的处理逻辑:

  • 执行Vue构造函数,开始初始化工作
  • 创建相关实例属性,例如options、uid
  • 相关选项初始化工作,例如状态(data选项、computed、methods等)、事件相关等
  • 执行beforeCreate、created生命周期函数
  • el和$mount两种挂载背后的处理
$mount -> mountComponent -> new Watcher() -> _render()执行渲染视图
每一个Vue实例都对应一个watcher实例,该watcher实例是用于控制视图渲染的
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

来源:https://www.jb51.net/javascript/322393qk2.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具