广告
返回顶部
首页 > 资讯 > 前端开发 > 其他 >聊聊Vue中如何实现数据双向绑定
  • 341
分享到

聊聊Vue中如何实现数据双向绑定

Vuevue.js 2022-11-24 22:11:09 341人浏览 八月长安
摘要

在我们使用Vue的时候,当数据发生了改变,界面也会跟着更新,但这并不是理所当然的,我们修改数据的时候vue是如何监听数据的改变以及当数据发生改变的时候vue如何让界面刷新的?当我们修改数据的时候vue是通过es5中的Object.defin

在我们使用Vue的时候,当数据发生了改变,界面也会跟着更新,但这并不是理所当然的,我们修改数据的时候vue是如何监听数据的改变以及当数据发生改变的时候vue如何让界面刷新的?

当我们修改数据的时候vue是通过es5中的Object.defineProperty方法来监听数据的改变的,当数据发生了改变通过发布订阅模式统计订阅者界面发生了刷新,这是一种设计模式。【学习视频分享:vue视频教程web前端视频】

下图,从new Vue开始创建Vue实例,会传入el和data,data会被传入一个观察者对象,利用Object.definproperty将data里数据转化成getter/setter进行数据劫持,data里的每个属性都会创建一个Dep实例用来保存watcher实例

而el则传入compile,在compile里进行指令的解析,当解析到el中使用到data里的数据会触发我们的getter,从而将我们的watcher添加到依赖当中。当数据发生改变的时候会触发我们的setter发出依赖通知,通知watcher,watcher接受到通知后去向view发出通知,让view去更新

image.png

数据劫持

html部分创建一个id为app的div标签,里面有span和input标签,span标签使用了插值表达式,input标签使用了v-model

<div class="container" id="app">
    <span>内容:{{content}}</span>
    <input type="text" v-model="content">
</div>

js部分引入了一个vue.js文件,实现数据双向绑定的代码就写在这里面,然后创建Vue实例vm,把数据挂载到div标签上

const vm=new Vue({
    el:'#app',
        data:{
        content:'请输入开机密码'
    }
})

new了一个Vue实例很明显需要用到构造函数,在vue的源码里定义类是使用了function来定义的,这里我使用es6class来创建这个Vue实例

然后设置constructor,形参设为obj_instance,作为new一个Vue实例的时候传入的对象,并把传进来的对象里的data赋值给实例里的$data属性

javascript里对象的属性发生了变化,需要告诉我们,我们就可以把更改后的属性更新到dom节点里,因此初始化实例的时候定义一个监听函数作为调用,调用的时候传入需要监听的数据

class Vue{//创建Vue实例
  constructor(obj_instance){
    this.$data=obj_instance.data
    Observer(this.$data)
  }
}
function Observer(data_instance){//监听函数
  
}

打印一下这个实例vm

image.png

实例已经创建出来了但是还需要为$data里的每一个属性进行监听,要实现数据监听就用到了Object.definePropertyObject.defineProperty可以修改对象的现有属性,语法格式为Object.defineProperty(obj, prop, descriptor)

  • obj:要定义属性的对象
  • prop:要定义或修改的属性的名称
  • descriptor:要定义或修改的属性描述符

监听对象里的每一个属性,我们使用Object.keys和foreach遍历对象里的每一个属性并且对每一个属性使用Object.defineProperty进行数据监听

function Observer(data_instance){
  Object.keys(data_instance).forEach(key=>{
    Object.defineProperty(data_instance,key,{
      enumerable:true,//设置为true表示属性可以枚举
      configurable:true,//设置为true表示属性描述符可以被改变
      get(){},//访问该属性的时候触发,get和set函数就是数据监听的核心
      set(){},//修改该属性的时候触发
    })
  })
}

Object.defineProperty前需要将属性对应的值存起来然后在get函数里面返回出来,不然到了get函数以后属性的值已经没了,返回给属性的值就变成了undefined

let value=data_instance[key]
Object.defineProperty(data_instance,key,{
  enumerable:true,
  configurable:true,
  get(){
    console.log(key,value);
    return value
  },
  set(){}
})

点击一下$data里的属性名就会触发get函数

5.gif

然后设置set函数,为set设置形参,这个形参表示新传进来的属性值,然后将这个新的属性值赋值给变量value,不需要return返回什么,只做修改,返回是在访问get的时候返回的,修改之后get也会访问最新的value变量值

set(newValue){
    console.log(key,value,newValue);
    value = newValue
}

6.gif

但是当前只为$data的第一层属性设置了get和set,如果还有更深的一层如

obj:{
    a:'a',
    b:'b'
}

