目前最火的框架vue最大的优点就是双向数据绑定,其中Object.defineProperty() 这个API就是Vue实现双向数据绑定的关键。

VUE实现:v-model

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>双向数据绑定</title>
</head>
<body>
    <div id="root">
      <p></p>
      <input v-model="message">
    </div>
    <script src="../js/vue.js"></script> 
    <script>
    //v-model,它使得在表单输入和应用状态中做双向数据绑定
    var app6 = new Vue({
      el: '#root',
      data: {
        message: '双向数据绑定'
      }
    });
   </script> 
</body>
</html>

原生js中的Object.defineProperty()实现

  • 通过id简单粗暴的方法
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>双向数据绑定</title>
</head>
<body>
    <div id="app">
        <input type="text" id="a">
        <span id="b"></span>
    </div>
    <script>
    var obj = {},
        val='';
    Object.defineProperty(obj, 'textVal', {
        get: function() {
            console.log('get val:' + val);
            return val;
        },
        set: function(newVal) {
            val = newVal;
            console.log('set val:' + val);
            document.getElementById('a').value = val;
            document.getElementById('b').innerHTML = val;
        }
    });
    document.addEventListener('keyup', function(e) {
        obj.textVal = e.target.value || '';
    });
    </script>
</body>

</html>
  • 复杂的方法
<!DOCTYPE html>
  <head></head>
  <body>
  <div id="app">
    <input type="text" id="a" v-model="text">
    
  </div>
  <script>
  function Compile(node, vm) {
      if(node) {
        this.$frag = this.nodeToFragment(node, vm);
        return this.$frag;
      }
    }
    Compile.prototype = {
      nodeToFragment: function(node, vm) {
        var self = this;
        var frag = document.createDocumentFragment();
        var child;

        while(child = node.firstChild) {
          self.compileElement(child, vm);
          frag.append(child); // 将所有子节点添加到fragment中
        }
        return frag;
      },
      compileElement: function(node, vm) {
        var reg = /\{\{(.*)\}\}/;

        //节点类型为元素
        if(node.nodeType === 1) {
          var attr = node.attributes;
          // 解析属性
          for(var i = 0; i < attr.length; i++ ) {
            if(attr[i].nodeName == 'v-model') {
              var name = attr[i].nodeValue; // 获取v-model绑定的属性名
              node.addEventListener('input', function(e) {
                // 给相应的data属性赋值,进而触发该属性的set方法
                 vm[name]= e.target.value;
              });
              // node.value = vm[name]; // 将data的值赋给该node
              new Watcher(vm, node, name, 'value');
            }
          };
        }
        //节点类型为text
        if(node.nodeType === 3) {
          if(reg.test(node.nodeValue)) {
            var name = RegExp.$1; // 获取匹配到的字符串
            name = name.trim();
            // node.nodeValue = vm[name]; // 将data的值赋给该node
            new Watcher(vm, node, name, 'nodeValue');
          }
        }
      },
    }
    function Dep() {
      this.subs = [];
    }
    Dep.prototype = {
      addSub: function(sub) {
        this.subs.push(sub);
      },
      notify: function() {
        this.subs.forEach(function(sub) {
          sub.update();
        })
      }
    }
    function Watcher(vm, node, name, type) {
      Dep.target = this;
      this.name = name;
      this.node = node;
      this.vm = vm;
      this.type = type;
      this.update();
      Dep.target = null;
    }

    Watcher.prototype = {
      update: function() {
        this.get();
        this.node[this.type] = this.value; // 订阅者执行相应操作
      },
      // 获取data的属性值
      get: function() {
        this.value = this.vm[this.name]; //触发相应属性的get
      }
    }
    function defineReactive (obj, key, val) {
      var dep = new Dep();
      Object.defineProperty(obj, key, {
        get: function() {
           //添加订阅者watcher到主题对象Dep
          if(Dep.target) {
            // JS的浏览器单线程特性,保证这个全局变量在同一时间内,只会有同一个监听器使用
            dep.addSub(Dep.target);
          }
          return val;
        },
        set: function (newVal) {
          if(newVal === val) return;
          val = newVal;
          console.log(val);
          // 作为发布者发出通知
          dep.notify();
        }
      })
    }
    function observe(obj, vm) {
      Object.keys(obj).forEach(function(key) {
        defineReactive(vm, key, obj[key]);
      })
    }

   function Vue(options) {
      this.data = options.data;
      var data = this.data;
      observe(data, this);
      var id = options.el;
      var dom =new Compile(document.getElementById(id),this);
      // 编译完成后,将dom返回到app中
      document.getElementById(id).appendChild(dom);
    }
    var vm = new Vue({
      el: 'app',
      data: {
        text: 'hello world'
      }
    });
  </script>
  </body>
</html>

详见【demo

参考:Vue2.0源码阅读笔记–双向绑定实现原理

以上文章来自:黄卉 , https://huanghui8030.github.io/vue/data-vue.html