前言

本文是vue2.x源码分析的第九篇,主要看响应式设计的处理过程!

实例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Vue</title>
  6. <script src="./vue9.js" type="text/javascript" charset="utf-8" ></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. {{message}}
  11. </div>
  12. <script>
  13. var vm=new Vue({
  14. el:'#app',
  15. name:'app',
  16. data:{
  17. message:'message',
  18. }
  19. });
  20. debugger;
  21. setTimeout(()=>vm.message='messages',0)
  22. </script>
  23. </body>
  24. </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开始分析

  1. Watcher = function Watcher (vm,expOrFn,cb,options) {
  2. this.vm = vm;
  3. vm._watchers.push(this);
  4. this.deep = this.user = this.lazy = this.sync = false;
  5. this.cb = cb;
  6. this.id = ++uid$2; // uid for batching
  7. this.active = true;
  8. this.dirty = this.lazy; // for lazy watchers
  9. this.deps = [];
  10. this.newDeps = [];
  11. this.depIds = new _Set();
  12. this.newDepIds = new _Set();
  13. this.expression = expOrFn.toString();
  14. if (typeof expOrFn === 'function') {
  15. this.getter = expOrFn;
  16. } else {
  17. ...
  18. }
  19. this.value = this.lazy
  20. ? undefined
  21. : this.get();
  22. };
  1. Watcher.prototype.get = function get () {
  2. pushTarget(this); //这个this即是刚创建的watcher实例,称之为render watcher,本质上执行Dep.target = this;
  3. var value;
  4. var vm = this.vm;
  5. if (this.user) {
  6. ...
  7. } else {
  8. value = this.getter.call(vm, vm); //this.getter得到执行,即是updateComponent函数被执行
  9. }
  10. if (this.deep) {
  11. traverse(value);
  12. }
  13. popTarget();
  14. this.cleanupDeps();
  15. return value
  16. };

接下来执行updateComponent

  1. updateComponent = function () {
  2. vm._update(vm._render(), hydrating);
  3. };

先执行vm._render

  1. Vue.prototype._render = function () {
  2. var vm = this;
  3. var ref = vm.$options;
  4. var render = ref.render;
  5. var staticRenderFns = ref.staticRenderFns;
  6. var _parentVnode = ref._parentVnode;
  7. ...
  8. var vnode;
  9. try {
  10. vnode = render.call(vm._renderProxy, vm.$createElement);
  11. } catch (e) {
  12. ...
  13. }
  14. vnode.parent = _parentVnode;
  15. return vnode
  16. };

以上主要执行的是render函数,该函数是通过AST得到的匿名函数

  1. function() {
  2. with(this){ //this是vm._renderProxy,而不是vm
  3. return _c('div',{attrs:{"id":"app"}},[_v(_s(message))])
  4. }
  5. }

注意_s(message),它在执行时会执行vm.message取值操作,从而触发vm.message的get函数,进而触发vm._data.message的get函数,看下该函数:

  1. get: function reactiveGetter () {
  2. var value = getter ? getter.call(obj) : val;
  3. if (Dep.target) { // Dep.target在上面被设为了render watcher
  4. dep.depend(); //data的每个属性都有一个dep实例对应,让render watcher收集该属性的dep
  5. ...
  6. }
  7. return value
  8. },
  1. Dep.prototype.depend = function depend () {
  2. if (Dep.target) {
  3. Dep.target.addDep(this);
  4. }
  5. };
  6. //addDep如下:
  7. Watcher.prototype.addDep = function addDep (dep) {
  8. var id = dep.id;
  9. if (!this.newDepIds.has(id)) {
  10. this.newDepIds.add(id);
  11. this.newDeps.push(dep);
  12. if (!this.depIds.has(id)) {
  13. dep.addSub(this); //dep的subs收集该watcher
  14. }
  15. }
  16. };
  17. //addSub如下:
  18. Dep.prototype.addSub = function addSub (sub) {
  19. this.subs.push(sub);//dep的subs收集该watcher
  20. };
  21. //render watcher收集了所有dep,同时每个dep又都收集了render watcher,这时this.\_render执行完毕,返回了Vnode

接下来执行vm._update函数

  1. Vue.prototype._update = function (vnode, hydrating) {
  2. var vm = this;
  3. if (vm._isMounted) {
  4. callHook(vm, 'beforeUpdate');
  5. }
  6. var prevEl = vm.$el;
  7. var prevVnode = vm._vnode;
  8. var prevActiveInstance = activeInstance;
  9. activeInstance = vm;
  10. vm._vnode = vnode;
  11. if (!prevVnode) {
  12. // initial render
  13. vm.$el = vm.__patch__(
  14. vm.$el, vnode, hydrating, false /* removeOnly */,
  15. vm.$options._parentElm,
  16. vm.$options._refElm
  17. );
  18. } else {
  19. ...
  20. }
  21. ...
  22. // updated hook is called by the scheduler to ensure that children are
  23. // updated in a parent's updated hook.
  24. };

