ECMAScript 6之Proxy

1. 概述

Proxy用于修改某些操作的默认行为,属于一种“元编程”(meta programming)。先看下面一个例子。

var obj= new Proxy({}, {
	get: function(target, pro){
		console.info(`getting ${pro}....`);
		return "jidi"
	}
})

obj.name; 
// getting name....
// "jidi"

上面代码,为一个空对象重新设置了属性的读取行为,对设置了Proxy的对象obj,读取他的属性,都会返回jidi

ES6 原生提供Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler);

Proxy对象的所有用法都是上面这种形式。其中,new Proxy表示生成一个Proxy实例,target表示要代理的对象,handler拦截器,表示代理行为,也是一个对象。

下面是另一个例子。

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

obj.count = 1; //  setting count!
obj.count; // 1
obj.count++; //  getting count! setting count!
obj.count; // 2

上面代码中,给空对象设置代理,读写obj的属性都会执行代理行为。

要使得Proxy起作用,必须针对Proxy实例(上例是obj对象)进行操作,而不是针对目标对象(上例是空对象{})进行操作。

如果handler没有设置任何操作,那就等同于直接通向原对象。

let target = {};
var obj = new Proxy(target, {});

obj.name = "jidi"
target.name; // "jidi"

上面代码中,handler是一个空对象,访问obj就等同于访问target

Proxy实例也可以作为其他对象的原型对象。

// 定义Proxy实例
var proxy = new Proxy({}, {
	get: function(){
		return "jidi"
	}
})

// 将Proxy实例设置为原型对象
let x = {};
Object.setPrototypeOf(x, proxy);	
x.y; // "jidi"	

上面代码中,对象x本身没有属性y,当执行x.y时,javaScript引擎就会在对象x的原型链上面去寻找,所以会在proxy对象上读取该属性,导致代理生效,返回jidi

同一个handler,可以设置多个操作。

let handler = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },
  apply: function(target, thisBinding, args) {
    return { value: args[0] };
  },
  construct: function(target, args) {
    return { value: args[1] };
  }
};

var proxy = new Proxy(function(x, y) {
  return x + y;
}, handler);

proxy(1, 2); // {value: 1}
new proxy(1, 2); // {value: 2}
proxy.prototype === Object.prototype; // true
proxy.foo === "Hello, foo"; // true

2. Proxy 实例的方法

Proxy ,一共支持13 种拦截操作 。

2.1 handler.get()

handler.get() 方法用于拦截对象的读取属性操作。

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

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

语法

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

参数
以下是传递给get方法的参数,this上下文绑定在handler对象上.。

  • target:目标对象
  • property:被获取的属性名。
  • receiver:Proxy或者继承Proxy的对象(可选)

返回值
get方法可以返回任何值。

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

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

示例一

let person = {
	name:"jidi"
}

let proxy = new Proxy(person, {
	get: function(target, property, receiver) {
		if (property in target) {
      		return target[property];
    	} else {
     		throw new ReferenceError("该属性不存在!");
   		}
	}
})

proxy.name; // "jidi"
proxy.sex; // Uncaught ReferenceError: 该属性不存在!

示例二

function getMemeberByIndexFromArray(...members) {
	let handler = {
		get:function(target, property, receiver){
			// 得到数组下标
			let index = Number(property);
			if(index < 0 ){ // 下标为负数,从后面取
				property= String(target.length + index);
			}
			return Reflect.get(target, property, receiver);
		}
	}
	let target = [...members];
	return new Proxy(target, handler);
}

let x = getMemeberByIndexFromArray("a", "b", "c");
x[-1]; // "c"

上面代码演示了,读取数组索引为负数的成员。

示例三

let proxy = new Proxy({}, {
	get: function(target, property, receiver){
		return receiver;
	}
})

proxy.a === proxy; // true

// 以proxy为原型构造对象
let x = Object.create(proxy);
x.a === x; // true 

上面代码,演示了handler.get()第三个参数的使用。

示例四

var obj = {};

