Appearance
四、数据响应实现
本节目标:理解“四、数据响应实现”的核心思路,并能把它用于实际开发或面试表达。
说明:原 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>