defineProperty 和 Proxy 对比,为什么vue3要用Proxy?

83 阅读3分钟

Proxy 代理的是整个对象,而不是对象的某个属性,故而不需要通过遍历来逐个进行数据绑定。另外使用了 proxy ,name所有的操作对象都是 proxy 返回的实例对象,而不是原对象。这点与 defineProperty 是不同的,defineProperty 是对原对象进行操作。

使用 Object.defineProperty 存在的问题有:

  • 一次只能对一个属性进行监听,需要遍历来处理监听所有属性。
  • 在遇到一个对象的属性也是一个对象时,需要递归监听
  • 对于对象的新增属性,需要手动监听
  • 对于数组通过 push、unshift 方法增加的元素,也无法监听
/**
*	新增属性,需要手动设置 getter 和 setter,且一次只能对一个属性进行监听
*/
let person = {
  age: 36,
};
const createReactive = (obj, key, val) => {
  Object.defineProperty(obj, key, {
    get() {
      console.log(`Getting value of ${key}: ${val}`);
      return val;
    },
    set(newVal) {
      console.log(`Setting value of ${key} to ${newVal}`);
      if (newVal !== val) {
        val = newVal;
      }
    },
    enumerable: true,
    configurable: true,
  });
};

createReactive(person, 'name', 'Alice');
let person = {
  name: "Older Join",
  age: 57,
}

const createReactive = obj => {
  Object.keys(obj).forEach(key => {
    reactiveImplement(obj, key, obj[key])
  })
}

const reactiveImplement = (obj,key,val) => {
  Object.defineProperty(obj, key, {
    get() {
      console.log(`初始化 属性名${key}`);
      return val;
    },
    set(newVal) {
      console.log(`设置属性${key}, 值为${newVal}`);
      val = newVal;
    }
  })
}

createReactive(person);

person.age; // 初始化 属性名age
person.name = 'Tom'; // 设置属性name, 值为Tom
let person = {
  name: 'Older Join',
  age: 57,
  children: [
    {
      name: 'Join',
      age: 37,
      children: [
        {
          name: 'little Join',
          age: 3,
        },
      ],
    },
  ],
};

const createReactive = obj => {
  if (typeof obj !== 'object' || obj === null) return;
  Object.keys(obj).forEach(key => {
    reactiveImplement(obj, key, obj[key]);
  });
};

const reactiveImplement = (obj, key, val) => {
  if (typeof val === 'object' && val !== null) {
    createReactive(val); // 递归地处理嵌套对象
  }
  Object.defineProperty(obj, key, {
    get() {
      console.log(`初始化 属性名${key}`);
      return val;
    },
    set(newVal) {
      if (typeof newVal === 'object' && newVal !== null) {
        createReactive(newVal); // 递归地处理新的对象值
      }
      console.log(`设置属性${key}, 值为${newVal}`);
      val = newVal;
    },
  });
};

createReactive(person);
// 测试嵌套对象的响应式
console.log(person.children[0].name); // 初始化 属性名name
person.children[0].name = 'John'; // 设置属性name, 值为John
console.log(person.children[0].name); // 初始化 属性名name
person.children[0].children[0].age = 4; // 设置属性age, 值为4
console.log(person.children[0].children[0].age); // 初始化 属性名age
let person = {
  name: 'John',
  age: 27,
};

const createReactive = target => {
  const handler = {
    get(target, key, receiver) {
      console.log('get', key);
      return Reflect.get(target, key, receiver);
    },

    set(target, key, value, receiver) {
      console.log('set', key, value);
      return Reflect.set(target, key, value, receiver);
    },
  };
  return new Proxy(target, handler);
};

const userInfo = createReactive(person);
userInfo.name = 'Tom'; // set name Tom
userInfo.age; // get age

下面的例子中,我们打印了嵌套对象的深层属性 age,但是 get 拦截的属性是 children

另外我们尝试 对 深层属性name赋值,结果打印的也是 get children,所以 set 拦截也没进入。

const createReactive = target => {
  const handler = {
    get(target, key, receiver) {
      console.log('get', key);
      return Reflect.get(target, key, receiver);
    },

    set(target, key, value, receiver) {
      console.log('set', key, value);
      return Reflect.set(target, key, value, receiver);
    },
  };
  return new Proxy(target, handler);
};

let person2 = {
  name: 'Older Join',
  age: 57,
  children: [
    {
      name: 'Join',
      age: 37,
      children: [
        {
          name: 'little Join',
          age: 3,
        },
      ],
    },
  ],
};

const depUserInfo = createReactive(person2);
depUserInfo.children[0].age; // get children
const createReactive = target => {
  const handler = {
    get(target, key, receiver) {
      console.log('get', key);
      // 处理 深层嵌套
      if (typeof target[key] === 'object') {
        return createReactive(target[key]);
      }
      return Reflect.get(target, key, receiver);
    },

    set(target, key, value, receiver) {
      console.log('set', key, value);
      return Reflect.set(target, key, value, receiver);
    },
  };
  return new Proxy(target, handler);
};

let person2 = {
  name: 'Older Join',
  age: 57,
  children: [
    {
      name: 'Join',
      age: 37,
      children: [
        {
          name: 'little Join',
          age: 3,
        },
      ],
    },
  ],
};

const depUserInfo = createReactive(person2);
depUserInfo.children[0].age;
depUserInfo.children[0].name = 'lucky Join';

Reactive 的懒响应性 指的是: Reactive 最初只会为复杂数据类型执行第一层的响应性。如果存在多层的复杂数据类型嵌套时,则会在使用到该子集时,才会再次为该子集包装 proxy 代理。参考上面的 depUserInfo.children[0].name = 'lucky Join';

另外,帖一下 vue3 中的相关代码:

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    const res = Reflect.get(target, key, receiver)
    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }
    return res
  }
}