这种的并没有设置get和set,我们需要一层一层的往属性里面进行数据劫持,因此使用递归再次监听自己,并在遍历之前进行条件判断,没有子属性了或者没有检测到对象就终止递归

function Observer(data_instance){
  //递归出口
  if(!data_instance || typeof data_instance != 'object') return
  Object.keys(data_instance).forEach(key=>{
    let value=data_instance[key]
    Observer(value)//递归-子属性的劫持
    Object.defineProperty(data_instance,key,{
      enumerable:true,
      configurable:true,
      get(){
        console.log(key,value);
        return value
      },
      set(newValue){
        console.log(key,value,newValue);
        value = newValue
      }
    })
  })
}

还有一个细节,如果我们将$data的content属性从字符串改写成一个对象,这个新的对象并没有get和set

1669019095973.png

因为我们在修改的时候根本没有设置get和set,因此在set里要调用监听函数

set(newValue){
    console.log(key,value,newValue);
    value = newValue
    Observer(newValue)
}

1669019254699.png

模板解析

劫持数据后就要把Vue实例里的数据应用带页面上,得要加一个临时内存区域,将所有数据都更新后再渲染页面以此减少dom操作

创建一个解析函数,设置2个参数,一个是Vue实例里挂载的元素,另一个是Vue实例,在函数里获取获取元素保存在实例了的$el里,获取元素后放入临时内存里,需要用到[createDocumentFragment]创建一个新的空白的文档片段

然后把$el的子节点一个一个加到fragment变量里,页面已经没有内容了,内容都被临时存在fragment里了

class Vue{
  constructor(obj_instance){
    this.$data=obj_instance.data
    Observer(this.$data)
    Compile(obj_instance.el,this)
  }
}
function Compile(ele,vm){
  vm.$el=document.querySelector(ele)
  const fragment=document.createDocumentFragment()
  let child;
  while (child=vm.$el.firstChild){
    fragment.append(child)
  }
  console.log(fragment);
  console.log(fragment.childnodes);
}

image.png

现在直接把需要修改的内容应用到文档碎片里面,应用后重新渲染,只需修改了fragment的childNodes子节点的文本节点,文本节点的类型是3,可以创建一个函数并调用来修改fragment里的内容

节点里面可能还会有节点,因此判定节点类型是否为3,不是就递归调用这个解析函数

节点类型为3就进行修改操作,但也不行把整个节点的文本都修改,只需修改插值表达式的内容,因此要使用正则表达式匹配,将匹配的结果保存到变量里,匹配的结果是一个数组,而索引为1的元素才是我们需要提取出来的元素,这个元素就是去除了{{}}和空格得到的字符串,然后就可以直接用Vue实例来访问对应属性的值,修改完后return出去结束递归

function Compile(ele,vm){
  vm.$el=document.querySelector(ele) //获取元素保存在实例了的$el里
  const fragment=document.createDocumentFragment() //创建文档碎片
  let child;
  while (child=vm.$el.firstChild){//循环将子节点添加到文档碎片里
    fragment.append(child)
  }
  
  fragment_compile(fragment)
  function fragment_compile(node){ //修改文本节点内容
    const pattern = /\{\{\s*(\S*)\s*\}\}/ //检索字符串中正则表达式的匹配,用于匹配插值表达式
    if(node.nodeType===3){
      const result = pattern.exec(node.nodeValue)
      if(result){
        console.log('result[1]')
        const value=result[1].split('.').reduce(//split将对象里的属性分布在数组里,链式地进行排列;reduce进行累加,层层递进获取$data的值
          (total,current)=>total[current],vm.$data
        )
        node.nodeValue=node.nodeValue.replace(pattern,value) //replace函数将插值表达式替换成$data里的属性的值
      }
      return 
    }
    node.childNodes.forEach(child=>fragment_compile(child))
  }
  vm.$el.appendChild(fragment) //将文档碎片应用到对应的dom元素里面
}

页面的内容又出来了,插值表达式替换成了vm实例里的数据

image.png

image.png

订阅发布者模式

虽然进行了数据劫持,和将数据应用到页面上,但是数据发生变动还不能及时更新,还需要实现订阅发布者模式

首先创建一个类用来收集和通知订阅者,生成实例的时候需要有一个数组存放订阅者的信息,一个将订阅者添加到这个数组里的方法和一个通知订阅者的方法,调用这个方法就回去遍历订阅者的数组,让订阅者调用自身的update方法进行更新

class Dependency{
  constructor(){
    this.subscribers=[] //存放订阅者的信息
  }
  addSub(sub){
    this.subscribers.push(sub) //将订阅者添加到这个数组里
  }
  notify(){
    this.subscribers.forEach(sub=>sub.update()) //遍历订阅者的数组,调用自身的update函数进行更新
  }
}

