Vue2 源码漫游(二)

描述:

    在(一)中其实已经把Vue作为MVVM的框架中数据流相关跑了一遍。这一章我们先看mount这一步,这样Vue大的主线就基本跑通了。然后我们再去看compile,v-bind等功能性模块的处理。

一、出发点

path:platforms\web\entry-runtime-with-compiler.js
这里对原本的公用$mount方法进行了代理.实际的直接方法是core/instance/lifecycle.js中的mountComponent方法。
根据组件模板的不同形式这里出现了两个分支,一个核心:分支:1、组件参数中有render属性:执行mount.call(this, el, hydrating)2、组件参数中没有render属性:将template/el转换为render方法核心:Vue.prototype._render公共方法
/* @flow */import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'import Vue from './runtime/index'
import { query } from './util/index'
import { compileToFunctions } from './compiler/index'
import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'const idToTemplate = cached(id => {const el = query(id)return el && el.innerHTML
})
//这里代理了vue实例的$mount方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {el = el && query(el)/* istanbul ignore if */if (el === document.body || el === document.documentElement) {process.env.NODE_ENV !== 'production' && warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`)return this}const options = this.$options// resolve template/el and convert to render function//解析template/el转化为render方法。这里就是一个大的分支,我们可以将它称为render分支if (!options.render) {//如果没有传入render方法,且template参数存在,那么就开始解析模板,这就是compile的开始let template = options.templateif (template) {if (typeof template === 'string') {if (template.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)}}} else if (template.nodeType) {template = template.innerHTML} else {if (process.env.NODE_ENV !== 'production') {warn('invalid template option:' + template, this)}return this}} else if (el) {template = getOuterHTML(el)}if (template) {/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile')}const { render, staticRenderFns } = compileToFunctions(template, {shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.staticRenderFns = staticRenderFns/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {mark('compile end')measure(`vue ${this._name} compile`, 'compile', 'compile end')}}}//调用公用mount方法return mount.call(this, el, hydrating)
}/*** Get outerHTML of elements, taking care* of SVG elements in IE as well.*/
function getOuterHTML (el: Element): string {if (el.outerHTML) {return el.outerHTML} else {const container = document.createElement('div')container.appendChild(el.cloneNode(true))return container.innerHTML}
}Vue.compile = compileToFunctionsexport default Vue

1、组件中有render属性

//最常见
new Vue({el: '#app',router,render: h => h(App),
});

如果有render方法,那么就会调用公共mount方法,然后判断一下平台后直接调用mountComponent方法

// public mount method
//入口中被代理的公用方法就是它,path : platforms\web\runtime\index.js
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {//因为是公用方法所以在这里有重新判断了一些el,其实如果有render属性的话,这里el就已经是DOM对象了el = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating)
}

接下来就是mountComponent。这里面有一个关键点 vm._watcher = new Watcher(vm, updateComponent, noop),这个其实就是上篇中说到的依赖收集的一个触发点。你可以想想,组件在这个时候其实数据已经完成了响应式转换,就坐等收集依赖了,也就是坐等被第一次使用访问了。

export function mountComponent (vm: Component,el: ?Element,hydrating?: boolean
): Component {vm.$el = el//这个判断其实只是在配置默认render方法createEmptyVNodeif (!vm.$options.render) {vm.$options.render = createEmptyVNodeif (process.env.NODE_ENV !== 'production') {/* istanbul ignore if */if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||vm.$options.el || el) {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 {warn('Failed to mount component: template or render function not defined.',vm)}}}//执行beforeMount回调函数callHook(vm, 'beforeMount')//这个updateComponent方法很重要,其实可以将它与Watcher中的参数expOrFn联系起来。他就是一个Watcher实例的值的获取过程,订阅者的一种真实身份。let updateComponent/* istanbul ignore if */if (process.env.NODE_ENV !== 'production' && config.performance && mark) {updateComponent = () => {const name = vm._nameconst id = vm._uidconst startTag = `vue-perf-start:${id}`const endTag = `vue-perf-end:${id}`mark(startTag)const 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 = () => {//实际方法,被Watcher.getter方法的执行给调用回来了,在这里先直接执行vm.render,这个就是compile的触发点vm._update(vm._render(), hydrating)}}//开始生产updateComponent这个动作的订阅者了,生产过程中调用Watcher.getter方法时又会回来执行这个updateComponent方法。看上面两排vm._watcher = new Watcher(vm, updateComponent, noop)hydrating = false// manually mounted instance, call mounted on self// mounted is called for render-created child components in its inserted hookif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm
}

