Vue监听

81 阅读2分钟

Observer 监听数据变化

数据劫持 发布-订阅



class Observer{
  constructor(value) {
    this.value = value;
    this.walk(value)
    
  }
  walk(data){
    //遍历所有key 监听
    Object.keys(data).forEach(key => {
     this.defineReactive(key,data[key])
      
    })
    
}

defineReactive(key, value) {
    let dep = new Dep();
   let childObj = this.observe(value)
  //监听属性 递归
    Object.defineProperty(this.value, key, {
      enumerable: true,
      configurable: false,
      get() {
        //添加依赖 订阅
        Dep.target&&dep.depend()
        return value;
      },
      set(newVal) {
        if (value == newVal) return;
        value = newVal;
        // 新的值是object的话,进行监听
        childObj = observe(newVal);
        //通知所有的依赖 发布
        dep.notify();
         console.log("监听到变化了。。。")
      }
    })
    
  }
  observe(val) {
    if (typeof val != "object") return;
    //继续监听
    return new Observer(val);
  }

}

walk 遍历obj的key 调用defineReactive 监听属性 数据已经监听了 , 需要通知订阅者 ,需要一个消息订阅器 发布 - 订阅 变化

Dep(发布-订阅)

//Dep 消息订阅器 订阅Watcher的数据依赖
var Dep = function Dep () {
    this.id = uid++;
    //消息队列
    this.subs = [];
  };
    //添加订阅
  Dep.prototype.addSub = function addSub (sub) {
    this.subs.push(sub);
  };
    //移除订阅
  Dep.prototype.removeSub = function removeSub (sub) {
    remove(this.subs, sub);
  };
    //判断是否通过Dep.target 添加的依赖???
  Dep.prototype.depend = function depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  };

  Dep.prototype.notify = function notify () {
    var subs = this.subs.slice();
    //发布订阅 通知 每个订阅者,调用update()方法
    for (var i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  };

 //全局的 订阅后重置
  Dep.target = null;
  

watch

在initMixin中 初始化完成后 如果传入el 则调用$mount

$mount =》 mountComponent =》

 if (vm.$options.el) {
       vm.$mount(vm.$options.el);
  }
  
  
  Vue.prototype.$mount = function (
   el,
   hydrating
 ) {
   el = el && inBrowser ? query(el) : undefined;
   return mountComponent(this, el, hydrating)
 };
 
 //挂载组件
 function mountComponent (
   vm,
   el,
   hydrating
 ) {
   vm.$el = el;
   //是否有render函数
   if (!vm.$options.render) {
     vm.$options.render = createEmptyVNode;
    
   }
   callHook(vm, 'beforeMount');

   var updateComponent;
     updateComponent = function () {
       vm._update(vm._render(), hydrating);
     };
     //
   new Watcher(vm, updateComponent, noop, {
     before: function before () {
       if (vm._isMounted && !vm._isDestroyed) {
         callHook(vm, 'beforeUpdate');
       }
     }
   }, true /* isRenderWatcher */);
   hydrating = false;
   if (vm.$vnode == null) {
     vm._isMounted = true;
     callHook(vm, 'mounted');
   }
   return vm
 }
 
 
 //mountComponent在构造新的Watcher对象传了当前vue实例、updateComponent函数、空函数
 // 订阅者初始化的时候 添加到订阅器中,调用get方法,添加 
 //只有初始化时才添加 添加成功后重置Dep.target
 



 class Watcher{
 constructor(vm,exp,cb) {
   this.vm = vm;
   this.cb = cb;
   this.exp = exp;
   this.depIds = {}
   //触发Observe 的get 方法  在dep上添加自己
   
   if (typeof exp === 'function') {
   //updateComponent函数
     this.getter = exp;
 } else {
     this.getter = this.parseGetter(exp.trim());
   }
   this.value = this.get();
 }

 addDep(attr) {
 //每次变化都会调用run,触发getter ==》dep.depend()=》addDep 判断当前dep.id是否是新的,如果是 添加依赖 不是则不需要添加依赖
   if (!this.depIds.hasOwnProperty(dep.id)) {
       dep.addSub(this);
       this.depIds[dep.id] = dep;
   }
}
 //属性变化收到通知
 update() {
   this.run();
 }
 run() {
   //取最新值
   let value = this.get();
   //旧值
   let oldVal = this.value;
   if (value != oldVal) {
     //更新value
     this.value = value;
     //执行回调  比如更新视图 执行回调 
     this.cb.call(vm,value,oldVal)
   }
   
 }
 parseGetter(attr) {
   if (/[^\w.$]/.test(attr)) return; 

   var exps = attr.split('.');

   return (obj)=> {
       for (var i = 0, len = exps.length; i < len; i++) {
           if (!obj) return;
           obj = obj[exps[i]];
       }
       return obj;
   }
}
 get() {
   Dep.target = this; //当前订阅指向自己
   let value = this.vm[exp];//触发getter  添加到自己的订阅器 
   //把当前的 `watcher` 订阅到这个数据持有的 `dep` 的 `watchers` 中 方便后续数据变化时能通知到watch
   Dep.target = null;//重置
   return value;
 }
}

Compiler


class Compile{
 constructor(el,vm) {
   this.$el = this.isElementNode(el) ? el : document.querySelector(el);
   this.$vm = vm;
   this.$fragment = "";
   this.init()
 }
 init() {
   if (this.$el) {
     //文档碎片
     this.$fragment = this.node2Fragment(this.$el);
     this.initFragment();
     this.$el.appendChild(this.$fragment)
   }
   
 }
 node2Fragment(el) {
   let fragment = document.createDocumentFragment(),child;
   while (child = el.firstChild) {
     fragment.appendChild(child);
   }
   return fragment;

 }
 //解析文档碎片
 initFragment() {
   this.compileElement(this.$fragment)
   
 }
 //遍历所有的子节点和节点
 compileElement(el) {
   let childNodes = el.childNodes;
   [...childNodes].forEach(node => {
     var text = node.textContent;
     var reg = /\{\{(.*)\}\}/;
     if (this.isElementNode(node)) {
       //元素节点
       this.compile(node)
     }else if (this.isTextNode(text)&&reg.test(text)) {
       //文本节点 执行文本指令
       this.compileText(node, RegExp.$1.trim());
       
     } 
     //遍历子节点
     if (node.childNodes && node.childNodes.length) {
       this.compileElement(node.childNodes)
       
     }
   })
   
 }
 compileText(node, str) {
   compileUtil.text(node, this.$vm, str);
 }
 compile(node) {
   let attrs = node.attributes;
   [...attrs].forEach(attr => {
     let name = attr.name;
     if (this.isDirective(name)) {
       let exp = attr.value;
       //v-on v-text
       let str = name.substring(2);
       if (this.isEventDirective(str)) {
         //v-on:click 绑定的事件
         compileUtil.eventHandler(node, this.$vm, exp, str);

       } else {
         compileUtil[str]&& compileUtil[str](node,this.$vm,exp,str)
         
       }
       // node.removeAttribute(attrName);
     }
   })
   
 }
 //是否是指令
 isDirective(attr) {
   return attr.indexOf('v-')==0
   
 }
 //是否方法
 isEventDirective(attr) {
   return attr.indexOf(on)==0
   
 }
 isElementNode(node) {
   return node.type ===1
 }

}



let compileUtil = {
 eventHandler(node, vm, exp, str) {
   //on:click

   var eventType = str.split(':')[1],
   fn = vm.$options.methods && vm.$options.methods[str];

   if (eventType && fn) {
     node.addEventListener(eventType, fn.bind(vm), false);
   }
     
 },
 text(node, vm, exp, str) {
   this.bind(node, vm, exp, 'text');
 },
 bind(node, vm, exp, attr) {
   //更新函数
   let updateFn = updater[attr + 'Updater'];
   updateFn && updateFn(node, this._getVMVal(vm, exp));
   //视图数据发生变化 通知watch  将这个指令初始化为一个订阅者,后续 exp 改变时,就会触发这个更新回调,从而更新视图
   new Watcher(vm, exp, (value, oldVal) => {
     updaterFn && updaterFn(node, value, oldValue);
     
   })


 },
 _getVMVal(vm, exp) {
   let val = vm;
   //obj.name
   exp = exp.split('.');
   exp.forEach(v => {
     val = val[v]
     
   })
   return v;
   
 }
}

let updater = {
 
 textUpdater(node, val) {
   node.textContent = val ? val : '';
 }
} 

双向绑定,view-->Model Model-》view viewModel 数据变了通知视图 视图变了通知model viewModel 职责: 监听: Observer 解析: Compiler

数据监听 通过数据劫持 + 发布订阅

Observer 监听数据 如果数据变化在set中触发notify调用消息订阅器 消息订阅器遍历所有消息,通知Watcher 调用Watcher的update方法,来执行更新回调

Watcher 初始化时,添加Watcher到消息队列 便于触发时拿到数据

Compiler:

  • Compile 编译模板指令,替换模板数据, 初始化视图

  • 将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器;