设置订阅者类,需要用到Vue实例上的属性,需要Vue实例和Vue实例对应的属性和一个回调函数作为参数,将参数都赋值给实例

然后就可以创建订阅者的update函数,在函数里调用传进来的回调函数

class Watcher{
  constructor(vm,key,callback){//将参数都赋值给Watcher实例
    this.vm=vm
    this.key=key
    this.callback=callback
  }
  update(){
    this.callback() 
  }
}

替换文档碎片内容的时候需要告诉订阅者如何更新,所以订阅者实例在模板解析把节点值替换内容的时候创建,传入vm实例,exec匹配成功后的索引值1和回调函数,将替换文本的执行语句复制到回调函数里,通知订阅者更新的时候就调用这个回调函数

回调函数里的nodeValue要提前保存,不然替换的内容就不是插值表达式而是替换过的内容

1669085039395.png

然后就要想办法将订阅者存储到Dependency实例的数组里,我们可以在构造Watcher实例的时候保存实例到订阅者数组里

Dependency.temp=this //设置一个临时属性temp

将新的订阅者添加到订阅者数组里且还要将所有的订阅者都进行同样的操作,那么就可以在触发get的时候将订阅者添加到订阅者数组里,为了正确触发对应的属性get,需要用reduce方法对key进行同样的操作

32022edb035588799072d6ce8c9cf04.png

b6b1eb7db9110d36607b58ebcef28e4.png

可以看到控制台打印出了Wathcer实例,每个实例都不同,都对应不同的属性值

image.png

Dependency类还没创建实例,里面的订阅者数组是不存在的,所以要先创建实例再将订阅者添加到订阅者数组里

1669086610689.png

1669086716726.png

修改数据的时候通知订阅者来进行更新,在set里调用dependency的通知方法,通知方法就会去遍数组,订阅者执行自己的update方法进行数据更新

1669087112449.png

但是update调用回调函数缺少设定形参,依旧使用split和reduce方法获取属性值

update(){
    const value =this.key.split('.').reduce(
        (total,current)=>total[current],this.vm.$data
    )
    this.callback(value)
}

在控制台修改属性值都修改成功了,页面也自动更新了

6.gif

完成了文本的绑定就可以绑定输入框了,在vue里通过v-model进行绑定,因此要判断哪个节点有v-model,元素节点的类型是1,可以使用nodeName来匹配input元素,直接在判断文本节点下面进行新的判断

if(node.nodeType===1&&node.nodeName==='INPUT'){
    const attr=Array.from(node.attributes)
    console.log(attr);
}

节点名字nodeName为v-model,nodeValue为name,就是数据里的属性名

1669087923831.png

因此对这个数组进行遍历,匹配到了v-model根据nodeValue找到对应的属性值,把属性值赋值到节点上,同时为了在数据更新后订阅者知道更新自己,也要在INPUT节点里新增Watcher实例

attr.forEach(i=>{
    if(i.nodeName==='v-model'){
        const value=i.nodeValue.split('.').reduce(
            (total,current)=>total[current],vm.$data
        )
        node.value=value
        new Watcher(vm,i.nodeValue,newValue=>{
            node.value=newValue
        })
    }
})

修改属性值,页面也作出修改

7.gif

最后剩下用视图改变数据,在v-model的节点上使用addEventListener增加input监听事件就行了

node.addEventListener('input',e=>{
    const arr1=i.nodeValue.split('.')
    const arr2=arr1.slice(0,arr1.length - 1)
    const final=arr2.reduce(
        (total,current)=>total[current],vm.$data
    )
    final[arr1[arr1.length - 1]]=e.target.value
})

7.gif

(学习视频分享:WEB前端开发编程基础视频)

以上就是聊聊Vue中如何实现数据双向绑定的详细内容,更多请关注编程网其它相关文章!

--结束END--

本文标题: 聊聊Vue中如何实现数据双向绑定

