5/29初识VUE插值表达式,手动封装实现声明式渲染和响应式

96 阅读2分钟

初学Vue就会接触到的就是这两个大括号{{}}——插值表达式,利用这玩意,我们可以轻松修改页面标签的内容。例如:

我们的html结构如下:

<body>
    <div id="box">
      <p>{{str}}</p>
    </div>
  </body>

而利用插值表达式,页面就会显示str变量所代表的内容;后续修改str,咱们的页面也会随之响应变化。

let a = new Vue({
          el: "#box",
          data: {
            str: "刘奇",
            num: "数字",
          },
        });

所以至此,咱们不难观察到Vue的两大特点:声明式渲染+响应式。 接下来可以尝试实现封装一下Vue的这两个基础特色。

声明式渲染

思路

  1. 利用元素的innerHTML获取页面结构
  2. 利用正则去匹配{{str}}类似的结构,将其修改为data中所记录的数据,也就是data[str]
  3. 最后,将处理后的页面结构重新赋值给元素,即可完成渲染。
function MyVue(option) {
          let { el, data } = option;
          this.$el = document.querySelector(el);
          let html = this.$el.innerHTML;
          console.log(html);
          //   <p>{{str}}</p><p>{{num}}</p><p>{{bool}}</p>
          console.log(this);
          let _this = this;

          function render() {
            let reg = /\{\{([^}]+)\}\}/g;
            let result = null;
            while ((result = reg.exec(html))) {
              console.log(result);
              //注意replace不改变原来的字符串
              html = html.replace(result[0], data[result[1]]);
            }
            //<p>liuqi</p><p>999</p><p>true</p>
            console.log(html);
            //此处需要用到指向实例的this,所以需要额外处理
            _this.$el.innerHTML = html;
          }
          render();
        }
        let example = new MyVue({
          el: "#app",
          data: {
            str: "liuqi",
            num: 999,
            bool: true,
          },
        });
      };

细节

如何匹配{{str}}?

利用正则表达式 /\{\{([^}]+)\}\}/g来匹配,exec()方法会依次匹配,直到找不到匹配项返回null,根据这个特色,我们就可以构建循环。

响应式

此时咱们的目的是data数据改变后,页面显示的内容会实时更新变化,而不需要刷新。所运用的思路就是,劫持data的每一个属性,每次读取(get)的时候会正常返回值;每次修改的时候(set),在修改的同时会重新渲染页面。

for (let key in data) {
    // 遍历data
    let defaultValue = data[key]; // 提前声明变量存储数据
    Object.defineProperty(data, key, {
      get: function () {
        // 取值拦截
        console.log("data取值拦截", key);
        return defaultValue;
      },
      set: function (val) {
        // 赋值拦截
        console.log("data赋值拦截", key, val);
        defaultValue = val;

        render(); // 数据发生改变之后 -> 重新渲染
      },
    });
  }
  console.log(this);

  // 给Vue实例添加数据拦截 => 属性和data上的属性保持一致
  for (let key in data) {
    // 遍历data
    Object.defineProperty(this, key, {
      get: function () {
        // 取值拦截
        console.log("实例化对象取值拦截", key);
        return data[key];
      },
      set: function (val) {
        // 赋值拦截
        console.log("实例化对象赋值拦截", key, val);
        data[key] = val;
      },
    });
  }