// 定义空对象属性name,为不可写不可配置
Object.defineProperty(obj, "name", {
	configurable: false, 
	enumerable: false, 
	value: "jidi", 
	writable: false 
})

// get方法返回值与定义时的value不一致
var proxy = new Proxy(obj, {
    get: function(target, property, receiver){
        return 10;
    }
})
proxy.name; // Uncaught TypeError: 'get' on proxy: property 'name' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value

// get方法返回值与定义时的value一致
var proxy = new Proxy(obj, {
    get: function(target, property, receiver){
        return "jidi";
    }
})
proxy.name; // "jidi"

上面代码演示了违反约束的情况。

2.2 handler.set()

handler.set() 方法用于拦截设置属性值的操作。

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

  • 指定属性值: proxy[foo] = barproxy.foo = bar
  • 指定继承者的属性值: Object.create(proxy)[foo] = bar
  • Reflect.set()

语法

var proxy  = new Proxy(target, {
	set: function(target, property, value, receiver){
		...
	}
})

参数
以下是传递给set方法的参数,this上下文绑定在handler对象上。

  • target:目标对象
  • property:被设置的属性名.
  • value:被设置的新值
  • receiver:最初被调用的对象。通常是proxy本身,但handlerset方法也有可能在原型链上或以其他方式被间接地调用(不一定是proxy本身)。

返回值
set方法返回一个布尔值,返回true表示属性设置成功,如果返回false且设置属性操作发生在严格模式下,会抛出TypeError

约束
如果违背以下的约束条件,proxy会抛出一个TypeError

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

示例一

var man= new Proxy({}, {
	set: function(target, property, value, receiver){
		if(property === "age"){
			if(!Number.isInteger(value)){
				throw new TypeError("年龄必须为整数!")
			}
			if(value > 150){
				throw new RangeError("年龄不合法!")
			}
		}
		target[property] = value
		return true
	}
})

man.age = "jidi"; // Uncaught TypeError: 年龄必须为整数! at Object.set
man.age = 1112; // Uncaught RangeError: 年龄不合法!at Object.set 
man.age = 12; // 12

示例二

var proxy = new Proxy({}, {
	set: function(target, property, value, receiver) {
		target[property] = receiver;
		return true;
	}
})

proxy.name = "jidi"
proxy.name === proxy; // true

上面代码,演示了第四个参数receiver的使用。一般情况下,是proxy实例本身。

示例三

var proxy = new Proxy({}, {
	set: function(target, property, value, receiver) {
		target[property] = receiver;
		return true;
	}
})

var obj = {};
// 将proxy设置为空对象的原型对象
Object.setPrototypeOf(obj , proxy);

obj.name = "jidi";
obj.name === obj; // true

上面代码,设置obj.name属性时,对象obj并没有name属性,会在它的原型链上去寻找该属性。obj的原型对象是proxy,它是一个Proxy实例,会触发set方法,此时receiver会指向原始赋值行为对象obj

示例四

"use strict"; // 开启严格模式
var proxy = new Proxy({}, {
	set: function(target, property, value, receiver){
		target[property] = value
		return false
	}
})

proxy.name = "jidi"; // Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'name '

上面代码,演示了在严格模式中,set函数返回false报错的情况。

2.3 handler.apply()

handler.apply() 方法用于拦截函数的调用。

handler.apply() 方法会拦截目标对象的以下操作。

  • proxy(...args)
  • Function.prototype.apply()Function.prototype.call()
  • Reflect.apply()

语法

var proxy = new Proxy({}, {
	apply: function(target, thisArg, argumentsList) {
		...
	}
})

参数
handler.apply()方法接受三个参数,this上下文绑定在handler对象上.。

  • target:目标对象(函数)
  • thisArg:被调用时的上下文对象
  • argumentsList:被调用时的参数数组

返回值
handler.apply()方法可以返回任何值。

约束
如果违反了以下约束,将抛出一个TypeError

  • target必须是一个函数对象。

示例

var target = function(x, y){
		return x + y }
		
var proxy = new Proxy(target, 
	{
		apply: function(target, thisArg, argumentsList) {
			return [...argumentsList]
		}
	}
)

