ES6 Proxy

Proxy

概述

用于修改某些操作默认行为,等同于在语言层面做出修改,元编程。实际上Proxy重载了点运算符

var obj=new Proxy({},{
	get:function(target,key,receiver){
		console.log(`getting ${key}!`);
		return Reflect.get(target,key,receiver);
	},
	set:function(target,key,value,receiver){
		console.log(`setting ${key}!`);
		return Reflect.get(target,key,value,receiver);
	}
	})

没有拦截直接通向原对象
一个技巧是将Proxy设置到object.proxy

var object={proxy: new Proxy(target, handler)};

Proxy实例也作为其他对象原型对象

var proxy = new Proxy({},{
	get:function(target,property){
		return 35;
	}
	});
let obj=Object.create(proxy);
obj.time //35

上面代码中。proxy是obj对象原型,obj对象本身没有time属性,所以根据原型链,会在proxy对象读取属性,导致被拦截。
共支持13中拦截操作。

get

var person={
	name: '张三'
};
var proxy=new Proxy(person,{
	get: function(target,property){
		if(property in target){
			return target[property];
		}else{
			throw new ReferenceError("Property \""+property+"\" does not exist.")
		}
	}
	})

get方法可以继承

let proto=new Proxy({},{
	get(target,propertyKey,receiver){
		console.log('GET'+propertyKey);
		return target[propertyKey];
	}
	});
let obj=Object.create(proto);
obj.foo //"GET foo"

get拦截,实现数组读取负数的索引

function createArray(...elements){
	let handler={
		get(target,propKey,receiver){
			let index=Number(propKey);
			if(index < 0){
				propKey=String(target.length + index);
			}
			return Reflect.get(target,propKey,receiver);
		}
	};
	let target=[];
	target.push(...elements);
	return new Proxy(target, handler);
}

let arr = createArray('a','b','c');
a[-1]//c

利用Proxy可将读取属性操作get,转变为执行某个函数,实现属性链式操作。

var pipe=(function(){
	return function(value){
		var funcStack=[];
		var oproxy=new Proxy({},{
			get:function(pipeObject,fnName){
				if(fnName === 'get'){
					return funcStack.reduce(function(val,fn){
						return fn(val);
						},value);
				}
				funcStack.push(window[fnName]);
				return oproxy;
			}
			});
			return oproxy;
	}
	}());

var double = n => n*2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("")|0;
pip(3).double.pow.reverseInt.get;//63

上面代码达到函数名链式调用效果。
下面例子利用get拦截,实现一个生成各种dom节点通用dom。

const dom=new Proxy({},{
	get(target,property){
		return function(attrs={},...children){
			const el=document.createElement(property);
			for(let prop of Object.keys(attrs){
				el.setAttribute(prop,attrs[prop]);
			}
			for(let child of children){
				if(typeof child === 'string'){
					child = document.createTextNode(child);
				}
				el.appendChild(child);
			}
			return el;
		}
	}
	});

	const el=dom.div({},
	"Hello, my name is ",
	dom.a({href:'//example.com'},'Mark'),
	'. I like:',
	dom.ul({},
		dom.li({},"The web"),
		dom.li({},"Food"),
		dom.li({},"...actually that\'s it")
		)
	);

	document.body.appendChild(el);

下面是一个get方法第三个参数的例子,总是指向原始的读操作所在的那个对象,一般就是proxy

const proxy = new Proxy({},{
	get: function(target,property,receiver){
		return [target,receiver];
	}
	});
	const d=Object.create(proxy);
	d.a===d

如果一个属性不可配置不可写,会报错

const target=Object.defineProperties({},{
	foo:{
		value:123,
		writable:false,
		configurable:false
	},
	});

	const handler={
		get(target,propKey){
			return 'abc';
		}
	};

	const proxy=new Proxy(target, handler);

	proxy.foo//error

set

拦截赋值操作,四个参数目标,属性,属性值,proxy实例
假定person有age属性,该属性不大于200整数

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');
			}
		}
		//对于满足条件age属性以及其他属性,直接保存
		obj[prop]=value;
	}
};
let person=new Proxy({}, validator);
person.age=100;
person.age//100
person.age='young'//报错
person.age=300//error

设置内部属性,防止外部读写

const handler={
	get(target, key){
		invariant(key,'get');
		return target[key];
	},
	set(target,key,value){
		invariant(key,'set');
		target[key]=value;
		return true;
	}
};
function invariant(key, action){
	if(key[0] === '_'){
		throw new Error('');
	}
}
const target={};
const proxy = new Proxy(target,handler);
proxy._prop

set的第四个参数,receiver指的是原始操作行为所在的那个对象,一般情况下是proxy实例本身

const handler={
	set:function(obj,prop,value,receiver){
		obj[prop]=receiver;
	}
};
const proxy=new Proxy({},handler);
const myObj={};
Object.setPrototypeOf(myObj,proxy);

