大前端学习-Vue首次渲染过程
文章内容输出来源:拉勾教育大前端高薪训练营最近在学习Vue源码,然后主要是对Vue的首次渲染做一个总结~首先来看下图把,接下来会一步一步讲解Vue源码地址,这里主要看src目录下面的源码一、从入口文件开始定义在 src/platform/web/entry-runtime-with-compiler.js二、Vue初始化过程1.首先取出Vue的$mount,对$mount进行重写,给$mount增
文章内容输出来源:拉勾教育大前端高薪训练营
最近在学习Vue源码,然后主要是对Vue的首次渲染做一个总结~
首先来看下图把,接下来会一步一步讲解
Vue源码地址,这里主要看src目录下面的源码
一、从入口文件开始
定义在 src/platform/web/entry-runtime-with-compiler.js
二、Vue初始化过程
1.首先取出Vue的$mount
,对$mount
进行重写,给$mount
增加新的功能
// src/platform/web/entry-runtime-with-compiler.js
// 保留 Vue实例的 $mount 方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
// 非ssr情况下的为false, ssr时候为true
hydrating?: boolean
): Component {
// 获取el对象
el = el && query(el)
...
}
2.判断是否有render
选项,如果没有render
选项,则会把模版template
取出,把模版编译成render
函数,接着调用mount
方法,渲染DOM
。
3.给Vue增加了一个静态的compile
方法,作用是把HTML
字符串编译成render
函数
if (!options.render) {
let template = options.template
if (template) {
...
}
}
Vue.compile = compileToFunctions
export default Vue
4.这个文件主要是通过extend
给Vue全局注册了指令和组件,组件是Transition
和TransitionGroup
,指令是v-model
和v-show
,接着在Vue
的原型上注册了 _patch_
函数, _patch_
函数作用是将虚拟DOM转换成真实DOM,在给patch
函数赋值的时候会判断是否是浏览器环境
5 我们继续找Vue的构造函数
// src/platforms/web/runtime/index.js
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)
// 判断是否是浏览器环境
Vue.prototype.__patch__ = inBrowser ? patch : noop
三、初始化静态成员
6.定义在src/core/index.js
- 在这个文件中调用
initGlobalAPI(Vue)
方法,给Vue的构造函数增加静态方法initGlobalAPI(Vue)
7.initGlobalAPI(Vue)
定义在src/core/global-api/index.js
- 初始化
Vue.config
对象 - 设置
keep-alive
组件 - 注册
Vue.use()
用来注册插件 - 注册
Vue.mixin()
实现混入 - 注册
Vue.extend()
基于传入的options返回一个组件的构造函数 - 注册
Vue.directive()
,Vue.component()
,Vue.filter
export function initGlobalAPI (Vue: GlobalAPI) {
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
// 初始化 Vue.config对象
Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
// 这些工具方法不视作全局Api的一部分,除非你已经意识到某些风险,否则不要去依赖他们
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
// 静态方法 set/delete/nextTick
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// ...
Vue.options._base = Vue
// 设置keep-alive 组件
extend(Vue.options.components, builtInComponents)
// 注册Vue.use()用来注册插件
initUse(Vue)
// 注册Vue.mixin()实现混入
initMixin(Vue)
// 注册Vue.extend()基于传入的options返回一个组件的构造函数
initExtend(Vue)
// 注册Vue.directive(), Vue.component(), Vue.filter
initAssetRegisters(Vue)
}
四、初始化实例成员
- _init()
- 定义在
src/core/instance/index.js
- 定义了构造函数,调用了
this._init(options)
方法 - 给Vue中混入了常用的实例成员
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 调用 _init()方法
this._init(options)
}
// 注册vm的_init()方法, 初始化vm
initMixin(Vue)
// 注册vm 的$data/$props/$set/$delete/$watch
stateMixin(Vue)
// 初始化事件相关方法
//$on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入 render
// $nextTick/_render
renderMixin(Vue)
export default Vue
初始化实例成员 init()
-
当静态成员和实例成员都初始化完成之后,接着调用Vue的构造函数,在构造函数中调用
_init()
方法 -
_init
是在initMixin
中初始化的,主要对Vue实例初始化
// vm的生命周期相关变量初始化
initLifecycle(vm)
// vm的事件监听初始化,父组件绑定在当前组件上的事件
initEvents(vm)
// vm的编译render初始化
// $slots/$scopedSlots/_c/$createElement/$attrs/$listeners
initRender(vm)
// beforeCreate 生命钩子的回调
callHook(vm, 'beforeCreate')
// 把inject的成员注入到vm上
initInjections(vm) // resolve injections before data/props
// 初始化vm的 _props/methods/_data/computed/watch
initState(vm)
// 初始化provide
initProvide(vm) // resolve provide after data/props
// created 生命钩子回调
callHook(vm, 'created')
初始化实例成员 initState()
- 初始化vm的
_props/methods/_data/computed/watch
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
-
在
instance/state.js
中,首先获取了Vue
实例中的$options
,然后判断options
中是否有props,methods,data
以及computed
和watch
这些属性,如果有的话,通过initProps
进行初始化
initProps(vm, opts.props)
接收了两个参数,一个是Vue
实例,一个是Props
属性,我们跳转到initProps
函数中,首先给Vue
实例定义了一个_Props
对象, 并且把它存储到了常量里面const props = vm._props = {}
-
紧接着,开始遍历
PropsOptions
的所有属性,它其实就是initProps
方法中的第二个参数,遍历每个属性,然后通过defineReactive
注入到Props
这个对象上,这个props
其实就是vm._props
所有的成员都会通过defineReacttive
转化为get
和set
,最后在Props
对象上存储,
注意 -
在开发模式中,如果我们直接给这个属性赋值的话,会发出一个警告,
-
生产环境中直接通过
defineReactive
把props
中的属性转化成get
和set
-
最后判断了
props
属性是否在Vue
实例中存在,不存在通过Proxy
这个函数把我们的属性注入到Vue
的实例中 -
在
Proxy
中,通过调用Object.defineProperty(target, key,sharePropertyDefinition)
总结
initProps
的作用就是把我们的Props
成员转化成响应式数据,并且注入到Vue
实例里面中
initMethods
-
在
initMethods(vm, opts.methods)
中,也是接收两个参数,Vue实例和选项中的methods
,首先获取了选项中的Props,接着遍历methods所有属性,接着判断当前的环境是否是开发或者生产开发环境会判断methods
是否是functicon
-
接着判断
methods
方法的名称是否在Props
对象中存在,存在就会发送一个警告,警告在属性在Props
中已经存在,因为Props
和methods
最终都要注入到Vue实例上,不能出现同名 -
下面继续判断
key
是否在Vue
中存在,并且调用了isReserved(key)
,判断我们的key
是否以_开头或$
开头
最后把methods
注入到Vue实例上来,注入的时候会判断是否是function
,如果不是返回noop
,是的话把函数返回bind(methods[key], vm)
总结
initMethods
作用就是把选项的methods
注入到vue实例,在注入之前,会先判断我们命名是否在Props
中存在,并且判断了命名的规范,不建议_和$开头
initData(vm)
-
当
options
中有data
选项时,会调用initData(vm)
-
当没有的时候此时会给vm初始化一个
_data属性observe(vm._data = {}, true)
然后调用observe函数,observe
是响应式中的一个函数 -
在
initData
中获取了options
的data
选项,判断了data
选项是否是function
,如果是调用getData(data,vm)
接着获取data
中的所有属性,同时获取了props,methods
中所有的属性
// src/core/instance/state.js
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
最后做一个响应式处理
observe(data, true)
在_init
函数的最后,又调用了$mount
来挂载整个页面
// src/core/instance/init.js
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
五、首次渲染过程总结
- 在首次渲染之前,首先进行Vue初始化,初始化实例成员和静态成员
- 当初始化结束之后,要调用Vue的构造函数
new Vue()
,在构造函数中调用了_init()
方法,这个方法相当于我们整个Vue的入口 - 在
_init
方法中,最终调用了$mount
,一共有两个$mount
,第一个定义在entry-runtime-with-compiler.js
文件中,也就是我们的入口文件$mount
,这个$mount()
的核心作用是帮我们把模板编译成render
函数,但它首先会判断一下当前是否传入了render
选项,如果没有传入的话,它会去获取我们的template
选项,如果template
选项也没有的话,他会把el
中的内容作为我们的模板,然后把模板编译成render
函数,它是通过compileToFunctions()
函数,帮我们把模板编译成render
函数的,当把render
函数编译好之后,它会把render
函数存在我们的options.render
中。 - 接着会调用
src/platforms/web/runtime/index.js
文件中的$mount
方法,在这个中首先会重新获取el
,因为如果是运行时版本的话,是不会走entry-runtime-with-compiler.js
这个入口中获取el,所以如果是运行时版本的话,我们会在runtime/index.js的$mount()中重新获取el。 - 接下来调用
mountComponent()
,这个方法在src/core/instance/lifecycle.js
中定义的,在mountComponent()
中,首先会判断render
选项,如果没有render
选项,但是我们传入了模板,并且当前是开发环境的话会发送一个警告,目的是如果我们当前使用运行时版本的Vue,而且我们没有传入render,但是传入了模版,告诉我们运行时版本不支持编译器。接下来会触发beforeMount这个生命周期中的钩子函数,也就是开始挂载之前。 - 然后定义了updateComponent(),在这个函数中,调用
vm._render
和vm._update
,vm._render
的作用是生成虚拟DOM,vm._update
的作用是将虚拟DOM
转换成真实DOM
,并且挂载到页面上 - 创建
Watcher
对象,在创建Watcher
时,传递了updateComponent
这个函数,这个函数最终是在Watcher
内部调用的。在Watcher
内部会用了get
方法,当Watcher创建完成之后,会触发生命周期中的mounted
钩子函数,在get方法中,会调用updateComponent()
- 挂载结束,最终返回Vue实例。
以上就是Vue的首次渲染过程
六、感谢
最后感谢您花宝贵的时间阅读这篇内容,如果你觉得这篇内容对你有帮助的话,就给本文点个赞吧,🌹🌹🌹
更多推荐
所有评论(0)