Proxy详解

一.Proxy基础

1. 什么是Proxy?

Proxy是一个构造函数,可以通过它生成一个Proxy实例。

const proxy = new Proxy(target, handler);
// target: 目标对象,待操作对象(可以是普通对象,数组,函数,proxy实例对象)
// handler: 一个对象,最多可包含13个操作方法,也可以是空对象

2. Proxy的作用

1. Proxy是一个ES6语法,它的作用主要是通过handler对象中的拦截方法拦截目标对象target的

某些操作(如读取,赋值,函数调用,new等),然后过滤或者改写这些操作的默认行为,

或者只是在完成这些操作的默认行为的基础上,增加一些副作用(如打印前置log等)。

2. 生成的实例对象是针对target对象的“拦截器”。也可以叫做“代理器”。

3. 然后必须通过操作proxy,即“拦截器”(拦截器对象本身性质和目标对象target一样,比如:target是函数,

那么proxy也是函数)才能触发拦截方法,来完成对目标对象的操作和访问。

4. 如果handler是个空对象({}),那么操作拦截器相当于直接操作目标对象target。

3. Proxy构造函数的特征

1. Proxy函数没有prototype属性,所以也就不能使用instanceof判断是否是Proxy实例

2. Proxy实例的数据类型和target数据类型一致。

var proxy1 = new Proxy([], {});
proxy1 instanceof Array; // true
var proxy2 = new Proxy(function(){}, {});
typeof proxy2; //"function"

二. Proxy拦截器handler方法

一共有13个拦截方法(对应Reflect的13个方法),可以大体分为两个部分。

1. 新的方法名

返回值是布尔值的方法有:

1. has(target, propKey)

作用:  拦截判断target对象是否含有属性propKey的操作

拦截操作: propKey in proxy;   不包含for...in循环

对应Reflect: Reflect.has(target, propKey)

语法:

const handler = {
    has(target, propKey){
        // ...
        //默认操作
        return propKey in target;
    }
}
const proxy = new Proxy(target, handler)

示例: 过滤某些私有属性

  const handler = {
    has(target, propKey) {
      if (propKey[0] === '_') {
        return false;
      }
      return propKey in target;
    }
  }
  const target = {_myprop: 1, a: 2, c:3};
  const proxy = new Proxy(target, handler);
  '_myprop' in proxy; // false

特殊情况: 目标target是不可扩展或者某个属性不可配置,只能返回默认行为结果;否则报错

var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
  has: function(target, prop) {
    return false; //只能是return prop in target;
  }
});
'a' in p; //Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible

2. deleteProperty(target, propKey)

返回:严格模式下只有返回true, 否则报错

作用: 拦截删除target对象的propKey属性的操作

拦截操作: delete proxy[propKey]

语法: 

const handler = {
    deleteProperty(target, propKey){
        // ...
        //默认操作; 操作成功并返回true;操作失败报错
        const bool = Reflect.deleteProperty(...arguments);
        if (bool) {
            return bool;
        } else {
            throw new Error("delete failed");
        }
    }
}
const proxy = new Proxy(target, handler)

示例:

var obj = { a: 10 };
var p = new Proxy(obj, {
  deleteProperty(target, prop) {
    console.log('delete propName ',prop); 
    return delete target[prop]; // 严格模式下操作成功必须返回true;否则报错
  }
});
delete p.a;
console.log(obj);
// 运行结果如下:
'delete propName a'
{}

 

特殊情况: 属性是不可配置属性时,不能删除; 但是对象不可扩展的时候,可以删除属性。

var obj = { a: 10 };
Object.defineProperty(obj, 'b', {
  value: 2, configurable: false
});
var p = new Proxy(obj, {
  deleteProperty(target, prop) {
    return delete target[prop];
  }
});
delete p.b; // Uncaught TypeError: Cannot delete property 'b' of #<Object>
console.log(obj);

3. ownKeys(target)

返回: 数组(数组元素必须是字符或者Symbol,其他类型报错)

