整理学习——ES6的Proxy

本文详细介绍了ES6的Proxy对象,用于自定义基本操作行为,如属性查找、赋值等。讲解了handler对象的各种方法,如get、set、has、deleteProperty等,并通过实例展示了如何使用Proxy进行对象操作的拦截和验证。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简单说明

Proxy对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。是一个用来代理基本操作的对象。可以利用它来过滤和改写用户的某些操作。

let p = new Proxy(target, handler);

target:用proxy包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至是另一个代理。)
handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数。

方法

Proxy.revocable(target, handler)

创建一个可撤销的Proxy对象。返回一个包含了所生成代理对象本身以及该代理对象的撤销方法的对象。

Proxy.revocable(target, handler);

示例说明

var revo = Proxy.revocable({},{
	get(target, name){
		return "\"" + name + "\"";
	}
});
console.log(revo);
//Output: {proxy: Proxy, revoke: ƒ}

返回值是一个对象,结构为{proxy: Proxy, revoke: revoke},其中
proxy:新生成的代理对象本身,和用一般方式创建的代理对象没有什么不同,只是可被撤销。
revoke:撤销方法,调用时不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。
一旦某个代理对象被撤销,它将变的几乎完全不可用,在它身上执行任何的可代理操作都会抛出TypeError,除可代理操作其他的操作不会抛出异常。代理对象一旦被撤销,这个代理对象永远不可能恢复到原来的状态,同时和它关联的目标对象以及处理器对象将有可能被垃圾回收掉。调用撤销方法多次将不会有任何效果,也不会报错。

var proxy = revo.proxy;
proxy.a;
//result: ""a""
revo.revoke();
//撤销proxy
proxy.a;
//result: Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.a = 1;
//result: Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.a;
//result: Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
typeof proxy;
//result: "object"
//因为typeof不属于代理操作

代理操作:就是handler对象的方法,详细见后续,包括getPrototypeOf()、setPrototypeOf()、isExtensible(),preventExtensions()、getOwnPropertyDescriptor()、defineProperty()、has()、get()、set()、deleteProperty()、ownKeys()、apply()、construct()

handler对象的方法

handler对象是一个占位符对象,包含Proxy的捕获器,也就是代理的方法。

handler.getPrototypeOf(target)

读取代理对象的原型时调用。当其被调用时,this指向的是它所属的处理器对象。其返回值必须是一个对象或者null
target:被代理的目标对象

触发运行

  1. Object.getPrototypeOf()
  2. Reflect.getPrototypeOf()
  3. __proto__
  4. Object.prototype.isPrototypeOf()
  5. instanceof

异常

以下情况会抛出TypeError异常

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

handler.setPrototypeOf(target, prototype)

主要用来拦截Oject.setPrototypeOf()。
若成功修改了[[Prototype]],setPrototypeOf方法返回true,否则返回false

