Vue双向绑定原理解析,理解发布订阅难点问题!

2022-12-26 16:10:44 浏览数 (1)

Vue双向绑定是面题的难点,之前看了很多视频都没有理解Vue双向绑定发布订阅的问题,终于在b站黑马视频找到讲的比较好的视频了(https://www.bilibili.com/video/BV1Dr4y1c7xS?from=search&seid=1122885673916184117)

源码 感兴趣的小伙伴可以去看视频,讲的很清楚了

代码语言:javascript复制
class Vue {
  constructor(options) {
    this.$data = options.data;

    // 调用数据劫持的方法
    Observe(this.$data);
    // 属性代理
    Object.keys(this.$data).forEach((key) => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return this.$data[key];
        },
        set(newValue) {
          this.$data[key] = newValue;
        },
      });
    });
    Compile(options.el, this);
  }
}
// 定义一个数据劫持方法
function Observe(obj) {
  // 获取obj的每个属性
  //  递归的终止条件
  if (!obj || typeof obj !== "object") {
    return;
  }
  const dep =new Dep()
  Object.keys(obj).forEach((key) => {
    // 需要为当前key所对应的属性,添加对应的属性,添加getter 和 setter
    let value = obj[key];
    Observe(value);
    Object.defineProperty(obj, key, {
      get() {
        //  Dep.target 只要执行了下面这一行,那么刚才的Watcher实例,
        //  就会放到dep.sub 数组中了
         Dep.target && dep.addSub(Dep.target)
        return value;
      },
      set(newValue) {
        value = newValue;
        Observe(value);
        // 通知执行
        dep.notify();
      },
    });
  });
}
//  对html 结构进行编译
function Compile(element, vm) {
  vm.$el = document.querySelector(element);
  // 创建文档碎片,提供dom操作性能
  let fragment = document.createDocumentFragment();
  while ((childNode = vm.$el.firstChild)) {
    fragment.appendChild(childNode);
  }
  //  进行模版编译
  replace(fragment, vm);
  vm.$el.appendChild(fragment);
}
//  负责对dom 模版进行编译的方法

function replace(node, vm) {
  //  定义匹配插值表达式

  const regMustache = /{{s*(S )s*}}/;

  if (node.nodeType === 3) {
    //   注意:文本子节点,也是一个dom对象,如果获取文本子几点
    const text = node.textContent;
    const execResult = regMustache.exec(text);
    if (execResult) {
      const value = execResult[1].split(".").reduce((newObj, k) => {
        return newObj[k];
      }, vm);
      node.textContent = text.replace(regMustache, value);
      //在这个时候,创建Watcher类的实例 
      console.log(execResult[1])
       new Watcher(vm,execResult[1],(newValue)=>{
         node.textContent = text.replace(regMustache, newValue);
       })
    }
    return;
  }
  // 证明不是文本节点,需要进行正则的替换
  node.childNodes.forEach((node) => replace(node, vm));
}
//  依赖收集的类/收集watcher 订阅者的类
class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(watcher) {
    this.subs.push(watcher);
  }
  notify() {
    this.subs.forEach((watcher) => watcher.update());
  }
}
// 订阅者的类
class Watcher {
  // cd 回调函数中,记录着当前 Wathcer 如何更新自己的文本内容
  // 但是,知道如何更新自己还不行,还必须拿到最新数据
  // 因此 还需要在new Watcher 期间,把vm也传递进来,因为vm中保存着最新的数据
  // 除此之外,还需要知道,在vm身上从多的数据中,那个数据,才是当前自己所需要的数据
  // 因此,必须在new Watcher 期间,指定 Watcher 对应的数据的名字
  constructor(vm, key, cd) {
    this.vm = vm;
    this.key = key;
    this.cd = cd;
    //  把创建的watcher 实例存在Dep实例中
    Dep.target =this
    key.split('.').reduce((newObj,k)=>newObj[k],vm)
    Dep.target=null
  }
  //  watcher 的实例,需要有update函数,更新dom
  update() {
      const value =  this.key.split('.').reduce((newObj,k)=>newObj[k],vm)
      this.cd(value)
  }
}

0 人点赞