这一次,彻底搞懂双向数据绑定03

563 阅读2分钟

这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战

一、将this.$data上的属性代理到vm实例上

所谓的将this.$data挂载到vm实例上,实际上就是说不需要通过vm.$data.属性来获取值,而是直接通过vm.属性获取值。

方法:通过Object.defineProperty将this.$data身上的属性代理到this身上。

class Vue {
  constructor(options) {
    this.$data = options.data;

    // 调用数据劫持的方法
    Observe(this.$data);

    // 将属性绑定到this身上
    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; 
        }
      })
    })
  }
}

二、文档碎片的作用

文档碎片是什么?

文档碎片的本质是一片内存空间,文档碎片的存在就是为了防止频繁的对DOM进行操作,造成重绘和重排影响性能,通过将页面中的DOM存储在文档碎片中,对文档碎片进行操作,可以有效的防止频繁的对DOM进行操作。

递归文本节点并使用正则进行替换

  function replace(node) {
    // 匹配插值表达式的正则
    const regMustache = /\{\{\s*(\S+)\s*\}\}/
    // 证明当前的node节点是一个文本子节点
    if (node.nodeType === 3) {
      const text = node.textContent;
      // 进行正则提取
      const execResult = regMustache.exec(text);

      if (execResult) {
        const value = execResult[1].split('.').reduce((newObj,k) => newObj[k],vm)
        node.textContent = text.replace(regMustache,value)
      }
      return
    }
    // 不是文本节点,则投入递归
    node.childNodes.forEach(child => replace(child))
  }

三、创建Dep类进行依赖收集

下面的Dep类主要实现三个功能:

  1. 在实例身上定义一个存放订阅者的数组。
  2. 定义一个向数组中增加订阅者的函数。
  3. 定义一个通知每一个订阅者watcher的方法。
// 收集watcher订阅者的类
class Dep {
  constructor() {
    this.subs = [];
  }

  addSub(watcher) {
    this.subs.push(watcher);
  }
  // 负责通知每个watcher的方法
  notify() {
    this.subs.forEach(watcher => watcher.update())
  }
}

四、创建Watcher类的实例

在replace函数第一次渲染的时候,创建Watcher实例:

  function replace(node) {
    // 匹配插值表达式的正则
    const regMustache = /\{\{\s*(\S+)\s*\}\}/
    // 证明当前的node节点是一个文本子节点
    if (node.nodeType === 3) {
      const text = node.textContent;
      // 进行正则提取
      const execResult = regMustache.exec(text);

      if (execResult) {
        const value = execResult[1].split('.').reduce((newObj,k) => newObj[k],vm)
        node.textContent = text.replace(regMustache,value)
        // 在这里创建watcher
        new Watcher(vm,execResult[1],(newValue) => {
          node.textContent = text.replace(regMustache,newValue);
        })
      }
      return
    }
    // 不是文本节点,则投入递归
    node.childNodes.forEach(child => replace(child))
  }

五、将watcher实例存储到dep.subs数组中

将watcher实例存储到dep.subs数组中主要是需要下面三个方面的配合:

  1. 编译函数在编译DOM节点的时候需要实例化watcher。
  2. watcher的构造函数中要有下面三行代码。这三行代码需要和Observer中的get进行配合使用
Dep.target = this;
key.split('.').reduce((newObj,k) => newObj[k],vm);
Dep.target = null;

发挥关键作用的在与第二行代码,看似是在调用,其实是在出发Observe中的getter。

  1. Observe中的getter
  get() {
    Dep.target && dep.addSub(Dep.target)
    return value;
  }