target(1, 3); // 4
proxy(1, 3); //  [1, 3]
proxy.call(null, 1, 2); // [1, 2]
proxy.apply(null, [1, 2]); // [1, 2]

2.4 handler.has()

handler.has() 方法可以看作是针对 in操作的钩子。

handler.has() 方法可以拦截以下操作。

  • 属性查询: property in proxy
  • 继承的属性查询: property in Object.create(proxy)
  • with 检查: with(proxy) { (property ); }
  • Reflect.has()

语法

var proxy = new Proxy(targrt, {
	has: function(target, property) {
		...
	}
})

参数
下面是传递给 has方法的参数, this上下文绑定在handler对象上.。

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

返回值
handler.has() 方法返回一个布尔值。

约束
如果违反了下面这些规则, proxy 将会抛出 TypeError

  • 如果目标对象的某一属性本身不可被配置,则该属性不能够被代理隐藏.
  • 如果目标对象为不可扩展对象,则该对象的属性不能够被代理隐藏

示例一

var target = {
	name: "jidi",
	age: 22
}

var proxy = new Proxy(target, {
	has: function(target, property){
		if(property === "age"){
			return false;
		}
		return property in target;
	}
})

"name" in target; // true
"age" in target; // true

"name" in proxy ; // true
"age" in proxy ; // false

上面代码中,通过proxyage属性隐藏起来,不会被in运算发现。

示例二

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

'a' in p; // Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible

上面代码演示违反约束的情况。

2.5 handler.construct()

handler.construct() 方法用于拦截 new操作符。

该拦截器可以拦截以下操作。

  • new proxy(...args)
  • Reflect.construct()

语法

var proxy = new Proxy(target, {
  construct: function(target, argumentsList, newTarget) {
  	...
  }
});

参数
下面的参数将会传递给construct方法,this绑定在handler上。

  • target:目标对象
  • argumentsList:constructor的参数列表
  • newTarget:最初被调用的构造函数

返回值
construct 方法必须返回一个对象。

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

  • 必须返回一个对象.

示例一

// 定义构造函数
function Person(name, age) {
	if(!(this instanceof Person)) {
		return new Person(name, age);
	}
	this.name = name;
	this.age = age;
}

var proxy = new Proxy(Person, {
	construct: function(target, argumentsList, newTarget) {
		return { ...argumentsList }
	} 
})

new proxy("jidi", 22); // {0: "jidi", 1: 22}

示例二

function Person(name, age) {
	if(!(this instanceof Person)) {
		return new Person(name, age);
	}
	this.name = name;
	this.age = age;
}

var proxy = new Proxy(Person, {
	construct: function(target, argumentsList, newTarget) {
		return newTarget;
	} 
})

new proxy("jidi", 22) === proxy; // true

上面代码,演示了第三个参数newTarget的使用,在上面例子中,newTarget就是proxy

示例三

var proxy = new Proxy({}, {
  construct: function(target, argumentsList, newTarget) {
    return {};
  }
});

new proxy(); // Uncaught TypeError: proxy is not a constructor

上面代码未能正确初始化Proxy。Prxoy在初始化时,target必须是一个有效的constructornew操作符调用。

示例四

function Person(name, age) {
	if(!(this instanceof Person)) {
		return new Person(name, age);
	}
	this.name = name;
	this.age = age;
}

var proxy = new Proxy(Person, {
	construct: function(target, argumentsList, newTarget) {
		return true;
	} 
})

new proxy("jidi", 22); // Uncaught TypeError: 'construct' on proxy: trap returned non-object ('true')

上面代码中,construct方法返回值不是对象,直接报错。

2.6 handler.deleteProperty()

handler.deleteProperty()方法用于拦截对对象属性的 delete 操作。

该方法会拦截以下操作.

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

语法

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

参数
deleteProperty方法将会接受以下参数。this被绑定在handler上。

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

返回值
deleteProperty必须返回一个 Boolean 类型的值,表示了该属性是否被成功删除。

