Object.defineproperty和proxy

Vue 的初始化过程,分别有Observer、Compiler和Watcher,当我们 new Vue 的时候,会调用Observer,通过 Object.defineProperty 遍历 vue 对象的 data、computed 或者 props(如果是组件的话)的所有属性进行监听。同时通过Compiler解析模板指令,解析到属性后就 new 一个Watcher并绑定更新函数到 watcher 当中,Observer 和 Compiler 就通过属性来进行关联.

Object.defineProperty

来个简单的 Object.defineProperty 例子

class Observer {
        constructor(data) {
          // 遍历参数 data 的属性,给添加到 this 上
          for (let key of Object.keys(data)) {
            if (typeof data[key] === "object") {
              data[key] = new Observer(data[key]);
            }
            Object.defineProperty(this, key, {
              enumerable: true,
              configurable: true,
              get() {
                console.log("你访问了" + key);//你访问了age
                return data[key];//20
              },
              set(newVal) {
                console.log("你设置了" + key); //你设置了age
                console.log("新的" + key + "=" + newVal); //新的age=20
                if (newVal === data[key]) {
                  return;
                }
                data[key] = newVal;
              }
            });
          }
        }
      }
 const obj = {
        name: "app",
        age: "18",
        a: {
          b: 1,
          c: 2,
        }
      };
const app = new Observer(obj);
app.age = 20;
console.log(app.age);
app.newPropKey = "新属性";
console.log(app.newPropKey); //新属性

从上面可以知道:Object.defineProperty 需要遍历所有的属性,这就造成了如果 vue 对象的data/computed/props 中的数据规模庞大,那么遍历起来就会慢很多.同理,如果 vue 对象的 data/computed/props 中的数据规模庞大,那么 Object.defineProperty 需要监听所有的属性的变化,那么占用内存就会很大.

我们来看看proxy

Object.defineProperty  这个方法有缺点,并且不能实现数组和对象的部分监听情况;

最新的 Proxy,相比 vue2 的 Object.define Property,能达到速度加倍、内存减半的成效。具体是怎么实现、以及对比旧的实现方法为啥能有速度加倍、内存减半的特性

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)

可以理解为在对象之前设置一个”拦截“,当监听的对象被访问的时候,都必须经过这层拦截。可以在这拦截中对原对象处理,返回需要的数据格式,也就是无论访问对象的什么属性,之前定义的或是新增的属性,都会走到拦截中进行处理。这就解决了之前所无法监听的问题。

const obj = {
        name: "krry",
        age: 24,
        others: {
          mobile: "mi10",
          watch: "mi4",
        },
      };
      const p = new Proxy(obj, {
        get(target, key, receiver) {
          console.log("查看的属性为:" + key);  
          return Reflect.get(target, key, receiver);
        },
        set(target, key, value, receiver) {
          console.log("设置的属性为:" + key); 
          console.log("新的属性:" + key, "值为:" + value); 
          Reflect.set(target, key, value, receiver);
        },
      });
      p.age = 22;
      console.log(p.age); 
      p.single = "NO";
      console.log(p.single);
      p.others.shoe = "boost";
      console.log(p.others.shoe);
    输出结果为:
    设置的属性为:age
    新的属性:age 值为:22
    查看的属性为:age22

    设置的属性为:single
    新的属性:single 值为:NO
    查看的属性为:single
    NO 

    查看的属性为:others
    查看的属性为:others
    boost

由上可知,新增或编辑属性,并不需要重新添加响应式处理,都能监听的到,因为 Proxy 是对对象的操作,只要你访问对象,就会走到 Proxy 的逻辑中。Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的,区别Proxy 和 Object.defineProperty 的使用方法看似很相似,其实 Proxy 是在更高维度上去拦截属性的。

Object.definePropertyVue2 中,对于给定的 data:如 { count: 1 },是需要根据具体的 key 也就是 count,去对 get 和 set 进行拦截,也就是:

Object.defineProperty(data, 'count', {
  get() {},
  set() {},
})

必须预先知道要拦截的 key 是什么,这也就是为什么 Vue2 里对于对象上的新增属性无能为力,所以 Vue 初始化的过程中需要遍历 data 来维持数据变化,造成速度变慢,内存变大的原因。

Proxy 而 Vue3 所使用的 Proxy,则是这样拦截的:

new Proxy(data, {
  get(key) { },
  set(key, value) { },
})

可以看到,proxy 不需要关心具体的 key,它去拦截的是修改 data 上的任意 key和读取 data 上的任意 key

所以,不管是已有的 key 还是新增的 key,都会监听到

兼容性

Proxy 对 IE 不友好,vue3 在检测到使用 IE 的情况下(包括 IE11),会自动降级为 Object.defineProperty 的数据监听系统。

上一篇:用JS模仿VUE实现响应式对象


下一篇:Vue底层数据绑定视图的原理