作用: 拦截获取键值的操作

拦截操作: Object.getOwnPropertyNames(proxy)

                Object.getOwnPropertySymbols(proxy)

                Object.keys(proxy)

                for...in...循环

语法: 

最后取到的结果不是return的值,而是会自动过滤

const handler = {
   ownKeys(target) {
// 所有的keys;也可以是其他的数组
return Reflect.ownKeys(target); } }

示例: 

var obj = { a: 10, [Symbol.for('foo')]: 2 };
Object.defineProperty(obj, 'c', {
  value: 3, enumerable: false
})
var p = new Proxy(obj, {
  ownKeys(target) {
    return [...Reflect.ownKeys(target), 'b', Symbol.for('bar')];
  }
});
const keys = Object.keys(p);  // ['a']
// 自动过滤掉Symbol/非自身/不可遍历的属性

for(let prop in p) { // 和Object.keys()过滤性质一样,只返回target本身的可遍历属性
  console.log('prop-',prop); // prop-a
}
const ownNames = Object.getOwnPropertyNames(p);// ['a', 'c', 'b']
// 只返回拦截器返回的非Symbol的属性,不管是不是target上的属性

const ownSymbols = Object.getOwnPropertySymbols(p);// [Symbol(foo), Symbol(bar)]
// 只返回拦截器返回的Symbol的属性,不管是不是target上的属性

const ownKeys = Reflect.ownKeys(p);// ['a','c',Symbol(foo),'b',Symbol(bar)]
// 返回拦截器返回的所有值

特殊情况:

1)如果某个属性是不可配置的,那么该属性在拦截器中必须被返回,否则报错

2)如果target对象是不可扩展的,那么拦截器返回的数组必须是操作的默认返回结果。

即必须是被拦截的操作的默认行为,如getOwnPropertyNames()

4. apply(target, thisArg, args)--target是函数

返回:函数执行结果

作用: 拦截proxy作为函数调用时的操作

拦截操作:proxy()

               proxy.call(obj, ...args)

               proxy.apply(obj, [...args])

               Reflect.apply(proxy, thisArg, args)

语法:

const handler = {
  apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) {
    return Reflect.apply(...arguments);
  }
}

示例:

const handler = {
  apply(target, contextThis/*上下文对象this*/, args/*参数数组*/) {
    console.log('---apply拦截器---',contextThis,'-',args);
    return Reflect.apply(...arguments)+'end';
  }
}
let target = function(a,b) {
  return a+b;
}
const proxy = new Proxy(target, handler);
console.log('proxy-->',proxy(5,6));
console.log('proxy.call-->',proxy.call(null, 5, 6));
console.log('proxy.apply-->',proxy.apply(null, [5,6]));
console.log('Reflect-->',Reflect.apply(proxy, null, [5,6]));

// 运行结果如下:
/*
---apply拦截器---undefined-[5,6]
proxy-->11end
---apply拦截器---null-[5,6]
proxy.call-->11end
---apply拦截器---null-[5,6]
proxy.apply-->11end
---apply拦截器---null-[5,6]
Reflect-->11end
*/

5. construct(target, args, newTarget)--target是构造函数

返回: 实例对象, 不是对象会报错 

作用: 拦截new命令

拦截操作: new proxy(...args)

语法:

const handler = {
  construct(target, args, newTarget){// args是参数数组, newTarget是生成的proxy实例
    console.log('----拦截new----',args,'-', newTarget === proxy);
    return Reflect.construct(target, args);// 默认行为,也可以return {a: b},只要是对象就可以
  }
}
const target = function(a,b) {};
var proxy = new Proxy(target, handler);
console.log('proxy type-->', typeof proxy);
const result = new proxy(1,2); // 触发拦截器
console.log('result type-->',typeof result)
// 运行结果如下: proxy type-->function ----拦截new----[1,2]-true result type-->object

2. 方法名和对象原有方法名一样

6. get(target, propKey, receiver)

返回: 返回读取的属性

