数据双向绑定的原理

发布时间 2023-11-16 22:30:27作者: 爱敲代码的关耳

一、双向绑定的逻辑介绍:

       双向绑定是一步步实现的,现在我们来考虑,在vue中,双向绑定的事情逻辑是什么。首先,要想实现数据双向绑定就要先实现单向绑定,也就是说,就要先实现vue里的data对象中的数据,能够替代写在template模板里的插值表达式中变量名,实现插值表达式获取数据对象的作用。

1、单项绑定——data渲染至插值表达式

       那么vue要做什么呢,vue要做的就是能够发现数据的变化,并把数据的变化通知到对应的dom元素,让对应的dom元素自己更新自己的文本内容。这短短的一句话就是data中的数据能够通过插值表达式渲染在dom元素中的原理。

       要实现上述的短短一句话,需要做哪些事情,我们可以捋一捋。首先第一点:vue能够发现数据的变化,第二点:能够通知到dom元素要更新了,第三点:dom元素接收通知自己更新自己,接下来我们来看,针对这三点如何实现。

①vue能够发现数据的变化

        vue里的数据变化,要么是你对某个数据重新赋值了,然后再对变化的数据取值,假如数据被赋值能够被某个函数发现,那么,vue也就实现了发现数据变化的功能。自然,js中存在这样一个函数——object.defineproperty(),该函数有三个参数,第一个参数是对象,第二个参数是属性,第三个是配置对象,可以给对象的某个属性加上一个配置对象,配置对象中有两个至关重要的参数,就是大家常说的getter和setter,getter会在该对象的属性值被获取时触发,setter会在该对象属性被赋值时触发。

 1 let obj={
 2         name:"xm",
 3         age:18,
 4     }
 5     Object.defineProperty(obj,"name",{
 6         enumerable:true,//允许属性被循环
 7         configurable:true,//允许属性被配置
 8         get(){
 9             console.log("我能够拦截到取obj.name的操作")
10         },
11         set(){
12             console.log("我能够拦截到给obj.name赋值的操作")
13         }
14     })

object.defineproperty()还可以给对象加上一个属性并给该属性加上配置对象

let obj={
        name:"xm",
        age:18,
    }
// 还能够给obj加一个adress属性,并给它添加对应的setter和getter
    Object.defineProperty(obj,"address",{
        enumerable:true,//允许属性被循环
        configurable:true,//允许属性被配置
        get(){
            console.log("我能够拦截到取obj.name的操作")
        },
        set(){
            console.log("我能够拦截到给obj.name赋值的操作")
        }

    })

②能够通知到dom元素要更新

        能够发现数据变化后就应该在发现数据变化后通知dom元素,如何通知呢,在vue实例创建时,数据劫持实现后,要调用一个更新数据方法,这个方法需要自定义,它需要完成以下几件事,第一,能够拿到当前文档的全部dom元素,能够拿到当前vue实例中的data数据,第二能够把dom元素的插值表达式提出来并把data对应的数据替换进去,第三能把替换完成的数据填回dom元素里,让其渲染到页面

要实现第一点:就要将vue实例对象的data和挂载点作为参数传进去,要做到第二点就要用document.querySelect()方法获取dom元素,出于性能的考虑,为了避免频繁的重绘和重排,就要将页面上的dom元素一个个添加到文档碎片中——documentfragment(),在文档碎片中完成数据匹配和替换工作,即写一个正则匹配并提取dom元素的插值表达式,把data中的数据替换进去。做到第三点就要把文档碎片中的数据取出

③dom元素接收通知自己更新自己

就是主动在setter和getter里调用封装好的自定义方法,即可完成更新。

       

vue数据双向绑定的面试回答:

主要用了两个思想来实现,即发布订阅者模式和数据劫持,数据劫持则通过object.defineproperty()方法来实现。

        首先,要实现data中的数据渲染到dom上,就要在vue实例创建时获取其控制的dom区域,将dom区域各元素的文本内容,也就是插值表达式所代表的变量替换为data中的变量,(如果是input输入框的话,则是将input的value替换为data中的变量,这里面可以做一个性能优化,为了避免重绘和重排,可以借助文档碎片documentfragm()来做一个渲染和数据替代的缓冲区,)。为了后续数据变化视图同步变化,要在vue实例创建时给其一个watcher,并将数据替换方法封装为一个cb回调函数保存到新建的watcher实例中,同时再将watcher实例交给dep也就是发布者。这样当创建一个vue实例时,页面里插值表达式或者是input输入框就已经可以正常显示data中的数据了。

       因为无法监听到数据变化,上述完成之后,只能在页面初次渲染时生效,再次刷新时仍旧做不到数据改变视图改变。要做到data数据更改,页面数据的同步变化,就要在vue实例生成时,劫持vue的data中的各项数据,同时在上一步的变量替换保存更新方法时,为了把watcher实例交给dep也就是发布者,会在watcher类调用reduce函数,reduce函数会触发被劫持数据的getter,从而在getter中,添加watcher实例到新建的dep发布者,添加完毕后,在setter中调用dep.notify方法,就可以实现数据变化,视图变化了(notify会循环其所有的watcher实例,调用watcher实例的update方法,进而触发更新dom元素内容的cb回调函数,)到此完成数据单向绑定。   

       input输入框数据变化,data数据变化的效果,则通过事件监听函数来实现。为input输入框添加一个事件监听函数,监听input事件,只要input输入有变化,就获取输入框的value值,将其赋给input绑定的数据对象,至此双向绑定结束。