• vue双向数据绑定原理


    几种实现双向绑定的做法

    目前几种主流的mvc框架都实现了单向数据绑定,而双向绑定无非就是在单项绑定的基础上给可输入元素添加了change事件,来动态修改model和view。

    实现数据绑定的做法有大致如下几种

         发布者-订阅者模式

         脏值检查

         数据劫持

    发布者-订阅者模式

        一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法时vm.set('property', value).

    脏值检查

        angular.js 时通过脏值检测的的方式比较数据是否有变更,来决定是否更新视图,最简单的方式就是通过setInterval() 定时轮询检测数据变动,angular的做法时,只有在指定的事件触发进入脏值检测

        DOM事件

        XHR响应事件

        浏览器Location变更事件

        Timer事件

        执行digest() 或 apply()

    数据劫持

        vue.js 则时采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter, 在数据变动时发布消息给订阅者,触发相应的监听回调。

        思路整理

        1、实现一个数据监听器Observer, 能够对数据对象的所有属性进行监听,如果变动可拿到最新值并通知订阅者

        2、实现一个指令解析器Compile, 对每个元素节点的指令进行扫描和解析, 根据指令模板替换数据,以及绑定相应的更新函数

        3、实现一个Watcher,作为连接Observer 和 Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

        4、入口函数,整合以上三者

    流程图

    数据监听器

    function observe(obj, vm) {
        // 对传入的对象  遍历  并分别添加 object.defineProperty
        Object.keys(obj).forEach((key) => {
               defineReactive(vm, key, obj[key])
        })
    }
    
    function defineReactive(vm, key, val){
        var dep = new Dep();
        Object.defineProperty(vm, key, val){
              get:  function () {
                   if(Dep.target)  dep.addSub(Dep.target)
                   return val;
              },
    
              set: function(newval) {
                  if(newval === val) return
                  val = newval;
                  // 通知订阅者
                  dep.notify();
              }
        }
    }
    
    // 需要实现一个消息订阅器
    function Dep() {
        // 消息订阅的容器是一个数组  数组的每一项都是指代一个view 和 model的中间者
        this.subs = [];
    }
    
    Dep.prototype = {
          addSub: function (sub){
             this.subs.push(sub)
          },
    
          notify: function () {
              this.subs.forEach((sub) => {
                     //在这里,需要配合watcher进行更新
                     sub.update()
              }) 
          }
    }

    实现Compile

    function nodeToFragment(node, vm) {
          var flag = document.createDocumentFragment();
          var child;
          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") {
                         // 此时 name为text
                         var name = attr[i].nodeValue;
                         // 增加数据的变化监听
                         node.addEventListener('input', (e)=>{
                              vm[name] = e.target.value;
                         })
                         // 在这里 因为 我们的数据监听器 已经封装了vm[name]
                         node.value = vm[name];
                         node.removeAttribute('v-model')
                   }
              }
              new Watcher(vm, node, name, 'input')
         }
    
          if(node.nodeType === 3){
                if(reg.test(node.nodeValue)){
                       var name + RegExp.$1;
                       name = name.trim();
                       new Watcher(vm, node, name, 'text')
                }
          }
    }

    watcher观察函数

    //订阅者 搭建数据监听变化和变异模板的桥梁
        function Watcher(vm, node, name, nodeType) {
            Dep.target = this;
            this.vm = vm;
            this.node = node;
            this.name = name;
            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
                }
            },
            get: function () {
                this.value = this.vm[this.name];
            }
        }

    入口函数

    function Vue(options) {
            // 将options里面的data属性 放入数据监听器
            this.data = options.data;
            var data = this.data;
            observe(data, this); // this指代vm
            // 对指定id的dom 进行页面的渲染
            this.$el = options.el;
            var id = this.$el;
            var Dom = nodeToFragment(document.getElementById(id), this);
            // 编译完成之后 将dom 添加到节点中
            document.getElementById(id).appendChild(Dom)
        }
    
        var vm = new Vue({
            el: 'app',
            data: {
                text: 'hello world',
                name: '你好,全世界'
            }
        });
        vm.data.text = 'majunchang'
    
    
        document.getElementsByClassName('btn')[0].onclick = function () {
            vm.text = 'majunchang'
            vm.name = '又疑瑶台镜,飞在青云端'
        }
  • 相关阅读:
    [已解决] Python logging 重复打印日志信息
    scrapy
    Python 元编程
    MySQL性能优化 分区
    SQL Mode
    Golang 接口
    Python partial
    栈、队列(链表实现)
    Golang 位向量
    Java50题——学习以及思考
  • 原文地址:https://www.cnblogs.com/zhishiyv/p/15918289.html
Copyright © 2020-2023  润新知