var p = new Proxy(target, {
	setPrototypeOf: function(target, prototype){}
}

target:被拦截的目标对象
prototype:对象新原型或为null

触发运行

  1. Object.setPrototypeOf()
  2. Reflect.setPrototypeOf()

异常

若target不可扩展,原型参数必须与Object.getPrototypeOf(target)的值相同。

handler.isExtensible(target)

判断一个代理对象是否是可扩展时触发。必须返回一个Boolean值或可转换成Boolean的值。

var p = new Proxy(target,{
	isExtensible(target){
	}
});

target:目标对象
其this绑定在handler对象上。

触发运行

  1. Object.isExtensible()
  2. Reflect.isExtensible()

异常

  1. Object.isExtensible(proxy)必须同Object.isExtensible(target)返回相同值。也就是必须返回true或为true的值,返回false和false的值都会抛出TypeError错误,表示不可扩展。

handler.preventExtensions(target)

用于设置对Object.preventExtensions()的拦截。返回一个布尔值

var p = new Proxy(target, {
	preventExtensions: function(target){
	}
});

target:所要拦截的目标对象

触发运行

  1. Object.preventExtensions()
  2. Reflect.preventExtensions()

异常

违反以下情况抛出TypeError

  1. 若Object.isExtensible(proxy)是false,则Object.preventExtensions(proxy)只能返回true

handler.getOwnPropertyDescriptor(target, prop)

在获取代理对象某个属性的属性描述是触发该操作。返回一个object或undefined

var p = new Proxy(target,{
	getOwnPropertyDescriptot(target, prop){}
})

target:目标对象
prop:所返回的属性的名称

触发运行

  1. Object.getOwnPropertyDescriptor()
  2. Reflect.getOwnPropertyDescriptor()

异常

以下情况将抛出TypeError

  1. getOwnPropertyDescriptor必须返回一个object或undefined
  2. 如果被代理的属性不可配置,则该属性无法报告为不存在
  3. 如果属性对应的被代理对象的属性存在,并且这个被代理的属性不可扩展,则代理的属性不可被报告为不存在。
  4. 如果对应的被代理的对象的属性不存在,并且这个被代理的对象的属性不可扩展,则不能将其报告为存在。
  5. Object.getOwnPropertyDescriptor(target)的结果会被目标对象应用在使用Object.defineProperty时,而且它不会抛出异常

handler.defineProperty(target, property, descriptor)

定义代理对象某个属性的属性描述时触发该操作。返回一个Boolean,表示定义该属性的操作成功与否。

var p = new Proxy(target, {
	defineProperty(target, property, descriptor){}
}

target:目标对象
property:带检索其描述的属性名
descriptor:待定义或修改的属性的描述符

触发运行

  1. Object.defineProperty()
  2. Reflect.defineProperty()
  3. proxy.property=‘value’

异常

违反以下情况,会抛出TypeError

  1. 若目标对象不可扩展,将不能添加属性
  2. 如果某属性不作为一个目标对象的不可配置的属性存在的话。那么不能添加或者修改一个属性为不可配置。
  3. 若目标对象存在一个对应的可配置属性,这属性可能不会是不可配置的。
  4. 若一个属性在目标对象中存在对应的属性,那么Object.defineProperty(target,prop, descriptor)将不会抛出异常。
  5. 严格模式下,false作为handler.defineProperty方法的返回值的话将会抛出TypeError异常。

handler.get(target, property, receiver)

用于拦截对象的读取属性操作。

var p = new Proxy(
	target, {
		get(target, property, receiver){}
	}
);

target:目标对象
property:被获取的属性名
receiver:Proxy或者继承Proxy的对象

触发运行(也就是拦截的操作)

  1. 访问属性:proxy[foo]和proxy.foo
  2. 访问原型链上的属性:Object.create(proxy)[foo]
  3. Reflect.get()

异常

  1. 访问目标属性是不可写以及不可配置,则返回的值必须与该目标属性值相同
  2. 访问目标属性没有配置访问方法,即get方法是undefined的,则返回值必须undefined
    以上两种情况如果不满足,会抛出TypeError

handler.set(target, property, value, receiver)

var p = new Proxy(target,{
	set(target, property, value, receiver){
	}
});

target:目标对象
property:被设置的属性名
value:被设置的新值
receiver:最初被调用的对象。通常是proxy本身,当handler的set方法也有可能在原型链上或以其他方式被间接地调用,因此不一定是proxy本身
应返回一个布尔值。true:设置属性成;false且属性操作发生在严格模式下,应抛出一个TypeError

触发运行

  1. 指定属性值:proxy[foo] = bar和proxy.foo = bar
  2. 指定继承者的属性值:Object.create(proxy)[foo] = bar
  3. Reflect.set()

异常

以下情况会抛出TypeError异常。

  1. 目标属性不可写及不可配置,则不能改变它的值
  2. 目标属性没有配置存储方法。即set方法是undefined,则不能设置它的值
  3. 在严格模式下,若set方法返回false,则会抛出一个TypeError异常

handler.has(target, prop)

在判断代理对象是否拥有某个属性时触发该操作。针对in操作符的代理方法。返回一个Boolean。true表示存在,false表示不存在。

var p = new Proxy(target, {
	has(target, prop){}
});

target:目标对象
prop:需要检查是否存在的属性

触发运行

  1. 属性查询:foo in proxy
  2. 继承属性查询:foo in Object.create(proxy)
  3. with检查:with(proxy){(foo);}

with语句不建议使用,因为它可能是混淆错误和兼容性问题的根源。

  1. Reflect.has()

异常

违反以下规则,则抛出异常

  1. 若目标对象的被代理属性本身不可被配置,则该属性不能够被代理报告为不存在
  2. 若目标对象的被代理属性,并且目标对象为不可扩展对象,则该对象的属性不可报告为不存在。

注意

  1. has方法拦截的是HasProperty操作,而不是HasOwnProperty操作。即has方法不判断一个属性是对象自身的属性,还是继承的属性。
  2. has拦截对for…in循环不生效。

handler.deleteProperty(target, property)

在删除代理对象的某个属性时触发该操作。返回一个Boolean表示该属性是否被成功删除。

var p = new Proxy(target,{
	deleteProperty: function(target, property){}
});

target:目标对象
property:待删除的属性名

触发运行

  1. 删除属性:delete proxy[foo] 和 delete proxy.foo
  2. Reflect.deleteProperty()

异常

违反以下规则,会抛出TypeError

  1. 若目标对象的被代理属性不可配置,那么该属性不能被删除

handler.ownKeys(target)

在获取代理对象的所有属性键时触发该操作。必须返回一个可枚举对象,包含了该对象的所有属性键。

var p = new Proxy(target, {
	ownKeys(target){}
})

target:目标对象

触发运行

  1. Object.getOwnPropertyNames()
  2. Object.getOwnPropertySymbols()
  3. Object.keys()
  4. Reflect.ownKeys()

异常

若违反以下情况,则抛出TypeError

  1. ownKeys的结果必须为一个数组
  2. 数组的元素类型是一个String或Symbol,不能为其他基础类型
  3. 结果列表必须包含目标对象的所有不可配置和自有属性的key
  4. 若目标对象不可扩展,那么结果列表必须包含目标对象的所有自由属性的key,不能有其他值。

handler.apply(target, thisArg, argumentsList)

用于拦截函数的调用。当目标对象为函数,且被调用时触发。可返回任何值

var p = new Proxy(target, {
	apply(target, thisArg, argumentsList){}
});

其this上下文绑定在handler对象上
target:目标对象(函数)
thisArg:被调用时的上下文对象
argumentsList:被调用时的参数数组

触发运行

  1. proxy(…args)
  2. Function.prototype.apply() 和 Function.prototype.call()
  3. Reflect.apply()

异常

违反以下规则,将抛出TypeError

  1. target必须是可被调用的,即其必须是一个函数对象

handler.construct(target, argumentsList, newTarget)

用于拦截new操作符。为了使new操作符在生成的Proxy对象上生效,用于初始化代理的目标对象自身必须具有的[[Construct]]内部方法(即new target必须是有效的)

var p = new Proxy(target, {
	construct(target, argumentsList, newTarget
})

target:目标对象
argumentsList:constructor的参数列表
newTarget:最初被调用的构造函数,就上面例子而言是p

触发运行

  1. new proxy(…args)
  2. Reflect.construct()

异常

必须返回一个对象,否则抛出错误TypeError

例子说明

基础例子

使用get

var defaultValue = 0;
let handler = {
	get: function(target, name){
		return name in target ? target[name] : defaultValue;
	}
}
let p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a,p.b,'c' in p, p.c)
//Output: 1 undefined false 0

in运算符:如果指定的属性在指定的对象或其原型链中,则in运算符返回true。

prop in object

prop 为一个字符串类型或者symbol类型的属性名或者数组索引(非symbol类型将会强制转为字符串)

无操作代理转发

let target = {};
let p = new Proxy(target, {});
p.a = 37;
console.log(target.a);
//Output: 37
target.a = 40;
console.log(p.a)
//Output: 40

这个代理会将对p的操作转发到target对象上。当获取p的a值时,其实是获取target的a。

代理验证

验证对象的传值。使用set

//定义验证器
let validator = {
	set: function(obj, prop, value) {
		if (prop === 'age') {
			if (!Number.isInteger(value)) {
				throw new TypeError('The age is not an integer');
			}
			if (value > 200) {
				throw new RangeError('The age seems invalid');
			}
		}
		// The default behavior to store the value
		obj[prop] = value;
	}
};
var person = new Proxy({}, validator);
person.age = 100;
console.log(person.age);
//Output: 100

person.age = "young";
//result: Uncaught TypeError: The age is not an integer

person.age = 300;
//result: Uncaught RangeError: The age seems invalid

上述代码中因为代理中有对设置值的验证,所以当设置了不满足条件的值时,会抛出异常,执行相应的操作。

*扩展构造函数

通过一个新构造函数来扩展一个已有的构造函数。该例子使用了constructor和apply

//继承父类并添加初始化函数
function extend(sup, base){

	//获取base的原型中的constructor的属性描述符
	var descriptor = Object.getOwnPropertyDescriptor(
		base.prototype, "constructor"
	);
	
	//base是子类的初始化函数
	//使子类的原型是父类的原型的实例,原型链继承
	base.prototype = Object.create(sup.prototype);
	
	//创建代理的方法
	var handler = {
	
		//用于拦截new操作符。
		//为了使new操作符在生成的Proxy对象上生效
		//用于初始化代理的目标对象自身必须具有[[Construct]]内部方法(即new target必须是有效的)
		construct(target, args) {
		
			//创建子类的实例
			var obj = Object.create(base.prototype);
			
			//并且将这个实例替换当前的sup、base
			this.apply(target, obj, args);
			
			//返回这个实例
			return obj;
		},
		
		//用于拦截函数的调用
		apply(target, that, args){
		
			//用传入的that替换sup和base
			sup.apply(that, args);
			base.apply(that, args)
		}
	};
	
	//创建代理
	var proxy = new Proxy(base, handler);
	
	//修改属性描述符,并使其通过代理进行初始化
	descriptor.value = proxy;
	
	//重新定义属性描述符
	Object.defineProperty(
		base.prototype,
		"constructor",
		descriptor
	);
	
	//返回代理
	return proxy;
}
var Person = function(name){
	this.name = name;
}

var Boy = extend(Person, function(name,age){
	this.age = age;
});

Boy.prototype.sex = "M";

var Peter = new Boy("Peter", 13);
console.log(Peter.sex, Peter.name, Peter.age);

操作DOM节点

切换两个不同的元素的属性或类名。使用了set

let view = new Proxy(
	{
		selected: null
	},{
		set(obj, prop, newval){
			let oldval = obj[prop];
			//修改selected属性
			if(prop == 'selected'){
				if(oldval){
					oldval.setAttribute('aria-selected', 'false');
				}
				if(newval){
					newval.setAttribute('aria-selected','true');
				}
			}
			obj[prop] = newval;
		}
	}
);
//触发view对于selected的set过程
let i1 = view.selected 
	= document.getElementById('item-1');

console.log(i1.getAttribute('aria-selected')); // 'true'

let i2 = view.selected = document.getElementById('item-2');
console.log(i1.getAttribute('aria-selected')); // 'false'
console.log(i2.getAttribute('aria-selected')); // 'true'

值修正及附加属性

products代理会计算传值并根据需要转换为数组。这个代理对象同时支持一个叫做latestBrowser的附加属性,这个属性可以同时作为getter和setter。

let products = new Proxy(
	{
		browsers: ['IE','Netscape']
	},{
		get(obj, prop){
			if(prop == 'lastestBrowser'){
				//返回最后一个元素
				return obj.browser[obj.browsers.length -1];
			}
			//默认值返回属性值
			return obj[prop];
		},
		set: function(obj, prop, value){
			if(prop == 'latestBrowser'){
				//添加
				obj.browsers.push(value);
				return;
			}
			//如果是字符串但不是数组则转为字符串数组
			if(typeof value === 'string'){
				//初始值
				value = [value];
			}
			//默认行为,保存属性值
			obj[prop] = value;
		}
	}
);
console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox'; // ?传入一个 string (错误地)
console.log(products.browsers); // ['Firefox'] <- ?没问题, ?得到的是一个 array

products.latestBrowser = 'Chrome';
console.log(products.browsers); // ['Firefox', 'Chrome']
console.log(products.latestBrowser); // 'Chrome'

通过属性查找数组中的特定对象

let products = new Proxy([
		{ name: 'firfox', type: 'browser'},
		{ name: 'seaMonkey', type: 'browser'},
		{ name: 'thunderbird', type: 'mailer'}
	],{
		get(obj,prop){
			// 默认返回属性值
			if(prop in obj){
				return obj[prop];
			}
			//获取products的number,是products.length的别名
			if(prop == 'number'){
				return obj.length;
			}
			let result, types = {};
			//获取表格对应需要返回的行数据
			for(let product of obj){
				if(product.name === prop){
					result = product;
				}
				if(types[product.type]){
					types[product.type].push(product)
				}else {
					types[product.type] = [product];
				}
			}
		
			if(result){
				return result;
			}
			if(prop in types){
				reutrn.types[prop];
			}
			if(prop  === "types"){
				return Object.keys(types);
			}
			return undefined;
		}
	}
);
console.log(products[0]); // { name: 'Firefox', type: 'browser' }
console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
console.log(products['Chrome']); // undefined
console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
console.log(products.types); // ['browser', 'mailer']
console.log(products.number); // 3

用于通过单元格来查找表格中的一行,target是table.target。在get中对所需的返回进行处理。

一个完整的traps列表实例

代理化一个非原生对象,适用于发布者document.cookie页面上的“小型框架”创建的docCookie全局对象。

/*
  var docCookies = ... get the "docCookies" object here:  
  https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
*/
var docCookies = new Proxy(docCookies, {
	"get": function(oTarget, sKey){
		return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
	},
	set(oTarget, sKey, vValue){
		if(sKey in oTarget){return false;}
		return oTarget.setItem(sKey, vValue);
	},
	deleteProperty(oTarget, sKey){
		if(sKey in oTarget){ return false; }
		return oTarget.removeItem(sKey);
	},
	enumerate(oTarget, sKey){
		return oTarget.keys();
	},
	ownKeys(oTarget, sKey){
		return oTarget.keys();
	},
	has(oTarget, sKey){
		return sKey in oTarget || oTarget.hasItem(sKey);;
	},
	defineProperty(oTarget, sKey, oDesc){
		if(oDesc && "value" in oDesc){
			oTarget.setItem(sKey, oDesc.value);
		}
		return oTarget;
	},
	getOwnPropertyDescriptor(oTarget, sKey){
		var value = oTarget.getItem(sKey);
		return value? {
			value: value,
			writable: true,
			enumerable: true,
			configurable: false
		}: undefined;
	}
});
/* Cookies 测试 */
alert(docCookies.my_cookie1 = "First value");
alert(docCookies.getItem("my_cookie1"));

docCookies.setItem("my_cookie1", "Changed value");
alert(docCookies.my_cookie1);

参考地址:
http://caibaojian.com/es6/proxy.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/in
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getPrototypeOf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值