初学Vue就会接触到的就是这两个大括号{{}}——插值表达式,利用这玩意,我们可以轻松修改页面标签的内容。例如:
我们的html结构如下:
<body>
<div id="box">
<p>{{str}}</p>
</div>
</body>
而利用插值表达式,页面就会显示str变量所代表的内容;后续修改str,咱们的页面也会随之响应变化。
let a = new Vue({
el: "#box",
data: {
str: "刘奇",
num: "数字",
},
});
所以至此,咱们不难观察到Vue的两大特点:声明式渲染+响应式。 接下来可以尝试实现封装一下Vue的这两个基础特色。
声明式渲染
思路
- 利用元素的innerHTML获取页面结构
- 利用正则去匹配{{str}}类似的结构,将其修改为data中所记录的数据,也就是data[str]
- 最后,将处理后的页面结构重新赋值给元素,即可完成渲染。
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;
},
});
}