ES6 入门教程
ECMAScript 6 入门
作者:阮一峰
本文仅用于学习记录,不存在任何商业用途,如侵删
文章目录
- ES6 入门教程
- 15 Proxy
- 15.2 Proxy 实例的方法
- 15.2.2 set()
- 15.2.3 apply()
15 Proxy
15.2 Proxy 实例的方法
拦截方法的详细介绍。
15.2.2 set()
set
方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
假定Person
对象有一个age
属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy
保证age
的属性值符合要求。
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;
return true;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
上面代码中,由于设置了存值函数set
,任何不符合要求的age
属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。
利用set
方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。
有时,我们会在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合get
和set
方法,就可以做到防止这些内部属性被外部读写。
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(`Invalid attempt to ${action} private "${key}" property`);
}
}
const target = {};
const 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
上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。
下面是set
方法第四个参数的例子。
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
return true;
}
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
proxy.foo === proxy // true
上面代码中,set
方法的第四个参数receiver
,指的是原始的操作行为所在的那个对象,一般情况下是proxy
实例本身,请看下面的例子。
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
return true;
}
};
const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);
myObj.foo = 'bar';
myObj.foo === myObj // true
上面代码中,设置myObj.foo
属性的值时,myObj
并没有foo
属性,因此引擎会到myObj
的原型链去找foo
属性。myObj
的原型对象proxy
是一个 Proxy 实例,设置它的foo
属性会触发set
方法。
这时,第四个参数receiver
就指向原始赋值行为所在的对象myObj
。
注意,如果目标对象自身的某个属性不可写,那么set
方法将不起作用。
const obj = {};
Object.defineProperty(obj, 'foo', {
value: 'bar',
writable: false
});
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = 'baz';
return true;
}
};
const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"
上面代码中,obj.foo
属性不可写,Proxy 对这个属性的set
代理将不会生效。
注意,set
代理应当返回一个布尔值。严格模式下,set
代理如果没有返回true
,就会报错。
'use strict';
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
// 无论有没有下面这一行,都会报错
return false;
}
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'
上面代码中,严格模式下,set
代理返回false
或者undefined
,都会报错。
15.2.3 apply()
apply
方法拦截函数的调用、call
和apply
操作。
apply
方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this
)和目标对象的参数数组。
var handler = {
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 the proxy"
上面代码中,变量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
调用),就会被apply
方法拦截。
另外,直接调用Reflect.apply
方法,也会被拦截。
Reflect.apply(proxy, null, [9, 10]) // 38