2、公共render方法

path : core\instance\render.js
Vue.prototype._render()这个方法的调用在整个源码中就两处,vm._render()和child._render()。
从中可以理解到一个执行链条:

$mount -> new Watcher -> watcher.getter -> updateComponent -> vm._update -> vm._render -> vm.createElement -> createComponent(如果存在子组件,调用createElement,如果没有执行createElement)
在render的这一个层面上的出发点,都是来自于vm.$options.render函数,这也是为什么在Vue.prototype.$mount方法中会对vm.$options.render进行判断处理从而分出有render函数和没有render函数两种不同的处理方式

看一下vm._render源码:


export function renderMixin (Vue: Class<Component>) {// install runtime convenience helpersinstallRenderHelpers(Vue.prototype)Vue.prototype.$nextTick = function (fn: Function) {return nextTick(fn, this)}Vue.prototype._render = function (): VNode {const vm: Component = thisconst { render, _parentVnode } = vm.$options//如果父组件还没有更新,那么就先把子组件存在vm.$slots中if (vm._isMounted) {// if the parent didn't update, the slot nodes will be the ones from// last render. They need to be cloned to ensure "freshness" for this render.for (const key in vm.$slots) {const slot = vm.$slots[key]if (slot._rendered) {vm.$slots[key] = cloneVNodes(slot, true /* deep */)}}}//作用域插槽vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject// set parent vnode. this allows render functions to have access// to the data on the placeholder node.vm.$vnode = _parentVnode// render selflet vnodetry {//执行$options.render,如果没传的就是一个默认的VNode实例。最后都会去掉用createElement公用方法core\vdom\create-element.js。这个就是大工程了。vnode = render.call(vm._renderProxy, vm.$createElement)} catch (e) {handleError(e, vm, `render`)// return error render result,// or previous vnode to prevent render error causing blank component/* istanbul ignore else */if (process.env.NODE_ENV !== 'production') {if (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}} else {vnode = vm._vnode}}// return empty vnode in case the render function errored outif (!(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()}// set parentvnode.parent = _parentVnodereturn vnode}
}

3、_createElement, createComponent


path : core\vdom\create-element.js
_createElement(context, tag, data, children, normalizationType)
export function _createElement (context: Component,tag?: string | Class<Component> | Function | Object,data?: VNodeData,children?: any,normalizationType?: number
): 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()}// object syntax in v-bindif (isDef(data) && isDef(data.is)) {tag = data.is}if (!tag) {// in case of component :is set to falsy valuereturn createEmptyVNode()}// warn against non-primitive keyif (process.env.NODE_ENV !== 'production' &&isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {warn('Avoid using non-primitive value as key, ' +'use string/number value instead.',context)}// support single function children as default scoped slotif (Array.isArray(children) &&typeof children[0] === 'function') {data = data || {}data.scopedSlots = { default: children[0] }children.length = 0}if (normalizationType === ALWAYS_NORMALIZE) {children = normalizeChildren(children)} else if (normalizationType === SIMPLE_NORMALIZE) {children = simpleNormalizeChildren(children)}let vnode, nsif (typeof tag === 'string') {let Ctorns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)if (config.isReservedTag(tag)) {// platform built-in elementsvnode = new VNode(config.parsePlatformTagName(tag), data, children,undefined, undefined, context)} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {// componentvnode = 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 childrenvnode = new VNode(tag, data, children,undefined, undefined, context)}} else {// direct component options / constructorvnode = createComponent(tag, data, context, children)}if (isDef(vnode)) {if (ns) applyNS(vnode, ns)return vnode} else {return createEmptyVNode()}
}
    path : core\vdom\create-component.jscreateComponent (Ctor, data, context, children, tag)Ctor : 组件Module信息,最后与会被处理成vm实例对象data : 组件数据context : 当前Vue组件children : 自组件tag :组件名
