js模板引擎可以认为是一个基于MVC的结构,我们通过建立模板作为视图,然后通过引擎函数作为控制器实现数据和视图的绑定,从而实现实现数据在页面渲染,但是当数据模型发生变化时,视图不能自动更新;当视图数据发生变化时,模型数据不能实现更新,这个时候双向数据绑定应运而生。检测视图数据更新实现数据绑定的方法有很多种,目前主要分为三个流派,Angular使用的是脏检查,只在特定的事件下才会触发视图刷新,Vue使用的是Getter/Setter机制,而React则是通过 Virtual DOM 算法检查DOM的变动的刷新机制。
创建标签:
<div id="div1">
<input type="text" v-model="name">
<br> 姓名:{{name}}
</div>
创建一个输入框,使用v-model属性绑定变量name,注意这里只是模仿vue绑定的形式,代码中并没有引入任何vue依赖。完全靠原生js实现。
单向绑定:
let el = document.getElementById('app');
let template = el.innerHTML;
let _data = {
name: '_BuzzLy'
};
let data = new Proxy(_data, {
set(obj, name, value) {
obj[name] = value;
render();
}
});
render();
function render() {
el.innerHTML = template.replace(/\{\{\w+\}\}/g, str => {
str = str.substring(2, str.length - 2);
return _data[str];
});
}
到这我们又实现了一遍单向绑定,想要实现数据的双向绑定其实很简单,只需稍微修改我们的render函数。
双向绑定:
function render() {
el.innerHTML = template.replace(/\{\{\w+\}\}/g, str => {
str = str.substring(2, str.length - 2);
return _data[str];
});
// 找到所有input标签
Array.from(el.getElementsByTagName('input'))
// 过滤得到其中带有v-model属性的标签
.filter(ele => ele.getAttribute('v-model'))
// 遍历这些input标签
.forEach(input => {
// 获取到v-model中绑定的key,从数据中找到key对应的value赋给input
// 这一步就相当于数据=>视图的绑定
let name = input.getAttribute('v-model');
input.value = _data[name];
// 为input绑定输入事件
input.oninput = function () {
// 当input修改时,将修改后的值赋给暴露在外的data对象
// 这一步就实现了视图=>数据的绑定
data[name] = this.value;
};
});
}
修改后的render函数通过过滤、遍历得到每一个拥有v-model属性的input标签,然后将数据绑定到视图上,并且视图修改也会触发数据的更新,这样就实现了数据的双向绑定。
看似复杂的绑定机制其实就是通过我们熟悉的js一些基础的操作来实现的。
完整代码:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="div1">
<input type="text" v-model="name">
<br> 姓名:{{name}}
</div>
</body>
<script>
let el = document.getElementById('div1');
let template = el.innerHTML;
let _data = {
name: '_BuzzLy'
};
let data = new Proxy(_data, {
set(obj, name, value) {
obj[name] = value;
render();
}
});
render();
function render() {
el.innerHTML = template.replace(/\{\{\w+\}\}/g, str => {
str = str.substring(2, str.length - 2);
return _data[str];
});
Array.from(el.getElementsByTagName('input'))
.filter(ele => ele.getAttribute('v-model'))
.forEach(input => {
let name = input.getAttribute('v-model');
input.value = _data[name];
input.oninput = function () {
data[name] = this.value;
};
});
}
</script>
</html>