Vue2 - 响应式

96 阅读3分钟

vue 的数据响应式

Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理非常简单直接。

简单来说就是当对象传入 Vue ,Vue 将遍历此对象并对其进行监听并加以改造,在此基础之上生成新的代理。每次的读写都会被Vue监控,Vue会在其变化时更新UI。

vue 如何追踪变化?

const vm = new Vue({data:{n:0}})

  • 当一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty把这些 property 全部转为 getter/setter
  • 也就是说对象一旦传入vue,vue就会对其进行监听并加以改造,在此基础之上生成新的代理。同理vue对methods和computed也有处理。这样在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时及时通知变更。

例:声明一个myData,初始值为 0 。要求当新值如果小于0则返回初始值,大于0则返回新值。利用vue的逻辑处理

let myData = {n:0}
let data2 = proxy2({data:myData});//括号里是匿名对象,无法访问。

function proxy2({data}){
  let value = data.n
  delete data.n
  //用value 存储这个n,然后把原始数据n删掉。(删除不写也行,因为下面生成虚拟对象会覆盖它)
  Object.defineProperty(data, 'n', {
    get(){
      return value
    },
    set(newValue){
      if(newValue<0) return
      value = newValue
      //如果小于0就返还不给赋值,如果大于0就将新的值赋值给data
    }
  })
  
  //以上是监听。用value记录原始的n,利用get/set得到新的虚拟属性n,不管myData是否被修改只要经过proxy2都会被监听到。
  //下面是代理,用obj代理data。
  
  let obj = {}
  Object.defineProperty(obj, 'n', {
    get(){
      return data.n
    },
    set(value){
      if(value<0) return
      data.n = value
    }
  })
  return obj
}

console.log(`结果:${data2.n}`)

myData.n = -1
console.log(`结果:${data2.n}`)

myData.n = 1
console.log(`结果:${data2.n}`)

输出结果为:

image.png

代码及效果链接:js.jirengu.com/refujeheji/…

注意事项

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

对于对象

由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的

例一:

new Vue({
  data:{},
  template:`
    <div>{{n}}</div>
  `,
}).$mount("#app")

实例没有定义 n ,Vue会给出一个警告,不会在页面进行渲染。因为无法在没有定义的情况下使用n

例二:

new Vue({
  data:{
    obj:{
      a:0 
      //obj.a会被vue监听和代理
    }
  },
  template:`
    <div>
      {{obj.b}}
      <button @click="setB">set b</button>
    </div>
  `,
  methods:{
    setB(){
      this.obj.b = 1;
    }
  }
}).$mount("#app")

//点击按钮,页面会出现 1 吗?

不会。

  • Vue检查到了obj.a,是有定义的内容,所以没有警告。但这里vue只监听到了obj.a。没有监听到obj.b。对于obj.b进行赋值变更vue是不知道的。

解决方式

  1. Vue.set(this.object, propertyName, value)
  2. this.$set(this.object,propertyName, value)
  • 两种方法作用相同,会帮助新增 key,自动创建代理和监听。同时会触发UI更新

例如

 methods:{
    setB(){
      Vue.set(this.obj, 'b', 1)
    }
  }
  
  或者
  
   methods:{
    setB(){
      this.$set(this.obj, 'b', 1)
    }
  }

对于数组

Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength
var vm = new Vue({  
  data: {  
    items: ['a', 'b', 'c']  
  }  
})  


vm.items[1] = 'x' // 不是响应性的  
vm.items.length = 2 // 不是响应性的

解决方式:

  1. Vue.set(this.object, propertyName, value)
  2. this.$set(this.object,propertyName, value)
  3. 七种数组变更方法 (这7个API在Vue中被更改,调用后会更新 UI)
方法作用
push()后面新增
pop()弹出最后一个
shift()弹出第一个
unshift()在第一个新增
splice()从中间删除某一项
sort()重新排序
reverse()倒序

这些方式会更新 UI,但不会创建监听和代理。

例如:声明一个数组,如何将["a","b","c"]变成["a","b","c",“d”]

new Vue({
  data:{
    array:["a","b","c"]
  },
  template:`
    <div>
      {{array}}
      <button @click="setD">set D</button>
    </div>
  `,
  methods:{
    setB(){
      this.$set(this.array,3, 'd')
      //Vue.set(this.array, 3, 'd')
      //this.array.push("d")
    }
  }
}).$mount("#app")

测试题

方方老师出的几道题:《Vue 自测题》中答错率最高的题的解释 - 知乎 (zhihu.com)