简要实现vue双向绑定

438 阅读2分钟

使用 vue 已经很久了,vue的一大特性,双向绑定的原理也了解一个大概,大致是通过 Object.defineProperty()的 set 和 get 方法来劫持属性,修改属性。这次疫情在家无事,看了不少这方面的源码,所以打算自己写一个简要版本的双向绑定.考虑不周,大家发现问题的话,欢迎提出来,一起学习。

实现思路

1.需要一个 oberser,用来监听所有的数据和属性变化,有变动通知订阅者

2.需要一个compile 扫描节点的指令,初始化模板,更新视图

3.需要一个watcher 接受受来自于oberser 的变化,然后执行响应的方法

oberser

Vue.prototype.oberser = function (obj) {
            let that = this
            
            Object.keys(obj).forEach(key => {
               var value = obj[key]; //获取循环每一项的值
                Object.defineProperty(that.data,key, {
                    configurable: true,  // 可以删除
                enumerable: true, // 可以遍历
                    get() {
                    return value; // 获取值的时候 直接返回
                },
                set(newVal) { // 改变值的时候 触发set
                    value = newVal;
                    that.Pool.notice() //执行方法更新视图
                  }
                })
            })
        }

这里直接使用 forEach 来循环传入的数据,然后我们就可以使用 Object.defineProperty来获取数据和设置新的数据。

compile

  Vue.prototype.compile = function (el) {
  
          let that = this,nodes = el.children;
          for(let i = 0;i<nodes.length;i++) {
              if(nodes[i].hasAttribute('v-model')  && nodes[i].tagName == 'INPUT'){
                  nodes[i].addEventListener('input',(function(key) {  //监听输入框的事件
                      var attVal = nodes[i].getAttribute('v-model');
                      that.Pool.add(
                        new Watcher(
                          nodes[i],
                          that,
                          attVal,
                          'value'
                        )
                      )
                      return function () {
                        that.data[attVal] = nodes[key].value;  // input值改变的时候 将新值赋给数据 触发set=>set触发watch 更新视图
                    }
                  })(i))
              }
              if (nodes[i].hasAttribute('v-bind')) { // v-bind指令 
                var attVal = nodes[i].getAttribute('v-bind'); // 绑定的data
                      that.Pool.add(new Watcher(
                          nodes[i],
                          that,
                          attVal,
                          'innerHTML'
              ))
            }
        }    
    }

循环 app,里面的子节点,如果是 输入框并且有 v-modle的话,就给输入框一个监听事件,获取值,并且往订阅池里面添加一个 watcher,后续用来更新输入框。 如果节点是有 v-bind 的话,也是往订阅池添加一个 watcher,但是这次是改成了innerHTML,后续用来改变文本

Watcher

 function Watcher(el,vm,val,attr) {
 
        this.el  =el;
        this.vm = vm;
        this.val = val;
        this.attr = attr;
        this.update();
        }
    Watcher.prototype.update = function () {
        this.el[this.attr] = this.vm.data[this.val]
    }
        

上面是一个watcher ,用来接收来自 compile 的数据,watcher有一个更新方法,根据传入的节点,更新页面

    function Pool() {
        this.subs = [],
        this.add(),
        this.notice()
    }
    Pool.prototype.add = function (sub) {
        if(sub) {
            this.subs.push(sub)
        }
    }
     Pool.prototype.notice = function () {
        this.subs.forEach(item => {
            item.update()
        })
    }

这是一个订阅池,用来收集所有的 watcher,里面有两个属性,一个是添加新的watcher,另一个循环执行 watcher 的更新方法

总结

最后所有的函数都需要页面加载之后,把他们放到一个主函数里面。

    window.onload = function () { 
             //加载好页面之后就加载实例
            var app = new Vue({
                el: '#app',
                data: {
                    data1: '888888'
                }
            })
        }
        function Vue(config = {}) {
            this.watcher = {}
            this.data = config.data
            this.Pool = new Pool()
            this.oberser(config.data)
            this.compile(document.querySelector(config.el))
        }

如说多层嵌套之后就不能使用了,但是精力有限,只能先到这里。

参考文献

juejin.cn/post/684490…