作用:拦截对象属性的读取

拦截操作:proxy[propKey]或者点运算符

语法:

const handler = {
  get(target, propKey, receiver) {// receiver是proxy实例
    return Reflect.get(target, propKey);
  }
}

示例: 实现函数的链式操作

const funsObj = {
  double(n) {return n*2},
  square(n) {return n**2}
}
var pipe = (value) => {
  const callStack=[];
  return new Proxy({}, {
    get(target, propKey, receiver) {
      console.log(propKey,callStack);
      if (propKey === 'get') {
        return callStack.reduce((val, fn) => {
          return fn(val);
        }, value)
      } else {
        callStack.push(funsObj[propKey]);
      }
      return receiver; //返回proxy实例才能实现链式调用
    }
  })
}
pipe(3).double.square.get; //36

get方法可以继承,但是receiver的值会是直接触发的那个对象。

const proxy= new Proxy({}, {
  get(target, propKey, receiver) {
    return receiver;
  }
})
const p = Object.create(proxy);
console.log(p.a === p); // true p.a返回receiver

特殊情况:如果对象的属性writable和configurable同时为false, 则拦截器只能返回默认行为

const target = {};
Object.defineProperty(target, 'a', {
  value: 1, 
  writable: false,
  configurable: false
})
const proxy = new Proxy(target, {
  get(target,propKey) {
    return 2;
    //应该return 1;不能返回其他值,否则报错
  }
})
proxy.a; // Uncaught TypeError

7.set(target,propKey, value,receiver)

返回:严格模式下返回true操作成功;否则失败,报错

作用: 拦截对象的属性赋值操作

拦截操作: proxy[propkey] = value

语法:

 

const proxy = new Proxy({}, {
  set(target, propKey, value,receiver) {// receiver时proxy实例
    const bool = Reflect.set(...arguments); 
    if (bool){
      return //!!!严格模式下操作成功必须返回true,否则报错;非严格模式下可以省略
//当Reflect.set传入的参数有receiver且属性writable=true时,会在receiver在定义属性,会触发defineProperty拦截
    } else {
        throw new Error("操作失败")
    }
    
  },
  defineProperty(target, propKey, propDesc) {
    console.log('defineProperty')
  }
})

set方法也可以继承,receiver也是最终调用的那个实例,和get方法一样。参照get的方法。

设置 target[propKey] = receiver;

当对象的属性writable为false时,该属性不能在拦截器中被修改;

const obj = {};
Object.defineProperty(obj, 'foo', {
  value: 'bar',
  writable: false,
  configurable: true,
});

const handler = {
  set: function(obj, prop, value, receiver) {
    return Reflect.set(...arguments);
  },
};
const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
console.log(obj); // {foo: bar} 说明修改失败

8. defineProperty(target, propKey,PropDesc)

返回: 严格模式下操作成功必须返回true;否则报错

作用:拦截定义属性或者重新定义属性操作

拦截操作: Object.defineProperty(proxy, propKey,propDesc)

                Reflect.defineProperty(proxy, propKey,propDesc)

语法兼示例:

const proxy = new Proxy({}, {
  defineProperty(target, propKey, propDescriptor) {// 最后一个参数是属性描述对象
    const bool = Reflect.defineProperty(...arguments);
    if (bool) {
       return bool;// !!严格模式下操作成功必须返回true,否则报错
    } else {
       throw new Error("定义属性失败")
    }
  }
})
Object.defineProperty(proxy, 'a', {value:5})

特殊情况:

如果对象是不可扩展的(preventExtensible(obj)),则不能添加属性;

如果对象的某属性writable和configurable同时为false, 则不能重新定义该属性的值;

如果上面的属性有其中一个是true,可以重新定义该属性的值。

9. getPrototypeOf(target)

返回: 返回对象或者null,否则报错

作用:拦截获取原型对象的操作

拦截操作:Object.getPrototypeOf(proxy)

               proxy.__proto__

              Object.isPrototypeOf(proxy)

              Reflect.getPrototypeOf(proxy)

              instanceof

