vue源码解析:vue实例方法之set方法的实现原理


还记得,我们在将vue响应式原理的时候说过,Object.defineProperty()这个方法对对象的属性方法的添加或者删除不能做到实时的监听,数组通过索引去 修改数组都是不能被检测?所以vue实现了set方法,那么实现的set方法的原理是什么呢?

vm.$set( target, propertyName/index, value )

参数:

  • {Object | Array} target
  • {string | number} propertyName/index
  • {any} value

用法:

向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性。

  • 注意:对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

源码: src/core/observer/index.js

 1 export function set (target, key, val){
 2     if (process.env.NODE_ENV !== 'production' &&
 3         (isUndef(target) || isPrimitive(target))
 4        ) {
 5         warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
 6     }
 7     if (Array.isArray(target) && isValidArrayIndex(key)) {
 8         target.length = Math.max(target.length, key)
 9         target.splice(key, 1, val)
10         return val
11     }
12     if (key in target && !(key in Object.prototype)) {
13         target[key] = val
14         return val
15     }
16     const ob = (target: any).__ob__
17     if (target._isVue || (ob && ob.vmCount)) {
18         process.env.NODE_ENV !== 'production' && warn(
19             'Avoid adding reactive properties to a Vue instance or its root $data ' +
20             'at runtime - declare it upfront in the data option.'
21         )
22         return val
23     }
24     if (!ob) {
25         target[key] = val
26         return val
27     }
28     defineReactive(ob.value, key, val)
29     ob.dep.notify()
30     return val
31 }

 

首先,判断在非生产环境,传入的target如果是undefinednull或是原始类型,则直接跑出错误。

其次,如果判断target如果是个数组,并且key是索引的话,那么就取当前数组长度与key这两者的最大值作为数组的新长度,然后使用数组的splice方法将传入的索引key对应的val值添加进数组。Array类型数据的变化侦测方式时说过,数组的splice方法已经被我们创建的拦截器重写了,也就是说,当使用splice方法向数组内添加元素时,该元素会自动被变成响应式的。

接下来,如果传入的target不是数组,那就当作是对象来处理。首先判断传入的key是否已经存在于target中,如果存在,表明这次操作不是新增属性,而是对已有的属性进行简单的修改值,那么就只修改属性值即可,接下来获取到traget__ob__属性,我们说过,该属性是否为true标志着target是否为响应式对象,接着判断如果tragte是 Vue 实例,或者是 Vue 实例的根数据对象,则抛出警告并退出程序,接着判断如果ob属性为false,那么表明target不是一个响应式对象,那么我们只需简单给它添加上新的属性,不用将新属性转化成响应式,最后,如果target是对象,并且是响应式,那么就调用defineReactive方法将新属性值添加到target上,defineReactive方会将新属性添加完之后并将其转化成响应式,最后通知依赖更新。

总的来说set的执行流程应该是这样的:

受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。要处理这种情况,我们可以使用$set()方法,既可以新增属性,又可以触发视图更新。

举例

给student对象新增age属性

data () {
  return {
      student: {
          name: '',
          sex: ''
      }
  }
}
mounted () {
  this.student.age = 24
}

可以发现可以新增属性,但是不会触发更新视图

当我们使用

mounted () {
  this.$set(this.student,"age", 24)
}

发现可以更新视图

 

举例:

<template>
   <div class="">
       <p>a: {{obj.a}}<p>
       <p>b: {{obj.b}}<p>
       <p>c: {{obj.c}}<p>
   </div>
<template>
<script>
export default {
   data () {
       return {
           obj: {
               a:0
          }
      }
  },
   created () {
       setTimeout(() => {
           this.obj.a = 1
      },200)
        setTimeout(() => {
          this.$set(this.obj, 'b', 2)
      },300)
        setTimeout(() => {
           this.obj.c = 3
      },400)
  }
}

结果:

1

2

分析原因:

vue不能检测到对象属性的添加或删除。所以属性必须在data对象上才能让vue转换它,让它响应。所以当我需要观测到这个对象的变化,目标对象没有该属性的时候,需要使用 $set。如果遇到对象赋值,视图不更新,第一种在对象里面加上我们想要渲染的属性,然后直接用this.obj.a 来修改,第二种用vue.set的方法,this.$set(this.obj, 'b', 2)来给对象加新属性和赋值

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM