说一下v-model的理解双向绑定 vue响应式原理

1.vue响应式原理 和proxy的对比

参考: 1. vue2 里的响应式其实有点像是一个半完全体,对于对象上新增的属性无能为力,对于数组则需要拦截他的原型方法来实现响应式. 2. 这个时候Vue提供了一个api :this.$set来实现新增的属性也拥有响应式的效果.
问题:但是很多时候需要小心翼翼的去判断到底什么情况下需要用 $set,什么时候可以直接触发响应式。
在 Vue3 中,这些都将成为过去。proxy 到底会给 Vue3 带来怎么样的便利。
例子1.

let vm = new Vue({
    
    
 data() {
    
    
 return {
    
    
 a: 1
 }}})
// ❌ oops,没反应!
vm.b = 2 
let vm = new Vue({
    
    
 data() {
    
    
 return {
    
    
 a: 1
 }
 },
 watch: {
    
    
 b() {
    
    
 console.log('change !!')
 }}})
 
// ❌ oops,没反应!
vm.b = 2

1.3 vue2 响应式原理

例1.利用input实现数据双向绑定

 理解:
    发布者
    订阅者
    观察者
 <!-- 发布者 -->
  <input type="text">
  <!-- 订阅者 -->
  <div class="demo"></div>

  <!-- 观察者 Object.defineProperty(目标对象,目标对象中的属性,{
    
    6个属性:set,get,value,枚举,可选值,是否可写}) -->
  <script>
    /* 观察者 */
    // let target = {} //目标对象
    // Object.defineProperty(target, 'msg', {
    
    
    //   get () { },
    //   set (value) {
    
    
    //     console.log(value, 5);
    //     demo.innerHTML = value
    //     ipt.value = value
    //   }
    // })

    // let ipt = document.querySelector('input')
    // let demo = document.querySelector('.demo')
    // ipt.addEventListener('input', (e) => {
    
    
    //   console.log(e.target.value, 99);
    //   target.msg = e.target.value
    // })

双向绑定

1.什么是双向绑定

单向绑定: javascript代码更新model时,view就会自动跟新双向绑定
在单向绑定的基础上,用户更新了View,Model的数据也自动被更新了

2.双向绑定的原理

Vue 是数据双向绑定的框架,双向绑定由三个重要部分构成

a.双向绑定由三个重要部分构成

数据层(Model):应用的数据及业务逻辑
视图层(View):应用的展示效果,各类UI组件
业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来

所以MVVM这里的控制层的核心功能便是 “数据双向绑定”

b.理解:业务逻辑层(ViewModel)
数据变化后更新视图
视图更新后更新数据
主要组成部分:
监听器(Observer):对所有数据的属性进行监听
解析器:(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

实现原理:
对象类型:通过Objeact.defineProperty()对属性的 setter getter在数据变动时放送给订阅者,触发相应的监听回调读取、修改进行拦截(数据劫持)
数组类型:通过重写更新数组的一系类方法拦截(对数组的变更方法进行包裹)
存在问题:
a.当对象中属性过多时Objeact.dfineProperty()需求对每一个属性进行遍历实现响应式,效率不高.
b.新增属性,删除属性,界面不会刷新;
c.只有configurable为true时候,该属性才能从对应的对象上被删除,单元数据不会响应式删除.
d.直接通过下标修改数组,界面不会自动更新.

**实现步骤:
a.第一步需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
b.第二步 compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
c.第三步 Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往属性订阅器(dep)里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的 update() 方法,并触发Compile中绑定的回调,则功成身退。
d.第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
**

例1:
在源数据上 添加数值 属性

<script>
        // 源数据
        let person = {
    
    
            name: '张三',
            age: 18
        }
        Proxy和Reflect是window上内置的函数
        let p = new Proxy(person, {
    
    
            get(target, propName) {
    
    
                console.log(`有人读取了p身上的${
      
      propName}属性`)
                return Reflect.get(target, propName)
            },
            set(target, propName, value) {
    
    
                console.log(`有人新增/修改了p身上的${
      
      propName}属性,我要去更新界面了!`)
                Reflect.set(target, propName, value)
            },
            deleteProperty(target, propName) {
    
    
                console.log(`有人删除了p身上的${
      
      propName}属性,我要去更新界面了!`)
                return Reflect.deleteProperty(target, prop)
            }
        })
    </script>

1.2 vue3 proxy响应式原理

实现原理:
通过Proxy(代理):拦截对象中任意属性变化,包括:属性值的读写,属性的添加,属性的删除等.
通过Reflect(反射):对源对象的属性进行操作.
MDN文档中描述的Proxy与Reflect:

Proxy   Reflect

例2:

 /* Proxy */
    let proxy = new Proxy({
    
    }, {
    
    
      // set (目标对象,key,value) {
    
    
      set (target, key, value) {
    
    
        console.log(target, key, value, 888);
        demo.innerHTML = value
        ipt.value = value
      }, // setter
      get () {
    
     } // getter  
    })

    let ipt = document.querySelector('input')
    let demo = document.querySelector('.demo')
    ipt.addEventListener('input', (e) => {
    
    
      console.log(e.target.value, 99);
      proxy.msg = e.target.value
    })

Proxy:

//语法:let p = new Proxy(target, handler)
target:要使用 Proxy 包装的目标对象,此处为person源数据;
handler:一个对象。可以只传一个空对象,也能实现增删改查操作let p = new Proxy(person, {
    
    });可以向上述代码一样传入含有的get set deleteproperty函数来拦截对象中任意属性的变化;

在这里插入图片描述

set函数 get函数 deleteProperty函数 用法

//get函数:

get(target, propName) {
    
    
    // target:目标对象,也就是person源数据;
    // propName:被获取的属性名;
    console.log(`target, propName`, target, propName)
    return target[propName]
},
 //set函数:

// set函数对新增或修改一个属性都可以拦截到
set(target, propName, value) {
    
    
   // value:新属性值
   console.log(`target, propName,value`, target, propName,value)
   target[propName] = value
}
 
 //deleteProperty函数:

deleteProperty(target, propName) {
    
    
  console.log(`有人删除了p身上的${
      
      propName}属性,我要去更新界面了!`)
  return delete target[propName]
}

Reflect:

Reflect函数身上的一些方法与Object相同,比如:

Reflect.get(target,propName)相当于get函数中直接target[propName];
Reflect.set(target,propName,value)相当于set函数中target[propName] = value;
Reflect.deleteProperty(target,propName)相当于deleteProperty函数中的delete target[propName]
当然,Reflect函数身上还有Reflect.defineProperty方法,与Object.defineProperty有所不同:

Object.defineProperty对同一个对象同一个属性重复操作时,系统会报错代码运行不下去,但Reflect.defineProperty不会报错只会运行第一条结果并继续执行后边代码;

Object.defineProperty:

let obj = {
    
     a: 1, b: 2 }
        Object.defineProperty(obj, 'c', {
    
    
            get() {
    
    
                return 3
            }
        })
        Object.defineProperty(obj, 'c', {
    
    
            get() {
    
    
                return 4
            }
        })

Reflect.defineProperty:

let obj = {
    
     a: 1, b: 2 }
        const x1 = Reflect.defineProperty(obj, 'c', {
    
    
            get() {
    
    
                return 3
            }
        })
        console.log('x1',x1)
 
        const x2 = Reflect.defineProperty(obj, 'c', {
    
    
            get() {
    
    
                return 4
            }
        })
        console.log('obj')  

注:一般组件封装用Reflect函数比较多,宽容度好,组件更健壮

猜你喜欢

转载自blog.csdn.net/qq_43944285/article/details/125141220