数据劫持

29 阅读3分钟
function Vue(options) {
  this._init(options)
}

Vue.prototype._init = function(options) {
  var vm = this;
  vm.$options = options
  initState(vm)
}

function initState(vm) {
  const options = vm.$options
  if (options.data) {
    initData(vm)
  }
}

function initData(vm) {
  var data = vm.$options.data
  vm._data = data = typeof data === 'function' ? data.call(vm) : data || {}
  for (const key in data) {
    // vue.title 这一步可以直接跳过vm._data[key]
    Object.defineProperty(vm, key, {
      enumerable: true,
      configurable: true,
      get() {
        return vm['_data'][key]
      },
      set(newVal) {
        vm['_data'][key] = newVal
      }
    })
  }
  // 我们需要观察这些数据
  observe(data)
}

/**
 * 我们只拦截数据和对象,如果不是对象或者null,我们就不进行观察
*/
function observe(data) {
  if (typeof data !== 'object' || data === null) {
    return
  }
  const ob = new Observer(data)
  return ob
}

class Observer {
  constructor(data) {
    this.data = data
    if (Array.isArray(data)) {
      this.ARR_METHODS = ['push', 'pop', 'shift', 'unshift', 'sort', 'splice', 'reverse']
      const arrayProto = Array.prototype
      this.arrayMethods = Object.create(arrayProto)
      // 数组的观察
      data.__proto__ = this.arrayMethods
      // 对ARR_METHODS里面的array方法进行重写
      this.rewriteArrayFunction()
      this.observeArr(data)
    } else {
      // 对象的观察
      this.walk(data)
    }
  }

  walk(data) {
    const keys = Object.keys(data)
    for(let i = 0; i < keys.length; i++) {
      var key = keys[i]
      var value = data[key]
      this.defineReactive(data, key, value)
    }
  }
  defineReactive(data, key, value) {
    // value也可能是对象,我们也需要对value进行进一步的观察
    observe(value)
    Object.defineProperty(data, key, {
      get() {
        return value
      },
      set(newVal) {
        if (newVal === value) {
          return
        }
        // 这个newVal有可能还是是一个对象,需要再次进行观察
        observe(newVal)
        newVal = value
      }
    })
  }
  rewriteArrayFunction(data) {
    // 这7种方法是会改变原数组的方法,我们需要对数组进行重写
    // 原数组发生变化,视图可能需要发生变更,所以我们需要进行数据劫持
    const _self = this
    this.ARR_METHODS.forEach((method) => {
      // 需要重新写ARR_METHODS里面的方法 例如arr.push = function() {}
      this.arrayMethods[method] = function() {
        // slice 方法用于创建一个新数组,该数组是调用该方法的数组的副本。
        var args = Array.prototype.slice.call(arguments);
        // 执行原数组方法,达到更改数据,然后做其他的事情
        const result = Array.prototype[method].apply(this, args)

        let newArr;
        switch (method) {
          case 'push':
          case 'unshift':
            newArr = args
            break
          case 'splice':
            newArr = args.slice(2)
            break
          default:
            break
        }
        // 添加响应式
        newArr && _self.observeArr(newArr)
        return result
      }
    })
  }
  observeArr(arr) {
    console.log(arr, 'arr==')
    for(let i = 0; i< arr.length; i++) {
      observe(arr[i])
    }
  }
}


class Dep {
  constructor() {
    this.subs = []
  }
  /**
   * 将订阅者添加到订阅列表中
   * 此方法用于将一个新的订阅对象添加到当前对象的订阅者列表中。它不检查订阅者是否存在,
   * 也不对添加的订阅者做任何验证。调用此方法时,应确保传入的订阅者是有效且未被订阅的。
   * 
   * @param {Object} sub 要添加的订阅者对象。这个参数应该是预定义的订阅者实例,包含了订阅所需的全部信息。
   */
  addSub(sub) {
    this.subs.push(sub)
  }
  
  /**
    * 将当前依赖添加到目标依赖列表中
    * 该方法主要用来维护依赖关系,以实现数据的响应式更新
  */
  depend() {
    Dep.target.addDep(this)
  }
  /**
   * 通知所有订阅者更新状态
   * 
   * 此方法首先创建当前订阅者列表的一个副本,以避免在迭代过程中直接修改原列表
   * 遍历这个副本列表,调用每个订阅者的update方法,通知它们状态已经改变
   * 这种设计模式有助于解耦,确保主题和订阅者之间的松散耦合
   */
  notify() {
    // 创建当前订阅者列表的一个副本,以避免在迭代过程中直接修改原列表
    const subs = this.subs.slice()
    // 遍历订阅者列表,通知每个订阅者更新状态
    for (let i = 0; i < subs.length; i++) {
      subs[i].update()
    }
  }
}