We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
我们看源码,我觉得最好带着问题去看源码,这样我们会专注于一个点去看源码,不会被源码的一些其他功能,把我们带离最初想去的地方。本章主要的目的是,弄明白 vue 是如何生成虚拟 DOM 的。
我们从入口文件一步一步慢慢的分析。先看入口文件。
web/entry-runtime-with-compiler.js
import Vue from './runtime/index' const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, // 根元素,可以是字符串或者是 DOM 元素 hydrating?: boolean // 服务端渲染相关,服务端渲染时为 true ): Component { ... const options = this.$options // 没有手写 render 方法时,获取 template(template 会被编译成 render 方法) if (!options.render) { // 先获取模板 let template = options.template ... if (template) { ... // render 方法会生成 vnode, template => render 方法 => vnode const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) // 将 render 方法添加到 this.$options 上 options.render = render options.staticRenderFns = staticRenderFns } } // 调用保存的 mount 函数,此时已获得由模板编译过来的 render 函数 return mount.call(this, el, hydrating) }
import Vue from 'core/index' import { mountComponent } from 'core/instance/lifecycle' ... // 在原型上,添加 $mount 方法,这个方法会返回 mountComponent 方法。 Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } ...
import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' ... initGlobalAPI(Vue) // 初始化全局 API,如 nextTick ...
// vue 的构造函数,代码模块化,方便维护 ... function Vue(options) { ... this._init(options) } // 在 vue prototype 上添加方法 initMixin(Vue) // _init 方法等 stateMixin(Vue) // 数据相关,如 $watch 方法等 eventsMixin(Vue) // 事件相关,如 $emit 方法等 lifecycleMixin(Vue) // _update 方法等 renderMixin(Vue) // _render 方法等
由上面的代码,我们可以看到,当我们new Vue() 时,会触发一系列的初始化,然后调用 _init() 方法。
new Vue()
_init()
既然 new Vue() 时,调用的是 _init() 方法 ,我们就先看看 _init() 方法主要做了什么事情。它是在 initMixin() 函数执行时,添加到原型上的方法。在 init 方法的最后,我们看到如下代码。它调用了vm.$mount方法。
initMixin()
init
vm.$mount
Vue.prototype._init = function (options?: Object) { ... if (vm.$options.el) { vm.$mount(vm.$options.el) } }
在上面的分析中,我们知道 Vue 中有两个$mount 方法。定义如下:
// 第一个 $mount 方法 Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } // 第二个 $mount 方法 const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, // 根元素,可以是字符串或者是 DOM 元素 hydrating?: boolean // 服务端渲染相关,服务端渲染时为 true ): Component { ... if (!options.render) { ... if (template) { ... // render 方法会生成 vnode, template => render 方法 => vnode const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) // 将 render 方法添加到 this.$options 上 options.render = render options.staticRenderFns = staticRenderFns } } // 调用保存的 mount 函数,此时已获得由模板编译过来的 render 函数 return mount.call(this, el, hydrating) }
我们可以看到:
第一个 $mount 方法:是给第二个 $mount 方法调用用的,它会返回mountComponent方法。
第一个 $mount
第二个 $mount
mountComponent
第二个 $mount 方法:将 template 编译成 render 方法,保存 render 方法到 $options 上。最后调用第一个 $mount 方法。
template
render
$options
接下来,我们看下mountComponent方法。
mountComponent 方法的主要代码如下:
// mountComponent export function mountComponent( vm: Component, // 组件实例 el: ?Element, // 挂载的元素 hydrating?: boolean // 服务端渲染相关 ): Component { ... updateComponent = () => { // vm._render(),生成 vnode,在 instance/render.js 中 // vm._update(),更新 dom vm._update(vm._render(), hydrating) } // watcher 会调用 updateComponent,先生成 vnode ,然后调用 update 更新 dom; new Watcher(vm, updateComponent, noop, { before() { ... } }, true /* isRenderWatcher */) return vm } // watcher export default class Watcher { constructor( vm: Component, // 组件实例 expOrFn: string | Function, cb: Function, // 当监听的数据变化时,会触发该回调 options?: ?Object, isRenderWatcher?: boolean ) { ... // expOrFn 是 `updateComponent` 方法 this.getter = expOrFn this.value = this.lazy ? undefined : this.get() } get() { ... try { // 相当于执行 updateComponent() value = this.getter.call(vm, vm) } catch (e) { ... } }
Watcher
updateComponent
vm._update(vm._render(), hydrating)
这个才是正主。前期的准备工作已做完,下面到了生成虚拟DOM的时候了。
Vue.prototype._render = function (): VNode { ... // render 方法是由模板编译过来的 const { render, _parentVnode } = vm.$options // render 生成 vnode vnode = render.call(vm._renderProxy, vm.$createElement) ... }
可以看到,vue 会调用由 template 编译过来的 render 方法生成 虚拟 DOM。需要注意的是:
createElement
DOM
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
然后 vue 具体是如何生成虚拟 DOM 的呢,且听下回分解。
vm.$mount(vm.$options.el)
$mount
mount
vm._render()
Vue原理解析(四):你知道被大家聊烂了的虚拟Dom是怎么生成的吗? Vue 实例挂载的实现
The text was updated successfully, but these errors were encountered:
No branches or pull requests
前言
我们看源码,我觉得最好带着问题去看源码,这样我们会专注于一个点去看源码,不会被源码的一些其他功能,把我们带离最初想去的地方。本章主要的目的是,弄明白 vue 是如何生成虚拟 DOM 的。
从入口开始
我们从入口文件一步一步慢慢的分析。先看入口文件。
入口文件:
web/entry-runtime-with-compiler.js
。runtime 文件:./runtime/index
core 文件: core/index
instance 文件:./instance/index
new Vue 时发生什么
由上面的代码,我们可以看到,当我们
new Vue()
时,会触发一系列的初始化,然后调用_init()
方法。生成虚拟 DOM
_init()
既然
new Vue()
时,调用的是_init()
方法 ,我们就先看看_init()
方法主要做了什么事情。它是在initMixin()
函数执行时,添加到原型上的方法。在init
方法的最后,我们看到如下代码。它调用了vm.$mount
方法。$mount 方法
在上面的分析中,我们知道 Vue 中有两个$mount 方法。定义如下:
我们可以看到:
第一个 $mount
方法:是给第二个 $mount
方法调用用的,它会返回mountComponent
方法。第二个 $mount
方法:将template
编译成render
方法,保存render
方法到$options
上。最后调用第一个 $mount
方法。接下来,我们看下
mountComponent
方法。mountComponent 方法
mountComponent
方法的主要代码如下:mountComponent
方法的作用是实例化一个Watcher
,同时也创建了一个updateComponent
方法。Watcher
:作用是监听数据的变化,实例化时会执行updateComponent
方法。它会执行下面这行代码。vm._render()
这个才是正主。前期的准备工作已做完,下面到了生成虚拟DOM的时候了。
可以看到,vue 会调用由 template 编译过来的 render 方法生成 虚拟 DOM。需要注意的是:
createElement
生成虚拟DOM
。createElement
生成虚拟DOM
。然后 vue 具体是如何生成虚拟 DOM 的呢,且听下回分解。
总结
new Vue()
时,会合并相关的配置项、执行一系列的初始化、以及调用_init()
。_init()
方法中,会执行vm.$mount(vm.$options.el)
$mount
方法有两个,第一个返回mountComponent
方法。第二个$mount
方法是将模板编译成render
方法,然后调用第一个mount
方法。mountComponent
方法中,实例化Watcher
,在此过程中,会执行updateComponent
方法,相当于调用vm._update(vm._render(), hydrating)
vm._render()
时,会调用由模板编译而来的render
方法,最后生成了 虚拟 DOM。参考
Vue原理解析(四):你知道被大家聊烂了的虚拟Dom是怎么生成的吗?
Vue 实例挂载的实现
The text was updated successfully, but these errors were encountered: