vue2响应式数据与双向数据绑定

首先,响应式数据 不等于 双向数据绑定

  1. 双向数据绑定
    通常就是指vue的内置指令v-model,实际上就是v-bindv-on的语法糖。
    <template>
     <input v-model='localValue'/>
     <!-- 等同于
         <input @input='onInput' :value='localValue' />
         -->
     <span>{{localValue}}</span>
    <template>
    <script>
     export default{
         data(){
         return {
             localValue:'',
         }
         },
         methods:{
             onInput(e){
                 //在input事件的处理函数中更新value的绑定值
                 this.localValue=e.target.value;
             }
         }
     }
     </script>
    
  2. 响应式数据
    推荐博主链接:https://juejin.cn/post/6932659815424458760 。以下是我自己学习的笔记,没有链接内容全面,但或许会帮助理解。
    • 数据劫持
      Object.defineProperty:
      Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
      此处主要是重写对象属性的set和get方法,重写了原有的行为,这也就是数据劫持的含义。

      //利用函数的闭包封装Object.defineProperty,将value当做闭包环境(小红包)
      //基本的设置setter和getter,value如果没有参数传入(读属性操作)则默认为obj[key]
      var defineReactive(obj,key,value=obj[key]){
          Object.defineProperty(obj, key, {
              get: function reactiveGetter() {
                  return value
              },
              set: function reactiveSetter(newValue) {
                  if (newValue === value) return
                  value = newValue
              }
          })
      }
      

      以上这种写法没有直接用Object.defineProperty,而是放在了一个函数里,优点是不需要设置一个全局变量value,很有可能会污染环境。

      <!--如果一个对象有多个属性需要响应式(设置setter和getter)-->
      class Observer {
          constructor(obj) {
              this.value = obj
              this.walk()
          }
          walk() {
              Object.keys(this.value).forEach((key) => defineReactive(this.value, key))
          }
      }
      const obj = { a: 1, b: 2 }
      new Observer(obj)
      

      其中,Object.keys(obj)的结果是将对象obj中可遍历的属性以字符串形式组成数组返回。
      这种写法只能为对象的最外层属性设置setter和getter,如果属性有嵌套是不能对内部属性进行设置的,优化写法如下:

      // 如果一个对象有多个属性且属性有嵌套需要响应式(设置setter和getter)
      // 入口函数
      function observe(obj) {
          //如果不是对象就直接返回,给自己添加getter和setter
          if (typeof obj !== 'object') return
          // 调用Observer,遍历对象的属性
          new Observer(obj)
      }
      // 类的作用是遍历对象的属性
      class Observer {
          constructor(obj) {
              this.value = obj
              this.walk()
          }
          walk() {
              // 遍历该对象,并进行数据劫持
              Object.keys(this.value).forEach((key) => defineReactive(this.value, key))
          }
      }
      
      function defineReactive(data, key, value = data[key]) {
          // 1.处理value的子属性:
              // 如果value是对象,递归调用observe来监测该对象
              // 如果value不是对象,observe函数会直接返回
          observe(value)
          //2.给该属性本身添加getter/setter
          Object.defineProperty(data, key, {
              get: function reactiveGetter() {
                  return value
              },
              set: function reactiveSetter(newValue) {
                  if (newValue === value) return
                  value = newValue
                  observe(newValue)
              }
          })
      }
      
      const obj = {
          a: 1,
          b: {
              c: 2
          }
      }
      observe(obj)
      

      数组响应式:
      vue底层改写了七个方法:push,pop,shift,unshift,splice,sort,reverse,这七个方法原生的定义在Array.prototype上,现在要重写(保留原有功能并且响应式),就构造一个子类继承原来的方法

      let newArrayPrototype = Object.create(Array.prototype)
      //准备好重写的内容
      ['push','pop','shift','unshift','sort','reverse','splice'].array.forEach(item => {
          newArrayPrototype[item]=function(){
              //更新视图
              console.log('视图改变了')
              //执行原本的功能
              Array.prototype[item].call(this,...arguments)
          }
      });
      
      let arr=[1,2,3,4,5]
      //使用重写的内容
      arr.__proto__=newArrayPrototype
      
    • 依赖收集
      数据state <——(订阅/依赖)watcher类
      数据state ——>(消息发布) watcher类 ——>收到数据改变信息后的操作(渲染页面之类的)

      • 依赖:用到该数据的组件是依赖(组件中有虚拟DOM和diff算法);在getter中收集依赖(谁读取该数据就会触发该数据的getter,即可以利用getter收集依赖),在setter中触发依赖
      • Dep类:依赖收集的代码封装的结果,用来管理依赖
      • watcher类:数据变化时由watcher类通知组件,可以将watcher看做依赖
      • 利用一个数组dep存放一个属性的所有依赖(watcher)
        vue1.x中依赖收集的逻辑是,在页面渲染时,遇到一个数据,就实例化一个watcher。watcher的实例化中,会调用自身类的get函数,这个get函数里需要读取这个数据,所以同样会触发这个数据本身的getter。
        `因此,可以在数据的getter中把watcher加入数据的dep数组,那么就需要在watcher类的get方法中把watcher放在windows上。
        执行顺序是: 遇到页面数据——>实例化watcher,执行到get方法,先把watcher放到windows上,再访问数据(还没执行完)——>执行数据的getter,把watcher加入dep数组——>watcher实例化完毕
    • 派发更新
      派发更新依靠的是数据的setter。当数据改变时,就会触发自身的setter,因此,在setter中遍历dep数组,触发每一个watcher的回调函数即可。

    • 代码总结:是前面描述的细化优化版本,解释在注释中。

        // 调用该方法来检测数据
        function observe(data) {
             if (typeof data !== 'object') return
             new Observer(data)
        }
      
        class Observer {
             constructor(value) {
                 this.value = value
                 this.walk()
             }
             walk() {
                 Object.keys(this.value).forEach((key) => defineReactive(this.value, key))
             }
        }
      
        // 数据拦截
        function defineReactive(data, key, value = data[key]) {
         //dep会作为闭包的一部分,因此每一个属性都会有独有的dep数组
             const dep = new Dep()
             observe(value)
             Object.defineProperty(data, key, {
                 get: function reactiveGetter() {
                     //添加依赖
                     dep.depend()
                     return value
                 },
                 set: function reactiveSetter(newValue) {
                     if (newValue === value) return
                     value = newValue
                     observe(newValue)
                     //遍历依赖并执行回调函数
                     dep.notify()
                 }
             })
        }
      
        // 将dep抽象成为一个类
        class Dep {
             constructor() {
                 this.subs = []
             }
             depend() {
                 //只有Dep.target不为空时才会添加一个新的依赖,有效控制了只有watcher实例初始化时才会添加依赖
                 if (Dep.target) {
                 this.addSub(Dep.target)
                 }
             }
             notify() {
                 const subs = [...this.subs]
                 subs.forEach((s) => s.update())
             }
             addSub(sub) {
                 this.subs.push(sub)
             }
        }
        //这是一个自定义的属性
        Dep.target = null
        const TargetStack = []
      
        function pushTarget(_target) {
             TargetStack.push(Dep.target)
             Dep.target = _target
        }
      
        function popTarget() {
             Dep.target = TargetStack.pop()
        }
      
        // watcher
        class Watcher {
             constructor(data, expression, cb) {
                 this.data = data
                 this.expression = expression
                 this.cb = cb
                 //只有watcher实例化时才会调用该类的get方法
                 this.value = this.get()
             }
      
             get() {
                 //只有调用了get方法才会给Dep.target赋值,并且将其放到全局环境中,加入TargetStack
                 pushTarget(this)
                 const value = parsePath(this.data, this.expression)
                 popTarget()
                 return value
             }
             update() {
                 const oldValue = this.value
                 this.value = parsePath(this.data, this.expression)
                 this.cb.call(this.data, this.value, oldValue)
             }
        }
      
        // 工具函数,为了根据表达式找到真正的数据
        function parsePath(obj, expression) {
             const segments = expression.split('.')
             for (let key of segments) {
                 if (!obj) return
                 obj = obj[key]
             }
             return obj
        }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值