主要执行vm.patch函数,该函数内部主要执行在上节提到的createElm函数,该函数的四步执行完后就能创建真实DOM结构了,于是页面首次渲染就完成了。

下面分析当vm.message的值发生变化,vue是如何追踪到变化并更新页面的:
首先message的值发生变化会触发vm.message的set函数,进而触发vm._data.message的set函数,看下该函数:

  1. set: function reactiveSetter (newVal) {
  2. var value = getter ? getter.call(obj) : val;
  3. /* eslint-disable no-self-compare */
  4. if (newVal === value || (newVal !== newVal && value !== value)) {
  5. return
  6. }
  7. /* eslint-enable no-self-compare */
  8. if ("development" !== 'production' && customSetter) {
  9. customSetter();
  10. }
  11. if (setter) {
  12. setter.call(obj, newVal);
  13. } else {
  14. val = newVal; //旧值被新值替换
  15. }
  16. childOb = observe(newVal);//观测新值
  17. dep.notify(); //这个dep与get函数中的是同一个,在get中dep.subs中已经订阅了render watcher
  18. }

看下notify函数:

  1. Dep.prototype.notify = function notify () {
  2. // stabilize(使稳固) the subscriber list first
  3. var subs = this.subs.slice(); //只有一个render watcher
  4. for (var i = 0, l = subs.length; i < l; i++) {
  5. subs[i].update(); //执行render watcher的update函数
  6. }
  7. };

看下update函数:

  1. Watcher.prototype.update = function update () {
  2. /* istanbul ignore else */
  3. if (this.lazy) {
  4. this.dirty = true;
  5. } else if (this.sync) {
  6. this.run();
  7. } else {
  8. queueWatcher(this);//执行该函数
  9. }
  10. };

看下queueWatcher函数:

  1. function queueWatcher (watcher) {
  2. var id = watcher.id;
  3. if (has[id] == null) { //has是个对象,存放watcher的id
  4. has[id] = true;
  5. if (!flushing) { //flushing可看成全局变量,默认false
  6. queue.push(watcher); //render watcher被推入queue数组
  7. } else {
  8. // if already flushing, splice the watcher based on its id
  9. // if already past its id, it will be run next immediately.
  10. var i = queue.length - 1;
  11. while (i >= 0 && queue[i].id > watcher.id) {
  12. i--;
  13. }
  14. queue.splice(Math.max(i, index) + 1, 0, watcher);
  15. }
  16. // queue the flush
  17. if (!waiting) {//waiting可看成全局变量,默认false
  18. waiting = true;
  19. nextTick(flushSchedulerQueue);
  20. }
  21. }
  22. }

看下flushSchedulerQueue函数:

  1. function flushSchedulerQueue () {
  2. debugger;
  3. flushing = true;
  4. var watcher, id, vm;
  5. queue.sort(function (a, b) { return a.id - b.id; });
  6. // do not cache length because more watchers might be pushed
  7. // as we run existing watchers
  8. for (index = 0; index < queue.length; index++) {
  9. watcher = queue[index];
  10. id = watcher.id;
  11. has[id] = null;
  12. watcher.run();
  13. ...
  14. }
  15. // reset scheduler before updated hook called
  16. var oldQueue = queue.slice();
  17. resetSchedulerState();
  18. // call updated hooks
  19. index = oldQueue.length;
  20. while (index--) {
  21. watcher = oldQueue[index];
  22. vm = watcher.vm;
  23. if (vm._watcher === watcher && vm._isMounted) {
  24. callHook(vm, 'updated');
  25. }
  26. }
  27. ...
  28. }

看下watcher.run函数:

  1. Watcher.prototype.run = function run () {
  2. debugger;
  3. if (this.active) {
  4. var value = this.get();
  5. ...
  6. }
  7. };

这个this.get就是初次渲染时调用过一次的this.get

  1. Watcher.prototype.get = function get () {
  2. pushTarget(this); //这个this即是刚创建的watcher实例,称之为render watcher,本质上执行Dep.target = this;
  3. var value;
  4. var vm = this.vm;
  5. if (this.user) {
  6. ...
  7. } else {
  8. value = this.getter.call(vm, vm); //this.getter得到执行,即是updateComponent函数被执行
  9. }
  10. if (this.deep) {
  11. traverse(value);
  12. }
  13. popTarget();
  14. this.cleanupDeps();
  15. return value
  16. };

然后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