Proxy(代理) 与 Object.defineProperty的区别

Proxy

概述

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

语法const p = new Proxy(target,handler);
参数

  • target:需要被代理的对象(任何类型的对象)
  • handler:通常以函数作为属性的对象

handler对象的方法

handler 对象是容纳一批特定属性的占位符对象。它包含有 Proxy 的各个捕获器(trap)。
捕获器的意思就是当你在对源对象进行某些操作的时候,捕获器会将你这种行为捕捉。

方法捕获器
属性读取handler.get()
属性设置handler.set()
Object.defineProperty()handler.defineProperty()
Object.getPrototypeOf()handler.getPrototypeOf()
Object.setPrototypeOf()handler.setPrototypeOf()
更多更多

示例
get 有3个参数 target(目标对象), property(被获取的属性名), receiver(Proxy 或者继承 Proxy 的对象)

const target = {//需要被代理的对象
	a:'我是a'
}
const handler = {//捕获器配置
	get:function(obj,prop){
		return prop in obj ? obj[prop]:`${prop}属性不存在`;
	}
}
const p = new Proxy(target,handler)
console.log(p.a)//我是a
console.log(p.b)//b属性不存在

注意,要使得Proxy起作用,必须针对Proxy实例(上例是p对象)进行操作,而不是针对目标对象(上例是target对象)进行操作。

如果handler没有设置任何拦截,那就等同于直接通向原对象。

const target = {};
const handler = {};
const proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

可取消的Proxy

Proxy.revocable()方法返回一个可取消的 Proxy 实例。

const target = {};
const handler = {};
const {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked

Proxy.revocable()方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

this问题

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m()  // true

递归代理

const target = {//需要被代理的对象
    a: '我是a',
    b: {
        c: {
            d: '我是d'
        }
    }
}
const handler = {//捕获器配置
    get: function (obj, prop) {
        console.log('getter:', prop)
        return prop in obj ? obj[prop] : `${prop}属性不存在`;
    }
}
const p = new Proxy(target, handler)
console.log(p.b.c.d)
/*
* getter: b
* 我是d
*/

从结果可以看出只有b属性被拦截了,也就是说只代理了上例的target对象,深层的对象并未被代理。
解决办法:当取得的属性值为引用类型的时候,再将其用Proxy代理

const target = {//需要被代理的对象
    a: '我是a',
    b: {
        c: {
            d: '我是d'
        }
    }
}
const handler = {//捕获器配置
    get: function (obj, prop) {
        console.log('getter:', prop)
        // 利用映射取值 未取到则返回 undefined
        const val = Reflect.get(obj, prop);
        // null == undefined true
        if (val != null && typeof val === 'object') {
            return new Proxy(val, handler)
        } else {
            return prop in obj ? obj[prop] : `${prop}属性不存在`;
        }

    }
}
const p = new Proxy(target, handler)
console.log(p.b.c.d)
//getter: b
//getter: c
//getter: d
//我是d

从这里可以看出,代理深层对象只需在取值的时候对其进行动态设置代理。

Object.defineProperty

Object.defineProperty() 会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
备注:应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。

概述

语法: Object.defineProperty(obj, prop, descriptor)
参数:

  • obj 要定义属性的对象。
  • prop 要定义或修改的属性的名称或 Symbol
  • descriptor 要定义或修改的属性描述符。

返回值: 被传递给函数的对象上例中的obj

该方法允许精确地添加或修改对象的属性。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改(immutable)的。

备注: 在 ES6 中,由于 Symbol 类型的特殊性,用 Symbol 类型的值来做对象的 key 与常规的定义或修改不同,而Object.defineProperty 是定义 key 为 Symbol 的属性的方法之一。

描述符

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。

数据描述符共享存取描述符
value(属性的值)configurable(属性是否可删除)get(属性的 getter 函数)
writable(value是否可改变)enumerable(是否可枚举)set(属性的 setter 函数)

描述默认值汇总:
拥有布尔值的键 configurable、enumerable 和 writable 的默认值都是 false。
属性值和函数的键 value、get 和 set 字段的默认值为 undefined。

示例

const obj = {}
Object.defineProperty(obj, 'name', {
    value: '张三'
})
console.log(obj.name);//张三
obj.name = '李四';
console.log(obj.name);//张三 未改变,默认不可写

由此可见 Object.defineProperty 一次只能代理某个属性,如想代理对象只能一次性递归对所有属性的代理。

自定义 Setters 和 Getters

下面的例子展示了如何实现一个自存档对象。当设置temperature 属性时,archive 数组会收到日志条目。

function Archiver() {
  var temperature = null;
  var archive = [];

  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

关于 Setters 和 Getters 更多示例…

总结

  • 对于兼容性,不必担心IE即将下架
  • Object.defineproperty 是对对象的属性进行劫持,所以不能操作数组
  • Proxy能劫持到属性的新增
  • 当要实现代理整个对象的时候,Proxy 只需要在需要的时候对内部引用进行代理,而Object.defineProperty 则需要先递归,影响性能
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值