JS内置对象-Proxy

367 阅读9分钟

什么是Proxy

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

const p = new Proxy(targte, handler);

handler对象的方法

handler.getPrototypeOf()

MDN-handler.getPrototypeOf()
它是一个代理(Proxy)方法,当读取代理对象的原型时,该方法会被调用。

以下五种操作(方法/属性/运算符)可以触发JS引擎读取一个对象的原型;

  • Object.getPrototypeOf()
  • Reflectt.getPrototype();
  • __protot__
  • Object.prototype.isPrototypeOf()
  • instanceof

如果遇到下面两种情况,JS引擎会抛出TypeError异常:

  • getPrototypeOf()方法返回的不是对象也不是null。
  • 目标对象是不可扩展的,且gePrototyoeOf()方法返回的原型不是目标对象本身的原型

参数

getPrototypeOf方法被调用时,this指向的是它所属于的处理器对象

target被代理的目标对象

返回值

返回值必须是一个对象或者null

示例

基本用法

let obj = {};
let proto = {};
let handler = {
    getPrototypeOf(target) {
        console.log(target === obj); // true
        console.log(this === handler); // true
        return proto;
    }
}
let p = new Proxy(obj, handler);
console.log(Object.getPrototypeOf(p) === proto); // true 执行这句时候,会输出三个 true

5种触发getPrototypeOf代理方法的方式

let obj = {};
let p = new Proxy(obj, {
    getPrototypeOf(target) {
        return Array.prototype;
    }
});
console.log(
    Object.getPrototypeOf(p) === Array.prototype, // true
    Reflect.getPrototypeOf(p) === Array.prototype, // true
    p.__proto__ === Array.prototype, // true
    Array.prototype.isPrototypeOf(p), // true
    p instanceof Array // true
)

两种情况下的异常

let obj = {}
let p = new Proxy(obj, {
    getPrototypeOf(target) {
        return 'foo';
    }
});
Object.getPrototypeOf(p); 
//Uncaught TypeError: 'getPrototypeOf' on proxy: trap returned neither object nor null


let obj2 = Object.preventExtensions({});
let p2 = new Proxy(obj2, {
    getPrototypeOf(target) {
        return {};
    }
});
Object.getPrototypeOf(p2);
// Uncaught TypeError: 'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype

handler.setPrototype()

MDN-Proxy-handler.setPrototypeOf()
用于拦截Object.setPrototypeOf()

可以拦截一下操作:

  • Object.setPrototypeOf()
  • Reflect.setPrototypeOf()

如果违反了下列规则,则proxy将抛出一个TypeError

  • 如果targte不可扩展,原型参数必须与Object.getPrototypeOf(target)的值相同

参数

参数描述
target被拦截的目标对象
prototype对象新原型或为null

示例

如果我们不想为对象设置一个新的原型,handler的setPrototypeOf方法可以返回false,也可以抛出异常。

let p = new Proxy({}, {
    setPrototypeOf(target, newProtot) {
        return false;
    }
});
Object.setPrototypeOf(p, {});
// Uncaught TypeError: 'setPrototypeOf' on proxy: trap returned falsish for property 'undefined'


Reflect.setPrototypeOf(p, {}); // return false

handler.isExtensible()

MDN-handler.isExtensible()
用于拦截对对象的Object.isExtensible();

该方法拦截目标对象的以下操作:

  • Object.isExtensible()
  • Reflect.isExtensible() 违背了以下约束,proxy会抛出TypeError:
  • Object.isExtensible(proxy)必须同Object.isExtensible(target)返回同样的值。

示例

// 示例1
let obj = {
    canEvolve: true
}
let proxy = new Proxy(obj, {
    isExtensible(target) {
        return Reflect.isExtensible(target);
    },
    preventExtensions(target) {
        target.canEvolve = false;
        return Reflect.preventExtensions(target)
    }
});
console.log(Object.isExtensible(proxy)); // true
Object.preventExtensions(proxy);
console.log(Obect.isExtensible(proxy)); // false
console.log(obj.canEvolve); // false

// 示例2
let p = new Proxy({}, {
    isExtensible(target) {
        console.log('calleed');
        return true;
    }
});
console.log(Object.isExtensible(p)); // calleed true

以下代码演示违反约束

var p = new Proxy({}, {
    isExtensible(target) {
        return false; // return 0; return NaN 等都会报错
    }
});
Object.isExtensible(p);
// Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')

handler.preventextensions()

MDN-handler.preventextensions()
拦截Object.preventExtensions返回一个布尔值。

这个trap可以拦截:

  • Object.preventEctensions()
  • Object.preventExtensions()

如果违反了下列规则,proxy会抛出一个TypeError

  • 如果目标对象可扩展的,返回false 与实际值不匹配

参数

target所要拦截的目标对象

示例

const monster1 = {
    canEvolve: true
}
const proxy = new Proxy(monster1, {
    preventExtnesions(target) {
        target.canEvolve = false;
        OBject.preventExtensions(target);
        return true;
    }
});
console.log(monster1.canEvolve); // true
Object.preventExtensions(proxy); // Proxy {canEvolve: true}
console.log(monster1.canEvolve); // true

