proxy的观察者模式探索

vueConf大会,尤小右实锤vue3.0将改definePrototype为proxy。

却之为何

1.在Vue中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。 为了解决这个问题,经过vue内部处理后可以使用以下几种方法来监听数组。(事实上,Object.defineProperty本身是可以监控到数组下标的变化的,参Vue为什么不能检测数组变动

1
2
3
4
5
6
7
push()
pop()
shift()
unshift()
splice()
sort()
reverse()

 

由于只针对了以上八种方法进行了hack处理,所以其他数组的属性也是检测不到的,还是具有一定的局限性。

Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。

而要取代它的Proxy有以下两个优点;

  1. 可以劫持整个对象,并返回一个新对象
  2. 有13种劫持操作

vue2.x之前之所以不用Proxy,主要Proxy是es6提供的新特性,兼容性不好,最主要的是这个属性无法用polyfill来兼容。

基于proxy的vue数据双向绑定实现

vue

■ Observer: 基于proxy处理代理,当属性修改时通知Dep。
■ compile: 解析指令,订阅数据变化,绑定更新函数。 根据对应指令绑定相应watcher。
■ Dep: 为全局对象subscribe添加对应属性,当数据变化,通知watcher,调用相关update方法
■ Watcher: 主要update方法,修改相关node节点数据。

1. 页面结构

1
2
3
4
5
6
7
8
9
10
<div id="app">
  <!-- input框, 包含v-model指令 -->
  <input type="text" v-model="num" />
  <!-- input框, 包含v-model指令 -->
  <input id="btn" type="button" value="添加到Todolist" v-click="addList" /><br />
  <span>您输入的是:</span>
  <!-- span, 包含v-bind指令 -->
  <span v-bind="num"></span>
  <ul id="list"></ul>
</div>

2. 参照vue调用

1
2
3
4
5
6
7
8
9
10
11
12
13
new proxyVue({
  el: "#app",
  data: {
    num: 0,
    arr: []
  },
  methods: {
    addList() {
      this.arr.push(this.num);
      Render.addList(this.num);
    }
  }
});

proxyVue实现

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
  // 渲染todolist列表
  const Render = {
    // 初始化
    init: function (arr) {
      const fragment = document.createDocumentFragment();
      for (let i = 0; i < arr.length; i++) {
        const li = document.createElement("li");
        li.textContent = arr[i];
        fragment.appendChild(li);
      }
      list.appendChild(fragment);
    },
    addList: function (val) {
      const li = document.createElement("li");
      li.textContent = val;
      list.appendChild(li);
    }
  };

class ProxyVue {
  constructor(options) {
    this.$options = options || {},
    this.$methods = this.$options.methods;
    const data = (this._data = this.$options.data);
    
    // subscribe对象结构:{
    //   num: [watcher, watcher]
    // }
    this.subscribe = {}; 
    this.observe(data);
    this.compile(options.el);
  }

  observe(data) {
    let handel = {
      get: (target, key) => Reflect.get(target, key),
      set: (target, key, value) => {
        // 需要将Reflect赋值并return
        let res = Reflect.set(target, key, value);

        // 当对象改变,遍历执行其属性对应的watcher数组,并调用自身update方法
        this.subscribe[key].map(item => {
          item.update();
        })
        return res;
      }
    }
    this.$data = new Proxy(data, handel);
  }

  compile(el){
    // 将nodes类数组转换成数组,以供遍历
    let nodes = Array.from(document.querySelector(el).children);
    let data = this.$data;

    nodes.map(node => {
      if (node.hasAttribute('v-model')) {
        let property = node.getAttribute('v-model');
        this.publish(new Watcher(node, "value", data, property));
        node.addEventListener("input", () => {
          data[property] = node.value;
        });
      }
      if (node.hasAttribute('v-bind')) {
        let property = node.getAttribute('v-bind');
        this.publish(new Watcher(node, "innerHTML", data, property));
    
      }
      if (node.hasAttribute('v-click')) {
        let methodName = node.getAttribute('v-click');
        let method = this.$methods[methodName].bind(data);
        node.addEventListener("click", method);
      }
    })
  }

  // 订阅消息
  publish(watcher) {
    if (!this.subscribe[watcher.property]) {
      this.subscribe[watcher.property] = []
    }
    this.subscribe[watcher.property].push(watcher);
  }
}

class Watcher{
  constructor(node, attr, data, property) {
    this.node = node;
    this.attr = attr;
    this.data = data;
    this.property = property;
  }
  update(){
    this.node[this.attr] = this.data[this.property];
  }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值