续vue源码分析系列一:new Vue的初始化过程
initMixin()里面调用了$mount()

if (vm.$options.el) {vm.$mount(vm.$options.el);// 挂载dom元素
}

$mount()方法定义

Vue 中我们是通过 $mount 实例方法去挂载 vm 的,$mount 方法在多个文件中都有定义,如 src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js。因为 $mount 这个方法的实现是和平台、构建方式都相关的。接下来我们重点分析带 compiler 版本的 $mount 实现,因为抛开 webpack 的 vue-loader,我们在纯前端浏览器环境分析 Vue 的工作原理,有助于我们对原理理解的深入。

compiler 版本的 $mount 实现非常有意思,先来看一下 src/platform/web/entry-runtime-with-compiler.js 文件中定义:

Vue.prototype.$mount = function() {el = el && query(el); // 表示如果el存在就执行query(el)方法if (el === document.body || el === document.documentElement) {// Vue 不能挂载在 body、html 这样的根节点上,因为它会替换掉这些元素process.env.NODE_ENV !== 'production' && warn("Do not mount Vue to <html> or <body> - mount to normal elements instead.");return this}// 如果有render方法,就直接 return 并调用原先原型原型上的 $mount 方法// 如果没有render方法,就判断有没有template// 如果没有template就会调用template = getOuterHTML(el)// 总之,最终还是会将template转换成render函数// 最后再调用 mountComponent 方法if (!this.$options.render) {if(this.$options.template){if (template.charAt(0) === '#') { // 我们这里的 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);}}}}// 然后开始编译if (template) {// 通过compileToFunction 生成 render 函数,渲染vnode的时候会用到var ref = compileToFunctions(); var render = ref.render;var render = ref.render;var staticRenderFns = ref.staticRenderFns; // 静态 render函数options.render = render; // 会在渲染 Vnode 的时候用到options.staticRenderFns = staticRenderFns;}return mount.call(this, el, hydrating)// 最后,调用原先原型上的 $mount 方法挂载。// 并执行 mountComponent() 方法// mountComponent 在 core/instance/lifecycle 中
}

这段代码首先缓存了原型上的 $mount 方法,再重新定义该方法,我们先来分析这段代码。首先,它对 el 做了限制,Vue 不能挂载在 body、html 这样的根节点上。接下来的是很关键的逻辑 —— 如果没有定义 render 方法,则会把 el 或者 template 字符串转换成 render 方法。这里我们要牢记,在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法,无论我们是用单文件 .vue 方式开发组件,还是写了 el 或者 template 属性,最终都会转换成 render 方法,那么这个过程是 Vue 的一个“在线编译”的过程,它是调用 compileToFunctions 方法实现的,编译过程我们之后会介绍。最后,调用原先原型上的 $mount 方法挂载。

原先原型上的 $mount 方法在 src/platform/web/runtime/index.js 中定义,之所以这么设计完全是为了复用,因为它是可以被 runtime only 版本的 Vue 直接使用的。

// public mount method
Vue.prototype.$mount = function ( // 这是最开始定义的$mount方法,在runtime-only版本中el,hydrating
) {//var inBrowser = typeof window !== 'undefined';el = el && inBrowser ? query(el) : undefined;return mountComponent(this, el, hydrating) // 然后执行 mountComponent方法
};

$mount 方法支持传入 2 个参数,第一个是 el,它表示挂载的元素,可以是字符串,也可以是 DOM 对象,如果是字符串在浏览器环境下会调用 query 方法转换成 DOM 对象的。第二个参数是和服务端渲染相关,在浏览器环境下我们不需要传第二个参数。

query(el)获取dom元素

