共计 4270 个字符,预计需要花费 11 分钟才能阅读完成。
本篇文章通过一个栗子🌰来聊聊 Vue 初始化和更新数据的大抵流程:
<div id="demo">
<child :list="list"></child>
<button @click="handleAdd">add</button>
</div>
<script>
Vue.component('child', {
props: {
list: {
type: Array,
default: () => []
}
},
template: '<p>{{list}}</p>'
})
new Vue({
el: "#demo",
data() {
return {list: [1,2]
}
},
methods: {handleAdd() {this.list.push(Math.random())
}
}
})
</script>
很简略的例子,一个父组件一个子组件,子组件承受一个 list,父组件有个按钮,能够往 list 里 push 数据扭转 list。
初始化流程:
-
首先从
new Vue({el: "#app"})
开始,会执行_init
办法。function Vue (options) { // 省略... this._init(options) }
-
_init
办法的最初执行了vm.$mount
挂载实例。Vue.prototype._init = function (options) { var vm = this; // 省略... if (vm.$options.el) {vm.$mount(vm.$options.el); } }
-
如果此时运行的版本是
runtime with compiler
版本,这个版本的$mount
会被进行重写,减少了把 template 模板转成 render 渲染函数的操作,但最终都会走到mountComponent
办法。Vue.prototype.$mount = function (el, hydrating) {el = el && inBrowser ? query(el) : undefined; return mountComponent(this,el,hydrating); }; var mount = Vue.prototype.$mount; // 缓存上一次的 Vue.prototype.$mount Vue.prototype.$mount = function (el, hydrating) { // 重写 Vue.prototype.$mount // 省略,将 template 转化为 render 渲染函数 return mount.call( this, el, hydrating ) };
-
mountComponent
里触发了beforeMount
和mounted
生命周期,更重要的是创立了Watcher
,传入的updateComponent
就是 Watcher 的getter
。function mountComponent(vm, el, hydrating) { // 执行生命周期函数 beforeMount callHook(vm, 'beforeMount'); var updateComponent; // 如果开发环境 if ("development" !== 'production' && config.performance && mark) {// 省略...} else {updateComponent = function () { vm._update(vm._render(), // 先执行_render, 返回 vnode hydrating ); }; } new Watcher( vm, updateComponent, noop, null, true // 是否渲染过得观察者 ); if (vm.$vnode == null) { vm._isMounted = true; // 执行生命周期函数 mounted callHook(vm, 'mounted'); } return vm }
-
在创立
Watcher
时会触发get()
办法,pushTarget(this)
将Dep.target
设置为以后 Watcher 实例。function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {if (typeof expOrFn === 'function') {this.getter = expOrFn;} this.value = this.lazy ? // 这个有是组件才为真 undefined : this.get(); // 计算 getter,并从新收集依赖项。获取值}; Watcher.prototype.get = function get() {pushTarget(this); var value; var vm = this.vm; try {value = this.getter.call(vm, vm); } catch (e) { } finally {popTarget(); } return value };
-
Watcher
的get()
里会去读取数据,触发initData
时应用Object.defineProperty
为数据设置的get
,在这里进行依赖收集。咱们晓得 Vue 中每个响应式属性都有一个__ob__
属性,寄存的是一个 Observe 实例,这里的childOb
就是这个__ob__
,通过childOb.dep.depend()
往这个属性的__ob__
中的 dep 里收集依赖,如下图。export function defineReactive ( obj: Object, key: string, val: any, customSetter?: Function ) { /* 在闭包中定义一个 dep 对象 */ const dep = new Dep() let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { /* 如果本来对象领有 getter 办法则执行 */ const value = getter ? getter.call(obj) : val if (Dep.target) { /* 进行依赖收集 */ dep.depend() if (childOb) {childOb.dep.depend() } if (Array.isArray(value)) {dependArray(value) } } return value }, set: function reactiveSetter (newVal) {}}) }
- 在咱们的例子中,这个 list 会收集两次依赖,所以它
__ob__
的 subs 里会有两个 Watcher
,第一次是在父组件data
中的 list,第二次是在创立组件时调用createComponent
,而后又会走到_init
=>initState
=>initProps
,在initProps
内对props
传入的属性进行依赖收集。有两个 Watcher 就阐明 list 扭转时要告诉两个中央,这很好了解。
. - 最初,触发
getter
,下面说过getter
就是updateComponent
,外面执行_update
更新视图。
上面来说说更新的流程:
-
点击按钮往数组中增加一个数字,在 Vue 中,为了监听数组变动,对数组的罕用办法做了重写,所以先会走到
ob.dep.notify()
这里,ob
就是 list 的__ob__
属性,下面保留着 Observe 实例,外面的 dep 中有两个Watcher
,调用notify
去告诉所有 Watcher 对象更新视图。[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] .forEach(function (method) {const original = arrayProto[method] def(arrayMethods, method, function mutator () { let i = arguments.length const args = new Array(i) while (i--) {args[i] = arguments[i] } /* 调用原生的数组办法 */ const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': inserted = args break case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) /*dep 告诉所有注册的观察者进行响应式解决 */ ob.dep.notify() return result }) })
-
notify
办法里去告诉所有Watcher
更新,执行Watcher
的update
办法,update
里的queueWatcher
过滤了一些反复的Watcher
, 但最终会走到Watcher
的run()
办法。Dep.prototype.notify = function notify() {var subs = this.subs.slice(); for (var i = 0, l = subs.length; i < l; i++) {subs[i].update();} }; Watcher.prototype.update = function update() {if (this.lazy) {this.dirty = true;} else if (this.sync) {this.run(); } else {queueWatcher(this); } };
-
run
办法里会调用get()
,get
办法里回去触发 Watcher 的getter
,下面说过,getter
就是updateComponent
。Watcher.prototype.run = function run() {if (this.active) { /* get 操作在获取 value 自身也会执行 getter 从而调用 update 更新视图 */ const value = this.get()} } updateComponent = function () { vm._update(vm._render(), hydrating ); };
- 最初在
_update
办法中,进行patch
操作,patch
里的具体逻辑就不在这里说了,有趣味的小伙伴能够去看看我的另一篇文章《Vue 源码学习 - 虚构 DOM+Diff 算法》。
结尾
我是周小羊,一个前端萌新,写文章是为了记录本人日常工作遇到的问题和学习的内容,晋升本人,如果您感觉本文对你有用的话,麻烦点个赞激励一下哟~