首先,我们应该对于vue框架要有一个基本的认识,Vue 是一个基于MVVM设计模式的渐进式框架, Vue 在背后做了大量工作。数据和 DOM 已经被建立了关联,所有东西都是响应式的。
Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。——《Vue.js 技术揭秘》
在一个组件实例中,只有在data里初始化的数据才是响应的,Vue不能检测到对象属性的添加或删除,没有在data里声明的属性不是响应的。 Vue在视图初始化和视图更新时都会调用render 方法进行重新渲染。触发这些数据的 get 方法从而收集到本次渲染的所有依赖。而当我们在修改这些收集到依赖的数据时,会触发数据中的 set 属性方法,该方法会修改数据的值并 notify 到依赖到它的观察者,从而触发视图的重新渲染。
而我们定义在data中的数据并没有set,get的计算属性,get、set方法是如何产生的呢?这便是Vue的数据响应式的核心工作,重写数据的 get 和 set 属性方法。
官方是这样解答响应式原理:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
每个vue实例都有一个watcher实例,它会在实例渲染时记录这些属性,并在setter触发时重新渲染。所以我们想让数据成为响应式就得在data中定义,哪怕是一个空值。 Object.defineProperty实现
Vue通过设定对象属性的 setter/getter 方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。
function render () {
console.log('视图渲染了')
}
let data = {
name: '慕筱佳'
}
observe(data)
function observe (obj) {
if (!obj || typeof obj !== 'object') {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
function defineReactive (obj, key, value) {
observe(value)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get reactiveGetter () {
console.log('get', value)
return value
},
set reactiveSetter (newVal) {
observe(newVal)
if (newVal !== value) {
console.log('set', newVal)
render()
value = newVal
}
}
})
}
}
但是Vue底层在设计这个的时候,也存在一些bug,官方是这样解释的。
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。——Vue文档
为了解决这个问题,官方也提供了一些解决方案。
.对于对象: Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
Vue 通过Object.defineProperty来将对象的key转换成getter/setter的形式来追踪变化,但getter/setter只能追踪一个数据是否被修改,无法追踪新增属性和删除属性。如果是删除属性,我们可以用vm.$delete实现,那如果是新增属性,该怎么办呢?
- 可以使用 Vue.set(object, propertyName, value) 方法向对象添加响应式 property。
Vue.set( target, propertyName/index, value )
- 使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名(同上)
2.对于数组 vue对数组也进行了改变,给数组加了一层原型,在其中Vue修改了7个方法覆盖了之前数组原型的7个方法。调用这些Vue新定义的方法时,在这些新方法里Vue会加上对新添的元素的监听(相当于进行了set操作),把新数据也进行代理,这样vue就能重新监测到数组的变化了更新UI操作。 这些数组的方法被重写,当数组数据变动,可以被vue监测到。
push() pop() unshift() shift() splice() sort() reverse()
filter(), concat(), slice()。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组。
日常开发中,有些人可能习惯于原生的js,使用数组[索引]的方式来增加/删除数组,但这样vue时检测不到数组的变动的。
关于Vue的响应式原理,还有很多需要探究的,对于Vue底层做了大量的工作,也是不好理解的。这篇文章只是总结了数据如何变为响应式的基本知识,如果想深入了解vue的响应式原理,可以尝试看看vue源码。。
|