本文链接: https://www.lsjlt.com/news/173575.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 聊聊Vue中如何实现数据双向绑定
    在我们使用vue的时候,当数据发生了改变,界面也会跟着更新,但这并不是理所当然的,我们修改数据的时候vue是如何监听数据的改变以及当数据发生改变的时候vue如何让界面刷新的?当我们修改数据的时候vue是通过es5中的Object.defin...
    99+
    2022-11-24
    Vue vue.js
  • vue如何实现数据双向绑定
    Vue.js是一个流行的前端框架,它提供了多种方式来实现数据双向绑定。数据双向绑定是Vue.js最重要的特性之一,它使得开发者能够根据数据的变化自动更新视图,同时也能根据视图的变化自动更新数据。因此,Vue.js的数据双向绑定也成为前端面试...
    99+
    2023-05-14
  • vue实现双向数据绑定
    本文实例为大家分享了vue实现双向数据绑定的具体代码,供大家参考,具体内容如下 vue中数组与对象采用了不同的绑定方式 1.vue对象数据绑定 (1)数据侦测 在js中,我们使用Ob...
    99+
    2022-11-12
  • Vue数据的双向绑定如何实现
    本篇内容介绍了“Vue数据的双向绑定如何实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!实现组件双向数据绑定我们先来看看抛弃 .sync ...
    99+
    2023-07-04
  • Vue中怎么实现数据双向绑定
    这篇文章主要介绍了Vue中怎么实现数据双向绑定的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue中怎么实现数据双向绑定文章都会有所收获,下面我们一起来看看吧。在我们使用vue的时候,当数据发生了改变,界面也会...
    99+
    2023-07-04
  • Vue 中怎么实现数据双向绑定
    本篇文章给大家分享的是有关 Vue 中怎么实现数据双向绑定,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。MVVM 架构的优点低耦合。视图(Vi...
    99+
    2022-10-19
  • vue中双向数据绑定怎么实现
    vue中双向数据绑定是通过数据劫持结合发布订阅模式的方式来实现的,其核心方法是Object.defineProperty()方法。实现双向数据绑定的示例:<!DOCTYPE html><html><h...
    99+
    2022-10-11
  • vue双向数据绑定怎么实现
    Vue的双向数据绑定是通过v-model指令来实现的。1. v-model是Vue提供的一个指令,用于在表单元素上实现双向数据绑定。...
    99+
    2023-08-09
    vue
  • vue实现简单数据双向绑定
    本文实例为大家分享了vue实现简单数据双向绑定的具体代码,供大家参考,具体内容如下 这里是简单的实现,有助于新手理解消化,当然vue要实现双向数据代理不可缺少,期待后续在更新 vue...
    99+
    2022-11-12
  • vue 数据双向绑定怎么实现
    这篇文章给大家分享的是有关vue 数据双向绑定怎么实现的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。1. 前言实现简易版的数据双向绑定2. 代码实现2.1 目的分析本文要实现的效果如下图所示:本文用到的HTML和...
    99+
    2023-06-06
  • Vue如何实现双向绑定
    这篇文章主要介绍Vue如何实现双向绑定,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!原理当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍...
    99+
    2022-10-19
  • VUE-Table中如何绑定Input通过render实现双向绑定数据
    这篇文章将为大家详细讲解有关VUE-Table中如何绑定Input通过render实现双向绑定数据,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。效果HTML的Table&...
    99+
    2022-10-19
  • 怎么在vue中实现数据双向绑定
    怎么在vue中实现数据双向绑定?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。vue是什么Vue是一套用于构建用户界面的渐进式JavaScript框架,Vue与其它大型框架的...
    99+
    2023-06-14
  • 如何使用vue代码实现数据双向绑定
    这篇文章主要介绍“如何使用vue代码实现数据双向绑定”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“如何使用vue代码实现数据双向绑定”文章能帮助大家解决问题。代码如下:* Object.d...
    99+
    2023-07-04
  • 如何实现一个vue双向绑定
    这篇文章主要讲解了“如何实现一个vue双向绑定”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何实现一个vue双向绑定”吧!开始开局一张图从图上可以看出new Vue()分为了两步走代理监听...
    99+
    2023-06-29
  • Vue数据双向绑定的实现方式讲解
    目录前言一、input和textarea二、radio和CheckBox三、select四、双向绑定的修饰符前言 在web开发应用中,很多项目都会用到表格一列的组件进行数据的传输、获...
    99+
    2022-11-13
  • Vue 3.0 中怎么实现双向绑定
    本篇文章为大家展示了Vue 3.0 中怎么实现双向绑定,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。双向绑定由两个单向绑定组成:模型 &mdash;>...
    99+
    2022-10-19
  • 如何实现JS原生数据双向绑定
    这篇文章主要介绍如何实现JS原生数据双向绑定,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!代码如下:<span  deep="7">...
    99+
    2022-10-19
  • MVVMLight怎么实现双向数据绑定
    这篇文章主要介绍“MVVMLight怎么实现双向数据绑定”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“MVVMLight怎么实现双向数据绑定”文章能帮助大家解决问题。第一步:先写一个Model,里面...
    99+
    2023-06-29
  • angular双向数据绑定怎么实现
    Angular双向数据绑定可以通过以下步骤来实现:1. 在HTML模板中使用双大括号({{}})将数据绑定到HTML元素上。例如:{...
    99+
    2023-09-21
    angular
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作