handler.getOwnPropertyDescriptor()

MDN-handler.getOwnPropertyDescriptor()
它是Object.getOwnProtopertyDescriptor的陷阱。

这个陷阱可以拦截一下操作:

  • Object.getOwnPropertyDescriptor()
  • Reflect.getOwnPropertyDescriptor()

如果下列不变量被违反,代理将抛出一个TypeError:

  • getOwnPropertyDescriptor必须返回一个object或undefined
  • 如果属性作为目标对象的不可配置的属性存在,则改属性无法报告为不存在
  • 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则不能将其报告为不存在。
  • 如是属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在。
  • 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置属性存在。
  • Object.getOwnPropertyDescriptor(target)的结果可以使用Object.defineProperty应用于对象,也不会抛出异常。

参数

参数描述
target目标对象
prop反悔属性名称的描述

示例

let p = new Proxy({age: 24}, {
    getOwnPropertyDescriptor(target, prop) {
        console.log('called: ' + prop);
        return Reflect.getOwnPropertyDescriptor(target, prop);
    }
});
console.log(Object.getOwnPropertyDescriptor(p, 'age'));
// called: age
// {value: 24, writable: true, enumerable: true, configurable: true}

以下代码则违反了不变量

const obj = {
    a: 10
}
Object.preventExtensions(obj);
var p = new Proxy(obj, {
    getOwnPropertyDescriptor: function(target, prop) {
        return undefined;
     }
});
Object.getOwnPropertyDescriptor(p, 'a');
// Uncaught TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned undefined for property 'a' which exists in the non-extensible proxy target

handler.defineProperty()

MDN-handler.defineProperty()
用于拦截对对象的Object.defineProperty()操作。

该方法会拦截目标对象的以下操作:

  • Object.defineProperty()
  • Reflect.defineProperty()
  • Proxy.property='value'

如果违背以下的不变量,proxy会抛出TypeError

  • 如果目标对象不可扩展,将不能添加属性
  • 不能添加或者修改一个属性为不可配置的,如果它作为一个目标对象的不可配置的属性存在的话。
  • 如果目标对象存在一个对应的可配置属性,那么Object。defineProperty(target, prop, descriptor)将不会抛出异常
  • 在严格模式下,false作为handler.defineProperty方法的反悔值的话讲抛出TypeError异常。

示例

let p = new Proxy({}, {
    defineProperty(target, prop, descriptor) {
        console.log('called: ' + prop);
        return true;
    }
});
Object.defineProperty(p, 'a',  {
    configurable: true,
    enumberable: true,
    value: 10
})

当调用Object.defineProperty()或者Reflect.definedProperty(),传递给definePropertydescriptor you一个现实-只有以下属性才有用,非标准的属性将会被无视。

  • enumberable
  • configurable
  • writable
  • value
  • get
  • set

handler.has()

MDN-Proxy-handler.has()
可以餐做是针对in操作的钩子。返回一个boolean属性的值
钩子可以拦截下面的这些操作

  • 属性查询foo in proxy
  • 继承属性查询 foo in Object.create(proxy)
  • with检查:with(proxy) { (foo) }
  • Reflect.has(); 如果违反了下面的这些规则,proxy将会抛出TypeError
  • 如果目标对象的某一些属性本身不可被配置,则改属性不能够被代理隐藏
  • 如果目标对象为不可扩展对象,则对象的属性不能被代理隐藏。

参数

参数描述
target目标对象
has需要检查是否存在的属性

示例

const obj = {
    _secret: 'easily scared',
    count: 4
}
const p = new Proxy(obj, {
    has(target, key) {
        if(key[0] === '_') {
            return false;
        }
        return key in target;
    }
});
console.log('count' in p); // true
console.log('_secret' in p); // false
console.log('_secret' in obj); // true

下面的代码违反了约束:

let obj = {
    a: 10
};
Object.preventExtensions(obj);
var p = new Proxy(obj, {
    has(target, key) {
        return false;
    }
});
'a' in p; 
// Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible

handler.get()

MDN-Proxy-handler.get()
拦截对象的读取属性的操作。可以返回任何值。

会拦截目标对象的以下的操作:

  • 访问属性:proxy[foo]proxy.bar
  • 访问原型链上的属性:Object.create(proxy)[foo]
  • Reflect.get();

如果违背了以下的约束。proxy会抛出TypeError

  • 如果要访问的目标属性是不可以写以及不可配置的,则返回的值必须与该目标属性的值相同。
  • 如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined。

参数

参数描述
targte目标对象
property被获取的属性名
receiverProxy或者继承Proxy的对象

示例

let p = new Proxy({}, {
    get(target, prop, receiver) {
        console.log('called: ' + prop);
        return 10;
    }
});
console.log(p.a);
// called: a
// 10 

以下代码演示违反了约束的情况