语法:

var p = new Proxy({}, {
  getPrototypeOf(target) {
    return Reflect.getPrototypeOf(target);
  }
});

示例:

var proxy = new Proxy({}, {
  getPrototypeOf(target) {
    return {a:1}
  }
});
Object.getPrototypeOf(proxy); // {a:1} 

特殊情况:

如果目标对象是不可扩展的,那么只能返回默认的原型对象。

10.setPrototypeOf(target, proto)

返回:严格模式下返回true,否则报错;只能是布尔值

作用:拦截设置原型对象的操作

拦截操作:Object.setPrototypeOf(proxy, proto)

               proxy.__proto__

               Reflect.setPropertyOf(proxy,proto)

语法:

var proxy = new Proxy({}, {
  setPrototypeOf(target, proto) {
    const bool = Reflect.setPrototypeOf(...arguments);
    if (bool) {
       return bool; // !!!严格模式下操作成功必须返回true;否则报错
    } else {
       throw new Error("设置原型对象失败");
    }
  }
})

特殊情况:

如果对象不可扩展,只能进行默认行为。不能修改。

11. isExtensible(target)---只能返回默认行为结果

返回:布尔值

 作用: 拦截是否可扩展方法的调用

拦截操作: Object.isExtensible(proxy)

                Reflect.isExtensible(proxy)

语法:

const proxy = new Proxy({}, {
  isExtensible(target) {
    return Reflect.isExtensible(target); //!!!返回结果只能是这个;即proxy和target的可扩展性必须是一致的
  }
})

12. preventExtensible(target)--遵循默认行为

返回:严格模式下返回true,否则报错;

拦截操作:Object.preventExtensible(proxy)

               Reflect.preventExtentsible(proxy)

语法:

const proxy = new Proxy({}, {
  preventExtensions(target) {
    return Reflect.preventExtensions(target); //严格模式下,必须存在
  }
})

13. getOwnPropertyDescriptor(target, propKey)

返回: 对象或者undefined

拦截操作: Object.getOwnPropertyDescriptor(proxy)

                Reflect.getOwnPropertyDescriptor(proxy)

语法:

const proxy = new Proxy({}, {
  getOwnPropertyDescriptor(target, propKey) {
    return Reflect.getOwnPropertyDescriptor(target,propKey);
  }
})

三. 静态方法-Proxy.revocable()

作用: 返回一个可取消的proxy实例

语法:

const {proxy, revoke} = Proxy.revocable(target,handler);
// proxy是实例
// revoke是个方法;直接调用后取消proxy实例

示例:

const {proxy, revoke} = Proxy.revocable({},{});
proxy.a=5;
revoke();
proxy.a // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

应用: 

用于一个只能访问一次的代理器

四. this指向

拦截器方法中的this指向proxy实例

五.Proxy应用

观察者模式:通过拦截对象的赋值操作,实时调用观察函数。

如: mobx中的observable, observe的实现;通过函数观察对象,并实时作出反应

const obj = {name: 'lyra', age: 18};//被观察者--使用Proxy
const fn1 = () => {
    obj.age = 16;
    console.log(`Hello, ${obj.name}, ${obj.age}`);
}
const fn2 = () => console.log('there must be sth changed'); 
const observeFuncs = new Set();
const observe = (fn) => observeFuncs.add(fn);
const observable = function(obj) {
    return new Proxy(obj, {
        set(target, prop, value) {
            observeFuncs.forEach(observer => observer()); //被观察者属性被赋值时,触发观察者函数调用
            return Reflect.set(...arguments); //默认行为
        }
    })
}
const objProxy = observable(obj);//指定被观察者对象
observe(fn1); //指定观察者函数
observe(fn2); 
objProxy.name = 'lyraLee';
// 运行结果如下:
Hello, lyraLee, 16
there must be sth changed

 

posted @ 2019-10-31 23:08  Lyra李  阅读(5625)  评论(0编辑  收藏  举报