vue-数据响应式原理

146 阅读2分钟

数据响应式,在vue2.0中是通过Object.defineProperty重写get和set方法,在vue3.0中则通过proxy重写get和set;两者区别就是,过Object.defineProperty需要对数据递归实现层层代理,容易导致性能问题;proxy避免了递归,但是可能会有兼容问题。 本文主要是基于Object.defineProperty描述vue的响应式原理。

创建observe实例

在vue内部,当数据时一个对象时,会创建observe实例,对数据进行劫持

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

Observer构造函数

Observer用于对不同数据类型进行观测

class Observer{
  constructor(data){
    Object.defineProperty(data, '__ob__', {
        configurable: false,
        enumerable: false,
        value: this,
    })
    if (Array.isArray(data)) { // 如果是数组 改变原型链,观察数组每一项
        data.__propto__ = arrayMethods; 
        this.observerArray(data);
    } else { // 如果是对象 遍历每一项,重新定义
        this.walk(data);
    }
  }
  observerArray (value){
    for(let i = 0;i<value.length;i++){
      observe(value[i]);
    }
  }
  walk(data){ 
    let keys = Object.keys(data);
    for (let i = 0;i<keys.length;i++){
      let key = keys[i];
      let value = data[key];
      defineReactive(data, key, value); // 定义响应式数据
    }
  }
}

数组劫持

对改变数组本身的七个方法push|shift|unshift|pop|reverse|sort|splice进行重写

  • 只有vm实例数据中的数组才需要进行劫持,所以,基于数组原型对象复制新的对象arrayMethods,这个对象上添加需要重写的数组方法;最后通过原型链实现继承
  • 如果新增了数组项,需要对新增项进行劫持
let oldArrayProtoMethods = Array.prototype;
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
  }
})

对象劫持

  • 通过Object.defineProperty重写get和set方法对数据的取值和设置值进行拦截
  • 需要对属性值进行观察,实现层级响应式
  • 需要对设置的值进行观察
function defineReactive(data, key, value){
  observe(value);
  Object.defineProperty(data, key, {
    configurable: true,
    enumerable: true,
    get() {
      return value
    },
    set(newValue){
      if (value === newValue) return
      observe(newValue);
      console.log('设置data')
      value = newValue
    }
  })
}