尤大大的vue3.0即将到来,虽然学不动了,但是还要学的啊,据说vue3.0是基于proxy来进行对值进行拦截并操作,所以es6的proxy也是要学习一下的。
Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等) --摘自MDN
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。 --摘自阮一峰的ES6入门
Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
Proxy 也可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
总结来说:Proxy对象就是要在目标对象上设置自定义的规则和方法,让它按照自己定义的规则去实行某些操作。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例,所以可以按照构造函数创建对象的形式去实例化一个Proxy对象。
var proxy = new Proxy({},{})
console.log(proxy) // Proxy{}
注意点:
1 实例化一个Proxy对象时,必须要传两个参数对象,否则会报错:Uncaught TypeError: Cannot create proxy with a non-object as target or handler,不能创建没有对象的proxy对象。
2 传两个空对象时,默认的是简单声明了一个Proxy实例,(好像没啥卵用……)
参数对象解释:
第一个参数:target,目标对象,是你要代理的对象.它可以是JavaScript中的任何合法对象.如: (数组, 对象, 函数等等)
tip:
var arr = []
var obj = {}
var Person = class{}
var foo = function (){}
console.log(Person instanceof Object) // true
console.log(foo instanceof Object) // true
console.log(arr instanceof Object) // true
console.log(obj instanceof Object) // true
第二个参数:handler,配置对象,用来定制拦截行为,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
Proxy支持的拦截操作,有13种,使用方法可以参考 阮一峰的ES6入门
var proxy = new Proxy({},handler)
这样直接代表着,拦截的对象是空的,所以直接对proxy对象进行操控。
var target = {};
var handler = {
get(target,propKey,receiver){
return 'peter'
}
};
var proxy = new Proxy(target, handler);
proxy.name = 'tom';
console.log(proxy.name) // tom
console.log(target.name) // undefined
上面的代码说明了:target是个空对象,但是操作了proxy,也影响不了target
ps:要使得Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象进行操作
var proxy = new Proxy(target,{})
handler没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.name = 'peter';
console.log(proxy.name) // peter
console.log(target.name) // peter
上面的代码说明了:handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target
Proxy实例化的对象默认带有get和set方法。也可以在这些基础上进行拦截操作,其他的13种方法也是如此。
receiver:proxy实例
例子:
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.");
}
}
});
proxy.name // "张三"
proxy.age // Property "age" does not exist.
参考阮一峰的例子,上述说明了,如果输入目标函数不存在的属性,就直接报错。
receiver:Proxy实例本身
例子:
var target = {}
var handler = {
set(target, propKey, value, receiver) {
if (typeof value !== 'string') {
target[propKey] = String(value);
}else{
target[propKey] = value;
}
}
}
var proxy = new Proxy(target, handler)
proxy.name = 'peter'
proxy.age = 25
console.log(typeof proxy.name) // string
console.log(typeof proxy.age) // string
上面例子就是拦截对象是不是字符串,不是字符串的话会强制转化为字符串。
arguments:目标对象的参数数组
例子:
var target = function(a,b){
return 10 + a + b
}
var handler = {
apply(target,context,arguments){
arguments[0] = 10
arguments[1] = 20
return arguments.reduce(function(prev, curr, idx, arr){
return prev + curr;
});
}
}
var proxy = new Proxy(target,handler)
console.log(proxy(1,2)) // 30
上面的例子,就是目标函数是要传两个参数,并且返回之和,拦截目标做的就是改变目标对象的参数,并且求和,所以这样写触发了apply方法,返回30,而不是13
key: 需查询的属性名,是一个字符串!!!!!
例子:
var target = {
name: 'peter',
age:25
}
var handler = {
has(target,key){
return key in target;
}
}
var proxy = new Proxy(target,handler)
console.log('age' in proxy) // true
console.log('colors' in proxy) // false
上面的例子是典型的has的方法,判断所要查询的属性名是不是在目标对象上的属性名,返回布尔值。
ps:has拦截对for...in循环不生效。
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
由此可见,是针对构造函数而言的,对目标对象的构造函数进行拦截。
拓展:
Object.defineProperty(),声明对象的属性,参数说明和上述一样
例子:
var obj = {}
Object.defineProperty(obj, "key", {
enumerable: false,
configurable: false,
writable: false,
value: "static"
});
例子:
var target = {
name: 'peter',
age:25
}
var handler = {
defineProperty(target,key,descriptor){
if(key === 'color'){
throw new Error('不能定义颜色')
}
Object.defineProperty(target, key, descriptor)
// return true
}
}
var proxy = new Proxy(target,handler)
var descriptor = {
writable : true,
enumerable : true,
configurable : true
}
descriptor.value = 'sport'
Object.defineProperty(proxy, 'favor', descriptor)
console.log(proxy.favor) // sport
descriptor.value = 'red'
Object.defineProperty(proxy, 'color', descriptor) // 不能定义颜色
console.log(proxy.color)
如果目标对象不可扩展(non-extensible),则defineProperty不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则defineProperty方法不得改变这两个设置。
例子:
var target = { _prop: 'foo' };
var handler = {
deleteProperty (target, key) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${target} private "${key}" property`);
}
delete target[key];
return true;
}
};
var proxy = new Proxy(target, handler);
delete proxy._prop // Error: Invalid attempt to delete private "_prop" property
上面代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错。
注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。
例子:
o = { bar: 42 };
d = Object.getOwnPropertyDescriptor(o, "bar");
console.log(d)
// d {
// configurable: true,
// enumerable: true,
// value: 42,
// writable: true
// }
例子:
var target = { _foo: 'bar', baz: 'tar' };
var handler = {
getOwnPropertyDescriptor (target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
}
};
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 }
上述说明:对于第一个字符为下划线的属性名会返回undefined。
例子:
function Baz() {}
var baz = new Baz();
console.log(Baz.prototype.isPrototypeOf(baz)); // true
例子:
var proto = {};
var obj = Object.create(proto);
Object.getPrototypeOf(obj) === proto; // true
例子:
var proto = {};
var p = new Proxy({}, {
getPrototypeOf(target) {
return proto;
}
});
Object.getPrototypeOf(p) === proto // true
上面代码中,getPrototypeOf方法拦截Object.getPrototypeOf(),返回proto对象。是个布尔值,true是可以添加,false不可以添加
例子:
let obj = {
name: 'peter',
age:25
}
console.log(Object.isExtensible(obj)) // true
例子:
var p = new Proxy({}, {
isExtensible: function(target) {
console.log("called");
return true;
}
});
Object.isExtensible(p) // called
这个方法有一个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则就会抛出错误。
即:Object.isExtensible(proxy) === Object.isExtensible(target)
例子
let obj = {
name: 'peter',
age:25
}
Object.preventExtensions(obj)
obj.color = 'red' // object is not extensible
例子:
let proto = {例子
var handler = {
setPrototypeOf (target, proto) {
throw new Error('Changing 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的原型对象,就会报错。例子:
function Person(){
this.name = 'peter'
this.age = 26
}
Person.prototype={
address:"北京"
}
let peter=new Person();
console.log(Object.getOwnPropertyNames(peter)); // ['name','age']
例子:
var obj = {};
var a = Symbol("a");
var b = Symbol.for("b");
obj[a] = "localSymbol";
obj[b] = "globalSymbol";
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols.length); // 2
console.log(objectSymbols) // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0]) // Symbol(a)
例子:
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // ['0', '1', '2']
例子之一:
var p = new Proxy({}, {
ownKeys: function(target) {
return ['a', 'b', 'c'];
}
});
Object.getOwnPropertyNames(p)
// [ 'a', 'b', 'c' ]
返回一个可取消的 Proxy 实例
例子:
var revocable = Proxy.revocable({}, {
get(target, propKey) {
return propKey + '啦啦啦';
}
});
var proxy = revocable.proxy;
console.log(proxy.foo) // foo啦啦啦
revocable.revoke(); // 执行撤销方法
console.log(proxy.foo); // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
刚开始学proxy时,都是懵逼的状态,阮一峰ES6入门一开始看代码有点难度,所以我一边看一边查资料,里面关于Object对象的方法居多,也顺便学习了一下,知识很多,需要日常回顾加深理解,经过查阅,对于代理模式 Proxy 的作用主要体现在三个方面:1拦截和监视外部对对象的访问,2降低函数或类的复杂度,3在复杂操作前对操作进行校验或对所需资源进行管理,目前还没有大量运用,最常见的应该是拦截和监听对象的变化吧。
我把笔记放到GitHub里了,如需要可以去看看,有什么不对的地方,欢迎指正,大家一起进步加油。
阮一峰ES6入门
详解ES6中的代理模式——Proxy
MDN
[译] 实例解析 ES6 Proxy 使用场景
原文:https://www.cnblogs.com/sqh17/p/10755165.html