Vue源码分析系列二:$mount()方法
栏目: JavaScript · 发布时间: 5年前
内容简介:提示:描述都写在代码的注释中 在initMixin()的最后执行了如下操作:vm的原型上本身就定义了一个$mount(如下所示),然后通过重写$mount方法,最后返回的时候,调用这个缓存$mount方法。直接调用的$mount代码如下
提示:描述都写在代码的注释中 在initMixin()的最后执行了如下操作:
if (vm.$options.el) { vm.$mount(vm.$options.el); } 复制代码
执行$mount()方法
- query()
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 } } 复制代码
vm的原型上本身就定义了一个$mount(如下所示),然后通过重写$mount方法,最后返回的时候,调用这个缓存$mount方法。
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代码如下
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 中 } 复制代码
mountComponent()方法
function mountComponent ( vm, el, hydrating ) { debugger vm.$el = el; // 对 el 进行缓存 if (!vm.$options.render) { // 如果没有render函数,包括 template 没有正确的转换成render函数,就执行 if 语句 vm.$options.render = createEmptyVNode; // createEmptyVNode if (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()方法 }; } 复制代码
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, // 是一个表达式还是一个 fn cb, // 回调 options, // 配置 isRenderWatcher // 是否是一个渲染watcher ) { this.vm = vm; if (isRenderWatcher) { // 如果是渲染 Watcher vm._watcher = this; // this 表示 Vue实例, 其中包括了你定义了的数据,也有 $options,还有_data,都可以获取到你定义的数据 } vm._watchers.push(this); // 然后将Vue 实例push到 _watchers中, 在initState中 vm._watchers = [] // options if (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 batching this.active = true; this.dirty = this.lazy; // for lazy watchers this.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 getter if (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实例vm try { 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 watching if (this.deep) { traverse(value); } popTarget(); this.cleanupDeps(); } return value }; 复制代码
总结
到这里,我们知道updateComponent方法会完成整个渲染工作,在系列三中,将深入分析 vm._render()方法以及vm._update()方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【MyBatis源码分析】insert方法、update方法、delete方法处理流程(上篇)
- 【MyBatis源码分析】insert方法、update方法、delete方法处理流程(上篇)
- Runtime源码 方法调用的过程
- invalidate方法知多少[-View-] 源码级
- SpringBoot源码解析之main方法推断
- 源码编译LNMP出现的问题及解决方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。