直面ES6中的Proxy和Reflect,发现很简单

        ES6对于今天来说,已经不算是一个很新的概念。从2015年第一版ES6发版之后,每一年都有新的版本产生,新版本是该年正式版本的语言标准。因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等。

        还有一点就是虽然我们现在也会说ES7、ES8这种,但实际上这种说法是不严谨的,因为ES官方自2016年开始,就不再以ES+版本号这种命名了。相反,这些版本按照年份来命名,例如ECMAScript 2016和ECMAScript 2017。这种命名方式更为准确和清晰,能够直接反映出版本的发布年份。

        其中Proxy和Reflect已经是ES2015第一版中出现的了,可见已经算是年代久远了。现在才想到看它实属惭愧。这其实也跟我们日常业务开发用到不多有关,但是正所谓脑子里没有,平常怎么能想到使用呢,因此我们还是有必要直面它的。废话不多说了,开始看一下它们到底是个啥。


Proxy是什么

MDN官方对于proxy的解释:

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

阮一峰ES6入门教程中对proxy的解释:

        Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

        Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

如果看完概念还不明白的话,那么我们举个场景例子:

        有一个对象object,其中有一个age参数,我们希望age的范围始终要大于18。那么我们则需要在每次更新age的时候都去增加一个判断。先不说每次更新都这样判断是否麻烦,但凡有一个地方没加判断,age就有可能设置成小于18的数了。

        因此我们就可以给object增加一个代理,让它在我们每次更新object中age的时候,对其进行一个判断,这样我们就只需要关注业务逻辑的更新,而不需要再去增加一些边界条件的判断,使得代码逻辑更清晰更灵活,相信这也是proxy设计的初衷。

        知道了概念之后,我们还需要明白,它能对哪些操作进行拦截呢,又是怎么拦截的呢?

Proxy能对哪些操作拦截

我们日常接触较多的操作

        1、对一个对象属性的修改。例如 object.age = 18 或 object['age'] = 18

        2、获取一个对象的属性。例如object.age 或 object['age']

        3、删除一个对象的属性。例如 delete object.age 或 delete object['age']

        4、查看一个对象中是否存在某个属性。例如 'age' in object

        5、获取对象自身所有属性。例如 Object.keys(obj) 或Object.getOwnPropertyNames(obj)

        6、对一个函数进行调用。例如 fn()

        7、new一个构造函数。例如 let obj = new Object()

还有一些日常接触不多的操作

        8、给对象定义或修改某个属性的描述。如下所示:

let obj = {}
Object.defineProperty(obj, 'age', {  
  value: 18,  
  writable: true,  
  enumerable: true,  
  configurable: true
});  

// 或者defineProperties。
Object.defineProperties(obj, {  
  'property1': {  
    value: true,  
    writable: true  
  },  
  'property2': {  
    value: 'Hello',  
    writable: false  
  }  
  // ... 可以定义更多属性  
});

题外话:

        defineProperty和defineProperties这二者区别就是前者一次只能对一个属性操作,后者一次则能操作多个属性。

        一个属性的数据描述符有四个

                 value(属性值)、

                writable(是否可写)、

                enumerable(是否可枚举)、

                configurable(是否可配置)。

        value没写或者说未明确指定的话,为undefined。其余三个未明确指定的话默认为false


        属性描述符除了上述数据描述符之外,还有存取描述符

                get(): 访问属性时会调用此函数、

                set():修改该属性时会调用此函数、

                enumerable(是否可枚举)、

                configurable(是否可配置)

        数据描述符和存取描述符是互斥的,不能同时在一个属性上设置两者,否则会抛出异常

        9、得到某个对象中某个属性的具体描述。如下所示:

const object = { age: 18 };
Object.getOwnPropertyDescriptor(object, 'age');
// {value: 1, writable: true, enumerable: true, configurable: true}

        10、设置一个对象不可扩展。如下所示:

let myObject = {};
Object.preventExtensions(myObject); // 将myObject设置过不可扩展
myObject.name = '张三';   // 无效
console.log(myObject) // {}

        11、判断一个对象是否可扩展。如下所示:

const myObject = {};
Object.isExtensible(myObject) // true

        12、设置一个目标对象的原型。如下所示:

const myObj = {};
Object.setPrototypeOf(myObj, Array.prototype);

        13、获取一个对象的原型。如下所示:

let p = {};
Object.getPrototypeOf(p)
// 或者
p.__proto__;
// 或者
p instanceof Object;

        以上所述的13种操作,都可以通过Proxy进行拦截。

Proxy怎么拦截呢

        我们知道了proxy能够对哪些操作进行拦截后,然后再来看一下如何通过proxy对这些操作进行拦截。

        ES6原生提供了一个Proxy构造函数,用来生成Proxy实例。

let proxy = new Proxy(target, handler);

        所有拦截方式都是通过上面这种形式来操作的。Proxy构造函数接收两个参数,target表示要拦截的目标对象,handler参数也是一个对象,通过在handler中定义不同方法来进行不同拦截。

        假设我们需要对一个获取属性的操作进行拦截。则可以在handle对象中定义一个get方法,get方法有三个参数,依次为目标对象、属性名、proxy实例本身(可选)。如下所示:

let obj = { age: 20 };
let handle = {
    get(target, propKey, receiver) {
        return 18;
        // return target[propKey];
    }
}
let proxyObj = new Proxy(obj, handle);
console.log(proxyObj.name); // 18
console.log(proxyObj.sex); // 18
console.log(proxyObj.age); // 18

//----------------------------------------------------------------
// 我们可以将handle对象,直接写到Proxy第二个参数里,更简洁一些
let proxyObj = new Proxy(obj, {
    get(target, propKey, receiver) {
        return 18;
        // return target[propKey];
    }
});

        上述代码,我们通过set方法拦截到了获取属性的操作,并且返回18,这时候我们不管获取什么属性,都会给我们返回18。注意,要想代理起作用,我们必须操作Proxy实例,也就是上述例子中的proxyObj,而不是针对目标对象。

       如果我们需要返回实际属性值的话,只需要return target[propKey]即可。

        其余操作也都有相对应的方法,同理,只需要在proxy的第二个参数里添加相应方法,即可对对象或函数的某种操作进行拦截,这里不再演示。

Proxy所有拦截操作的方法名

        1、set(target, propKey, value, receiver):拦截目标对象的修改。方法有四个参数,依次为所拦截目标对象、待设置的属性名、待设置的属性值、一般为proxy实例。

        2、get(target, propKey, receiver):拦截目标对象属性的读取。方法有三个参数,依次为所拦截目标对象、待获取的属性名、proxy实例。

        3、deleteProperty(target, propKey):拦截目标对象属性的删除。方法有两个参数,依次为所拦截目标对象、待删除的属性名。

        4、has(target, propKey):拦截检查目标对象中是否存在某个属性的操作。方法有两个参数,依次为拦截目标,待检查的属性名。

        5、ownKeys(target):拦截获取目标对象所有属性的操作。方法有一个参数,就是拦截目标。

        6、apply(target, object, args):拦截对目标函数的调用。方法有三个参数,依次为所拦截目标函数,目标的上下文对象(this)、被调用函数的参数数组。

        7、construct(target, args, newTarget):拦截对目标构造函数的new操作。方法有三个参数,依次为目标构造函数、构造函数的参数数组、一般为proxy实例。

        8、defineProperty(target, propKey, propDesc):拦截对目标对象属性描述的操作。方法有三个参数,依次为目标对象,目标对象的属性名、待定义或修改的属性的描述符。

        9、getOwnPropertyDescriptor(target, propKey):拦截获取目标对象属性描述符的操作。方法有两个参数,依次为目标对象、目标对象属性名的描述。

        10、preventExtensions(target):拦截设置对象不可扩展的操作。方法有一个参数,就是目标对象。

        11、isExtensible(target):拦截判断对象是否可扩展的操作。方法就一个参数,就是目标对象。

        12、setPrototypeOf(target, proto):拦截设置目标对象原型的操作。方法有两个参数,依次为目标对象,待设置的新原型或null。

        13、getPrototypeOf(target):拦截获取目标对象原型的操作。方法有一个参数,就是目标对象。

Reflect是什么

        Reflect是一个对象,其中目前有13个方法。包含Object对象中对对象操作的一些方法,以及将一些对象操作的命令变为函数方法(例如 'age' in obj 等同于 Reflect中的has(obj, 'age')方法、还有delete obj[age] 等同于 Relect中的deleteProperty(obj, 'age')方法)

        之所以将Object中的一些方法和操作单独放到一个新的Reflect对象中,有以下几个目的:

        1、一些对对象的操作明显是语言内部的方法,放在Object上可能会有点怪怪的,放在Reflect上会更清晰,无歧义。

        2、优化某些操作,让方法更健壮,结果更合理。

        3、将部分操作变为函数行为,例如上文所讲的in、或delete命令。

        4、未来如果有对象操作的新方法,会只部署在Reflect对象上。

        5、此外相信大家能够发现,Reflect上有13种方法,Rroxy也有13种方法。没错,它们是一一对应的,包括参数(参考上文proxy所有拦截操作的方法名)。因此用Reflect来作为Proxy拦截的默认操作简直太爽太配套。如下所示。

let obj = {};
var proxyObj = new Proxy(obj, {
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    // 当我们不希望影响该操作的结果时,我们只需要使用Reflect中相对于的方法进行默认操作即可
    return Reflect.set(target, propKey, value, receiver);
  },
  get(target, name) {
    console.log('get', target, name);
    // return target[name];    // bad
    return Reflect.get(target, name); // good
  },
  deleteProperty(target, name) {
    console.log('delete' + name);
    return Reflect.deleteProperty(target, name);
  },
  has(target, name) {
    console.log('has' + name);
    return Reflect.has(target, name);
  }

  //...

});

        最后也建议大家在日常开发过程中用到以上13种操作之一的话,最好使用Reflect中的方法,更好一些。

参考链接:

ES6 入门教程

Proxy - JavaScript | MDN

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值