vue数据劫持的实现

参照源码版本:2.5.17

怪异问题:明明数据变了,为什么视图不更新?

相信大家刚接触vue的时候经常遇到这个问题,并且百思不得其解。这种情况下建议大家可以花点时间看看源码实现,深入了解vue响应式原理的实现。
    可详见源码_init方法开始。
    对于数据劫持关键方法是observe、Observer 简化后的方法如下(去掉了其他无关逻辑)

源码部分
function observe (value) {
    // 只监听对象
  if (!isObject(value)) {
    return
  }
  return new Observer(value);
}

function Observer(value) {
  if (Array.isArray(value)) {
    this.observeArray(value)
  } else {
    this.walk(value)
  }
}

function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};

function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i]);
  }
};

// 关键部分
function defineReactive(obj, key, val) {
  val = obj[key]
  var childOb =  observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      var value =  val
      if (childOb) {
        if (Array.isArray(value)) {
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      var value = val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      val = newVal
      childOb = observe(newVal)
      // 此处省略了通知视图更新的方法
    }
  })
}

大致流程如下,初始化遍历data,如果是对象则继续watch,如果是数组,数组中存的是对象,则继续watch。当监听到数据的变化时候,会尝试重新建立observer。

测试对比案例
// 初始化tableData = []
api.then((res)=>{
//方式一
    this.tableData = res.data
    this.tableData.forEach((item)=>{
        item.playing = false
    })
    // 以上这么做,修改playing字段的值时,发现视图没有跟着变化
    // 然后调整为以下格式后,视图会跟着字段值变化
    // 方式二
    this.tableData = res.data.map((item)=>{
        return {
            ...item,
            playing:false
        }
    })
})

第一种写法:第一种写法是直接往上加属性,因为对象是引用类型,此时不会触发tableData的变化,从而无法触发视图层的变化。

但是第二种写法:当tableData第一次赋值时,会执行observe(val),监听新的对象,所以是有对playing属性进行watch,当playing变化时是会通知视图进行更新


关于数组项的监听
对于数组的某一项监听和数据变化需要使用$set 或者自己手动变更整个数组的方式去实现,见下例


arr = [1,2,3,4]
arr[0] = 99999
// 这种情况是监测不到变化, 因为数组没有去监听单项的变化。(原因估计得问尤雨溪为什么这么处理了,也许是基于性能考虑)
可以用下边的方式处理
this.$set(arr,[0],9999)
或 arr = [9999,2,3,4]


意外情况


data(){
    return {
        arr:[1,2,3,4],
        age:18
    }
}

this.arr[0] = 99999
this.age = 19
// 这种情况下,视图是可以响应到arr的变化的
// 因为age的变化触发视图的更新。视图更新时会去获取arr,发现其有变化,并调整视图




vue3.0已经解决了新增属性无法监测到的问题(基于proxy可以获取到对象属性的添加)

关于作者

it_jwejo3k6
获得点赞
文章被阅读