const hooksToMerge = Object.keys(componentVNodeHooks)export function createComponent (Ctor: Class<Component> | Function | Object | void,data: ?VNodeData,context: Component,children: ?Array<VNode>,tag?: string
): VNode | void {//Ctor不能为undefined || nullif (isUndef(Ctor)) {return}//const baseCtor = context.$options._base// plain options object: turn it into a constructor// 如果Ctor为对象,合并到vm数据,构建Ctorif (isObject(Ctor)) {Ctor = baseCtor.extend(Ctor)}// if at this stage it's not a constructor or an async component factory,// reject.if (typeof Ctor !== 'function') {if (process.env.NODE_ENV !== 'production') {warn(`Invalid Component definition: ${String(Ctor)}`, context)}return}// 异步组件let asyncFactoryif (isUndef(Ctor.cid)) {asyncFactory = CtorCtor = resolveAsyncComponent(asyncFactory, baseCtor, context)if (Ctor === undefined) {// return a placeholder node for async component, which is rendered// as a comment node but preserves all the raw information for the node.// the information will be used for async server-rendering and hydration.return createAsyncPlaceholder(asyncFactory,data,context,children,tag)}}data = data || {}// resolve constructor options in case global mixins are applied after// component constructor creation//解析组件实例的optionsresolveConstructorOptions(Ctor)// transform component v-model data into props & eventsif (isDef(data.model)) {transformModel(Ctor.options, data)}// extract propsconst propsData = extractPropsFromVNodeData(data, Ctor, tag)// functional componentif (isTrue(Ctor.options.functional)) {return createFunctionalComponent(Ctor, propsData, data, context, children)}// extract listeners, since these needs to be treated as// child component listeners instead of DOM listenersconst listeners = data.on// replace with listeners with .native modifier// so it gets processed during parent component patch.data.on = data.nativeOnif (isTrue(Ctor.options.abstract)) {// abstract components do not keep anything// other than props & listeners & slot// work around flowconst slot = data.slotdata = {}if (slot) {data.slot = slot}}// merge component management hooks onto the placeholder node// 合并钩子函数 init 、 destroy  、 insert 、prepatchmergeHooks(data)// return a placeholder vnode// 最终目的生成一个vnode,然后就是一路的return出去const name = Ctor.options.name || tagconst vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,data, undefined, undefined, undefined, context,{ Ctor, propsData, listeners, tag, children },asyncFactory)return vnode
}

总结:

过程线条:

$mount -> new Watcher -> watcher.getter -> updateComponent -> vm._update -> vm._render -> vm.createElement -> createComponent(如果存在子组件,调用createElement,如果没有执行createElement)

上面这个线条中其实都围绕着vm.$options进行render组件。现在大部分项目都是使用的.vue组件进行开发,所以使得对组件的配置对象不太敏感。
因为将.vue的内容转化为Vue组件配置模式的过程都被vue-loader处理(我们在require组件时处理的),其中就包括将template转换为render函数的关键。我们也可以定义一个配置型的组件,然后触发Vue$3.prototype.$mount中的mark('compile')进行处理。但是我觉得意义不是太大。
过程,这也是导致我们在源码运行中总是看见在有无render函数分支,的时候总是能看见render函数,然后就进入对组件 vm._update(vm._render(), hydrating)。
我们先记住这条主线,下一章我们进入到vue-loader中去看看

