在3.0以前,vue主要的双向绑定原理就是通过object.defineProperty 自己只是知道大概原理,现在手动实现下
首先是data-binding.html的书写
在内部定义一个new Vue 然后简易的进行数据绑定 input绑定p h1 h21
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<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta name="buppt" content="https://github.com/buppt">
  <title>data binding</title>
</head>
<body>
  <h1 id="name"></h1>
  <p id="num"></p>
  <h2 id="name2"></h2>
  <input id="input" type="text">
  <script src="./instance/index.js"></script>
  <script src="./observer/index.js"></script>
  <script src="./observer/dep.js"></script>
  <script src="./observer/watcher.js"></script>
  <script>
    var name1 = document.getElementById("name");
    var num = document.getElementById("num");
    var name2 = document.getElementById("name2");
    var input = document.getElementById("input");
    var selfVue = new Vue({
      name: "hello",
      a: 1
    });
    selfVue.bindData(name1, "name"); //name1元素绑定name属性
    selfVue.bindData(num, "a");
    selfVue.bindData(name2, "name");
    selfVue.bindData(input, "name");
  </script>
</body>
随后是vue类的定义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
42class Vue {
  constructor(data) {
    this.data = data;
    observe(data); //调用observe类 来将该对象变为可监听的
  }
  bindData (elm, name) {
    var self = this;
    if (elm.tagName == 'INPUT') {
      elm.addEventListener('input', function (e) {
        var newValue = e.target.value;
        var val = self.data[name]
        if (val === newValue) {
          return;
        }
        self.data[name] = newValue;
      });
    } else {
      elm.innerHTML = this.data[name]
    }
    new Watcher(this, name, function (val) {  //在这里会触发watcher的构造函数,随后在构造函数内部调                                       用get
      elm.innerHTML = val;
    });
  };
  debounce (func, wait) {     //因为是频繁触发,所以最好是用防抖方式来做到缓冲
    var timeout = null;
    return function () {
      var args = arguments;
      var context = this;
      if (timeout)
        clearTimeout(timeout);
      timeout = setTimeout(function () {
        func.apply(context, args);
      }, wait);
    }
  }
}
Observer类的定义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
48class Observer {
  constructor(data) {
    this.data = data;
    this.walk(data); //调用walk遍历属性
  } 
  walk (data) {
    //遍历对象中的属性
    Object.keys(data).forEach(function (key) {
      defineReactive(data, key, data[key]); //对每个属性调用defineReactive来变成可监听的
    })
  }
}
function observe (value) {
  if (!value || typeof value !== "object") {
    return;
  }
  return new Observer(value);
}
function defineReactive (data, key, val) {
  //为每一个属性确立一个Dep类 然后dep类里面有deps数组
  const dep = new Dep();
  let childOb = observe(val); //子属性也要做
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      if (Dep.target) { //Dep.target的设定尤其重要
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
        }
      }
      return val;
    },
    set: function (newVal) {
      if (val == newVal) {
        return;
      } else {
        val = newVal;
        dep.notify(); //set的时候对应的watcher触发跟新操作
      }
    }
  })
}
Dep类的实现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
30class Dep {
  constructor() {
    this.subs = [];
  }
  addSub (sub) {
    this.subs.push(sub); //用来保存watcher 实际在vue源码中会判断id 防止重复push
  }
  notify () {
    this.subs.forEach((sub) => {
      sub.update(); //每个进行更新
    })
  }
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this); //执行watcher的addDep
    }
  }
}
Dep.target = null;
const targetStack = []
function pushTarget (_target) { 
  if (Dep.target)
    targetStack.push(Dep.target);
  Dep.target = _target;
  // console.log(targetStack);
}
function popTarget () {
  Dep.target = targetStack.pop();
}
这里主要用到堆栈的概念 因为相对于vue1的绑定到每个dom元素这么的细腻,
vue2的组件跟新通知到组件级别 组件级别内部再使用diff算法,所以比如A在渲染的时候遇到B,所以A要先保存,再渲染B,B渲染好以后弹出,A再渲染
watcher类的实现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
31class Watcher {
  constructor(vm, expression, cb) {
    this.cb = cb;
    this.vm = vm;
    this.expression = expression;
    this.value = this.get();
  }
  update () {
    this.run();
  }
  run () {
    const value = this.get(); //这里也会触发get操作 所以会在addSub那里判断是否是重复性加入组件
    var oldVal = this.value;
    this.value = value;
    if (value != oldVal) {
      this.value = value;
      this.cb.call(this.vm, value, oldVal);
    }
  }
  get () {
    Dep.target = this;
    pushTarget(this);
    //属性值触发  触发属性的get操作进行Dep.target的添加
    var value = this.vm.data[this.expression];
    popTarget();
    return value;
  }
  addDep (dep) {
    dep.addSub(this);
  }
}
详见
https://segmentfault.com/q/1010000010095427
https://www.cnblogs.com/natsu07/p/10371448.html
github连接:https://github.com/KevinSwiftiOS/VueDataBinding