/*** Query an element selector if it's not an element already.*/
function query (el) {if (typeof el === 'string') {var selected = document.querySelector(el);if (!selected) { // 如果存在 el 元素,就直接返回 selected,否则如下process.env.NODE_ENV !== 'production' && warn('Cannot find element: ' + el);return document.createElement('div') // 如果不存在el元素,就返回一个空div}return selected} else { // 否则直接返回dom元素return el}
}

mountComponent()方法

function mountComponent (vm,el,hydrating
) {debuggervm.$el = el; // 对 el 进行缓存if (!vm.$options.render) { // 如果没有render函数,包括 template 没有正确的转换成render函数,就执行 if 语句vm.$options.render = createEmptyVNode; // createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||vm.$options.el || el) {// 如果使用了template而不是render函数但是使用的runtime-only版本,就报这个警告// 如果使用了template 但是不是用的 compile 版本,也会报警告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 {// 如果没有使用 template 或者 render 函数,就报这个警告warn('Failed to mount component: template or render function not defined.',vm);}}}callHook(vm, 'beforeMount');var updateComponent;/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {// mark 是 util 工具函数中的perf,这里先不作深入研究,主要研究主线。// 性能埋点相关// 提供程序的运行状况,updateComponent = function () {var name = vm._name;var id = vm._uid;var startTag = "vue-perf-start:" + id;var endTag = "vue-perf-end:" + id;mark(startTag);var 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 = function () {// vm._render() 方法渲染出来一个 VNode// jydrating 跟服务端渲染相关,如果没有启用的话,其为 false// 当收集好了依赖之后,会通过 Watcher 的 this.getter(vm, vm) 来调用 updateComponent() 方法vm._update(vm._render(), hydrating); // 然后执行 vm._render()方法};}

从上面的代码可以看到,mountComponent 核心就是先实例化一个渲染Watcher,在它的回调函数中会调用 updateComponent 方法,在此方法中调用 vm._render 方法先生成虚拟 Node,最终调用 vm._update 更新 DOM。

Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数,这块儿我们会在之后的章节中介绍。

函数最后判断为根节点的时候设置 vm._isMountedtrue, 表示这个实例已经挂载了,同时执行 mounted 钩子函数。 这里注意 vm.$vnode 表示 Vue 实例的父虚拟 Node,所以它为 Null 则表示当前是根 Vue 的实例。
mountComponent 方法的逻辑也是非常清晰的,它会完成整个渲染工作

updateComponent()

在这个方法里有两个方法需要调用:vm._render() and vm._update(),先调用 _render 方法生成一个vnode,然后将这个vnode传入到 _update()方法中

updateComponent = function () {// vm._render() 方法渲染出来一个 VNode// jydrating 跟服务端渲染相关,如果没有启用的话,其为 false// 当收集好了依赖之后,会通过 Watcher 的 this.getter(vm, vm) 来调用 updateComponent() 方法vm._update(vm._render(), hydrating); // 然后执行 vm._render()方法
};

new Watcher()

渲染watcher,Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数

new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);
var Watcher = function Watcher (vm,expOrFn, // 是一个表达式还是一个 fncb, // 回调options, // 配置isRenderWatcher // 是否是一个渲染watcher
) {this.vm = vm;if (isRenderWatcher) { // 如果是渲染 Watchervm._watcher = this; // this 表示 Vue实例, 其中包括了你定义了的数据,也有 $options,还有_data,都可以获取到你定义的数据}vm._watchers.push(this); // 然后将Vue 实例push到 _watchers中, 在initState中 vm._watchers = []// optionsif (options) {this.deep = !!options.deep;this.user = !!options.user;this.lazy = !!options.lazy;this.sync = !!options.sync;} else {this.deep = this.user = this.lazy = this.sync = false;}this.cb = cb;this.id = ++uid$1; // uid for batchingthis.active = true;this.dirty = this.lazy; // for lazy watchersthis.deps = [];this.newDeps = [];this.depIds = new _Set();this.newDepIds = new _Set();// expOrFn: updateComponent = function() { vm._update(vm._render(), hydrating); }this.expression = process.env.NODE_ENV !== 'production'? expOrFn.toString() : '';// parse expression for getterif (typeof expOrFn === 'function') { // 判断expOrFn是否是一个函数this.getter = expOrFn; // this => Watcher} else {this.getter = parsePath(expOrFn);if (!this.getter) {this.getter = function () {};process.env.NODE_ENV !== 'production' && warn("Failed watching path: \"" + expOrFn + "\" " +'Watcher only accepts simple dot-delimited paths. ' +'For full control, use a function instead.',vm);}}this.value = this.lazy? undefined: this.get(); // this表示Watcher,其原型上定义了get方法
};

Watcher.prototype.get()

在Watcher的构造函数中定义了getter函数:this.getter = expOrFn。这个expOrFn 是updateComponent方法,在Watcher.prototype.get()方法中通过this.getter.call(vm, vm)来调用updateComponent方法,然后执行vm._update(vm._render, hydrating)

Watcher.prototype.get = function get () {// 依赖收集pushTarget(this);var value;var vm = this.vm; // this => Watcher,里面有当前的Vue实例vmtry {value = this.getter.call(vm, vm);} catch (e) {if (this.user) {handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));} else {throw e}} finally {// "touch" every property so they are all tracked as// dependencies for deep watchingif (this.deep) {traverse(value);}popTarget();this.cleanupDeps();}return value
};

总结

到这里,我们知道updateComponent方法会完成整个渲染工作,在系列三中,将深入分析 vm._render()方法以及vm._update()方法。

vue源码分析系列二:$mount()和new Watcher()的执行过程相关推荐

  1. vue源码分析系列一:new Vue的初始化过程

    import Vue from 'vue'(作者用的vue-cli一键生成) node环境下import Vue from 'vue'的作用是什么意思? 在 NPM 包的 dist/ 目录你将会找到很 ...

  2. vue源码分析系列三:render的执行过程和Virtual DOM的产生

    render 手写 render 函数,仔细观察下面这段代码,试想一下这里的 createElement 参数是什么 . new Vue({el: '#application',render(crea ...

  3. Android 4.0 Launcher源码分析系列(二)

    原文:http://mobile.51cto.com/hot-314700.htm 上一节我们研究了Launcher的整体结构,这一节我们看看整个Laucher的入口点,同时Laucher在加载了它的 ...

  4. Vue源码分析系列四:Virtual DOM

    前言 当我们操作Dom其实是一件非常耗性能的事,每个元素都涵盖了许多的属性,因为浏览器的标准就把 DOM 设计的非常复杂.而Virtual Dom就是用一个原生的JS对象去描述一个DOM节点,即VNo ...

  5. Vue源码解析系列——数据驱动篇:patch的执行过程

    准备 vue版本号2.6.12,为方便分析,选择了runtime+compiler版本. 回顾 如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:<Vue源码分析系列:目录> ...

  6. [Vue源码分析] 模板的编译

    最近小组有个关于vue源码分析的分享会,提前准备一下- 前言: Vue有两个版本:Runtime + Compiler . Runtime only ,前者是包含编译代码的版本,后者不包含编译代码,编 ...

  7. vue 计算属性_lt;Vue 源码笔记系列6gt;计算属性 computed 的实现

    1. 前言 原文发布在语雀: <Vue 源码笔记系列6>计算属性 computed 的实现 · 语雀​www.yuque.com 上一章我们已经学习过 watch,这一章就来看一下计算属性 ...

  8. k8s源码分析 pdf_Spark Kubernetes 的源码分析系列 - features

    1 Overview features 包里的代码,主要是用于构建 Spark 在 K8S 中的各类资源所需要的特征,个人觉得可以理解成这些 features 就是帮你写各类 Kind 的 YAML ...

  9. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

最新文章

  1. 深入Java虚拟机读书笔记[10:20]
  2. webapi 控制json的字段(key)显示顺序
  3. java中i++和++i与c里的区别
  4. 算法------判定字符是否唯一
  5. 大数据日知录要点整理
  6. 【频谱共享】基于认知无线电的VCG拍卖机制频谱共享算法的MATLAB仿真
  7. Kubernetes系统架构简介--转
  8. css优先级计算规则
  9. Cloudera将被私有化,Hadoop时代或将落幕
  10. 神经网络与深度学习——TensorFlow2.0实战(笔记)(五)(Matplotlib绘图基础<散点图>python)
  11. 计算机win7无法安装,新机装不了Win7?照这个方法5分钟搞定!
  12. PyQt5笔记(08) – 输入对话框
  13. 卧槽!这个价值百万的Github开源项目绝对要火!涵盖OCR、目标检测,NLP,语音合成多方向...
  14. Easy UI combobox实现类似 Select2的效果,下拉带搜索框
  15. linux驱动数码管-基于74HC164D
  16. windows开发机做路由器映射大坑
  17. Qt C++制作桌面天气,无边框、透明窗口,心知天气API,开放源码
  18. php调用nexmo发送短信,在 Laravel 中 “规范” 的开发短信验证码发送功能
  19. POJ 1273 EK算法
  20. 凸优化—凸松弛(Convex Relaxation)

热门文章

  1. fdisk并创建LVM
  2. 一定会好好的、慢慢的来
  3. 天线接收功率计算公式_天线增益的计算公式
  4. 计算机分析建筑风,武汉城住宅小区风环境计算机模拟分析-图学学报.PDF
  5. spatialreg | 空间计量模型的结果解读——直接效应和间接效应
  6. PHP设计模式之——工厂模式
  7. 新玺配资:上证指数再探3600点 能否一举突破?
  8. 这些95后表示:是时候打破对程序员的刻板印象了!
  9. 无线通信:Wi-Fi 基本知识
  10. 七年级计算机基本结构,七年级信息技术教案7-2.2 第二节 计算机的基本组成及工作原理...