Vue数据响应式
- 问题: 为什么改变data中的数据,视图就能监听到并且重新渲染???这中间做了些什么???
如何追踪数据的变化
- 把一个普通的JavaScript对象传入Vue实例作为data选项,Vue将遍历此对象所有的property,并使用 Object.difineProperty 把这些属性全部转为 getter/setter 进行处理。
- getter/setter让Vue能够对data中的property 追踪依赖,在property被访问和修改时通知变更。
知识点:
那么如何通过getter/setter和Object.defineProperty实现对数据的追踪监听,被访问和修改时进行变更通知,由以下一步一步来实现
注:只是提供如何实现数据监听的 思路,并非VUE源码(以下例子都只使用属性n演示)
步骤一: 用Object.defineProperty给对象定义一个 n 属性
let data1 = {}
Object.defineProperty(data1, 'n', { value: 0 })
console.log(`步骤一: ${data1.n}`)
// 意思是给data1增加了个属性n , 值为0
步骤二: 给个要求,n的值不能小于 0
- 就是说data2.n = -1时应该是无效的,可以 data2.n = 1
let data2 = {}
// 用一个别的属性来偷偷存储 n 的值
data2._x = 0
Object.defineProperty(data2, 'n', {
get() {
return this._x
},
set(value) {
if (value < 0) return
this._x = value
}
})
console.log(`步骤二: ${data2.n}`)
data2.n = -1
console.log(`步骤二: ${data2.n} 设置为 -1 失败了`)
data2.n = 1
console.log(`步骤二: ${data2.n} 设置为 1 成功`)
步骤三
- 使用data2._x这个属性用来存储n的值,那么别人要是直接访问data2._x呢???
- 使用代理解决
let data3 = proxy({data: {n: 0}})
function proxy({data}) {
const obj = {}
Object.defineProperty(obj, 'n', {
get() {
return data.n
},
set(value) {
if (value < 0) return
data.n = value
}
})
return obj
}
// 这里很好的使用了 {data: {n: 0}} 代替了 步骤二的 data2._x , 使别人无法访问
// 所以data3就是返回的obj, 修改data3就是修改obj
console.log(`步骤三: ${data3.n}`)
data3.n = -1
console.log(`步骤三: ${data3.n}, 设置为 -1 失败`)
data3.n = 1
console.log(`步骤三: ${data3.n}, 设置为 1 成功`)
步骤四
- 那么如果有人并不按规范使用:
// 不这样子用
let data3 = proxy({data: {n: 0}})
// 非要这样子用呢
let myData = {n: 0}
let data4 = proxy({data: myData})
// 那这样子确实可以让他绕过,通过修改myData.n不受监听
console.log(`杠精: ${data4.n}`)
myData.n = -1
console.log(`杠精: ${data4.n}, 设置为 -1 居然成功了。。。`)
步骤五: 就算用户擅自修改myData, 也要拦住它
let myData5 = {n: 0}
let data5 = proxy2({data: myData5})
function proxy2({data}) {
// 这里是监听data的值,防止直接修改myData
let value = data.n
Object.defineProperty(data, 'n', {
get() {
return value
},
set(newVal) {
if (newVal < 0) return
value = newVal
}
})
// 这里是代理
let obj = {}
Object.defineProperty(obj, 'n', {
get() {
return data.n
},
set(value) {
if (value < 0) return
data.n = value
}
})
return obj
}
console.log(`步骤五 ${data5.n}`)
myData5.n = -1
console.log(`步骤五 ${data5.n}, 设置为 -1 失败了`)
myData5.n = 1
console.log(`步骤五 ${data5.n},设置为 1 成功了`)
- 步骤五就实现了对数据 n 进行监听追踪,n数据的获取和修改都能追踪到,并监听 n 不能小于 0
let data5 = proxy2({data: {n: 0}})
和创建vue实例const vm = new Vue({data: {n: 0}})
差不多- 所以vue的数据响应是对data上的属性进行追踪监控,当发现属性改变了,就触发render(data)
Object.defineProperty的问题
Object.defineProperty(data, 'n', {...})
,那么就必须先定义好 n 属性才能监听 n- 如果在vue中没有在data中定义n变量,在template上直接使用,vue就会给出警告
- Vue只会检查第一层属性
new Vue({
template: `
<div>{{obj.a}}</div>
<div>{{obj.b}}</div>
<button @click="update">设置b</button>
`,
data: {
obj: {
a: 1
}
},
methods: {
update() {
this.obj.b = 2
}
},
}).$mount('#app')
-
就比如上面的代码,点击按钮时,页面会监听到obj.b修改了并更新视图吗???
- 答案是并不会,因为一开始的时候没有定义Obj.b的这个属性,所以并没有对b这个属性进行监听追踪 =>
Object.definedProperty(obj, 'a', {....})
, 没有b属性,所以就追踪不到b属性的变化
- 答案是并不会,因为一开始的时候没有定义Obj.b的这个属性,所以并没有对b这个属性进行监听追踪 =>
-
解决方法
- this.$set 和 Vue.set()
- 把属性都提前定义好
this.$set(obj, 'b', value)
Vue.set(obj, 'b', value)
// 猜测在这个方法内部进行了Object.defineProperty(obj, 'b', {...})
数组数据
对象数据可以先把属性都提前定义好,那数组呢?难道每次修改都通过Vue.set() 或this.$set()修改么
-
尤雨溪的做法
-
篡改了数组的API
- push
- pop
- shift
- unshift
- splice
- reverse
- sort
-
这7个API被Vue篡改,调用后悔更新UI
-
-
那么是如何修改的呢
- 它在数组变量和array类之间增加了一层实例
class VueArray extends Array {
push(...args) {
const oldLength = this.length // this 就是当前数组
super.push(...args)
console.log('你执行了 push ')
for (let i = oldLength; i < this.length; i++) {
Vue.set(this, i, this[i])
}
}
}
注: 这只是思路代码
所以数组调用上面7个API就能实现数据和视图的关联
转载自:https://juejin.cn/post/7244435485174775865