约束
如果违背了以下约束,代理将会抛出一个 TypeError

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

示例一

var person = {
	name:"jidi",
	age: 22
}

var proxy = new Proxy(person, {
	deleteProperty: function(target, property) {
		if(property === "age") {
			throw new ReferenceError("该属性不能删除!")
		}
		return delete target[property]
	}
})

delete proxy.age; // Uncaught ReferenceError: 该属性不能删除! at Object.deleteProperty

delete proxy.name; // true
person; // {age: 22}

示例二

var person  ={};

Object.defineProperty(person, "name", {
	value: "jidi",
	configurable: false
})

var proxy = new Proxy(person, {
	deleteProperty: function(target, property) {
		delete target[property]
		return true;
	}
})

delete proxy .name; // Uncaught TypeError: 'deleteProperty' on proxy: trap returned truish for property 'name' which is non-configurable in the proxy target

上面代码演示了目标对象(target)自身的不可配置(configurable = false)的属性,被deleteProperty方法删除的情况。

2.7 handler.defineProperty()

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

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

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

语法

var proxy = new Proxy(target, {
	defineProperty: function(target, property, descriptor){
		...
	}
})

参数
下列参数将会被传递给 defineProperty 方法。this 绑定在 handler 对象上。

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

返回值
defineProperty 方法必须以一个 Boolean 返回,表示定义该属性的操作成功与否。

约束
如果违背了以下的约定,proxy会抛出 TypeError:

  • 如果目标对象不可扩展,则不能增加目标对象上不存在的属性,否则会报错。
  • 如果目标对象的某个属性不可写或不可配置,则不得改变这两个设置。
  • 在严格模式下,false 作为返回值将会抛出 TypeError 异常.

示例

var person = {
	name: "jidi"
}

var proxy = new Proxy(person, {
	defineProperty: function(target, property, descriptor) {
		if(property === "age") {
			return false;
		}
		Object.defineProperty(target, property, descriptor)
		return true
	}
})

proxy.age = 12; 
person; // { name: "jidi" }

proxy.sex = "男"; 
person; // { name: "jidi", sex: "男" }

上面例子中,如果设置属性agedefineProperty返回false,将不会成功。设置属性sex,目标对象会增加一个属性sex

2.8 handler.getOwnPropertyDescriptor()

handler.getOwnPropertyDescriptor() 方法是 Object.getOwnPropertyDescriptor() 的钩子。

该方法可以拦截以下操作:

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

语法

var proxy = new Proxy(target, {
  getOwnPropertyDescriptor: function(target, property) {
  	...
  }
});

参数
下列参数将会被传递给 getOwnPropertyDescriptor 方法。this 绑定在 handler 对象上。

  • target:目标对象
  • property:返回属性名称的描述

返回值
getOwnPropertyDescriptor方法必须返回一个属性描述对象或 undefined

示例

var proxy = new Proxy({ name: "jidi"}, {
  getOwnPropertyDescriptor: function(target, property) {
    return { configurable: true, enumerable: true, value: 10 };
  }
});

Object.getOwnPropertyDescriptor(proxy, "name"); // {value: 10, writable: false, enumerable: true, configurable: true}

2.9 handler.getPrototypeOf()

handler.getPrototypeOf方法主要用来拦截获取对象原型。

该方法会拦截下面这些操作:

  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • __proto__
  • Object.prototype.isPrototypeOf()
  • instanceof

语法

var proxy = new Proxy(target, {
  getPrototypeOf(target) {
  	...
  }
});

参数
下列参数将会被传递给 getPrototypeOf 方法。this 绑定在 handler 对象上。

  • target:被代理的目标对象

返回值
getPrototypeOf 方法的返回值必须是一个对象或者 null

约束
如果违背了以下的约定,proxy会抛出 TypeError:

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

示例

var proto = {};
var proxy = new Proxy({}, {
  getPrototypeOf(target) {
    return proto;
  }
});
Object.getPrototypeOf(proxy ) === proto // true

2.10 handler.isExtensible()

