lakeiedward's lakeiedward's
首页
标签
  • UI组件库

    • vue-luckyui (opens new window)
    • vue-luckyui文档 (opens new window)
  • 若依页面分层工具

    • ry-layer-page (opens new window)
    • ry-layer-page文档 (opens new window)
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于
掘金 (opens new window)
GitHub (opens new window)

lakeiedward

首页
标签
  • UI组件库

    • vue-luckyui (opens new window)
    • vue-luckyui文档 (opens new window)
  • 若依页面分层工具

    • ry-layer-page (opens new window)
    • ry-layer-page文档 (opens new window)
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 书单
    • 关于
掘金 (opens new window)
GitHub (opens new window)
  • 手写一个简单的Vue数据响应式吧

    • 一、什么是数据的双向绑定(MVVM)
      • 二、响应式数据效果图
        • 三、实现数据的响应式
          • Object.defineProperty监听
          • Proxy监听
        • 四、Object.defineProperty和Proxy的区别
          • 五、参考文献
          lakeiedward
          2022-12-02
          前端技术
          目录

          手写一个简单的Vue数据响应式吧

          # 一、什么是数据的双向绑定(MVVM)

          微信图片_20220824103413.jpg

          数据的双向绑定主要有以下两个方面:

          1. 改变代码层面的数据,view视图层也同步变化,即Data->view的变化
          2. 改变视图层输入框中的内容,Data数据也同步变化,即view->Data的变化

          其一,Data数据层->view视图层的变化我们要通过Object.defineProperty或Proxy来实现; 其二,view视图层->Data数据层的变化可以通过事件监听来实现,也就是v-model的语法糖。只有两个步骤全都实现才是数据的双向绑定,实现其中一个只是数据的单向绑定!

          # 二、响应式数据效果图

          kk 2022-08-24 15-03-49.gif

          # 三、实现数据的响应式

          依照vue的模板格式来,首先初始化内容,创建一个MVVM类,html内容都放在#app下。

           <div id="app">
             <h2>通过改变视图=>改变数据</h2>
             <input type="text" placeholder="姓名" v-model="info" />
             <div>
               <p>人物:<span>{{info}}</span></p>
             </div>
           </div>
           <button onclick="ModifyData()">通过改变数据=>改变视图</button>
           <button onclick="getData()">获取当前数据</button>
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
           function ModifyData() {
             p.setData("info", "武大郎");
           }
           function getData() {
             console.log(p._data);
           }
           
           class MVVM {
             constructor(el, data) {
               this.init(el, data);
             }
             //初始化
             init(el, v) {
               this._data = v.data();
               this.el = document.querySelector(el);
             }
           }
           let p = new MVVM("#app", {
             data() {
               return {
                 info: "潘金莲",
               };
             },
           });
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24

          以下各个函数的作用:

          • init 负责初始化数据内容
          • observer 对目标对象的属性进行遍历,确保每个属性都被监听到
          • defineReactive 对目标对象属性进行监听
          • matchMustache 匹配html中Mustache的内容,用data中的数据进行替换,需要递归获取文本节点
          • bindInput 获取绑定v-model的输入框节点,改变输入框内容时,数据跟着改变
          • updateView 数据发生变化时,触发页面更新
          • setData 对外暴露的一个修改数据的接口

          执行逻辑为:

          new一个MVVM ==> 调用 observer(data) 将对象变成响应式 ==> matchMustache函数把中的内容替换为data的数据 ==> bindInput函数获取v-model的值用data的值进行替换 ==> 输入框改变数据 ==> 触发set更新视图 ==> 点击ModifyData事件,调用setData方法 ==> 触发set更新视图。

          # Object.defineProperty监听

          class MVVM {
             constructor(el, data) {
                this.reg = /\{\{(.*?)\}\}/;
                this.modelValue = {};
                this.init(el, data);
             }
             
             //初始化
             init(el, v) {
               this._data = v.data();
               this.el = document.querySelector(el);
               //监听对象
               this.observer(this._data)
               //初始化data内容到视图层
               this.matchMustache(this.el);
               this.bindInput();
             }
             
            //对目标对象属性进行监听
            defineReactive(object,key, value) {
              let _this = this;
              如果值是个对象则深度监听;
              _this.observer(value);
              Object.defineProperty(object, key, {
                get() {
                  return value;
                },
                set(newValue) {
                  value = newValue;
                  //新增是个对象时也深度监听
                  _this.observer(newValue);
                  //修改触发更新
                  _this.updateView(key, value);
                },
              });
            }
            
            //监听data对象
            observer(target) {
              //如果不是对象则不进行监听
              if (typeof target !== "object" || typeof target === null) {
                return;
              }
              // this.defineReactive(target);
              for (let key in target) {
                this.defineReactive(target, key, target[key]);
              }
            }
            
            //匹配Mustache语法的内容
            matchMustache(el) {
              const childNodes = el.childNodes;
              childNodes.forEach((item) => {
                if (item.nodeType === 3) {
                  const _value = item.nodeValue;
                  if (_value.trim().length) {
                    let _valied = this.reg.test(_value);
                    if (_valied) {
                      const _match = item.nodeValue.match(this.reg)[1];
                      this.modelValue[_match] = item.parentNode;
                      item.parentNode.innerText = item.nodeValue.replace(
                        this.reg,
                        this._data[_match] || undefined
                      );
                    }
                  }
                }
                item.childNodes && this.matchMustache(item);
              });
            }
            
            //绑定输入框的内容
            bindInput(key, value) {
              const _inputs = document.querySelectorAll("input");
              _inputs.forEach((item) => {
                const _model = item.getAttribute("v-model").trim();
                if (key && _model === key) {
                  item.value = value;
                } else {
                  item.value = this._data[_model];
                  if (_model) {
                    item.addEventListener("input", (e) => {
                      this._data[_model] = e.target.value;
                    });
                  }
                }
              });
            }
            
            //更新视图
            updateView(key, value) {
              this.modelValue[key].innerText = value;
            }
            
            //改变数据 更新view
            setData(key, value) {
              this._data[key] = value;
              this.bindInput(key, value);
            }
          }
          
          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
          96
          97
          98
          99
          100

          # Proxy监听

          把上面defineReactive函数中Object.defineProperty的监听改为下面Proxy监听

           defineReactive(object) {
             let _this = this;
             this._data = new Proxy(object, {
               get(target, prop) {
                 return Reflect.get(target, prop);
               },
               set(target, prop, value, receiver) {
                 //修改触发更新
                 _this.updateView(prop, value);
                 return Reflect.set(...arguments);
               },
             });
           }
           
           observer(target) {
             //如果不是对象则不进行监听
             if (typeof target !== "object" || typeof target === null) {
               return;
             }
             this.defineReactive(target);
           } 
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21

          # 四、Object.defineProperty和Proxy的区别

          • 前者监听的是对象的属性,后者监听的是整个对象
          • 前者不能监听到新增属性和删除属性,后者可以监听
          • 前者不能监听到数组的变化,后者可以监听
          • 前者的兼容性比后者要好,因为Proxy是ES6提供的一个新的API
          • 前者的性能比后者要好

          虽然proxy性能和兼容性差,但是proxy作为新标准将受到浏览器厂商重点持续的性能优化, 性能这块会逐步得到改善

          # 五、参考文献

          「Object.defineProperty」 深入浅出 (opens new window)

          数据双向绑定 (opens new window)

          Reflect.get (opens new window)

          Reflect.set (opens new window)

          Proxy (opens new window)

          #vue#JS
          上次更新: 2022/12/02, 09:00:55
          Theme by Vdoing | Copyright © 2017-2023 lakeiedward | blog 皖ICP备2023006581号-1
          • 跟随系统
          • 浅色模式
          • 深色模式
          • 阅读模式