let obj = {};
Object.defineProperty(obj, 'a', {
    configurable: false,
    enumberable: false,
    value: 10,
    writable: false
});
let p = new Proxy(obj, {
    get(target, prop) {
        return 20;
    }
});
p.a;
// Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '10' but got '20')

handler.set()

MDN-Proxy-handler.set()
用于拦截设置属性值的操作。
该方法会拦截目标对象的以下操作:

  • 指定属性值:proxy[foo] = barproxy.foo = bar
  • 指定继承者的属性值:Object.create(proxy)[foo] = bar
  • Reflect.set() 如果违背了以下约束条件,proxy会抛出TypeError异常
  • 若目标属性是一个不可写及不可配置的数据属性,则不可以改变它的值
  • 如果目标属性没有配置存储方法,即[[Set]]属性的是undefined,则不能设置它的值。
  • 在严格模式下,如果set()方法反悔false,那么也会抛出TypeError异常

参数

参数描述
target目标对象
property将设置为属性名或Symbol
value新属性值
receiver最初被调用的对象,通常是proxy本身,但handler的set方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是proxy本身)

假设有一段代码执行obj.name = 'jen',obj不是一个proxy,且自身不含name属性,但是他的原型链上有一个proxy,那么。那个proxy的set()处理器会被调用,而此时,obj回座位receiver参数传进来。

返回值

set()方法应当返回一个布尔值。

  • 返回true代表属性设置成功
  • 严格模式下,如果set()方法返回false,那么会抛出TypeError异常

示例

const obj = {
    count: 4
}
let p = new Proxy(obj, {
    set(obj, prop, value) {
        if(prop === 'count' && (value % 2) !== 0) {
            console.log('obj must have an even number');
        }
        else {
            return Reflect.set(...arguments);
        }
    }
});
p.count = 1; // obj must have an even number
console.log(p.count); // 4

p.count = 8;
console.log(p.count); // 8

handler.deleteProperty()

MDN-Proxy-handler.deleteProperty() 方法可以拦截delete操作。 必须反悔一个Boolean值,表示该属性是否成功删除。 该方法会拦截以下操作:

  • 删除属性:delete proxy[foo]delete proxy.foo
  • Reflect.deleteProperty()

如果违背了胰腺癌不变量,proxy将会抛出一个TypeError

  • 如果目标对象的属性是不可配置的,那么该属性不能删除

参数

参数描述
target目标对象
property待删除的属性名

示例

let p = new Proxy({a: 10}, {
    deleteProperty(target, prop) {
        console.log('called: ' + prop);
        return Reflect.deleteProperty(target, prop);
    }
});
delete p.a; // called: a

handler.ownKeys()

MDN-Proxy-handler.ownKeys() 方法用于拦截Reflect.ownKeys()
该拦截器可以拦截以下操作:

  • Object.getOwnPropertyNames();
  • Object.getOwnPropertySymbols();
  • object.keys();
  • Reflect.ownKeys(); 如果违反了下面的约束,proxy将抛出错误TypeError:
  • ownKeys的结果必须是一个数组
  • 数据的元素类型要么是String,要么是Symbol
  • 结果列表必须包含目标对象的所有不可配置(no-configuable)、自由(own)属性的key。
  • 如果目标对象不可扩展,那么结果列表必须包含目标对象所有自由(own)属性的key,不能有其他的值。

示例

const obj = {
    _age: 11,
    [Symbol('secret')]: 'this is secret',
    money: 4
}
const p = new Proxy(obj, {
    ownKeys(target) {
        return Reflect.ownKeys(target);
    }
});
console.log(Object.keys(p)); // ['_age', 'money']

handler.apply()

MDN-Proxy-handler.apply() 用于拦截函数的调用。可以返回任何值。
方法或拦截目标对象的以下操作

  • proxy(...args);
  • Function.prototype.apply() 和 Function.prototype.call()
  • Reflect.apply();
    违反了以下约束,proxy将抛出TypeError
  • target必须是被调用的,也就是,它必须是一个函数对象。

参数

参数描述
target目标对象(函数)
thisArg被调用时的上下文对象
argumentList被调用是的参数数组

示例

let p = new Proxy(function() {}, {
    apply(func, thisArg, argumentList) {
        console.log('called:' + argumentList.join(','));
        return Reflect.apply(func, thisArg, argumentList);
    }
});
console.log(p(1, 2, 3));
// called:1,2,3
// undefined

handler.construct()

MDN-Proxy-handler.construct()
方法用于拦截new操作符。必须返回一个对象。
该拦截器可以拦截以下操作

  • new Proxy(...agrs)
  • Reflect.construct()

如果违反了以下约定,代理将会抛出错误TypeError

  • 必须返回一个对象

参数

参数描述
target目标对象
argumentListconstructor的参数列表
newTarget最初被调用的构造函数

示例

function Foo(disposition) {
    this.disposition = disposition;
}
const P = new Proxy(Foo, {
    construct(target, args) {
        console.log('Foo constructor called');
        return new target(...args);
    }
});
console.log(new P('fierce').disposition);
// Foo constructor called
// fierce