10分钟快速实现数据双向绑定

说在前面

双向绑定概念其实很简单,就是视图(View)的变化能实时让数据模型(Model)发生变化,而数据的变化也能实时更新到视图层。我们所说的单向数据绑定就是从数据到视图这一方向的关系。

分析

1、响应式数据

使用Object.defineProperty、Proxy对数据进行监听拦截。

//obj:必需。目标对象
//prop:必需。需定义或修改的属性的名字
//descriptor:必需。目标属性所拥有的特性
Object.defineProperty(obj, prop, descriptor)

vue3.0 开始 Proxy代替Object.defineProperty

let p = new Proxy(target, handler);

2、input事件监听

绑定事件处理函数,实时修改数据。

3、相关dom操作

将数据与相关dom节点绑定在一起,修改数据的时候对应的dom节点也应一起改变。

实现

1、html页面

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>v-model</title>
 </head>
 <body>
  <div id="app">
   <input type="text" v-model="name" placeholder="姓名"/>
   <input type="text" v-model="age" placeholder="年龄"/>
   <div>
    <p>
     姓名:<span>{{name}}</span>
    </p>
    <p>
     年龄:<span>{{age}}</span>
    </p>
    <p>
     <p>
      年龄:<span>{{age}}</span>
     </p>
    </p>
   </div>
   <button id="btn">修改名字</button>
  </div>
 </body>
 <script src="./VModel.js"></script>
 <script>
  const app = new VModel('#app',{
   name:'',
   age:''
  });
  document.getElementById('btn').addEventListener('click',function({
   app.setData('name','名字改变了');
  })
 
</script>
</html>
10分钟快速实现数据双向绑定
在这里插入图片描述

2、VModel.js

(1)构造VModel

class  VModel {
 constructor(el,data) {
  this.el = document.querySelector(el);
  //存放数据对象
  this._data = data;
  //存放绑定数据的dom节点
  this.domPoll = {};
  
  this.init();
 }
}

(2)初始化数据对象

使用Object.defineProperty
initData () {
 const _this = this;
 this.data = {};
 for(let key in this._data){
  Object.defineProperty(this.data,key,{
   get(){
    console.log("获取数据",key,_this._data[key]);
    return _this._data[key];
   },
   set(newVal){
    console.log("设置数据",key,newVal);
    _this.domPoll[key].innerText = newVal;
    _this._data[key] = newVal;
   }
  });
 }
}
使用Proxy
initData () {
 const _this = this;
 this.data = {};
 this.data = new Proxy(this.data,{
  get(target,key){
   return Reflect.get(target,key);
  },
  set(target,key,value){
   // _this.domPoll[key].innerText = value;
   _this.domPoll[key].forEach(item => {
    item.innerText = value;
   })
   return Reflect.set(target,key,value);
  }
 })
}

(3)绑定dom节点

bindDom(el){
 const childNodes = el.childNodes;
 childNodes.forEach(item => {
  //nodeType为3时该dom节点为文本节点
  if(item.nodeType === 3){
   const _value = item.nodeValue;
  
   if(_value.trim().length){
    //匹配是否有两个花括号包裹的数据
    let  _isValid = /{{(.+?)}}/.test(_value);
    if(_isValid){
     const _key = _value.match(/{{(.+?)}}/)[1].trim();
     // this.domPoll[_key] = item.parentNode;
     //一个数据可以被多个dom节点绑定,所以应该用数组来进行保存
      //未定义时先初始化
      if(!this.domPoll[_key]) this.domPoll[_key] = [];
      this.domPoll[_key].push(item.parentNode);
     //替换绑定的值
     item.parentNode.innerText = this.data[_key] || undefined;
    }
   }
  }
  //递归遍历子节点
  item.childNodes && this.bindDom(item);
 })
}

####(4)输入框数据绑定

bindInput(el){
 //获取input所有元素节点
 const _allInput = el.querySelectorAll('input');
 _allInput.forEach(input => {
  const _vModel = input.getAttribute('v-model');
  //判断是否有v-model属性
  if(_vModel){
//监听输入事件   input.addEventListener('keyup',this.handleInput.bind(this,_vModel,input),false);
  }
 })
}

handleInput(key,input){
 const _value = input.value;
 //数据变化的时候会同步修改dom节点绑定的数据。
 this.data[key] = _value;
}

(4)完整代码

class  VModel {
 constructor(el,data) {
  this.el = document.querySelector(el);
  this._data = data;
  this.domPoll = {};
  
  this.init();
 }
 
 init(){
  this.initData();
  this.initDom();
 }
 initDom(){
  this.bindDom(this.el);
  this.bindInput(this.el);
  console.log('domPoll',this.domPoll);
 }
 
 initData () {
  const _this = this;
  this.data = {};
  // for(let key in this._data){
  //  Object.defineProperty(this.data,key,{
  //   get(){
  //    console.log("获取数据",key,_this._data[key]);
  //    return _this._data[key];
  //   },
  //   set(newVal){
  //    console.log("设置数据",key,newVal);
  //    _this.domPoll[key].innerText = newVal;
  //    _this._data[key] = newVal;
  //   }
  //  });
  // }
  this.data = new Proxy(this.data,{
   get(target,key){
    return Reflect.get(target,key);
   },
   set(target,key,value){
    // _this.domPoll[key].innerText = value;
    _this.domPoll[key].forEach(item => {
     item.innerText = value;
    })
    return Reflect.set(target,key,value);
   }
  })
 }
 
 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){
      const _key = _value.match(/{{(.+?)}}/)[1].trim();
      // this.domPoll[_key] = item.parentNode;
      if(!this.domPoll[_key]) this.domPoll[_key] = [];
      this.domPoll[_key].push(item.parentNode);
      item.parentNode.innerText = this.data[_key] || undefined;
     }
    }
   }
   
   item.childNodes && this.bindDom(item);
  })
 }
 
 bindInput(el){
  const _allInput = el.querySelectorAll('input');
  _allInput.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;
  // console.log(this.data);
 }
 
 setData(key,value){
  this.data[key] = value;
 }
}
10分钟快速实现数据双向绑定
在这里插入图片描述

公众号

关注公众号『前端也能这么有趣』,获取更多有趣内容。

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。


原文始发于微信公众号(前端也能这么有趣):10分钟快速实现数据双向绑定

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/243610.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!