Vue 中数据双向绑定的代码实现

Vue 中数据双向绑定核心

  1. 数据劫持
  2. 发布订阅

遍历 data ,通过 Object.defineProperty() 方法来劫持各个属性的 setter,getter,添加发布者,在数据变动时发布消息给订阅者,触发相应的监听回调函数

通过Observer来进行数据观测,通过Compile来解析编译模板指令,通过watcher进行数据的更新渲染

示例代码:

// 发布者  publish
class Dep{
  constructor(){
  	// 订阅者数组
    this.subs = []; 
  }
  // 订阅方法
  addSub(watcher){
  // 往订阅者数组中添加新的订阅者
    this.subs.push(watcher);
  }
  // 发布方法
  notify(newVal){
  // 遍历订阅者数组,调用每个订阅者的 update 方法
    this.subs.forEach(sub=>{
      sub.update(newVal);
    })
  }
}

// 订阅者   subscribe
class Watcher{
  constructor(cb){
    this.cb = cb;
  }
  // 接收到消息时的更新函数
  update(newVal){
    console.log('更新了....');
    this.cb(newVal);
  }
}

// 在数据劫持的时候,一个key劫持执行前,选new一个Dep
// new Dep()
// 第一次渲染前,先new一个watcher
// new Watcher()
// 第一次渲染的时候,添加订阅者
// addSub()
// 更新数据,执行发布
// notify()

// 防止重复添加订阅者,使用一个全局变量
let watcher = null;

class MVVM{
  constructor(options){
    // 配置实例上的基础属性
    this.$options = options;
    this.$data = this._data = options.data;

    // 添加数据观测
    this.observer(this.$data);

    // 编译模版
    this.compiler(options.el);

  }

  // 数据观测
  observer(data){
    // 获得data的所有key和value
    Object.entries(data).forEach(([key, value])=>{
      // 创建发布者
      console.log('发布者:', key);
      const dep = new Dep();
      // 对key进行数据观测
      Object.defineProperty(data, key, {
        configurable: true,
        enumerable: true,
        // 设置数据
        set(newVal){
          // console.log('set run......');
          if(newVal !== value){
            // 设置新数据
            value = newVal;
            // 重新渲染dom
            console.log('执行发布.....');
            dep.notify(newVal);
          }
        },
        // 读取数据
        get(){
          // console.log('get run......');
          console.log('执行订阅.....');
          if(watcher){
            dep.addSub(watcher);
            watcher = null;
          }
          
          return value;
        }
      });
    })

  }

  // 编译模版
  compiler(el){
    // 获得实例作用的dom
    const element = document.querySelector(el);
    // 遍历实例作用的dom
    this.compilerNode(element);
  }

  compilerNode(element){
    // 获得需要编译的dom的每一个子节点
    const childNodes = element.childNodes;
    // 转为可遍历的对象,进行遍历
    Array.from(childNodes).forEach(node=>{
      const {nodeType, textContent} = node;
      //判断是文本节点
      if(nodeType === 3){
        // 判断是否有插值表达式在文本节点中
        let reg = /\{\{\s*(\S*)\s*\}\}/;
        //有
        if(reg.test(textContent)){
          
          //数据变,dom需要更新
          // 创建订阅者
          console.log('订阅者:', RegExp.$1);

          watcher = new Watcher((newVal)=>{
            //更新数据的渲染
            node.textContent = newVal;
          });
          
          // 第一次渲染dom
          node.textContent = this.$data[RegExp.$1];
        }
      }

      // 判断是标签
      else if(nodeType === 1){
        // 拿到标签的所有属性
        let attrs = Array.from(node.attributes);
        // 遍历每一属性
        attrs.forEach(attr=>{
          //判断属性是否是指令
          if(attr.name.startsWith('v-')){
            //是指令,取指令名字
            let dirName = attr.name.substr(2);
            if(dirName === 'model'){  //v-model="message"
              let key = attr.value;
              // 创建订阅者
              watcher = new Watcher((newVal)=>{
                node.value = newVal;
              });
              // 设置初始值
              node.value = this.$data[key];
              // 添加输入事件监听
              node.addEventListener('input', (ev)=>{
                this.$data[key] = ev.target.value;
              });
            }
            else if(dirName === 'bind'){

            }
          }
        })
        
      }

      // 有子节点,需要编译子节点
      if(node.childNodes.length > 0){
        // 遍历子节点
        this.compilerNode(node);
      }

    })
  }
}

使用 Object.defineProperty() 的弊端:

  1. Object.defineProperty() 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历
  2. 监测不到对象属性的添加、删除
  3. 监测不到数组长度的变化、给数组的某一项直接赋值也检测不到

而在 Vue 3.0 开始采用了 ES6 中新增的一个特性 Proxy(代理),它可以直接劫持整个对象,并返回一个新对象,性能和操作都会比 Object.defineProperty() 强上许多,具体可参考:

vue3.0 尝鲜 – 摒弃 Object.defineProperty,基于 Proxy 的观察者机制探索

实现双向绑定Proxy比defineproperty优劣如何

发布了16 篇原创文章 · 获赞 25 · 访问量 511

猜你喜欢

转载自blog.csdn.net/weixin_44691775/article/details/104425881