Skip to content

四、数据响应实现 ​

本节目标:理解“四、数据响应实现”的核心思路,并能把它用于实际开发或面试表达。

说明:原 OneNote 中有图片引用,但图片未包含在导出目录中;本页保留了可用的文字与代码内容。 Vue普遍走的就是数据劫持方式。 不同的在于使用DefineProperty还是Proxy。 也就是一次一个属性劫持还是一次劫持一个对象。 当然后者比前者听着就明显有优势。这也就是Vue3的响应式原理。

Proxy/Reflect是在ES2015规范中加入的,Proxy可以更好的拦截对象行为,Reflect可以更优雅的操纵对象。 优势在于

针对整个对象定制而不是对象的某个属性,所以也就不需要对keys进行遍历。

支持数组,这个DefineProperty不具备。这样就省去了重载数组方法这样的Hack过程。

Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富
Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法.
可以通过递归方便的进行对象嵌套。
说了这么多我们先来一个小例子
var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
})
obj.abc = 132

这样写如果你修改obj中的值,就会打印出来。 也就是说如果对象被修改就会得的被响应。

当然我们需要的响应就是重新更新视图也就是重新运行render方法。
首先制造一个抽象的数据响应函数
// 定义响应函数
let effective
observed = new Proxy(config.data(), {
  set(target, key, value, receiver) {
    const ret = Reflect.set(target, key, value, receiver)
    // 触发函数响应
    effective()
    return ret
  },
})
在初始化的时候我们设置响应动作为渲染视图
const dom = document.querySelector(container)
// 设置响应动作为渲染视图
effective = () => render(observed, dom)
render(observed, dom)
**1.** **视图变化的监听**
浏览器视图的变化,主要体现在对输入项变化的监听上,所以只需要通过绑定监听事件就可以了。
document.querySelector('input').addEventListener('keyup', function () {
  data.message = this.value
})
**2.** **完整的代码**
<html lang="en">
<body>
  <div id="app"></div>
  <script>
    const Vue = {
      createApp(config) {
        // 编译过程
        const compile = (template) => (content, dom) => {
          // 重新渲染
          dom.innerText = "";
          input = document.createElement("input");
          input.addEventListener("keyup", function () {
            content.state.message = this.value;
          });
          input.setAttribute("value", content.state.message);
          dom.appendChild(input);
          let button = dom.querySelector("button");
          button = document.createElement("button");
          button.addEventListener("click", () => {
            return content.click.apply(content.state);
          });
          button.innerText = content.state.message;
          dom.appendChild(button);
        };
        // 生成渲染函数
        const render = compile(config.template);
        return {
          mount: function (container) {
            const dom = document.querySelector(container);
            const setupResult = config.setup();
            effective = () => render(setupResult, dom);
            render(setupResult, dom);
          },
        };
      },
    };
    // 定义响应函数
    let effective;
    const App = {
      // 视图
      template: `
                <input v-model="message"/>
                <button @click='click'>{{message}}</button>
                `,
      setup() {
        // 数据劫持
        const state = new Proxy(
          {
            message: "Hello Vue 3!!",
          },
          {
            set(target, key, value, receiver) {
              const ret = Reflect.set(target, key, value, receiver);
              // 触发函数响应
              effective();
              return ret;
            },
          }
        );
        const click = () => {
          state.message = state.message.split("").reverse().join("");
        };
        return { state, click };
      },
    };
    const { createApp } = Vue;
    createApp(App).mount("#app");
  </script>
</body>
</html>

本站总访问 — 次