ES6 Proxy用法详解

Proxy

前不久换工作的时候面试某大厂被问到关于Proxy的问题,脑子里有点印象但是又说不出具体使用方法,主要还是自己平时积累不够,所以赶紧来恶补一下。

Metaprogramming

  • 正式开始之前,先一起来理解下什么是Metaprogramming(元编程),因为Proxy就是一种基于Metaprogramming概念的对象 ,维基百科上这样解释:

Metaprogramming is a programming technique in which computer programs have the ability to treat other programs as their data.

  • 翻译过来就是

元编程就是可以使用其他程序来作为输入数据的一种编程技术

Proxy

Proxy wraps objects and intercepts their behavior through traps

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

简单说来就是Proxy可以用来改变对象的默认操作,比如自行定义set和get等,常用的有以下这些:

  • apply
  • construct
  • defineProperty
  • deleteProperty
  • get
  • getOwnPropertyDescriptor
  • getPrototypeOf
  • has
  • isExtensible
  • ownKeys
  • preventExtensions
  • set
  • setPrototypeOf

Proxy中需要理解的三个属性:

  • target: an Object which the proxy virtualizes.(目标对象)
  • handler: a Placeholder Object which contains traps.(包含重写方法的对象)
  • trap: the Methods that provide property access of the target object.(重写的方法,比如get和set)

例子

新建一个Proxy对象

let p = new Proxy(target, handler);

新建一个employee对象然后输出其中的一些属性

const employee = {
    firstName: 'Tapas',
    lastName: 'Adhikary'
};

console.group('employee');
console.log(employee.firstName);
console.log(employee.lastName);
console.log(employee.org);
console.log(employee.fullName);
console.groupEnd()

上面的输出如下所示:

employee
Tapas
Adhikary
undefined
undefined
输出结果

接下来使用Proxy来更改一些默认行为:

  • 第一步 新建一个使用trap的handler
    以下是一个覆盖get方法的handler,当target中取不到相应的fieldName时,你可以自定义输出:
let handler = {
    get: function(target, fieldName) {        

        if(fieldName === 'fullName' ) {
            return `${target.firstName} ${target.lastName}`;
        }

        return fieldName in target ?
            target[fieldName] :
                `No such property as, '${fieldName}'!`

    }
};
  • 第二步 新建一个Proxy对象,并把之前定义的employee作为target传入
let p = new Proxy(employee, handler);
  • 第三步 输出p的属性
console.group('proxy');
console.log(p.firstName);
console.log(p.lastName);
console.log(p.org);
console.log(p.fullName);
console.groupEnd()

然后就可以看到输出结果和之前不一样了,期望结果与实际结果如下所示:

proxy
  Tapas
  Adhikary
  No such property as, 'org'!
  Tapas Adhikary
输出结果

现在,清楚Proxy的作用了吧,其实就是用来更改对象默认方法的。

下面是改变set方法来实现一个验证的例子:
新建一个handler,重新命名为validator:

const validator = {
    set: function(obj, prop, value) {
        if (prop === 'age') {
            if(!Number.isInteger(value)) {
                throw new TypeError('Age is always an Integer, Please Correct it!');
            }
            if(value < 0) {
                throw new TypeError('This is insane, a negative age?');
            }
        }
    }
};

然后再新建一个Proxy:

let pr = new Proxy(employee, validator);

然后可以尝试设置一个不合法的属性,比如:

pr.age = "test";

你将会得到如下报错:

Uncaught TypeError: Age is always an Integer, Please Correct it!
    at Object.set (<anonymous>:5:23)
    at <anonymous>:1:7
set @ VM2381:5
(anonymous) @ VM2434:1

或者:

pr.age = -1;

结果:

Uncaught TypeError: This is insane, a negative age?
    at Object.set (<anonymous>:8:23)
    at <anonymous>:1:7
set @ VM2381:8
(anonymous) @ VM2531:1

由上可见,Proxy的功能还是很强大也非常实用,常用应用场景有:

  • 保护ID字段不受删除(重写deleteProperty)
  • 取值和写值(数据绑定)(重写get,set)
  • 改变 in 操作的默认行为等

this指向

  • 需要注意的是,proxy会改变target中的this指向,一旦proxy代理了target,target内部的this则指向了proxy,而不是target。
const target = {
  get: function () {
    console.log(this === proxy);
  }
};
const handler = {};
const proxy = new Proxy(target, handler);

target.get() // false
proxy.get()  // true

而有些原生对象的内部属性,只有通过正确的this才能拿到,所以proxy也无法代理这些原生对象的属性。

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

对于以上的代码,getDate方法只能在Date对象的实例上面拿到,如果this不是Date就会报错。可以通过this绑定原始对象来解决这个问题。

const target = new Date('2019-07-11');
const handler = {
  get(target, prop) {
    if (prop === 'getDate') {
      return target.getDate.bind(target);
    }
    return Reflect.get(target, prop);
  }
};
const proxy = new Proxy(target, handler);

proxy.getDate() // 11

这里又引入了另一个ES6的新增的对象Reflect,Reflect对象就是用来获取对象中默认方法的,这个下篇文章再具体讨论。

总结

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • defineProperty() 学习书籍《ECMAScript 6 入门 》 Proxy Proxy 用于修改某...
    Bui_vlee阅读 712评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,817评论 1 32
  • let and constDestructuring 解构赋值Spread and Reset Spre...
    小宝薯阅读 1,573评论 0 13
  • Proxy 概述 Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(met...
    pauljun阅读 3,329评论 0 1
  • 泪止不住的流 思念波涛汹涌 爱岂又在朝朝暮暮 一年又一年 坚定不移的心 多了沧桑 少了盼望 日复一日 年复一年 多...
    阿月浑子i阅读 217评论 2 4

友情链接更多精彩内容