首页前端开发JavaScriptvue 数据双向绑定的实现方法

vue 数据双向绑定的实现方法

时间2024-02-01 01:00:03发布访客分类JavaScript浏览709
导读:收集整理的这篇文章主要介绍了vue 数据双向绑定的实现方法,觉得挺不错的,现在分享给大家,也给大家做个参考。 目录1. 前言2. 代码实现2.1 目的分析2.2 实现过程@L_777_4...
收集整理的这篇文章主要介绍了vue 数据双向绑定的实现方法,觉得挺不错的,现在分享给大家,也给大家做个参考。
目录
  • 1. 前言
  • 2. 代码实现
    • 2.1 目的分析
    • 2.2 实现过程
      • @L_777_4@
      • 2.2.2 页面初始化
      • 2.2.3 视图影响数据
      • 2.2.4 数据影响视图
  • 3. 未来的计划

    1. 前言

    本文适合于学习Vue源码的初级学者,阅读后,你将对Vue的数据双向绑定原理有一个大致的了解,认识Observer、Compile、Wathcer三大角色(如下图所示)以及它们所发挥的功能。

    本文将一步步带你实现简易版的数据双向绑定,每一步都会详细分析这一步要解决的问题以及代码为何如此写,因此,在阅读完本文后,希望你能自己动手实现一个简易版数据双向绑定。

    2. 代码实现

    2.1 目的分析

    本文要实现的效果如下图所示:

    本文用到的HTML和JS主体代码如下:

    div id="app">
          h1 v-text="msg">
        /h1>
          input tyPE="text" v-model="msg">
          div>
            h1 v-text="msg2">
        /h1>
            input type="text" v-model="msg2">
          /div>
        /div>
        
    let vm = new Vue({
        el: "#app",    data: {
          msg: "hello world",      msg2: "hello xiaofei"    }
      }
        )

    我们将按照下面三个步骤来实现:

    • 第一步:将data中的数据同步到页面上,实现 M ==> V 的初始化;
    • 第二步:当input框中输入值时,将新值同步到data中,实现 V ==> M 的绑定;
    • 第三步:当data数据发生更新的时候,触发页面发生变化,实现 M ==> V 的绑定。

    2.2 实现过程

    2.2.1 入口代码

    首先,我们要创造一个Vue类,这个类接收一个 options 对象,同时,我们要对 options 对象中的有效信息进行保存;

    然后,我们有三个主要模块:Observer、Compile、Wathcer,其中,Observer用来数据劫持的,Compile用来解析元素,Wathcer是观察者。可以写出如下代码:(Observer、Compile、Wathcer这三个概念,不用细究,后面会详解讲解)。

    class Vue {
        // 接收传进来的对象    constructor(options) {
              // 保存有效信息      this.$el = document.querySelector(options.el);
              this.$data = options.data;
          // 容器: {
    属性1: [wathcer1, wathcer2...], 属性2: [...]}
    ,用来存放每个属性观察者      this.$watcher = {
    }
        ;
              // 解析元素: 实现Compile      this.COMpile(this.$el);
         // 要解析元素, 就得把元素传进去      // 劫持数据: 实现 Observer      this.observe(this.$data);
     // 要劫持数据, 就得把数据传入    }
        compile() {
    }
        observe() {
    }
      }
        

    2.2.2 页面初始化

    在这一步,我们要实现页面的初始化,即解析出v-text和v-model指令,并将data中的数据渲染到页面中。

    这一步的关键在于实现compile方法,那么该如何解析el元素呢?思路如下:

    • 首先要获取到el下面的所有子节点,然后遍历这些子节点,如果子节点还有子节点,那我们就需要用到递归的思想;
    • 遍历子节点找到所有有指令的元素,并将对应的数据渲染到页面中。

    代码如下:(主要看compile那部分)

    class Vue {
        // 接收传进来的对象    constructor(options) {
              // 获取有用信息      this.$el = document.querySelector(options.el);
              this.$data = options.data;
          // 容器: {
    属性1: [wathcer1, wathcer2...], 属性2: [...]}
          this.$watcher = {
    }
        ;
              // 2. 解析元素: 实现Compile      this.compile(this.$el);
         // 要解析元素, 就得把元素传进去      // 3. 劫持数据: 实现 Observer      this.observe(this.$data);
     // 要劫持数据, 就得把数据传入    }
        compile(el) {
              // 解析元素下的每一个子节点, 所以要获取el.children      // 备注: children 返回元素集合, childNodes返回节点集合      let nodes = el.children;
              // 解析每个子节点的指令      for (VAR i = 0, length = nodes.length;
         i  length;
     i++) {
                let node = nodes[i];
            // 如果当前节点还有子元素, 递归解析该节点        if(node.children){
                  this.compile(node);
            }
            // 解析带有v-text指令的元素        if (node.hasAttribute("v-text")) {
                  let attrVal = node.getAttribute("v-text");
                  node.textContent = this.$data[attrVal];
     // 渲染页面        }
            // 解析带有v-model指令的元素        if (node.hasAttribute("v-model")) {
                  let attrVal = node.getAttribute("v-model");
                  node.value = this.$data[attrVal];
            }
          }
        }
        observe(data) {
    }
      }
        

    这样,我们就实现页面的初始化了。

    2.2.3 视图影响数据

    因为input带有v-model指令,因此我们要实现这样一个功能:在input框中输入字符,data中绑定的数据发生相应的改变。

    我们可以在input这个元素上绑定一个input事件,事件的效果就是:将data中的相应数据修改为input中的值。

    这一部分的实现代码比较简单,只要看标注那个地方就明白了,代码如下:

    class Vue {
        constructor(options) {
              this.$el = document.querySelector(options.el);
              this.$data = options.data;
                this.$watcher = {
    }
        ;
                this.compile(this.$el);
              this.observe(this.$data);
        }
        compile(el) {
              let nodes = el.children;
              for (var i = 0, length = nodes.length;
         i  length;
     i++) {
                let node = nodes[i];
            if(node.children){
                  this.compile(node);
            }
            if (node.hasAttribute("v-text")) {
                  let attrVal = node.getAttribute("v-text");
                  node.textContent = this.$data[attrVal];
            }
            if (node.hasAttribute("v-model")) {
                  let attrVal = node.getAttribute("v-model");
                  node.value = this.$data[attrVal];
                  // 看这里!!只多了三行代码!!          node.addEventListener("input", (ev)=>
    {
                    this.$data[attrVal] = ev.target.value;
                // 可以试着在这里执行:console.LOG(this.$data),            // 就可以看到每次在输入框输入文字的时候,data中的msg值也发生了变化          }
    )        }
          }
        }
        observe(data) {
    }
      }
        

    2.2.4 数据影响视图

    至此,我们已经实现了:当我们在input框中输入字符的时候,data中的数据会自动发生更新;

    本小节的主要任务是:当data中的数据发生更新的时候,绑定了该数据的元素会在页面上自动更新视图。具体思路如下:

    1) 我们将要实现一个 Wathcer 类,它有一个update方法,用来更新页面。观察者的代码如下:

    class Watcher{
        constructor(node, updatedAttr, vm, exPression){
              // 将传进来的值保存起来,这些数据都是渲染页面时要用到的数据      this.node = node;
              this.updatedAttr = updatedAttr;
              this.vm = vm;
              this.exPRession = expression;
              this.update();
        }
        update(){
              this.node[this.updatedAttr] = this.vm.$data[this.expression];
        }
      }
    

    2) 试想,我们该给哪些数据添加观察者?何时给数据添加观察者?

    在解析元素的时候,当解析到v-text和v-model指令的时候,说明这个元素是需要和数据双向绑定的,因此我们在这时往容器中添加观察者。我们需用到这样一个数据结构:{ 属性1: [wathcer1, wathcer2...], 属性2: [...]} ,如果不是很清晰,可以看下图:

    可以看到:vue实例中有一个$wathcer对象,$wathcer的每个属性对应每个需要绑定的数据,值是一个数组,用来存放观察了该数据的观察者。(备注:Vue源码中专门创造了Dep这么一个类,对应这里所说的数组,本文属于简易版本,就不过多介绍了)

    3) 劫持数据:利用对象的访问器属性getter和setter做到当数据更新的时候,触发一个动作,这个动作的主要目的就是让所有观察了该数据的观察者执行update方法。

    总结一下,在本小节我们需要做的工作:

    1. 实现一个Wathcer类;
    2. 在解析指令的时候(即在compile方法中)添加观察者;
    3. 实现数据劫持(实现observe方法)。

    完整代码如下:

      class Vue {
        // 接收传进来的对象    constructor(options) {
              // 获取有用信息      this.$el = document.querySelector(options.el);
              this.$data = options.data;
          // 容器: {
    属性1: [wathcer1, wathcer2...], 属性2: [...]}
          this.$watcher = {
    }
        ;
              // 解析元素: 实现Compile      this.compile(this.$el);
         // 要解析元素, 就得把元素传进去      // 劫持数据: 实现 Observer      this.observe(this.$data);
     // 要劫持数据, 就得把数据传入    }
        compile(el) {
              // 解析元素下的每一个子节点, 所以要获取el.children      // 拓展: children 返回元素集合, childNodes返回节点集合      let nodes = el.children;
              // 解析每个子节点的指令      for (var i = 0, length = nodes.length;
         i  length;
     i++) {
                let node = nodes[i];
            // 如果当前节点还有子元素, 递归解析该节点        if (node.children) {
                  this.compile(node);
            }
            if (node.hasAttribute("v-text")) {
                  let attrVal = node.getAttribute("v-text");
                  // node.textContent = this.$data[attrVal];
               // Watcher在实例化时调用update, 替代了这行代码          /**           * 试想Wathcer要更新节点数据的时候要用到哪些数据?            * e.g.   p.innerHTML = vm.$data[msg]           * 所以要传入的参数依次是: 当前节点node, 需要更新的节点属性, vue实例, 绑定的数据属性          */          // 往容器中添加观察者: {
    msg1: [Watcher, Watcher...], msg2: [...]}
              if (!this.$watcher[attrVal]) {
                    this.$watcher[attrVal] = [];
              }
              this.$watcher[attrVal].push(new Watcher(node, "innerHTML", this, attrVal))        }
            if (node.hasAttribute("v-model")) {
                  let attrVal = node.getAttribute("v-model");
                  node.value = this.$data[attrVal];
                  node.addEventListener("input", (ev) =>
     {
                    this.$data[attrVal] = ev.target.value;
              }
    )          if (!this.$watcher[attrVal]) {
                    this.$watcher[attrVal] = [];
              }
              // 不同于上处用的innerHTML, 这里input用的是vaule属性          this.$watcher[attrVal].push(new Watcher(node, "value", this, attrVal))        }
          }
        }
        observe(data) {
              Object.keys(data).foreach((key) =>
     {
                let val = data[key];
      // 这个val将一直保存在内存中,每次访问data[key],都是在访问这个val        Object.defineProperty(data, key, {
              get() {
                    return val;
      // 这里不能直接返回data[key],不然会陷入无限死循环          }
    ,          set(newVal) {
                if (val !== newVal) {
                      val = newVal;
        // 同理,这里不能直接对data[key]进行设置,会陷入死循环              this.$watcher[key].forEach((w) =>
     {
                        w.update();
                  }
    )            }
              }
            }
    )      }
    )    }
      }
      class Watcher {
        constructor(node, updatedAttr, vm, expression) {
              // 将传进来的值保存起来      this.node = node;
              this.updatedAttr = updatedAttr;
              this.vm = vm;
              this.expression = expression;
              this.update();
        }
        update() {
              this.node[this.updatedAttr] = this.vm.$data[this.expression];
        }
      }
      let vm = new Vue({
        el: "#app",    data: {
          msg: "hello world",      msg2: "hello xiaofei"    }
      }
        )

    至此,代码就完成了。

    3. 未来的计划

    用设计模式的知识,分析上面这份源码存在的问题,并和Vue源码进行比对,算是对Vue源码的解析

    以上就是vue 数据双向绑定的实现方法的详细内容,更多关于vue 数据双向绑定的资料请关注其它相关文章!

    您可能感兴趣的文章:
    • Vue2.0/3.0双向数据绑定的实现原理详解
    • 关于vuex强刷数据丢失问题解析
    • Vue 如何追踪数据变化
    • vue+canvas实现数据实时从上到下刷新瀑布图效果(类似QT的)
    • vue 数据(data)赋值问题的解决方案
    • Vue 重置data的数据为初始状态操作
    • Vue组件传值过程中丢失数据的分析与解决方案
    • SpringBoot+Vue实现数据添加功能
    • 手写Vue2.0 数据劫持的示例
    • Vue中避免滥用this去读取data中数据
    • 用vue设计一个数据采集器

    声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

    vue数据绑定

    若转载请注明出处: vue 数据双向绑定的实现方法
    本文地址: https://pptw.com/jishu/594712.html
    15道C语言开发面试题(原题分享) vue3.0封装轮播图组件的步骤

    游客 回复需填写必要信息