likes
comments
collection
share

Vue数据响应式

作者站长头像
站长
· 阅读数 19
  • 问题: 为什么改变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的问题

  1. Object.defineProperty(data, 'n', {...}) ,那么就必须先定义好 n 属性才能监听 n
    • 如果在vue中没有在data中定义n变量,在template上直接使用,vue就会给出警告
  2. 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属性的变化
  • 解决方法

    • 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类之间增加了一层实例

Vue数据响应式

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就能实现数据和视图的关联