myObj.foo='bar';
myObj.foo===muObj

会到muObj原型链去找foo,这时receiver就指向原始赋值行为所在的对象myObj

apply

拦截函数调用、call、apply,三个参数目标对象,目标对象的上下文和目标对象参数数组。

var hanler={
	apply(target, ctx, args){
		return Reflect.apply(...arguments);
	}
}
var target=function(){ return 'I am the target'; };
var handler={
	apply:function(){
		return "I am the proxy";
	}
}
var p=new Proxy(target,handler);
p()//i am 

p是proxy实例,作为函数掉用时(p()),被apply方法拦截

var twice={
	apply(target, ctx, args){
		return Reflect.apply(...arguments)*2;
	}
};
function sum(left,right){
	return left + right;
}
var proxy=new Proxy(sum, twice);
proxy(1,2)//6
proxy.call(null,5,6)//22
proxy.apply(null,[7,8])//30

每当执行proxy函数或者调用call,apply,就会被拦截
直接调用Reflect.apply方法,也会被拦截

Reflect.apply(proxy,null,[9,10]);//30

has()

has拦截HasProperty操作,即判断对象是否具有某个属性,in,
两个参数,目标对象,需查询的属性名
下面利用has方法隐藏某些属性,不被in发现

var handler={
	has(target, key){
		if(key[0] === '_'){
			return false;
		}
		return key in target;
	}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false

如果原对象不可配置或者进制扩展,has拦截会报错。

var obj={ a:10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
	has: function(target, prop){
		return false;
	}
	})
	'a' in p//typeError

如果某个属性不可配置或目标对象不可扩展,has方法就不得隐藏
has拦截的是HasProperty操作,而不是HasOwnProperty操作,即has不判断一个属性是自身属性还是继承属性
对for … in 不生效

construct()

用于拦截new

var handler={
	construct (targetm,args,newTarget){
		return new target(...args);
	}
};

接受两个参数,target目标对象,args构造函数参数对象,newTarget创造实例对象时,new命令作用的构造函数。

var p=new Proxy(function(){},{
	construct:function(target, args){
		console.log('called:'+args.join(', '));
		return { value: args[0]*10 };
	}
	});

	(new p(1)).value
	//called 1 10

必须返回对象,否则报错

deleteProperty()

拦截delete,这个方法抛出错误或者return false,当前属性就无法被delete

var handler={
	deleteProperty(target, key){
		invariant(key, 'delete');
		delete target[key];
		return true;
	}
}
function invariant(key, action){
	if(key[0] === '_'){
		throw new Error(`Invalid attempt to `);
	}
}
var target={_prop: 'foo'};
var proxy._prop
//Error

目标对象自身的不可配置configurable属性,不能删除,否则报错

defineProperty()

拦截Object.defineProperty操作

var handler={
	defineProperty(target,key,descriptor){
		return false;
	}
}
var target={}
var proxy=new Proxy(target, handler);
proxy.foo='bar'//不会生效

如果目标对象不可扩展,define不能增加目标对象不存在的属性,否则会报错,如果目标对象某个属性不可写不可配置,不得改变这两个设置

getOwnPropertyDescriptor

getOwnPropertyDescriptor方法拦截Object.getOwnPropertyDescriptor() 返回一个属性描述对象或者undefined。

