vue-数据劫持

196 阅读1分钟

Vue构造函数内部会进行数据初始化:获取用户传入的data,执行观察observe

function initData(vm) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
  observe(data);
}

observe:判断数据是一个对象,且不是null时,创建observer实例

function observe(data) {
  if (typeof data !== 'object' || data === null){
    return 
  }
  return new Observer(data)
}

Observer构造函数:不同数据类型的劫持方法不同

class Observer{
  constructor(data){
    Object.defineProperty(data, '__ob__', { // 在数据上添加__ob__属性,是为了在重写数据原型方法时,便于新增的数组项进行观察
      configurable: false,
      enumerable: false,
      value: this,
    })
    if (Array.isArray(data)) {
      data.__propto__ = arrayMethods; 
      this.observeArray(data);
    } else {
      this.observeObj(data);
    }
  }
}
对象:遍历对象,通过Object.defineProperty对每个属性进行劫持;如果数据的属性值是对象,或者设置的属性值是对象,会进行深度劫持
observeObj(data){
    let keys = Object.keys(data);
    for (let i = 0;i<keys.length;i++){
      let key = keys[i];
      let value = data[key];
      observe(value); // 深度劫持
      Object.defineProperty(data, key, {
        get() {
          return value
        },
        set(newValue){
          if (value === newValue) return
          observe(newValue); // 劫持设置的值
          console.log('设置data')
          value = newValue
        }
      })
    }
}

数组:重写原有方法,且观察数组项是对象的项,不劫持索引
  • 重写数组方法'push', 'shift', 'unshift', 'pop', 'reverse', 'sort', 'splice':通过Object.create创建新的数组原型arrayMethods,在arrayMethods上重写以上方法;使用时,通过__proto__指向arrayMethods(只要vm实例的数据才需要劫持,所有创建新的数组原型)
  • 新增的数组项,需要被观察;可能新增数组项的方法如 'push', 'unshift', 'splice'
let oldArrayProtoMethods = Array.prototype;
export let arrayMethods = Object.create(oldArrayProtoMethods);
const methods = ['push', 'shift', 'unshift', 'pop', 'reverse', 'sort', 'splice']

methods.forEach(method => {
  arrayMethods[method] = function(...args) {
    let r = oldArrayProtoMethods[method].apply(this, args); 
    let inserted;
    let ob = this.__ob__;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice[2]; 
        break;
      default:
        break
    }
    if (inserted) ob.observerArray(inserted) // 观察新增的内容
    console.log('调用数组的新方法')
    return r
  }
})
  • 观察数组的每一项
observerArray (value){
    for(let i = 0;i<value.length;i++){
      observe(value[i]);
    }
}