唉!你好前端面试!(ㄒoㄒ)MVVM数据驱动视图更新机制

205 阅读4分钟

问题一:MVVM框架中编译数据到视图

  • 编译数据到视图的原理(JS原生方法理解)

    • 创建可实例化的对象(对象内部接受指定的参数,及自身的方法)

    • 视图当中采用指定的形式进行对象的创建以及模板语法

    • 实例化对象当中实现以下方法(函数)功能:

      • 模板语法的查找与解构
      • 数据的解析与定向赋值
    <body>
        <div id="app">
            {{message}}
            <div>
                {{message}}
            </div>
        </div>
    </body>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                message: "测试数据"
            }
        })
    ​
    </script>
    
    class Vue {
      constructor(opts) {
        this.opts = opts;
        this._data = this.opts.data;
        this.compile();
      }
       // 标签循下的文本采用递归查找与替换的方法进行数据的渲染
      compile() {
        let ele = document.querySelector(this.opts.el);
        let childNodes = ele.childNodes;
          //将查找和插入分离出来,构写新的的方法,以便后续的方法自身递归调用
        this.compileNode(childNodes);   
      }
      compileNode(childNodes) {
        childNodes.forEach((node) => {
          if (node.nodeType === 3) {
              //获取该文本节点下的内容数据
            let textContent = node.textContent;
              //使用正则匹配进行文本数据的拆分,获取相应的字段信息
            let reg = /{{\s*([^{}\s*]+)\s*}}/g;
            if (reg.test(textContent)) {
              let $1 = RegExp.$1;
              console.log($1);
                //将获取到的属性进行值的替换
              node.textContent = node.textContent.replace(reg, this._data[$1]);
            }
          } else if (node.nodeType === 1) {//如果当前节点为标签节点
            if (node.childNodes.length > 0) {   //判断子节点是否存在
              this.compileNode(node.childNodes);    //存在则进行函数递归查询,重新执行直到解析完毕
            }
          }
        });
      }
    }
    ​
    

问题二:实现数据驱动视图更新的底层对象Object的defineProperty方法(原理解读)

  • Object.defineProperty()能够做什么或有什么作用与特性

    • 直接在一个对象上定义一个新属性

    • 能够修改一个对象的现有属性,并返回此对象

    应当直接在Object构造器对象上调用此方法,而不是在任意一个Object类型的实例上调用

    //修改一个已定义的对象现有属性的方法(代码实践)
    const object1 = {};
    Object.defineProperty(object1,"name",{
        value: 42,
        writable: false     //是否可修改
    })
    object1.name = 77;      //writable为false时,不能改变其值
    console.lob(object1.name)   //42
    

    该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for……in或Object.keys方法),可以改变属性值、删除属性。

    默认情况下,使用Object.defineProperty()添加的属性值是不可修改(immutable)的

    对象里目前存在有:数据描述符(具有值的属性,可写可不写)和存取描述符(由getter和setter两个函数所描述的属性) 。描述符(对象形式)只能两者选其一

  • 共享以下可选键值: configurable(值为true时,该属性描述符valuewritable 特性外的其他特性能够被改变,该属性能从对应的对象上被删除)、enumerable(值为true时,该属性会在出现的对象的枚举属性中)、

  • 数据描述符还具有以下可选键值: value、writable(值为true时,value能被赋值运算符改变

  • 存取描述符还具有以下可选键值: get(属性的getter函数,没有则为undefined。访问该属性,调用此函数。执行时会传入this对象。返回值会被用作属性的值。)、set(属性的setter函数,没有则为undefined。同上……)

    如果一个描述符不具有value、writable、get和set中的任意一个键,则被认为是一个数据描述符,一项选项不一定是自身属性,也可能来自继承的属性,在设置之前可使用Object.prototype进行冻结。

    o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则 enumerable 及其他属性值均为 true

  • 实现数据驱动视图时Object.defineProperty()的作用:

    在进行数据的获取和数据的更改时会触发Object.defineProperty()方法的get和set属性

    //更改对象的类型,实现数据的更改
    Object.defineProperty(obj, "name", {
        configurable: true,
        enumerable: true,
        get() {
            console.log("获取name时采用get??");
            return "李四"
        },
        set(newValue) {
            console.log("触发set方法进行值修改", newValue);
        }
    })
    // console.log(obj);
    console.log(obj.name);
    obj.name = "王五" //改变name的值是否调用set方法// 总结:当一个对象是Object.defineProperty形式创建的时候,
    // 进行值获取和值改变的时候会触发get和set方法
    
  • 通过Object.defineProperty()+extends EventTarget(自定义事件)实现数据驱动视图更新

    <body>
        <div id="app">
            {{message}}
        </div>
    </body>
    <script>
        let vm = new Vue({
            el: "#app",
            data: {
                message: "测试数据"
            }
        })
        console.log(vm._data);
        setTimeout(function () {
            console.log("修改了数据");
            vm._data.message = "修改了数据"
        }, 1000)
    
    </script>
    
    class Vue extends EventTarget {
      //添加事件基础 extends EventTarget
      constructor(opts) {
        super(); //事件继承关联
        this.opts = opts;
        this._data = this.opts.data;
        // 调用observe方法
        this.observe(this._data);
        this.compile();
      }
    
      // 创建一个具备get和set属性方法的对象,触发事件进行数据的获取与改变、视图的重新渲染
      observe(data) {
        for (let key in data) {
          let _this = this; //将this提前保存
          let value = data[key]; //提起将枚举到的data[key]值进行保存,防止在内部的递归
          //构造Object对象,调用defineProperty方法
          Object.defineProperty(data, key, {
            configurable: true,
            enumerable: true,
            get() {
              console.log('get……');
              return value;
            },
            set(newValue) {
              console.log('set……'); //当数据放生更改的时候希望能够同时更新视图,调用方法(自定义事件)
              // 触发自定义事件更新
              let event = new CustomEvent(key, {
                detail: newValue, //detail是干什么的
              });
              // this.dispatchEvent(event); //报错,this指向发生改变
              _this.dispatchEvent(event);
              value = newValue;
            },
          });
        }
      }
      // 标签循下的文本采用递归查找与替换的方法进行数据的渲染
      compile() {
        let ele = document.querySelector(this.opts.el);
        let childNodes = ele.childNodes;
        this.compileNode(childNodes);
      }
      compileNode(childNodes) {
        childNodes.forEach((node) => {
          if (node.nodeType === 3) {
            let textContent = node.textContent;
            let reg = /\{\{\s*([^\{\}\s*]+)\s*\}\}/g;
            if (reg.test(textContent)) {
              let $1 = RegExp.$1;
              // console.log($1);
              node.textContent = node.textContent.replace(reg, this._data[$1]);
              //绑定自定义事件 关键字 key
              this.addEventListener($1, (e) => {
                // console.log('触发了更新', e);    //获取自定义事件e对象的返回值,detail进行的值存取
                //自定义事件触发后,进行视图数据更新
                let newValue = e.detail;
                let oldValue = this._data[$1];
                let updateReg = new RegExp(oldValue);
                node.textContent = node.textContent.replace(updateReg, newValue);
              });
            }
          } else if (node.nodeType === 1) {
            if (node.childNodes.length > 0) {
              this.compileNode(node.childNodes);
            }
          }
        });
      }
    }