var handler={
	getOwnPropertyDescriptor(target,key){
		if(key[0] === '_'){
			return ;
		}
		return Object.getOwnPropertyDescriptor(target,key)
	}
};
var target = { _foo: 'bar', baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat')
// undefined
Object.getOwnPropertyDescriptor(proxy, '_foo')
// undefined
Object.getOwnPropertyDescriptor(proxy, 'baz')
// { value: 'tar', writable: true, enumerable: true, configurable: true }

getPrototypeOf()

拦截获取对象原型
Object.prototype.proto
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
例子

var ptoto={};
var p = new Proxy({},{
	getPrototypeOf(target){
		return proto;
	}
	});
Object.getPrototypeOf(p) === proto

必须返回nul或者对象,不可扩展,必须返回目标原型

isExtensible()

拦截Object.isExtensible操作

var p=new Proxy({},{
	isExtensible: function(target){
		console.log('called');
		return true;
	}
	});
Object.isExtensible(p);
//called true

只返回布尔值,必须与目标对象isExtensible属性保持一致

ownKeys()

ownKeys拦截以下操作
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for…in循环

let target={
	a:1,
	b:2,
	c:3
};
let handler={
	ownKeys(target){
		return ['a'];
	}
};

let proxy=new Proxy(target, handler);

Object.keys(proxy);
//["a"]

拦截了target的Object.keys()
下面拦截第一个字符为下划线的属性名

let target={
	_bar:'foo',
	_prop:'bar',
	prop:'baz'
};
let handler={
	ownKeys(target){
		return Reflect.ownKeys(target).filter(key => key[0] !== '_');
	}
}
let proxy=new Proxy(target, handler);
for(let key of Object.keys(proxy)){
	console.log(target[key]);
}

三类属性会被ownKeys自动过滤
目标对象不存在的属性,属性名为Symbol属性,不可遍历enumerable属性

let target = {
  a: 1,
  b: 2,
  c: 3,
  [Symbol.for('secret')]: '4',
};

Object.defineProperty(target, 'key', {
  enumerable: false,
  configurable: true,
  writable: true,
  value: 'static'
});

let handler = {
  ownKeys(target) {
    return ['a', 'd', Symbol.for('secret'), 'key'];
  }
};

let proxy = new Proxy(target, handler);

Object.keys(proxy)
// ['a']

ownKeys拦截Object.getOwnPropertyNames()

var p=new Proxy({},{
	ownKeys:function(target){
		return ['a','b','c'];
	}
	});
Object.getOwnPropertyNames(p)
//['a','b','c']

for…in循环也受到ownKeys方法的拦截

const obj={ hello: 'world' };
const proxy = new Proxy(obj,{
	ownKeys:function(){
		return ['a', 'b'];
	}
	});
for(let key in proxy){
	console.log(key)
}

ownkeys只返回a和b属性,由于obj没有这两个属性,因此不会有输出
ownKeys方法返回的数组成员,如果有其他类型的值或者不是数组,就会报错

var obj={};
var p=new Proxy(obj, {
	ownKeys: function(target){
		return [123,true,undefined,null,{},[]];
	}
	});
Object.getOwnPropertyNames(p);

每一个数组成员不是字符串或者Symbol值。
如果该目标对象自身包含不可配置属性,必须被返回,否则报错。

var obj = {};
Object.defineProperty(obj, 'a', {
  configurable: false,
  enumerable: true,
  value: 10 }
);

var p = new Proxy(obj, {
  ownKeys: function(target) {
    return ['b'];
  }
});

Object.getOwnPropertyNames(p)
// Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a'

如果目标对象是不可扩展的,这时候ownKeys方法返回数组之中,必须包含原对象所有属性,不能包含多余属性,否则报错。

var obj = {
  a: 1
};

Object.preventExtensions(obj);

var p = new Proxy(obj, {
  ownKeys: function(target) {
    return ['a', 'b'];
  }
});

Object.getOwnPropertyNames(p)
// Uncaught TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible

preventExtensions()

拦截Object.preventExtensions()
必须返回一个布尔值,否则会被自动转为布尔值。
有一个限制,只有目标对象不可扩展Object.isExtensible(proxy)为false,proxy.preventExtensions返回true

var proxy = new Proxy({}, {
  preventExtensions: function(target) {
    return true;
  }
});

Object.preventExtensions(proxy)
// Uncaught TypeError: 'preventExtensions' on proxy: tr

为了防止出现这个问题,要在proxy.preventExtensions方法里调用Object.preventExtensions

var proxy = new Proxy({}, {
  preventExtensions: function(target) {
    console.log('called');
    Object.preventExtensions(target);
    return true;
  }
});

Object.preventExtensions(proxy)
// "called"
// Proxy {}

setPrototypeOf()

用来拦截Obejct.setPrototypeOf方法
下面的一个例子

var handler={
	setPrototypeOf(target,proto){
		throw new Error('Changeing the prototype is forbidden');
	}
};
var proto={};
var target = function(){};
var proxy=new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden

上面代码中,只要修改target的原型对象,就会报错。

注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf方法不得改变目标对象的原型。

Proxy.revocable()

返回一个可取消的Proxy实例

let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo // 123

revoke();
proxy.foo // TypeError: Revoked

Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

this问题

虽然Proxy代理针对目标对象访问,proxy代理情况下,目标对象内部的this关键字会指向 Proxy 代理。

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};

const proxy = new Proxy(target, handler);

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

Proxy无法代理目标的例子

const _name = new WeakMap();

class Person {
  constructor(name) {
    _name.set(this, name);
  }
  get name() {
    return _name.get(this);
  }
}

const jane = new Person('Jane');
jane.name // 'Jane'

const proxy = new Proxy(jane, {});
proxy.name // undefined

有些原生对象,需要通过this拿到,proxy也无法代理

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

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

通过this绑定原始对象拿到

const target = new Date('2015-01-01');
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() // 1

实例: Web服务的客户端

Proxy可拦截目标任意属性,适合写Web服务的客户端

const service = createWebService('http://example.com/data');

service.employees().then(json => {
  const employees = JSON.parse(json);
  // ···
});

这个借口返回各种数据,Proxy可以拦截这个对象的任意属性,不用每种数据写一个方法,写一个Proxy就行了

function createWebService(baseUrl) {
  return new Proxy({}, {
    get(target, propKey, receiver) {
      return () => httpGet(baseUrl+'/' + propKey);
    }
  });
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值