面试中,双向数据绑定几乎是面试必备;
不只是要求会描述出来,而且要求能手写出来
下面就分享一下,
思路:
1. 数据 -> 转为响应式数据 Object.defineProperty Proxy
即:将存放数据的对象属性,与实例化对象的属性对应
2. input -> input / keyup -> 事件处理函数的绑定 -> 改变数据
3. 绑定dom {{}} 中的属性,更新数据的同时,更新对应dom节点
目的:this.domPool[key] = 对应的dom节点
具体代码:
html文件:
代码语言:javascript复制<div id="app">
<input type="text" placeholder="姓名" v-model="name"/>
<input type="text" placeholder="年龄" v-model="age"/>
<input type="text" placeholder="年龄" v-model="sex"/>
<input type="text" placeholder="email" v-model="email"/>
<input type="text" placeholder="电话" v-model="tel"/>
<p>姓名:<span>{{ name }}</span></p>
<p>年龄:<span>{{ age }}</span></p>
<p>年龄:<span>{{ sex }}</span></p>
<p>email:<span>{{ email }}</span></p>
<p>电话:<span>{{ tel }}</span></p>
<p><button id="btn">change</button></p>
</div>
<script src="MVVM.js"></script>
<script type="text/javascript">
const app = new MVVM('#app',{
name:'',
age:'',
sex:'',
email:'',
tel:''
})
// 添加一个按钮事件,测试,数据改变时,dom也跟着改变
var btn = document.getElementById("btn");
btn.addEventListener("click",function () {
app.setData('name','doubleyong');
},false)
</script>
MVVM.js
对于双向数据绑定的封装
代码语言:javascript复制class MVVM {
constructor(el,data) {
this.el = document.querySelector(el);
// this._data = data;
this.data =data;
this.domPool={};
this.init();
}
// 初始化
init(){
this.initData(); //初始数据
//实现数据的响应
this.initDom(); //初始dom
}
//目的:暴露的实例,可以直接操作data中的属性
// 实例对象.属性名 进行获取
// 实例对象.属性名 = 值; 进行设置
initData(){
let _this = this;
this.data = {};
for(let key in _this._data){
// let key 与var key, 作用域不同
Object.defineProperty(_this.data,key,{
get() {
return _this._data[key];
},
set(v) {
_this._data[key] = v;
// 数据修改后,对应dom显示也进行更新
_this.domPool[key].innerText = v;
}
})
}
}
initDom(){
this.bindDom(this.el); //绑定dom {{}} 中的属性,与dom节点
this.bindInput(this.el); //实现更新input的value值,数据更新
}
// 目的:将对应的键值,和对应显示的dom进行关联
bindDom(el){
const childNodes = el.childNodes;
childNodes.forEach(item =>{
if(item.nodeType == 3)
{
const _value = item.nodeValue;
if(_value.trim().length){
let _isValid = /{{(. ?)}}/.test(_value);
if(_isValid){
//将对应{{ }} 中的值,添加到dompool中
const _key = _value.match(/{{(. ?)}}/)[1].trim();
this.domPool[_key] = item.parentNode;
item.parentNode.innerText = this.data[_key] || undefined;
}
}
}
item.childNodes && this.bindDom(item);
})
}
// 目的:绑定input控件,input值改后,更新对应data值
bindInput(el){
const _allInputs = document.querySelectorAll('input');
_allInputs.forEach(input=>{
const _vModel = input.getAttribute('v-model');
if(_vModel){
input.addEventListener('keyup',this.handleInput.bind(this,_vModel,input),false);
}
})
}
handleInput(key,input){
const _value = input.value;
this.data[key] = _value;
}
setData(key,value){
this.data[key] = value;
}
}