Vue2 Object.defineProperty 与 Vue3 Proxy 的区别及实现原理

553 阅读3分钟

Vue2 Object.defineProperty 与 Vue3 Proxy 的区别及实现原理

区别一:

Object.defineProperty 是劫持对象的属性,如果新增元素,那么Vue2需要再次调用 Object.defineProperty,而 Proxy 劫持的是整个对象,不需要做特殊处理。

Vue2里面使用的是 Object.defineProperty进行数据劫持,来对属性进行操作的时候可以获取到对应属性的 get 和 set,但是vue2这种数据劫持有个非常大的问题,比如给里面新增一些对象的时候,必须自己对这个对象再做一次 Object.defineProperty 处理,比如新增一个属性 {name, age} 属性, 后来动态给这个对象新增加一个height属性,那么这个height这个属性是不会做数据劫持的,要想做数据劫持就得再调一次 Object.defineProperty才可以,所以说对新增对象往往是无能为力的。 所以在Vue2提供了一些api 如Vue.$set(), 通过他可以再做一次Object.defineProperty劫持。

在Vue3的时候使用 Proxy 代理, Proxy它可以劫持整个对象,即使新增属性也可以帮助劫持。

区别二:

修改对象不同,Vue2使用 Object.defineProperty时,我们修改(get、 set、return) 返回的还是原来的Obj对象, Vue3 使用 Proxy的时候 返回的是(retunr new Proxy) 代理Proxy对象,修改的也是代理Proxy对象

区别三:

Proxy 能观察的类型比 Object.defineProperty 更丰富 除了 get set 还有如下:

  • has: in 操作符的捕获器;
  • deleteProperty: delete 操作符的捕获器 (delete info[name]) 等等其他操作

区别四

Proxy 作为新标准将受到浏览器厂商重点持续的性能优化:性能要比 defineProperty更高,但是Proxy是不兼容ie浏览器

Vue2响应式实现 Object.defineProperty

class Dep {
  constructor() {
    this.subscribers = new Set();
  }

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }

  notify() {
    this.subscribers.forEach(effect => {
      effect();
    })
  }
}

let activeEffect = null;
function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}

// Map({key: value}): key是一个字符串
// WeakMap({key(对象): value}): key是一个对象, 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
  // 1.根据对象(target)取出对应的Map对象
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  // 2.取出具体的dep对象
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}

// vue2对raw进行数据劫持
function reactive(raw) {
  Object.keys(raw).forEach(key => {
    const dep = getDep(raw, key);
    let value = raw[key];

    Object.defineProperty(raw, key, {
      get() {
        dep.depend();
        return value;
      },
      set(newValue) {
        if (value !== newValue) {
          value = newValue;
          dep.notify();
        }
      }
    })
  })

  return raw;
}

// 测试代码
const info = reactive({counter: 100, name: "wwww"});
const foo = reactive({height: 1.88});

// watchEffect1
watchEffect(function () {
  console.log("effect1:", info.counter * 2, info.name);
})

// watchEffect2
watchEffect(function () {
  console.log("effect2:", info.counter * info.counter);
})

// watchEffect3
watchEffect(function () {
  console.log("effect3:", info.counter + 10, info.name);
})

watchEffect(function () {
  console.log("effect4:", foo.height);
})

// info.counter++;
// info.name = "why";

foo.height = 2;

Vue3响应式实现 Proxy

class Dep {
  constructor() {
    this.subscribers = new Set();
  }

  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }

  notify() {
    this.subscribers.forEach(effect => {
      effect();
    })
  }
}

let activeEffect = null;
function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}


// Map({key: value}): key是一个字符串
// WeakMap({key(对象): value}): key是一个对象, 弱引用
const targetMap = new WeakMap();
function getDep(target, key) {
  // 1.根据对象(target)取出对应的Map对象
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  // 2.取出具体的dep对象
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}


// vue3对raw进行数据劫持
function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const dep = getDep(target, key);
      dep.depend();
      return target[key];
    },
    set(target, key, newValue) {
      const dep = getDep(target, key);
      target[key] = newValue;
      dep.notify();
    }
  })
}

// const proxy = reactive({name: "123"})
// proxy.name = "321";


// // 测试代码
// const info = reactive({counter: 100, name: "wwww"});
// const foo = reactive({height: 1.88});

// // watchEffect1
// watchEffect(function () {
//   console.log("effect1:", info.counter * 2, info.name);
// })

// // watchEffect2
// watchEffect(function () {
//   console.log("effect2:", info.counter * info.counter);
// })

// // watchEffect3
// watchEffect(function () {
//   console.log("effect3:", info.counter + 10, info.name);
// })

// watchEffect(function () {
//   console.log("effect4:", foo.height);
// })

// info.counter++;
// info.name = "hehe";

// foo.height = 2;