Vue2双向绑定数据模型实现

2022 年 8 月 10 日 星期三
/ ,
10

Vue2双向绑定数据模型实现

响应性原理

Vue 使用 Object.defineProperty方法进行响应式数据劫持,利用 set、get 访问器描述符来检测数据的读写。

作为MVVM 框架主要包含两个方面,数据变化更新视图,视图变化更新数据。

在vue中每个组件实例都对应一个渲染 watcher 实例,它会在组件渲染的过程中把“接触”过的响应式数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

8d11592e24978f47bf42de6439c80230

8d11592e24978f47bf42de6439c80230

代码实现

html部分

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <button id="btn">click</button>
  <script src="./index.js">
  </script>
  <script>
    const app = new Vue({
      el: '#app',
      data() {
        return {
          message: 'hello world',
          girlfriend: {
            iKun: 'kunkun'
          }
        }
      },
      methods: {
        changeMessage() {
          this.message = this.message ===  'hello world' ? '你好 世界' : 'hello world'
        }
      }
    })
   let btn = document.querySelector('#btn')
    btn.addEventListener('click',() => {
    app.methods.changeMessage.call(app)
   })
  </script>
</body>
</html>

js部分

//依赖收集器
class Dep {
  constructor() {
    this.deps = new Set();
  }
  addDep(dep) {
    this.deps.add(dep);
  }
  notify() {
    this.deps.forEach(dep => {
      dep.update()
    })
  }
}
//监听器
class Observer {
  constructor(data) {
    this.data = data;
    //递归添加响应性
    this.walk(data);
  }
  walk(obj) {
    //先不处理数组
    if (Array.isArray(obj)) return;
    Object.keys(obj).forEach((key) => {
      this.defineReactive(key, obj[key], obj);
    });
  }
  defineReactive(key, value, obj) {
    if (typeof value === 'object') observer(value);
    const dep = new Dep();
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        if (Dep.target) {
          dep.addDep(Dep.target);
          Object.defineProperty(obj, '_Observer', {
            enumerable: false,
            configurable: true,
            writable: true,
            value: dep,
          });
        }
        return value;
      },
      set(newValue) {
        if (value === newValue) return;
        value = newValue;
        dep.notify();
      },
    });
  }
}
//订阅器
class Watcher {
  constructor(vm, exp, cb) {
    this.vm = vm;
    this.exp = exp;
    this.cb = cb;
    this.get();
  }
  get() {
    Dep.target = this;
    this.vm.patch();
    Dep.target = null;
  }
  update(dep) {
    this.cb.call(this.vm);
  }
}
function observer(value) {
  if (typeof value !== 'object') return;
  new Observer(value);
}
//Vue类
class Vue {
  constructor(options) {
    this.el = document.querySelector(options.el);
    this._data =
      typeof options.data !== 'function' ? options.data : options.data();
    this.proxyToThis(this._data);
    observer(this._data);
    new Watcher(this, '', this.patch);
    this.methods = options.methods;
    console.log(this._data);
  }
  //模拟render-patch
  patch() {
    this.el.innerHTML = this.message;
  }
  proxyToThis(data) {
    if (typeof data !== 'object') return;
    Object.keys(data).forEach((key) => {
      Object.defineProperty(this, key, {
        enumerable: true,
        configurable: true,
        get() {
          return data[key];
        },
        set(newValue) {
          if (data[key] === newValue) return;
          data[key] = newValue;
        },
      });
    });
  }
}

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...