YBFACC / blog

仅记录个人学习使用
3 stars 0 forks source link

vue双向绑定 #26

Open YBFACC opened 4 years ago

YBFACC commented 4 years ago

双向绑定

通过Object.defineProperty劫持属性。

通过订阅-发布模式来修改dom节点。

初始化流程:

23

运行流程:

24

代码参考来自vue 的双向绑定原理及实现

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>self-vue</title>
</head>
<style>
  #name {
    text-align: center;
  }
</style>

<body>
  <h1 id="name">{{name}}</h1>
</body>

<script>

  function SelfVue(data, el, exp) {
    this.data = data
    observe(data)
    el.innerHTML = this.data[exp] // 初始化模板数据的值
    new Watcher(this, exp, function (value) {
      el.innerHTML = value
    })
    return this
  }

</script>

<script>function Observer(data) {
    this.data = data
    this.walk(data)
  }
  Observer.prototype = {
    walk: function (data) {
      var self = this
      Object.keys(data).forEach(function (key) {
        self.defineReactive(data, key, data[key])
      })
    },
    defineReactive: function (data, key, val) {
      var dep = new Dep()
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function () {
          if (Dep.target) {
            dep.addSub(Dep.target)
          }
          return val
        },
        set: function (newVal) {
          if (newVal === val) {
            return
          }
          val = newVal
          dep.notify()
        }
      })
    }
  }

  function observe(value, vm) {
    if (!value || typeof value !== 'object') {
      return
    }
    return new Observer(value)
  }

  function Dep() {
    this.subs = []
  }
  Dep.prototype = {
    addSub: function (sub) {
      this.subs.push(sub)
    },
    notify: function () {
      this.subs.forEach(function (sub) {
        sub.update()
      })
    }
  }
  Dep.target = null
</script>

<script>
  function Watcher(vm, exp, cb) {
    this.cb = cb;
    this.vm = vm;
    this.exp = exp;
    this.value = this.get();  // 将自己添加到订阅器的操作
  }

  Watcher.prototype = {
    update: function () {
      this.run();
    },
    run: function () {
      var value = this.vm.data[this.exp];
      var oldVal = this.value;
      if (value !== oldVal) {
        this.value = value;
        this.cb.call(this.vm, value, oldVal);
      }
    },
    get: function () {
      Dep.target = this;  // 缓存自己
      var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
      Dep.target = null;  // 释放自己
      return value;
    }
  };
</script>

<script>

  var ele = document.querySelector('#name');
  var selfVue = new SelfVue({
    name: 'hello world'
  }, ele, 'name');

  window.setTimeout(function () {
    console.log('name值改变了');
    selfVue.data.name = 'canfoo';
  }, 2000);

</script>

</html>

参考

vue 的双向绑定原理及实现