前言
本文是vue2.x源码分析的第九篇,主要看响应式设计的处理过程!
实例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Vue</title><script src="./vue9.js" type="text/javascript" charset="utf-8" ></script></head><body><div id="app">{{message}}</div><script>var vm=new Vue({el:'#app',name:'app',data:{message:'message',}});debugger;setTimeout(()=>vm.message='messages',0)</script></body></html>
1、关键断点
initData(vm)
proxy(vm,”data”,’message’)
observe(data,true/_asRootdata/)
vm.$mount(vm.options.el)
mount.call(this,el,hydrating)
mountComponent(this,el,hydrating)
vm._watcher=new Watcher(vm,updateComponent,noop)
2、详细分析
从Watcher开始分析
Watcher = function Watcher (vm,expOrFn,cb,options) {this.vm = vm;vm._watchers.push(this);this.deep = this.user = this.lazy = this.sync = false;this.cb = cb;this.id = ++uid$2; // uid for batchingthis.active = true;this.dirty = this.lazy; // for lazy watchersthis.deps = [];this.newDeps = [];this.depIds = new _Set();this.newDepIds = new _Set();this.expression = expOrFn.toString();if (typeof expOrFn === 'function') {this.getter = expOrFn;} else {...}this.value = this.lazy? undefined: this.get();};
Watcher.prototype.get = function get () {pushTarget(this); //这个this即是刚创建的watcher实例,称之为render watcher,本质上执行Dep.target = this;var value;var vm = this.vm;if (this.user) {...} else {value = this.getter.call(vm, vm); //this.getter得到执行,即是updateComponent函数被执行}if (this.deep) {traverse(value);}popTarget();this.cleanupDeps();return value};
接下来执行updateComponent
updateComponent = function () {vm._update(vm._render(), hydrating);};
先执行vm._render
Vue.prototype._render = function () {var vm = this;var ref = vm.$options;var render = ref.render;var staticRenderFns = ref.staticRenderFns;var _parentVnode = ref._parentVnode;...var vnode;try {vnode = render.call(vm._renderProxy, vm.$createElement);} catch (e) {...}vnode.parent = _parentVnode;return vnode};
以上主要执行的是render函数,该函数是通过AST得到的匿名函数
function() {with(this){ //this是vm._renderProxy,而不是vmreturn _c('div',{attrs:{"id":"app"}},[_v(_s(message))])}}
注意_s(message),它在执行时会执行vm.message取值操作,从而触发vm.message的get函数,进而触发vm._data.message的get函数,看下该函数:
get: function reactiveGetter () {var value = getter ? getter.call(obj) : val;if (Dep.target) { // Dep.target在上面被设为了render watcherdep.depend(); //data的每个属性都有一个dep实例对应,让render watcher收集该属性的dep...}return value},
Dep.prototype.depend = function depend () {if (Dep.target) {Dep.target.addDep(this);}};//addDep如下:Watcher.prototype.addDep = function addDep (dep) {var id = dep.id;if (!this.newDepIds.has(id)) {this.newDepIds.add(id);this.newDeps.push(dep);if (!this.depIds.has(id)) {dep.addSub(this); //dep的subs收集该watcher}}};//addSub如下:Dep.prototype.addSub = function addSub (sub) {this.subs.push(sub);//dep的subs收集该watcher};//render watcher收集了所有dep,同时每个dep又都收集了render watcher,这时this.\_render执行完毕,返回了Vnode
接下来执行vm._update函数
Vue.prototype._update = function (vnode, hydrating) {var vm = this;if (vm._isMounted) {callHook(vm, 'beforeUpdate');}var prevEl = vm.$el;var prevVnode = vm._vnode;var prevActiveInstance = activeInstance;activeInstance = vm;vm._vnode = vnode;if (!prevVnode) {// initial rendervm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */,vm.$options._parentElm,vm.$options._refElm);} else {...}...// updated hook is called by the scheduler to ensure that children are// updated in a parent's updated hook.};
主要执行vm.patch函数,该函数内部主要执行在上节提到的createElm函数,该函数的四步执行完后就能创建真实DOM结构了,于是页面首次渲染就完成了。
下面分析当vm.message的值发生变化,vue是如何追踪到变化并更新页面的:
首先message的值发生变化会触发vm.message的set函数,进而触发vm._data.message的set函数,看下该函数:
set: function reactiveSetter (newVal) {var value = getter ? getter.call(obj) : val;/* eslint-disable no-self-compare */if (newVal === value || (newVal !== newVal && value !== value)) {return}/* eslint-enable no-self-compare */if ("development" !== 'production' && customSetter) {customSetter();}if (setter) {setter.call(obj, newVal);} else {val = newVal; //旧值被新值替换}childOb = observe(newVal);//观测新值dep.notify(); //这个dep与get函数中的是同一个,在get中dep.subs中已经订阅了render watcher}
看下notify函数:
Dep.prototype.notify = function notify () {// stabilize(使稳固) the subscriber list firstvar subs = this.subs.slice(); //只有一个render watcherfor (var i = 0, l = subs.length; i < l; i++) {subs[i].update(); //执行render watcher的update函数}};
看下update函数:
Watcher.prototype.update = function update () {/* istanbul ignore else */if (this.lazy) {this.dirty = true;} else if (this.sync) {this.run();} else {queueWatcher(this);//执行该函数}};
看下queueWatcher函数:
function queueWatcher (watcher) {var id = watcher.id;if (has[id] == null) { //has是个对象,存放watcher的idhas[id] = true;if (!flushing) { //flushing可看成全局变量,默认falsequeue.push(watcher); //render watcher被推入queue数组} else {// if already flushing, splice the watcher based on its id// if already past its id, it will be run next immediately.var i = queue.length - 1;while (i >= 0 && queue[i].id > watcher.id) {i--;}queue.splice(Math.max(i, index) + 1, 0, watcher);}// queue the flushif (!waiting) {//waiting可看成全局变量,默认falsewaiting = true;nextTick(flushSchedulerQueue);}}}
看下flushSchedulerQueue函数:
function flushSchedulerQueue () {debugger;flushing = true;var watcher, id, vm;queue.sort(function (a, b) { return a.id - b.id; });// do not cache length because more watchers might be pushed// as we run existing watchersfor (index = 0; index < queue.length; index++) {watcher = queue[index];id = watcher.id;has[id] = null;watcher.run();...}// reset scheduler before updated hook calledvar oldQueue = queue.slice();resetSchedulerState();// call updated hooksindex = oldQueue.length;while (index--) {watcher = oldQueue[index];vm = watcher.vm;if (vm._watcher === watcher && vm._isMounted) {callHook(vm, 'updated');}}...}
看下watcher.run函数:
Watcher.prototype.run = function run () {debugger;if (this.active) {var value = this.get();...}};
这个this.get就是初次渲染时调用过一次的this.get
Watcher.prototype.get = function get () {pushTarget(this); //这个this即是刚创建的watcher实例,称之为render watcher,本质上执行Dep.target = this;var value;var vm = this.vm;if (this.user) {...} else {value = this.getter.call(vm, vm); //this.getter得到执行,即是updateComponent函数被执行}if (this.deep) {traverse(value);}popTarget();this.cleanupDeps();return value};
然后updateComponent函数又一次被执行,从而this._render,this._update都得到执行,DOM结构得以创建,唯一不同的是message值已经被修改成新值了,从而页面实现了更新。
3、总结:
第一次渲染:
- observe(data) 为下次页面更新做准备
- updateComponent 只要该函数得到执行,就会生成真实DOM
- new Watcher(vm,updateComponent,noop),在内部updateComponent得到执行
第二次渲染(更新):
- 触发set函数
- dep.notify
- watcher.update
- queueWatcher
- flushSchedulerQueue
- watcher.run
- updateComponent再次执行,生成新DOM
