Vue技术探究Vue.js专区

Vue源码分析(9)--实例分析响应式设计

2017-07-08  本文已影响106人  风之化身呀

前言

本文是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 batching
      this.active = true;
      this.dirty = this.lazy; // for lazy watchers
      this.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,而不是vm
            return _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 watcher
        dep.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 render
      vm.$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 first
  var subs = this.subs.slice(); //只有一个render watcher
  for (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的id
        has[id] = true;
        if (!flushing) { //flushing可看成全局变量,默认false
          queue.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 flush
        if (!waiting) {//waiting可看成全局变量,默认false
          waiting = 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 watchers
      for (index = 0; index < queue.length; index++) {
        watcher = queue[index];
        id = watcher.id;
        has[id] = null;
        watcher.run();
        ...
      }
      // reset scheduler before updated hook called
      var oldQueue = queue.slice();
      resetSchedulerState();
      // call updated hooks
      index = 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、总结:

第一次渲染:

第二次渲染(更新):

上一篇下一篇

猜你喜欢

热点阅读