前端学习笔记--ES6Proxy

352 阅读4分钟

Proxy

一、概述

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

Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

代理对象构造函数:

var proxy = new Proxy(target, handler)

target是目标对象,handler定义拦截行为。

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

注意:如果handler是空对象,则相当于直接操作目标对象

var obj = {};
var proxy = new Proxy(obj,{});
proxy.name=1;
obj.name // 1 ,此时在obj对象上有了name
 var o1= {}
        var proxy2 = new Proxy(o1, {
            get:function(target,prop) {
                console.log("get")
            },
            set:function() {
                console.log("set")
            }
        })
        proxy2.age = 1// set
        console.log(o1.age// undefined
        // 定义了handler非空对象,则不会直接操作目标对象。实行了拦截。

对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

下面是 Proxy 支持的拦截操作一览,一共 13 种。

  • 「get(target, propKey, receiver)」:拦截对象属性的读取,比如 proxy.foo 和 proxy['foo'] 。
  • 「set(target, propKey, value, receiver)」:拦截对象属性的设置,比如 proxy.foo = v 或 proxy['foo'] = v ,返回一个布尔值。
  • 「has(target, propKey)」:拦截 propKey in proxy 的操作,返回一个布尔值。
  • 「deleteProperty(target, propKey)」:拦截 delete proxy[propKey] 的操作,返回一个布尔值。
  • 「ownKeys(target)」:拦截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、 Object.keys(proxy) 、 for...in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。
  • 「getOwnPropertyDescriptor(target, propKey)」:拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。
  • 「defineProperty(target, propKey, propDesc)」:拦截 Object.defineProperty(proxy, propKey, propDesc) 、 Object.defineProperties(proxy, propDescs) ,返回一个布尔值。
  • 「preventExtensions(target)」:拦截 Object.preventExtensions(proxy) ,返回一个布尔值。
  • 「getPrototypeOf(target)」:拦截 Object.getPrototypeOf(proxy) ,返回一个对象。
  • 「isExtensible(target)」:拦截 Object.isExtensible(proxy) ,返回一个布尔值。
  • 「setPrototypeOf(target, proto)」:拦截 Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • 「apply(target, object, args)」:拦截 Proxy 实例作为函数调用的操作,比如 proxy(...args) 、 proxy.call(object, ...args) 、 proxy.apply(...) 。
  • 「construct(target, args)」:拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(...args) 。

二、proxy实例的方法

1. get()

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

 var o1 = {}
    let p1 = new Proxy(o1,{
        get(target,prop,reciver){
            console.log("getter");
            console.log("target===o1",target===o1); // true
            console.log("prop",prop); //name
            console.log("reciver===p1",reciver===p1); // true
        }
    })
    p1.name;

2.set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

    var o1 = {}
    let p1 = new Proxy(o1,{
        get(target,prop,reciver){
            console.log("getter");
            console.log("target===o1",target===o1); // true
            console.log("prop",prop); //name
            console.log("reciver===p1",reciver===p1); // true
        },
        set(target,prop,value,reciver){
            console.log("setter");
            console.log("target===o1",target===o1); // true
            console.log("prop",prop); 
            console.log("value",value)
            console.log("reciver===p1",reciver===p1); // true
            target[prop] = value; // 这里需要赋值,如果不赋值,o1里面是没有age属性的
        }
    })
    p1.age = 12;

注意,如果目标对象自身的某个属性,不可写且不可配置,那么 set 方法将不起作用。

    var obj = {};
    Object.defineProperty(obj,'foo',{
        writable:false, 
        value:"hello"// set default value
        enumerable:true
    })
    console.log(obj.foo// hello

    var proxy = new Proxy(obj,{
        set(target,prop,value,reciver){
            console.log("setter")
        }
    })
    proxy.foo = 'world'// setter
    console.log(obj.foo// hello 值没有改变

3.apply()

apply方法拦截函数的调用、 call 和 apply 操作。

apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象( this )和目标对象的参数数组

    var obj = function(){
        console.log("我是个函数")
    }
    var proxy = new Proxy(obj,{
        apply(target,ctx,args){
            console.log("apply");
            console.log("target",target);
            console.log("ctx",ctx);
            console.log("args",args)
            target(args)
        }
    })
    var instance = {f:proxy}
    instance.f()

4.has()

has方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in 运算符。

    var obj = {name:"小红"};
    var proxy = new Proxy(obj,{
        has(target,prop){
            console.log('has');
            return prop in target;
        }
    })

    console.log'name' in proxy); // has  true
    console.log(obj.hasOwnProperty('name')) //true 不拦截
     for(let key in obj){  // 不拦截
        console.log(key)
    }

另外,虽然 for...in 循环也用到了 in 运算符,但是 has 拦截对 for...in 循环不生效。

5.construct()

construct方法用于拦截new命令,下面是拦截对象的写法。

   var Person  = function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    var proxy = new Proxy(Person,{
        construct(target,args,newTarget){
            console.log(target);
            console.log(args);
            console.log(newTarget);
            console.log(proxy === newTarget)
            return {} // 必须返回一个对象
        }
    })
    new proxy("hello",12)

三、Proxy.revocable()

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

revoke是一个函数,调用该函数后,取消proxy实例,再访问proxy实例就会抛出一个错误。

   let obj = {name:"hello"};
    let {proxy,revoke} = Proxy.revocable(obj,{})
    proxy.age = 1;   // 正常

    revoke(); // 取消
    proxy.age=2; // error  Cannot perform 'set' on a proxy that has been revoked

四、this

Proxy 代理的情况下,目标对象内部的 this 关键字会指向 Proxy 代理。

let target = {
        foo(){
            console.log(this)
            console.log(this === proxy)
        }
    }
    let proxy = new Proxy(target,{ })
    target.foo();  // false
    proxy.foo(); // true

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

const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate(); // error