get方法
该方法用来代理属性的读取操作。当用户希望访问对象的某个属性时,会直接触发这个方法,而不是默认的读取属性方法。
栗子:
var car = {
brank: "Benz"
};
var proxy = new Proxy(car, {
get: function(target, property) {
if (property in target) {
return target[property];
} else {
throw new ReferenceError("Property \"" + property + "\" does not exist.");
}
}
});
proxy.brank // "Benz"
proxy.year // Error:Property year does not exist.
本栗中,如果访问的属性存在,将会返回属性值,但是如果属性不存在,则会抛出错误。在平常的情况下,如果访问的属性不存在,只是会返回undefined,不会报错。
利用proxy,可以实现函数的链式调用:(代码来自阮一峰大神ES6博客)
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;
pipe(3).double.pow.reverseInt.get; // 63
代码分析:如果想要实现函数的链式调用,就需要将可以调用某函数的对象作为函数的返回值。在上面这个栗子中,对oproxy的属性(方法其实也可以看做属性)的访问总会被代理函数get拦截,并根据我们部署的代码,在函数的最后返回oproxy对象。这样,对于这个对象的方法的访问,就可以链式不断的调用下去,因为每个方法调用后,都会返回原对象。
限制:如果对象的属性被设置为configurable:false或者writable:false,那么这时候,get属性将不能被代理,通过Proxy访问该属性会报错。
const cantChangeObj = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
},
});
const handler = {
get(target, propKey) {
return 'geting'+propKey+'from'+target;
}
};
const proxy = new Proxy(cantChangeObj, handler);
proxy.foo
// TypeError: Invariant check failed
set方法
set方法用来代理对象某个属性的赋值操作。通过set,我们就可以获得对属性值的类型、大小等等的掌控能力。
let validate = {
set: function(obj, prop, value) {
if (prop === 'year') {
if (!Number.isInteger(value)) {
throw new TypeError('The year must be an integer');
}
if (year < 2000) {
throw new RangeError('The year seems invalid');
}
}
// 对于year以外的属性,可以直接保存,或使用if增加其他判断
obj[prop] = value;
}
};
let car = new Proxy({}, validate);
car.year = 2001;
car.year // 2001
car.year = 'Benz' // 报错
car.year = 1999 // 报错
上述代码对car对象的year属性设置了范围:首先必须是数字,其次,必需要在2000以后(太老旧的车不能通过验证)。
应用1: 对于set方法我们还可以将数据与DOM元素绑定,每当元素值发生变化,DOM也就相应的发生变化。
应用2: 有些对象的内部属性,是我们不希望他人有意或无意修改的,通常会用下划线“_”开头,set方法可以帮助我们切实的阻止他人对此类对象的访问。
var 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(`Invalid attempt to ${action} private "${key}" property`);
}
}
var target = {};
var proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property
限制:同get一样,如果目标对象自身的某个属性,不可写也不可配置,那么set不得改变这个属性的值,只能返回同样的值,否则报错。
apply方法
apply可以代理call和apply两个操作(因为这两个操作本质上做的事情都一样嘛)。
apply方法的三个参数:目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
一个最简单的栗子:
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 the proxy"
当p被作为函数调用的时候(p()),代理方法apply就会生效,返回的是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
总上面几个代码我们可以总结出——apply代理的途径包括:1、当直接执行proxy函数时,2、proxy调用call或者apply时。
has方法
has方法将作为方法HasProperty的代理,HasProperty的功能是判断对象是否具有某个属性。
应用1: 使用has方法,可以帮助我们隐藏某些属性,不被in运算符发现。
var handler = {
has (target, key) {
if (key === 'privateProp') {
return false;
}
return key in target;
}
};
var target = { privateProp: 'myPassword', prop: 'foo' };
var proxy = new Proxy(target, handler);
'privateProp' in proxy // false
需要注意的是,has方法代理的方法是HasProperty(在没有任何其他配置的前提下,一个对象自身的属性和继承的属性都可以被此方法找到),而不是HasOwnProperty(此方法尽可以找到属于对象自身的属性)。还需要注意,has只对in运算符有效,而不是for…in…。
限制:如果原对象被设置为不可配置/禁止扩展,那么,该操作就会报错:
var obj = { a: 10 };
Object.preventExtensions(obj);
var p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});
'a' in p // TypeError is thrown