• Vue笔记_01双向数据绑定原理


    [1]什么叫双向数据绑定?

    • 视图中的数据发生了变化,data中的数据也要对应改变;
    • data中的数据发生了变化,视图上的数据也要对应改变;

    [2]双向绑定原理

    vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;

    vue2.x实现双向绑定

    在vue2.x中数据双向绑定的核心是使用 Object.defineProperty(对象,key,{get(),set()})方法来给对象的属性添加getset方法实现的!

    Object.defineProperty对象(es6新增)

    常用的给对象添加/修改属性有如下几个方法

    • [1] 声明一个对象时添加属性
      const person = {
        name: 'chaochao'
      }
      
      • 1
      • 2
      • 3
    • [2] 通过点语法给对象添加属性
      const person = {}
      person.name = 'chaochao'
      
      • 1
      • 2
      const person = {}
      person['name'] = 'chaochao'
      
      • 1
      • 2

    除了以上添加属性的方式,还可以使用Object.defineProperty定义新属性或修改原有的属性

    语法
    Object.defineProperty(obj,key,{
      value: undefined,
      enumerable: false,
      writable:false,
      configurable:false, // 
      // 当获取该属性时就会调用get方法 ,返回值为获取到的值
      get(){
        return xxx
      }
      // 当给该属性进行赋值时就会调用set方法
      set(val){
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • value: 属性默认值,默认为undefiend

      Object.defineProperty(person, 'name', {
        value: 'chaochao'
      })
      console.log('name', person.name) // 'chaochao'
      
      • 1
      • 2
      • 3
      • 4
    • writable: 该属性是否可以被修改,默认值为false

      Object.defineProperty(person, 'name', {
        value: 'chaochao'
      })
      document.getElementById('btn').onclick = function(){
        person.name = 'niuniu'
        console.log('name', person.name) // 'chaochao'
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      上述示例中点击按钮修改name属性值却没有修改成功,原因是writable属性值为falae --> 不可以修改属性值

       Object.defineProperty(person, 'name', {
        value: 'chaochao'writable: true
      })
      
      • 1
      • 2
      • 3
      • 4

      此时再修改就可以成功了

    • enumerable: 该属性是否可以枚举(在循环便利时是否可以获取到),默认值为false

      const person = {
        age: 20
      }
      Object.defineProperty(person, 'name', {
        value: 'chaochao'
      })
      for(let key in person){
        console.log('key', key) // 只能遍历到age属性,遍历不到name属性
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • configurable: 该属性是否可以被删除,默认值为false

    • get/set: 是属性的拦截器

      当要读取该属性值时就会走get方法,方法的返回值就是读取的值

      当要修改该属性值时就会走set方法,可以在此判断是否修改值

      此时需要注意在get和set方法中不要直接获取该属性,否则会陷入死循环

      let person = {}
      Object.defineProperty(person, 'age', {
        get(){
          return person.age
        },
        set(val){
          person.age = val
        }
      })
      console.log('person', person.age)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      此时会报错:Maximum call stack size exceeded

      原因是当通过点语法获取age属性时就会走到get方法,而get方法中又通过点语法获取数据,就会一直循环下去

      let person = {
        _age: 18
      }
      Object.defineProperty(person, 'age', {
        get(){
          return person._age
        },
        set(val){
          person._age = val
        }
      })
      console.log('person', person.age)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      以上代码就可以正常获取了

      其实使用这个语法,可以防止用户不合理的修改,比如这个年龄,若是用户输入小于0的值就可以不修改

      set(val){
        if (val>0) {
           person._age = val
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

    tips: value + writable属性不能和 get+set属性共存

    let person = {
      _age: 18
    }
    Object.defineProperty(person, 'age', {
      value: person._age,
      get(){
        return person._age
      },
      set(val){
        person._age = val
      }
    })
    console.log('person', person.age)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    此时会报错“无效属性”
    在这里插入图片描述
    原因是: value属性与set都是写入属性,这样可能存在数据冲突,因此两者不可兼容。

    使用Object.defineProperty进行双向绑定

    需求:现在有如下代码,希望person对象存在一个age属性,属性值为number变量的值且 number值与age属性值能够实时双向绑定

    let number = 18
    let person = {
      name:'chaochao',
      sex:'女'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    实现:

    <button id="btn1">editNumber</button>
    <button id="btn2">editAge</button>
    <script>
      /*
        现在有如下代码,希望person对象存在一个age属性,属性值为number变量的值且 number值与age属性值能够实时双向绑定
        let number = 18
        let person = {
          name:'chaochao',
          sex:'女',
          age: 属性值与number变量双向绑定
        }
      */  
      let number = 18
      let person = {
        name: 'chaochao',
        sex:'女',
      }
      Object.defineProperty(person, 'age', {
        get(){
          return number
        },
        set(val){
          number = val
        }
      })
      
      document.getElementById('btn1').onclick = function(){
        number++
        console.log('number', number)
        console.log('person', person)
      }
      document.getElementById('btn2').onclick = function(){
        person.age = 25
        console.log('number', number)
        console.log('person', person)
      }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    结果:
    在这里插入图片描述

    踩坑 - 给某属性进行双向绑定

    在此示例中给data对象的name属性通过defineProperty进行 读取/赋值;

    /*
      现在有如下代码,希望person对象存在一个age属性,给age属性添加双向数据绑定
      let person = {
        name: 'chaochao',
        sex:'女',
        age: 18
      }
    */  
    let person = {
      name: 'chaochao',
      sex:'女',
      age: 18
    }
    Object.defineProperty(person, 'age', {
      get(){
        return person.age
      },
      set(val){
        person.age = val
      }
    })
    console.log('person', person)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    原因:[1]点击…时调用get方法;[2]get方法内部 return person.age 在通过点语法获取age时又调用了get方法;造成死循环。

    改正:

    <button id="btn2">editAge</button>
    <script>
    /*
      现在有如下代码,希望person对象存在一个age属性,给age属性添加双向数据绑定
      let person = {
        name: 'chaochao',
        sex:'女',
        age: 18
      }
    */  
    let person = {
      name: 'chaochao',
      sex:'女',
      age: 18
    }
    person._age = person.age
    Object.defineProperty(person, 'age', {
      get(){
        console.log('@@@get@@@')
        return person._age
      },
      set(val){
        console.log('@@@set@@@')
        person._age = val 
      }
    })
    console.log('person', person)
    document.getElementById('btn2').onclick = function(){
      person.age = 20
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    踩坑 - 给对象的所有属性进行双向绑定
      let data = {
      name: 'chaochao',
      sex:'女',
      say:''
    }
    for(let key in data){
      Object.defineProperty(data, key, {
        get () {
          return data[key] // 无限调用
        },
        set (str) {
          data[key] = str // error: Maximum call stack size exceeded
        }
    })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    改正:

    <button id="btn2">editAge</button>
    <script>
    /*
      现在有如下代码,希望person对象存在多个属性,给所有属性添加数据绑定
      let person = {
        name: 'chaochao',
        sex:'女',
        age: 18
      }
    */  
    let person = {
      name: 'chaochao',
      sex:'女',
      age: 18
    }
    Object.keys(person).forEach(key=>{
      defineProperty(person, key, person[key])
    })
    function defineProperty(obj, key, value){
      Object.defineProperty(obj, key, {
        get(){
          console.log('@@@get@@@', value)
          return value
        },
        set(val){
          console.log('@@@set@@@', val)
          value = val // 引用数据 等价于person[key] = val
        }
      })
    }
    console.log('person', person)
    document.getElementById('btn2').onclick = function(){
      person.age = 20
      console.log('person', person)
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    vue2.x实现双向绑定

    在通过new关键字实例化对象时,传入的配置项中添加了 data属性

    const vm = new Vue({
       el:'#app',
       data:{
         name:'chaochao',
         sex:'女',
         say:'hello word'
       }
     })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    vue会将data存在在实例化对象的_data属性中;
    在这里插入图片描述

    const data = {
      name:'chaochao',
      sex:'女',
      say:'hello word'
    }
    const vm = new Vue({
      el:'#app',
      data
    })
    console.log(data==vm._data) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    将data中的数据平铺在vue实例化对象身上
    在这里插入图片描述
    使用Object.defineProperty实现数据代理。
    模拟数据代理过程

    // 模拟配置项data
    const data = {
      name:'chaochao',
      sex:'女',
      say:'hello word'
    }
    
    // 模拟vue实例化对象 代码中更新的是_data中的数据,视图上更改的是vue实例化对象身上的数据
    const vm ={
      _data:data, // 存储数据
      ...data // 数据劫持-> 更新试图
    }
    for(let key in vm._data){
      Object.defineProperty(vm._data, key , {
        //  当获取属性值时返回vue实例化对象身上的对应的属性的属性值
        get(){
          return vm[key]
        },
        // 当修改属性值时-> 将vue实例化对象身上对应的属性值一起修改
        set(val){
          vm[key] = val
        }
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    vue2.x实现双向绑定缺点
    • 对象:只能给对象中已经存在的属性进行双向绑定!
      • 新增属性、删除属性,视图不会更新
    • 数组:直接通过下标修改数组,视图不会更新

    所以在vue2.x中有时会出现双向绑定失败(实例化对象中的数据改变了,但是视图上并没有重新渲染)的现象-可以使用 $set 解决

    vue3.x实现双向绑定

    在vue3.x中数据双向绑定的核心是使用new Proxy(对象,{get(),set()})方法来给对象的属性添加getset方法实现的!

    Proxy构造函数(es6新增)

    语法

    let data = {
      name:'chaochao',
      sex:'女',
      say:''
    }
    //data为源数据 p为代理数据
    const p = new Proxy(data,{
      // 拦截读取属性值  ->  当读取p身上的属性时就会触发该方法,该函数return的值就是读取到的值
      // target:源数据; prop:key值
      get (target, prop) {
        console.log('@@@读取数据了')
        return target[prop] // 默认返回的是源数据的值
      },
      // 拦截设置属性值或添加新属性 -> 当修改p身上的属性的属性值或给p添加新属性时就会触发该方法
      set (target, prop, value) {
        console.log('@@@修改数据')
        target[prop] = value // 当修改了代理数据就去修改源数据的值
      },
      // 拦截删除属性 -> 当删除p身上的属性时就会触发该方法
      deleteProperty (target, prop) {
        console.log('@@@删除数据 ')
        return delete[prop] // 当删除了代理数据的属性就删除源数据的属性并返回是否删除成功
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    Reflect对象

    可以通过Reflect对象去增删改查对象中的属性
    语法

    let data = {
      name:'chaochao',
      sex:'女',
      say:''
    }
    // 新增属性
    console.log(Reflect.set(data,'age', 18)) // true
    // 获取属性
    console.log(Reflect.get(data,'name'), Reflect.get(data,'age')) // chaochao, 18
    // 修改属性
    console.log(Reflect.set(data,'name','niuniu')) // true
    // 删除属性
    console.log(Reflect.deleteProperty(data,'sex')) // true
    // 查看对象
    console.log(data) // {name: 'niuniu', say: '', age: 18}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    优点是容错性强!

    vue3.x双向绑定
     new Proxy(data,{
        get (target, prop) {
          return Reflect.get(target,prop)
        },
        set (target, prop, value) {
          Reflect.set(target,prop,value)
        },
        deleteProperty (target, prop) {
          return Reflect.deleteProperty(target, prop)
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
    • 通过Reflect(反射): 对源对象的属性进行操作。

  • 相关阅读:
    js: less-vars-to-js将 LESS 变量转换为 JavaScript 对象
    二维数组的最小路径和问题
    Clickhouse—时间窗口函数
    SAP PO精炼图
    Linux下Qt打包
    GIS 制图:交互式地图的类型和应用
    C++中的fsanitize指令
    Java---数据库---数据库约束,设计,多表,事务
    【强化学习】03 ——马尔可夫决策过程
    SQL常见问题之如何分析周活跃率?(每周的活跃率)——步骤具体且明确
  • 原文地址:https://blog.csdn.net/qq_43260366/article/details/128053147