聊聊Object.defineProperty和Proxy__Vue.js
发布于 3 年前 作者 banyungong 1141 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green, qklhk-chocolate

贡献主题:https://github.com/xitu/juejin-markdown-themes

theme: juejin highlight:Object.defineproperty和Proxy

写在开头,我相信只要稍微是对vue源码有一定了解的人,都知道vue2.0是通过Object.defineProperty来实现数据的劫持的,而vue3.0换了以一种数据监听的方式proxy,毋庸置疑,proxy相比于Object.defineProperty肯定是有一定优势的,不然尤大大也不会花费力气重写,所以今天我们来聊一聊Object.definePropertyProxy

Object.defineProperty的劣势

  • 无法实现对数组的监听。 但是使用过vue2.0的人都知道,当数组发生改变的时候,视图也会发生改变,它是怎么实现的呢?下面为大家揭开它什么的面纱。最主要的原因是vue2.0它重写了Array.prototype的方法,当被监听数据的类型是数组是,改变它的原型。
function updateView() {
  console.log("视图要更新了");
}
// 数组的原型
const oldPrototype = Array.prototype;
// 创建一个原型指向Array.prototype的空对象,数组的新原型对象
const newPrototype = Object.create(Array.prototype);
// 数组应该存在方法,下面只是一部分
const arrayMethod = ["push", "pop", "shift", "unshift"];

arrayMethod.forEach((methodName) => {
  newPrototype[methodName] = function () {
    oldPrototype[methodName].call(this, ...arguments);
    updateView();
  };
});

上篇文章当中,数据劫持还存在一定的缺陷,没有对数组进行监听,现在我们既然知道了原理,接下来,看看具体的代码实现:

function observer(data) {
  if (isBaseType(data)) return data;
  // 重写数组的原型对象
  if(isArray(data)) data.__proto__ = newPrototype
  Object.keys(data).forEach((key) => {
    defineReactive(data, key, data[key]);
  });
}

虽然这样能够实现数组的监听,但是相比于proxy来说,还是比较麻烦的。

  • 实现对象的深度监听,需要一次性递归到底。对于层级比较深的数据来说,计算量比较大。 具体怎么实现对象的深度监听,可以参考我的上篇文章
  • 无法监听新增属性/删除属性(但是vue2.0提供了另外的api,分别是Vue.set和Vue.delete) 综合上述的和其他种种原因,尤大大决定更换数据监听的方式。

Proxy

proxy是es6新增的一个api,proxy在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截,所以我们在这一层拦截当中可以完成我们想完成的事情。

下面我们来看看proxy的具体用法,首先要生成一个Proxy实例,如下:

const proxy = new Proxy(target, handler);

target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。继续看下面的代码:

// 现在有个对象
const target = {
   name: 'poetries',
 };
const handler = {
   get: function(target, key, receive) {
     const result = Reflect.get(target, key,receive);
     console.log(`${key} 被读取`,result);
     return result;
   },
   set: function(target, key, value, receive) {
     const result = Reflect.set(target, key, value, receive);
     console.log(`${key} 被设置为 ${value}`);
     return result;
   },
   deleteProperty: function(target, key){
     const result = Reflect.deleteProperty(target, key);
     console.log(`${key}`被删除);
     return result;
   }
 }

从上面的代码我们可以看出,当操作完成对象的某个属性之前,我们都可以任意执行我们的业务逻辑。接下来就是利用proxy代替Object.defineProperty来实现数据劫持。

function observer(data) {
  if (isBaseType(data)) return data;
  const handler = {
     get: function(target, key, receive) {
       // 只处理自身上的属性(不包括原型)
       const ownKeys = Reflect.ownKeys(target);
       if(ownKeys.includes(key)){
          console.log(`${key},你已被监听`);
       }
       const result = Reflect.get(target, key,receive);
       console.log(`${key} 被读取`,result);
       return observer(result);
   },
   set: function(target, key, value, receive) {
     if(target[key] === value){
        // 如果前后两次的value相同,则直接跳过,不做处理
        return true;
     }
     const result = Reflect.set(target, key, value, receive);
     console.log(`${key} 被设置为 ${value}`);
     // 深度监听,和Object.defineProperty不同是,只有当使用这个对象才会去监听对象里面的属性。
     return result;
   },
   deleteProperty: function(target, key){
     const result = Reflect.deleteProperty(target, key);
     console.log(`${key}`被删除);
     return result;
   }
  }
  const observedData = new Proxy(data,handler);
  return observedData;
}

所以,proxy很好的规避了Object.defineProperty的问题。

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 明洁 原文链接:https://juejin.im/post/6933223111114358798

回到顶部