Vue 最重要的部分,就是响应式原理了
data
众所周知,给 vue 实例设置数据一般是这么写的
1 | const vm = new Vue({ |
现在改写一下上述代码
1 | let rawData = { |
有什么不同呢?在第一个例子中你不能观测到 vue 对 data 返回的对象做了些什么,因为这个对象是个匿名对象
现在你发现,打印出来的 rawData 变了!它不再是个单纯的 {n:0}
,而是变成了
1 | {__ob__: _e} |
类似上述模样,n 不再是一个单纯的 0 —— 尽管你访问 rawData.n 的时候,还是可以取到 0
这是怎么回事呢??
原来 vue 在通过 data 取得实例数据之后,立即对实例数据进行了包装,将实例对象内的所有值都通过 Object.defineProperty
转化为了一组 getter/setter
,然后拦截其 set 事件,获取更新消息,及时刷新视图
新增字段
上面提到,当在视图中使用了 data 中的数据,且该数据已经设置了监听时,数据更新会触发视图更新
但是这个自动设置监听的事件,是发生在生命周期的 beforeCreate 到 created 阶段执行的,简称为初始化阶段完成了数据监听的设置
1 | const vm = new Vue({ |
如上代码,视图上只会显示字符串 x,字段 b 因为是后加入的字段,所以没有设置监听
那不是初始化阶段添加的数据,要怎么渲染呢
一般有两种办法
- 预先设置占位符
- 使用 vue 提供的设置方法
对于占位符法,非常简单,只需要先定义好 b 就行了
1 | const vm = new Vue({ |
这样就会刷新视图
但是一般不知道未来会有多少字段,所以一般用 vue 提供的设置方法 Vue.set
定义如下
1 | Vue.set( target, propertyName/index, value ) |
第一个参数是要设置的目标,第二个是字段名,第三个是字段值
上例中为 vm 这个实例设置字段 b,并将其值设置为字符串 233
通过这种方法添加的字段,自动带有监听器,更新时可以触发视图刷新
当 vue 实例是具名实例(如 vm)时,也可以使用 vm.$set
来设置字段,使用方法同 Vue.set
,因为 vm.$set
就是 Vue.set
的一个别名
数组变异方法
需要注意的是,虽然数组定义约等于对象定义,但是 Vue.set
或 vm.$set
不可以为数组内元素添加监听器
为了监听数组的变化,vue 提供了 7 个基于 Array 的变异方法
- push
- pop
- shift
- unshift
- splice
- sort
- reverse
之所以称之为”变异方法”,是因为 vue 实际上是对原方法进行了代理,除了正常调用原方法外,还会额外将数组变化通知给 vue 示例,从而在通过以上 7 个方法操作数组的时候,视图也会更新
nextTick
刚才虽然说,不通过 Vue.set
添加的属性,是没有监听器,不能渲染到页面上的
但是还是有特例
1 | const vm = new Vue({ |
本来 b 应该说是后加入且没有监听器的,所以 b 理论上不会出现在视图上
但实际情况是,字符串 a 和 b 都出现在了视图上,也就是说两句赋值都成功了
这是怎么回事呢
结合之前学习的 javascript 事件循环,原来 vue 会缓冲在一次操作中涉及的视图变化,在当前宏任务中不会渲染视图,而是在下一个事件循环开始前才渲染视图
所以此处发生的情况如下
改变了 vm.a,且 vm.a 有设置监听器,所以 vue 收到通知,将在下一个事件循环开始前渲染视图
当前事件循环还没结束,又对 vm.b 进行了赋值,所以现在 vm 上也有了数据字段 b
此时 b 上虽然没有监听器,但是监听器的作用就是触发视图渲染,而视图渲染已经被 a 触发了,所以 b 是躺赢2333
当然,此处是先改变 a,如果后改变 a,效果也是一样的,只要触发渲染,就会出现 b
当前事件循环结束,渲染视图,b 搭上了便车
所以,有时候会因为某些后添加的变量搭上了便车,而造成一种这个变量也设置了监听的错觉,会引发一些潜在的 bug
出于面试需要,了解这个搭便车就可以了,实际写码的时候建议使用 Vue.set
感谢阅读