最近在看Vue的响应式原理,记录下自己的总结,方便后续查看。
如果这篇文章能给你一些思考,不要吝啬的帮我点上👍 哦
要是写的有啥错误 ❌ ,也拜托大佬们不吝赐教
Vue初始化
下面是vue的初始化模板 , 这段代码很简单,最后会在页面输出 hello world,下面我们从new Vue
开始来分析下他是如何实现的
<template>
<div>
{{ message }}
</div>
</template>
<script>
new Vue({
data() {
return {
message: "hello world",
};
},
});
</script>
执行流程
在 new Vue
之前,Vue初始化实例成员和静态成员的地方我们就不过多赘述,直接从new Vue()
开始`
调试
在new Vue
的时候打了个断点,之后进入函数,去执行_init()
方法
Vue.prototype._init
这个函数在/core/instance/init.js
下面定义,里面初始化了一些vue的实例方法,其中关键的代码块为
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
vm.$mount
这个函数在/platforms/web/entry-runtime-with-compiler.js
里面定义,这个函数的核心作用是把模板编译成render函数 ,大致分为三个部分
- 首先判断如果el是
body
或者html
标签,在开发环境会报警告。 - 之后去判断用户传递参数options.render函数,如果不传,解析模板
return mount.call(this, el, hydrating)
下面重点看一下对于第二点的代码块,下面是对源码的简要摘录,主要是去理解首次渲染的流程
if (!options.render) {
let template = options.template
// 如果模板存在
if (template) {
if (typeof template === 'string') {
// 如果模板是id选择器,例如我们传递的‘#app’
if (template.charAt(0) === '#') {
// 获取对应DOM对象的innerHMTL
template = idToTemplate(template)
/* 在生产环境对于空模板告警 */
}
} else if (template.nodeType) {
// 如果模板是dom元素,返回innerHTML
template = template.innerHTML
} else {
/* 两种情况都不是,发出警告当前模板不合法,并且返回当前实例 */
return this
}
} else if (el) {
// 如果没有template,获取el的outerHTML作为模板
template = getOuterHTML(el)
}
/**----------上面的一大段代码,其实就是根据不同的情况解析模板,给template赋值,最后template的值 都是对应dom的innnerHTM---------------L */
if (template) {
// 将模板转换成render函数
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// 将render函数存储到options选项中去
options.render = render
options.staticRenderFns = staticRenderFns
}
}
继续往下走会执行mount这个方法
// 调用mount方法渲染dom
return mount.call(this, el, hydrating)
mount
这个函数在/platforms/web/runtime/index.js
里面定义,是对Vue.prototype.$mount
的改写,这个函数的核心作用是渲染dom
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mountComponent
这个函数在core/instance/lifecycle.js
下面定义,主要分为几个步骤
- 首先判断当前选项是不是存在
render
函数,如果没有在生产的时候会发出警告⚠️( 这个判断的目的是如果我们当前是 运行时 环境,并且我们通过选项传入了模板,此时如果是开发环境会发出警告,提示当前使用的是运行时版本,编译器是无效的,应该传入render
函数或者使用带有编译器的版本) - 触发
beforeMount
生命周期函数 - 给
updateComponent
函数赋值 - 创建
Watcher
对象的时候传递了updateComponent
- 最后触发了
mounted
生命周期函数
这个函数的核心代码块是3,4步骤,下面我们根据源码分析下他具体做了什么
let updateComponent
// 如果是开发环境并启动了性能监测
if(process.env.NODE_ENV !== 'production' && config.performance && mark){
...
}else{
updateComponent = () => {
// _update函数的作用是把虚拟dom转成真实dom,更新到界面上。
// 注意此时是赋值并没有执行呢
vm._update(vm._render(), hydrating)
}
}
// 创建Watcher对象的时候传递了updateComponent,所以updateComponent的执行是在Watcher中调用的
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
接下来我们就看updateComponent
在 new Watcher
里面什么时候调用的
new Watcher
这个函数在/core/observer/watcher.js
中定义,这里提一下,我们在vue中的Watcher有三种,一种是渲染watcher,也就是我们当前创建的watcher。第二种是计算watcher。第三种监听器的watcher。剩下的两种watcher在以后的文章中进行分析
- 为
this.getter
赋值
这个函数在开始创建了很多属性,然后执行一个判断,判断第二个参数expOrFn
是否为函数, 首次渲染的时候,我们传递的是updateComponent,所以是expOrFn是函数,我们直接把这个函数赋值给getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
...
}
- 为
this.value
赋值
之后给this.value
赋值,当前是渲染watcher,this.lazy = false
,也就是不延迟执行,所以就是把get()方法赋值给了this.value
。下面看get()做了什么
this.value = this.lazy
? undefined
: this.get()
- 定义
get()
方法
get () {
// 把当前的watcher对象存入到栈中
pushTarget(this)
let value
const vm = this.vm
try {
// 这个地方调用了updateComponent,并改变函数内部指向为vue实例
// 也就是说,这个函数执行完,我们界面的数据就会完成更新
value = this.getter.call(vm, vm)
} catch (e) {
...
} finally {
...
}
return value
}
总结
- Vue初始化,实例成员,静态成员
- new Vue()
- this._init()
- vm.$mount
- 这是
/platforms/web/entry-runtime-with-compiler.js
的$mount, 这个函数的核心作用是帮我们把模板编译成render
函数 - 如果没有传递
render
,把模板编译成render
函数 compileToFunction()
生成render()
渲染函数options.render = render
- 这是
- vm.$mount
- 这个是
/platforms/web/runtime/ind ex.js
中的$mount - 调用
mountComponent()
- 这个是
- mountComponent(this,el)
/core/instance/lifecycle.js
中定义- 判断是否有
render
选项, 如果没有但是传入了模板,并且当前是开发环境会发出警告 - 触发
beforeMount
- 定义
updateComponent
vm._update(vm.render(), ... )
vm.render()
渲染,渲染虚拟DOM
vm._update()
更新,将虚拟DOM
转换成真实DOM
- 创建
Watcher
实例- 传入了
updateComponent方法
- 调用
get()
方法
- 传入了
- 触发
mounted
return vm
- watcher.get()
- 创建完
watcher
会调用一次get
- 调用
updateComponent()
- 调用
vm.render()
创建VNode - 调用
vm._update(vnode, ...)
- 创建完