Vue2 源码漫游(二)相关推荐

  1. Vue2 源码漫游(一)

    Vue2 源码漫游(一) 描述: Vue框架中的基本原理可能大家都基本了解了,但是还没有漫游一下源码. 所以,觉得还是有必要跑一下. 由于是代码漫游,所以大部分为关键性代码,以主线路和主要分支的代码为 ...

  2. 初学者也能看懂的 Vue2 源码中那些实用的基础工具函数

    1. 前言 大家好,我是若川.最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 想学源码,极力推荐之前我写的<学习源码整体架构系列>jQuery.underscore.l ...

  3. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  4. LambdaMART简介——基于Ranklib源码(二 Regression Tree训练)

     LambdaMART简介--基于Ranklib源码(二 Regression Tree训练) 上一节中介绍了 λ λ 的计算,lambdaMART就以计算的每个doc的 λ λ 值作为label ...

  5. mybatis源码阅读(二):mybatis初始化上

    转载自  mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...

  6. 电竞比分源码/免买分源码/可二开/支持最新PHP7.3/LOL,王者,吃鸡等等电竞比分源码

    简介: [独家分享]比分源码/免买分源码/可二开/支持PHP7.3 OS6.5 到7.5 推荐7.4 Nginx -Tengine2.2.3 MySQL 8.0.16 PHP 7.3 Redis 5. ...

  7. 一点一点看JDK源码(二)java.util.List

    一点一点看JDK源码(二)java.util.List liuyuhang原创,未经允许进制转载 本文举例使用的是JDK8的API 目录:一点一点看JDK源码(〇) 1.综述 List译为表,一览表, ...

  8. Spring Cloud 2.2.2 源码之二十九nacos客户端获取配置原理四

    Spring Cloud 2.2.2 源码之二十九nacos客户端获取配置原理四 MetricsHttpAgent的httpGet ServerHttpAgent的httpGet HttpSimple ...

  9. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

最新文章

  1. 【开源方案共享】无序点云快速的线段分割算法
  2. vue 项目配置sass
  3. python实现syn半扫描_python 使用raw socket进行TCP SYN扫描实例
  4. python3d动态图-Python图像处理之gif动态图的解析与合成操作详解
  5. __VA_ARGS__宏
  6. GPU Gems1 - 3 Dawn Demo中的皮肤渲染(Skin in the Dawn Demo)
  7. Linux 学习重点内容(第二节)
  8. java获取当前日期时间_Java日期时间API系列10-----Jdk8中的DateTimeFormatter
  9. 【python】yaml文件操作
  10. 计算机应用基础win7.pdf,计算机应用基础WIn7操作题(12页)-原创力文档
  11. Unity3D 多平台_预编译相关宏定义
  12. bootstrap 快速入门
  13. 【TSP】基于matlab自适应动态邻域布谷鸟混合算法求解旅行商问题【含Matlab源码 1513期】
  14. Java 简单工厂模式和工厂模式(类图及实现)
  15. 蓝桥杯省赛C++A组B组题解整理(第十、九、八、七、六、五、四、三届)
  16. 厦门龙凤419_福建生物工程职业技术学校2019招生通知书EMS单号
  17. PTA 乙级 【1005】继续(3n+1)猜想
  18. Angular4中使用PrimeNG calendar
  19. CSDN怎么改变字体颜色
  20. SQL语法 Access

热门文章

  1. wget使用代理下载
  2. 通过memcached来实现对tomcat集群中Session的共享策略 .
  3. ARM指令集 VS Thumb指令集
  4. java 提取内容并排序
  5. flex图表数据动态更新效果示例
  6. 新手也能立即上手,用Python90多行代码画出“樱花园”仙境(源码+注释)
  7. java基本数据类型自动转包装类_java基本数据类型和包装类相互转换
  8. linux虚拟机启动网卡命令,命令行下无法联网怎么办,vmware下安装archlinux实现网络连接,实机grub引导启动linux...
  9. vacode允许c_Visual Studio Code 配置C/C++环境
  10. vps建网站python_VPS配置python web环境真吐血