// src/core/instance/index.js :8
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’)
}
this._init(options)
}
当我们调用new Vue()的时候,会直接调用Vue实例上的_init方法。
// src/core/instance/init.js :15
Vue.prototype._init = function (options?: Object) {
//…
initLifecycle(vm) // 初始化生命周期相关属性
initEvents(vm) // 初始化事件
initRender(vm) //初始化渲染函数
callHook(vm, ‘beforeCreate’)
}
在_init方法中,先对实例上的属性进行一些处理,比如合并组件options,初始化生命周期相关属性(绑定父组件,根组件等),初始化事件(父组件添加的事件),初始化渲染函数(给实例添加$attrs
和$listeners
属性),然后调用beforeCreate生命周期钩子函数。
beforeCreate被调用完成之后做了以下几件事
// src/core/instance/init.js :15
Vue.prototype._init = function (options?: Object) {
//…
callHook(vm, ‘beforeCreate’)
initInjections(vm) // 初始化inject
initState(vm) //初始化state
initProvide(vm) // 初始化provide
callHook(vm, ‘created’) //调用created生命周期
}
-
初始化inject
-
初始化state
-
初始化props
-
初始化methods
-
初始化data
-
初始化computed
-
初始化watch
-
初始化provide
在initState方法中:
//src/core/instance/state.js :48
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) //初始化props
if (opts.methods) initMethods(vm, opts.methods) //初始化methods
if (opts.data) {
initData(vm) //初始化data
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed) //初始化computed
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch) //初始化watch
}
}
由方法执行顺序可知,在data中可以使用props,不会报错,反过来则不行。然后执行created钩子函数created执行完成之后,会去调用vm.$mount()
方法,开始挂载组件到dom上。
//src/core/instance/init.js :68
//…
callHook(vm,“created”)
//…
if (vm.$options.el) {
vm. m o u n t ( v m . mount(vm. mount(vm.options.el)
}
感觉$mount
方法比较重要,先看看$mount
方法。
// src/platforms/web/runtime/index.js :37
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
其中核心是mountComponent方法
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== ‘production’) {
/* istanbul ignore if */
if ((vm.KaTeX parse error: Expected 'EOF', got '&' at position 18: …tions.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
)
}
}
}
callHook(vm, ‘beforeMount’)
//…
}
这一步主要是判断当前实例是否含有render函数,如果有render函数那么就准备开始执行beforeMount钩子函数,否则直接提示报错“Failed to mount component: template or render function not defined”。
如果使用了runtime-with-compile版本(没有render函数)详情见官网运行时 + 编译器 vs. 只包含运行时 在实例化Vue时,将传入的template通过一系列编译生成render函数。
-
编译这个template,生成AST抽象语法树。
-
优化这个AST,标记静态节点。(渲染过程中不会变得那些节点,优化性能)。
-
根据AST,生成render函数。
对应代码如下:
const ast = parse(template.trim(),options)
if(options.optimize !== false){
optimize(ast,options)
}
const code = generate(ast,options)
总之,在有了render函数之后,就可以进行渲染步骤了,执行beforeMount钩子函数。
beforeMount执行完成之后接着往下走:
//…
callHook(vm,‘beforeMount’)
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== ‘production’ && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const 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 = () => {
vm._update(vm._render(), hydrating)
}
}
//…
定义一个渲染组件的函数updateComponent
updateComponent = () => {
vm._update(vm._render,hydrating)
}
vm._render就是调用render函数生成一个vnode,而vm._update方法则会对这个vnode进行patch操作,帮我们把vnode通过cleateElm函数创建新节点,并且渲染到dom中。
//src/core/instance/lifecycle.js :59
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
//…
vm.KaTeX parse error: Expected group after '_' at position 9: el = vm._̲_patch__(vm.el, vnode, hydrating, false /* removeOnly */)
//…
}
看完updateComponent方法里面的具体实现之后
接下来就是要执行,updateComponent方法了。
执行是由Watcher类负责执行的。
//src/core/instance/lifecycle.js :197
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, ‘beforeUpdate’)
}
}
}, true /* isRenderWatcher */)
为什么要用Watcher来执行呢?因为在执行过程中,需要去观测这个函数依赖了哪些响应式的数据,将来在数据更新的时候,我们需要再重新执行updateComponent函数。
如果是更新后调用updateComponent函数,updateComponent内部的patch就不再是初始化的时候创建节点,而是通过diff算法将差异的地方找到,以最小化的代价更新到真实dom上。
Watcher中有一个before方法,逻辑是当vm._isMounted,也就是第一次挂载完成之后,再次更新视图之前,会先调用beforeUpdate钩子函数。
注意:如果在render过程中有子组件的话,此时子组件也会有一系列的初始化过程,也会走之前所说的所有过程,因此这是一个递归构建过程。
当有子组件时生命周期执行过程:
-
父 beforeCreate
-
父 created
-
父 beforeMount之后—>render
-
子 beforeCreate
-
子 created
-
子 beforeMount之后—>render
-
子 mounted
-
父 mounted
//src/core/instance/lifecycle.js :208
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, ‘mounted’)
}
最终mounted生命周期钩子函数触发
更新流程
当一个响应式属性被更新,触发了Watcher的回调函数,也就是updateComponent 方法
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, ‘beforeUpdate’)
}
}
}, true /* isRenderWatcher */)
在更新之前会先进行判断,是否是更新vm._isMounted如果是更新那么就会直接执行beforeUpdate生命周期钩子。
Vue 异步执行 DOM 更新
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
学习分享,共勉
题外话,毕竟我工作多年,深知技术改革和创新的方向,Flutter作为跨平台开发技术、Flutter以其美观、快速、高效、开放等优势迅速俘获人心
CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
外链图片转存中…(img-bcm4Oew0-1712057249054)]
[外链图片转存中…(img-0PS3OCcu-1712057249056)]
[外链图片转存中…(img-qbl6cp5R-1712057249057)]
[外链图片转存中…(img-RSEIAraB-1712057249058)]
[外链图片转存中…(img-EXidW23d-1712057249058)]
[外链图片转存中…(img-Qwz8Sn1S-1712057249059)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-Yjja7WUO-1712057249059)]
学习分享,共勉
题外话,毕竟我工作多年,深知技术改革和创新的方向,Flutter作为跨平台开发技术、Flutter以其美观、快速、高效、开放等优势迅速俘获人心
CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】