vue动态给 data 增加的成员是否是响应式数据

我们点击按钮的时候动态给 data 增加的成员是否是响应式数据(代码如下),如果不是的话,如果把新增成员设置成响应式数据,它的内部原理是什么。

let vm = new Vue({
 el: '#el'
 data: {
  o: 'object',
  dog: {}
 },
 method: {
  clickHandler () {
   // 该 name 属性是否是响应式的
   this.dog.name = 'Trump'
  }
 }
})
  • 答:不是响应式数据。响应式对象和响应式数组是指在vue初始化时期,利用Object.defineProperty()方法对其进行监听,这样在修改数据时会及时体现在页面上。
    • 设置为响应式数据有两种方法:
      • 1、给 dog 的属性 name 设置一个初始值,可以为空字符串或者 undefined 之类的,代码和原因如下:

        let vm = new Vue({
            el: '#app',
            data: {
                msg: 'object',
                dog: {
                    name: ''
                }
            },
            method: {
                clickHandler() {
                    // 该 name 属性是否是响应式的
                    this.dog.name = 'Trump'
                }
            }
        })
        
        • 原因:vm[key] setter 操作的时候会触发 data[key] 的 setter 操作,data[key] 的 setter 操作会 walk 这个新的值(walk方法是给data里的对象类型的值设置响应式),而题目中的 data 的 dog 是个空对象,没有任何属性,所以初始化 Vue 实例的时候,在给 dog 设置 proxy 的时候没有任何属性有 getter 和 setter 方法,所以在点击按钮动态的给 dog 添加 name 属性,并设置值的时候是不会触发 dog 对象下的属性 name 的 setter 方法,故不是响应式数据。而给 dog 对象添加了 name 的初始值后,dog 对象的 name 属性就有了 getter 和 setter 方法,故可以实现响应式。代码如下:
          • vue.js(监听 vm[key]的 getter 和 setter 操作 )
            class Vue {
              constructor(options) {
                  // 1、通过属性保存选项的数据
                  this.$options = options || {}
                  this.$data = options.data || {}
                  this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
                      // 2、把 data 中的成员转换成 getter 和 setter,并注入到 vue 实例中
                  this._proxyData(this.$data)
                      // 3、调用 observer 对象,监听数据的变化
                  new Observer(this.$data)
                      // 4、调用 compiler 对象,解析指令和插值表达式
                  new Compiler(this)
              }
              _proxyData(data) {
                  // 遍历 data 中的所有属性
                  Object.keys(data).forEach(key => {
                      // 把 data 的属性注入到 vue 实例中
                      Object.defineProperty(this, key, {
                          enumerable: true,
                          configurable: true,
                          get() {
                              return data[key]
                          },
                          set(newValue) {
                              if (newValue !== data[key]) {
                                  data[key] = newValue
                              }
                          }
                      })
                  })
              }
          }
          
          • observe.js (data[key] 的 setter 操作会 walk 这个新的值,walk方法是给data里的对象类型的值设置响应式)
            class Observer {
                constructor(data) {
                    this.walk(data)
                }
                walk(data) {
                    // 1、判断 data 是否是对象
                    if (!data || typeof data !== 'object') {
                        return
                    }
                    // 2、遍历 data 对象的所有属性
                    Object.keys(data).forEach(key => {
                        this.defineReactive(data, key, data[key])
                    })
                }
                defineReactive(obj, key, val) {
                    let that = this
                        // 负责收集依赖,并发送通知
                    let dep = new Dep()
                        // 如果 val 是对象,把 val 内部的属性转换成响应式数据
                    that.walk(val)
            
                    Object.defineProperty(obj, key, {
                        enumerable: true,
                        configurable: true,
                        get() {
                            // 收集依赖
                            Dep.target && dep.addSub(Dep.target)
                            return val
                        },
                        set(newValue) {
                            console.log(newValue, '>>>>>', val)
                            if (newValue === val) {
                                return
                            }
                            val = newValue
                            that.walk(newValue)
                                // 发送通知
                            dep.notify()
                        }
                    })
                }
            }
            
      • 2、使用 Vue.set(target, key, value) 时,target 为需要添加属性的对象,key 是要添加的属性名,value 为属性 key 对应的值, vue 中 set 的源码如下:

        export function set (target: Array<any> | Object, key: any, val: any): any {
          if (process.env.NODE_ENV !== 'production' &&
            (isUndef(target) || isPrimitive(target))
          ) {
            warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
          }
          if (Array.isArray(target) && isValidArrayIndex(key)) {
            target.length = Math.max(target.length, key)
            target.splice(key, 1, val)
            return val
          }
          if (key in target && !(key in Object.prototype)) {
            target[key] = val
            return val
          }
          const ob = (target: any).__ob__
          if (target._isVue || (ob && ob.vmCount)) {
            process.env.NODE_ENV !== 'production' && warn(
              'Avoid adding reactive properties to a Vue instance or its root $data ' +
              'at runtime - declare it upfront in the data option.'
            )
            return val
          }
          if (!ob) {
            target[key] = val
            return val
          }
          defineReactive(ob.value, key, val)
          ob.dep.notify()
          return val
        }
        
      • 上面源码的执行逻辑如下(参考链接:https://www.cnblogs.com/heavenYJJ/p/9559439.html):

        • 1、如果是在开发环境,且 target 未定义(为 null、undefined )或 target 为基础数据类型(string、boolean、number、symbol)时,抛出告警;
        • 2、如果 target 为数组且 key 为有效的数组 key 时,将数组的长度设置为 target.length 和 key 中的最大的那一个,然后调用数组的 splice 方法( vue 中重写的 splice 方法)添加元素;
        • 3、如果属性 key 存在于 target 对象中且 key 不是 Object.prototype 上的属性时,表明这是在修改 target 对象属性 key 的值(不管 target 对象是否是响应式的,只要 key 存在于 target 对象中,就执行这一步逻辑),此时就直接将 value 直接赋值给 target[key];
        • 4、判断 target,当 target 为 vue 实例或根数据 data 对象时,在开发环境下抛错;
        • 5、当一个数据为响应式时,vue 会给该数据添加一个 ob 属性,因此可以通过判断target对象是否存在 ob 属性来判断 target 是否是响应式数据,当 target 是非响应式数据时,我们就按照普通对象添加属性的方式来处理;当 target 对象是响应式数据时,我们将 target 的属性 key 也设置为响应式并手动触发通知其属性值的更新;
      • defineReactive(ob.value, key, val) ,将新增属性设置为响应式; ob.dep.notify() 手动触发通知该属性值的更新, 所以我们可以修改代码如下:

        let vm = new Vue({
                el: '#app',
                data: {
                    msg: 'object',
                    dog: {
                        name: undefined
                    }
                },
                method: {
                    clickHandler() {
                        // 该 name 属性是否是响应式的
                        this.$set(this.data.dog, name, 'Trump')
                    }
                }
            })
        
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值