了解vue2.0响应式中的Observer,Dep,Watcher

42 阅读2分钟

前言

  • 最近看了一下vue的源码,记录了一下自己对于vue2.0响应式原理的理解,如果有什么错误的地方,希望大佬能够指点出来。
  • 大家都知道vue的响应式原理是通过Object.defineProperty来实现的,但这种回来显然抽象了很多,如果问一下更细致的部分,可能就有点不知道了,当你把这篇文章弄懂之后,就能够细说一二了,话不多说,上正文。

先上代码

   <div id="app">
      <div>
        <p>{{email}}</p>
        <p>{{name}}</p>
        <p>{{tel}}</p>
        <p>{{age}}</p>
      </div>
    </div>
 class Vue {
            constructor(options) {
                this.$options = options;
                this.$el = el;
                this.$data = options.data || {};
                this.doomPool = {}  //放置绑定了data的dom元素
                this._definePropety(this.$data); //将data绑定到vue上
                new Observe(this.$data);将data转化为响应式数据
                new Compiler(this);  //模板解析,将{{data}}转化为正常值
            }
            _definePropety(data) {
                Object.keys(data).forEach((key) => {
                    Object.defineProperty(this, key, {
                        enumerable: true,
                        configurable: true,
                        get() {
                            return data[key];
                        },
                        set(newValue) {
                            if (newValue === data[key]) {
                                return;
                            }
                            data[key] = newValue;
                        },
                    });
                });
            }
        }
        //给数据添加get set属性
        class Observe {
            constructor(data) {
                this.walk(data);
            }
            walk(data) {
          //判断data是不是对象
              if (typeof data !== "object" || !data) {
                     return;
          }
          //遍历data
          Object.keys(data).forEach((key) => {
            this.definedReactive(data, key, data[key]);
          });
        }
            definedReactive(obj, key, value) {
                let that = this;
                //负责收集依赖(观察者), 并发送通知
                let dep = new Dep();
                Object.defineProperty(obj, key, {
                    enumerable: true,
                    configurable: true,
                    get() {
                        //收集依赖  Dep.target就是watcher
                        Dep.target && dep.addSubs(Dep.target);
                        return value;
                    },
                    set(newValue) {
                        if (newValue === value) {
                            return;
                        }
                        value = newValue;
                        //数据变换 ,发送通知给watcher的update ,在渲染视图里的数据
                        dep.notify();
                    },
                });
            }
        }
  //依赖
       class Dep {
            constructor() {
                //收集观察者
                this.subs = [];
            }
            //添加观察者
            addSubs(watcher) {
                if (watcher && watcher.update) {
                    this.subs.push(watcher);
                }
            }
            //数据变换,就调watcher的update方法
            notify() {
                this.subs.forEach((watcher) => {
                    watcher.update();
                });
            }
        }
        //观察者
        class Watcher {
            constructor(vm, key, callback) {
            // 向Dep的静态属性上添加watcher
                Dep.target = this;
                const self = this;
                this.vm = vm;
                this.callback = callback;
                this.key = key
                this.oldValue = this.vm[key]
                Dep.target = null;
            }
            //当数据变化的时候,更新视图
            update() {
                let newValue = this.vm[this.key]
                if (this.oldValue === newValue) {
                    return
                }
                this.callback(newValue, this.node)
            }
        }
        
//模板渲染
class Compiler {
        constructor(vm) {
          //传vue实例
          this.el = vm.$el;
          this.vm = vm;
          this.compile(this.el);
        }
        compile(el) {
          const child = el.childNodes;
          child.forEach((item) => {
            if (item.nodeType === 3) {
              const _value = item.nodeValue;
              if (_value.trim().length) {
                let reg_isValid = /\{\{.*?}\}/.test(_value);
                if (reg_isValid) {
                  const _key = _value.match(/\{\{(.*?)}\}/)[1].trim();
                  this.vm.doomPool[_key] = item.parentNode; //将dom节点保存到dom池中
                  item.parentNode.innerText = this.vm[_key] || undefined; //这里会触发收集依赖
                  new Watcher(this.vm, _key, (newValue) => {
                    this.vm.doomPool[_key].innerText = newValue;
                  });
                }
              }
            }
            item.childNodes && this.compile(item);
          });
        }
      }
      var vue = New Vue({
         el: "#app",
         data: {
          name: "123",
          email: "456",
          age: "789",
          tel: "123132",
        },
      })

这里我们就简单的实现了一个vue的响应式,接下来我们就重点讲一下watcher,dep和observer的关系。

内容讲解

  1. 我们通过_definePropety函数将data中的值绑定到vue上面,
  2. 我们将data中的值通过Observe函数添加get和set属性。
  3. 我们通过Compiler函数对整个dom元素进行遍历,然后解析遇到的{{}},然后给它赋值,并保存要改变的dom元素。
  4. 通过addSubs给要渲染的值添加一个watcher,与渲染无关的值并不会在依赖收集器中添加到监听。当我们改变data中的值的时候,如果是渲染的值那么就会先执行deep中的notify函数来对整个subs进行遍历,通知所有Watcher实例更新。
  5. 总之observer的作用就是给让数据变成响应式数据,然后dep就是存放watcher和对watcher的管理,当数据变动时就通知存放的所有watcher进行更新,watcher就负责更新视图。