js基础编程-题目11 数据双向绑定

整理下初学时做过的js基础编程题目和大家分享以下,如果大家觉得有用,别忘了点一下赞哦

vue响应式数据

定义一个data对象,实现对象内属性值的改变的监听操作

效果展示:抖音视频链接

这里问的是数据劫持结合发布者和订阅者模式,我直接来实现一个vue2.0数据的双向绑定。
首先 我们得有一个html

<div id="app">
    <div>
      <input type="text" v-model="name" placeholder="姓名">
      <input type="text" v-model="age" placeholder="年龄">
      <input type="text" v-model="email" placeholder="邮箱">
      <input type="text" v-model="tel" placeholder="电话">
    </div>
    <div>
      <p>姓名:<span>{{ name }}</span></p>
      <p>年龄:<span>{{ age }}</span></p>
      <p>邮箱:<span>{{ email }}</span></p>
      <p>电话:<span>{{ tel }}</span></p>
    </div>
  </div>
  <script src="./mvvm.js"></script>
  <script>
    new MVVM("#app",{
      name:"",
      age:"",
      email:"",
      tel:""
    })

然后,我们实现 MVVM 的中的 VM,我们要知道要完成

  • 监听数据源的变化(input)
  • 主动更新DOM的变化

实现过程得有
在这里插入图片描述

  1. VM:数据劫持,用Object.defineProperty实现,给每个_data中的数据都加上数据劫持,这里要注意取值器函数的使用,return obj[key]也会触发取值器,从而导致死循环,我这里采用用一个空对象劫持,返回_data数据。

  2. M:监听input变化,得给 id 为 app的标签内每个带 v-model的input都附加一个input事件,只要input.value一改变,就出触发数据劫持。

  3. V:实现主动更新DOM,主要分为两点,第一点是一开始就要根据数据更新一次,第二点是每次input的值要更新下DOM,具体思路是我要找到id为app的标签下的所有带{{}}的文本节点,然后取他们对应的内容,通过文本节点的元素父节点修改innerText,在第二点中input的变化,通过
    input的数据改变 -> 数据劫持 -> Dom变化 ,这里就不详说了,我也是和 小野森森 学的,这是他的视频链接
    小野老师视频地址

//mvvm.js
class MVVM {
class MVVM {
  constructor(el, data) {
    this.el = document.querySelector(el);//要渲染的区域
    this._data = data; //数据
    this.dompool = {};// 存储数据和dom的dom池
    this.init();
  }
  init() {
    this.initData(); //数据劫持
    this.bindInput(this.el); //找到'v-model',实现input数据的自动更新
    this.bindDom(this.el); //实现dom更新
  }
  initData() {
    const self = this;
    this.data = {};
    for (let key in this._data) {
      Object.defineProperty(self.data, key, {
        get() {
          console.log("查看数据", key, self._data[key]);
          return self._data[key];
        },
        set(newVal) {
          console.log("设置新数据", key, newVal);
          // 实现数据更新时,数据 -> 视图
          self.dompool[key].inner.innerText = newVal;
          self._data[key] = newVal;
        },
      });
    }
  }
  bindInput(el) {
    const self = this;
    const inputs = el.querySelectorAll("input");
     // 给每个带有 "v-model" 属性节点的 input 添加 input事件
    inputs.forEach((input) => {
      const Vmodel = input.getAttribute("v-model");
      if (Vmodel) {
        input.addEventListener(
          "input",
          function () {
            // 实现监听input的变化,数据的自动同步
            self.data[Vmodel] = input.value;
          },
          false
        );
        self.dompool[Vmodel] = {ipt : input}
      }
    });
  }
  bindDom(el) {//实现dom的初始化更新
    const self = this;
    const childrens = el.childNodes; // el标签下的所有节点
    const reg = /\{\{(.+)\}\}/g;
    // 筛选带有 插值语法 {{}} 的文本节点
    childrens.forEach((node) => {
      if (node.nodeType === 3 && node.nodeValue.trim().length) {
        if (reg.test(node.nodeValue)) {
          const inner = node.nodeValue.match(/\{\{(.+)\}\}/g)[0]
          const value =inner.replace(/\{|\}/g,'')
          // 把 属性名 => dom元素节点 的键值对存储
          self.dompool[value].inner = node.parentElement;
          // 实现初始化 
          // {{}}  渲染数据
          self.dompool[value].inner.innerText = self.data[value] || '未输入数据'
          // v-model 数据渲染input框
          self.dompool[value].ipt.value = self.data[value] || '未输入数据'
        }
      }
      // 递归遍历其他里层节点,其中forEach就是个终止条件
      node.childNodes && self.bindDom(node);
    });
  }
}

这里我扩展下 this指向的问题,如果不想用 self 来拿外部作用域的this,我们也可以用 bind