handler.isExtensible()用于拦截对对象执行Object.isExtensible()

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

  • Object.isExtensible()
  • Reflect.isExtensible()

语法

var proxy = new Proxy(target, {
  isExtensible: function(target) {
  	...
  }
});

参数
下列参数将会被传递给isExtensible方法。 this绑定在handler对象上。

  • target:目标对象

返回值
isExtensible方法必须返回一个 Boolean值或可转换成Boolean的值。

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

  • Object.isExtensible(proxy) 必须同Object.isExtensible(target)返回相同值。也就是必须返回true或者为true的值,返回false和为false的值都会报错。

示例一

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

Object.isExtensible(proxy); // true

示例二

var proxy = new Proxy({}, {
  isExtensible: function(target) {
    return false;
  }
});

Object.isExtensible(proxy ); // Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true') at Function.isExtensible

2.11 handler.isExtensible()

ownKeys方法用来拦截对象自身属性的读取操作。

该方法可以拦截以下操作:

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • Reflect.ownKeys()
  • for...in循环

语法

var proxy = new Proxy(target, {
  ownKeys: function(target) {
  	...
  }
});

参数
下面的参数被传递给ownKeysthis被绑定在handler上。

  • target:目标对象

返回值
ownKeys 方法必须返回一个可枚举对象

约束
如果违反了下面的约束,proxy将抛出错误 TypeError:

  • ownKeys 的结果必须是一个数组.
  • 数组的元素类型要么是一个 String ,要么是一个Symbol
  • 结果列表必须包含目标对象的所有不可配置、自有属性的key
  • 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有属性的key,不能有其它值

示例

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

Object.getOwnPropertyNames(proxy );  // [ 'a', 'b', 'c' ]         

2.12 handler.preventExtensions()

handler.preventExtensions()方法用于设置对Object.preventExtensions()的拦截。

该方法可以拦截以下操作:

  • Object.preventExtensions()
  • Reflect.preventExtensions()

语法

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

参数
以下参数传递给 preventExtensions方法. this会绑定到这个handler

  • target: 所要拦截的目标对象.

返回值
preventExtensions 方法返回一个布尔值

约束
如果违反了下列约束, proxy会抛出一个 TypeError:

  • 如果Object.isExtensible(proxy)falseObject.preventExtensions(proxy)只能返回true

2.12 handler.setPrototypeOf()

handler.setPrototypeOf() 方法主要用来拦截 Object.setPrototypeOf()

这个方法可以拦截以下操作:

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

语法

var proxy = new Proxy(target, {
  setPrototypeOf: function(target, prototype) {
  	...
  }
});

参数
以下参数传递给 setPrototypeOf方法

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

返回值
如果成功返回 true,否则返回 false

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

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

3 Proxy.revocable()

Proxy.revocable() 方法可以用来创建一个可撤销的代理对象。一旦某个代理对象被撤销,它将变得几乎完全不可调用,在它身上执行任何的可代理操作都会抛出TypeError 异常。

语法

Proxy.revocable(target, handler);

参数
以下参数传递给 revocable方法:

  • target:将用 Proxy 封装的目标对象。可以是任何类型的对象。
  • handler:一个对象,其属性是一批可选的函数,这些函数定义了对应的操作被执行时代理的行为。

返回值
返回一个对象。该对象结构为{"proxy": proxy, "revoke": revoke},其中:

  • proxy:表示新生成的代理对象本身,和用 new Proxy(target, handler)创建的代理对象没什么不同,只是它可以被撤销掉。
  • revoke:撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。

示例

// 可撤销的proxy对象
var revocable = Proxy.revocable({}, {
  get(target, name) {
    return "[[" + name + "]]";
  }
});
var proxy = revocable.proxy;
proxy.name; // "[[name]]"

// 执行撤销方法
revocable.revoke();

proxy.sex = "男"; // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.age; // Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked

4 参考链接

本篇博文是我自己学习笔记,原文请参考:ECMAScript 6 入门MDN网站
如有问题,请及时指出!
欢迎沟通交流,邮箱:jidi_jidi@163.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值