# 简单双向绑定

[转][https://github.com/bison1994/two-way-data-binding/blob/master/index.html]

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Two-way-data-binding</title>
  </head>
  <body>
    <div id="app">
      <input type="text" v-model="text" />
      {{ text }}
    </div>

    <script>
      function observe(obj, vm) {
        Object.keys(obj).forEach(function (key) {
          defineReactive(vm, key, obj[key]);
        });
      }

      function defineReactive(obj, key, val) {
        var dep = new Dep();

        Object.defineProperty(obj, key, {
          get: function () {
            // 添加订阅者 watcher 到主题对象 Dep
            if (Dep.target) dep.addSub(Dep.target);
            return val;
          },
          set: function (newVal) {
            if (newVal === val) return;
            val = newVal;
            // 作为发布者发出通知
            dep.notify();
          },
        });
      }

      function nodeToFragment(node, vm) {
        var flag = document.createDocumentFragment();
        var child;
        // 许多同学反应看不懂这一段,这里有必要解释一下
        // 首先,所有表达式必然会返回一个值,赋值表达式亦不例外
        // 理解了上面这一点,就能理解 while (child = node.firstChild) 这种用法
        // 其次,appendChild 方法有个隐蔽的地方,就是调用以后 child 会从原来 DOM 中移除
        // 所以,第二次循环时,node.firstChild 已经不再是之前的第一个子元素了
        while ((child = node.firstChild)) {
          compile(child, vm);
          flag.appendChild(child); // 将子节点劫持到文档片段中
        }

        return flag;
      }

      function compile(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
              node.removeAttribute("v-model");
            }
          }

          new Watcher(vm, node, name, "input");
        }
        // 节点类型为 text
        if (node.nodeType === 3) {
          if (reg.test(node.nodeValue)) {
            var name = RegExp.$1; // 获取匹配到的字符串
            name = name.trim();

            new Watcher(vm, node, name, "text");
          }
        }
      }

      function Watcher(vm, node, name, nodeType) {
        Dep.target = this;
        this.name = name;
        this.node = node;
        this.vm = vm;
        this.nodeType = nodeType;
        this.update();
        Dep.target = null;
      }

      Watcher.prototype = {
        update: function () {
          this.get();
          if (this.nodeType == "text") {
            this.node.nodeValue = this.value;
          }
          if (this.nodeType == "input") {
            this.node.value = this.value;
          }
        },
        // 获取 data 中的属性值
        get: function () {
          this.value = this.vm[this.name]; // 触发相应属性的 get
        },
      };

      function Dep() {
        this.subs = [];
      }

      Dep.prototype = {
        addSub: function (sub) {
          this.subs.push(sub);
        },

        notify: function () {
          this.subs.forEach(function (sub) {
            sub.update();
          });
        },
      };

      function Vue(options) {
        this.data = options.data;
        var data = this.data;

        observe(data, this);

        var id = options.el;
        var dom = nodeToFragment(document.getElementById(id), this);

        // 编译完成后,将 dom 返回到 app 中
        document.getElementById(id).appendChild(dom);
      }

      var vm = new Vue({
        el: "app",
        data: {
          text: "hello world",
        },
      });
    </script>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
最后更新时间: 5/26/2021, 5:52:52 PM