  • bind的用法
    注意:箭头函数是没有this的,所以用call,apply,bind方法会报错
bindInput(el){
    const inputs = el.querySelectorAll("input")
    inputs.forEach(function(input){
      let Vmodel = input.getAttribute("v-model")
      if(Vmodel){
        input.addEventListener("input",function(){
          // 实现监听input的变化,数据的自动同步
          this.data[Vmodel] = input.value
        }.bind(this),false)
      }
    }.bind(this))
  }
  • 箭头函数"更改"this指向

代替self,一种转牛角尖的写法,不推荐使用,用箭头函数"更改"Object.defineProperty的this指向,本来Object.defineProperty中的this是指向第一个参数,就是需要定义属性的那个对象。

initData(){
    const self = 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:(newValue)=>{
          console.log("设置数据",key,newValue)
          //实现数据 -> dom变化  dom的自动更新
          this.dompool[key].innerText = newValue
          this._data[key] = newValue
        }
      })
    }
  }

最后得出,不用self的版本,仅仅用来转牛角尖,不推荐使用

class MVVM{
  constructor(el,data){
    this.el = document.querySelector(el)
    this._data = data
    this.dompool = {}
    this.init()
  }
  init(){
    this.initData()
    this.bindInput(this.el)
    this.bindDom(this.el)
  }
  initData(){
    const self = 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:(newValue)=>{
          console.log("设置数据",key,newValue)
          //实现数据 -> dom变化  dom的自动更新
          this.dompool[key].innerText = newValue
          this._data[key] = newValue
        }
      })
    }
  }
  bindInput(el){
    const inputs = el.querySelectorAll("input")
    inputs.forEach(function(input){
      let Vmodel = input.getAttribute("v-model")
      if(Vmodel){
        input.addEventListener("input",function(){
          // 实现监听input的变化,数据的自动同步
          this.data[Vmodel] = input.value
        }.bind(this),false)
      }
    }.bind(this))
  }
  bindDom(el){
    const childNodes = el.childNodes
    childNodes.forEach(node=>{
      // 筛选带有 插值语法 {{}} 的文本节点
      if(node.nodeType === 3 && node.nodeValue.trim().length && /\{\{(.+)\}\}/g.test(node.nodeValue)){
          let value = node.nodeValue.replace(/\{|\}/g,"").trim()
          this.dompool[value] = node.parentElement;
          this.dompool[value].innerText = this.data[value] || "--未输入数据--"
      }
      //递归遍历其他里层节点,其中forEach就是个终止条件
      this.bindDom(node)
    })
  }
}

这里在扩展下关于DOM节点的 childNodes 和children

  • childNodes 和children的区别
    1.定义
    childNodes 返回一个包含元素的所有子节点的类数组
    children 返回一个包含元素的所有元素节点的类数组
    2.里面包含的方法
    childNodes自带entriesforEach方法
    在这里插入图片描述
    children不自带这些两个数组的方法
    在这里插入图片描述
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳晓黑胡椒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值