理解ES6的Proxy
Opened this issue · 0 comments
Proxy是ES6中的新特性之一。Proxy 用于修改某些操作的默认行为,比如属性get\set,属性枚举,函数调用等。借助Proxy,我们可以对javascript元级别进行编程,也即元编程。
Proxy可以理解成是对目标对象的一种拦截,或者代理,对目标对象的访问都必须通过这层拦截。所以可以在拦截这一层做过滤、改写等等操作。
Proxy基础使用
ES6原生提供Proxy构造函数,用于生成Proxy实例。该构造函数需要传递两个参数:
- target 目标对象,即要代理的对象
- handler 包含一系列拦截行为的配置对象
一个简单的拦截读取属性行为的例子:
let target = {
name: 'allin';
};
console.log(target.name); // allin
let p = new Proxy(target, {
get: function(target, property) {
return 'zoe'; // 拦截并返回一个新的返回值
}
});
p.name; // zoe
上面的例子中,我实例化了一个Proxy实例,对target对象的属性访问进行了拦截,其中,handler配置对象有一个get方法,get方法的两个参数分别是目标对象和所要访问的属性。因此,访问任何属性都会返回'zoe'。
Proxy支持的拦截操作
Proxy支持的拦截操作有很多,我只列出一些常用的拦截行为,具体的见Proxy-MDN。
-
get(target, property, receiver)
该方法用于拦截属性的读取,最后的receiver参数是可选参数,当target对象设置了propKey属性的get函数,receiver对象会绑定get函数的this对象。 -
set(target, property, receiver)
该方法用于拦截对象属性的设置。例如:
let handler = { set: function(obj, prop, value) { if (prop === 'age') { if(!Number.isInteger(value)) { throw new Error('年龄必须是整数!') } } obj[prop] = value; } } let person = new Proxy({}, handler); person.name = 'allin'; person.age = 'old'; // ERROR person.age = 18; // GREAT person.age; //18
上面的例子我们设置了set方法,用于校验age属性是否是整数,如果不是则抛出类型错误,这种数据校验在对一个对象赋值时是非常有用的。
-
defineProperty(target, propKey, propDesc)
该方法用于拦截Object.defineProperty。例子:
var p = new Proxy({}, { defineProperty: function(target, key, des) { return false; } }); Object.defineProperty(p, name, { value: 'allin' }); // Uncaught TypeError: 'defineProperty' on proxy: trap returned falsish for property ''
上面例子中定义了对defineProperty的拦截,导致后面在添加新属性时报错。
-
apply(target, object, args)
该方法用于拦截Proxy实例作为函数调用的操作。比如proxy(...args), proxy.call(obj, ...args), proxy.apply(...)。例子:
var target = function() { return 'target' }; var p = new Proxy(target, { apply: function(target, ctx, args) { return 'proxy'; } }); p() == target(); // false
上面例子中,定义了apply拦截符,当调用p时,就会返回新的字符串。
-
construct(target, args, proxy)
该方法用于拦截Proxy实例作为构造函数调用的操作。比如 new Proxy(...)。例子:
var p = new Proxy(function() {}, { construct: function(target, args) { return { value: args[0] * 2 } } }); new p(1).value; // 2
上面的例子中,使用了construct拦截了new命令,使其实例化时返回一个新的对象。
Proxy 的用途
Proxy的功能非常强大,配合另一个ES6特性Reflect。Proxy能用来数据验证,数据绑定,数据观察等等。下面举个简单的例子:
const { METHODS } = require('http')
const api = new Proxy({},
{
get(target, propKey) {
const method = METHODS.find(method =>
propKey.startsWith(method.toLowerCase()))
if (!method) return
const path =
'/' +
propKey
.substring(method.length)
.replace(/([a-z])([A-Z])/g, '$1/$2')
.replace(/\$/g, '/$/')
.toLowerCase()
return (...args) => {
const finalPath = path.replace(/\$/g, () => args.shift())
const queryOrBody = args.shift() || {}
// You could use fetch here
// return fetch(finalPath, { method, body: queryOrBody })
console.log(method, finalPath, queryOrBody)
}
}
}
)
// GET /
api.get()
// GET /users
api.getUsers()
// GET /users/1234/likes
api.getUsers$Likes('1234')
// GET /users/1234/likes?page=2
api.getUsers$Likes('1234', { page: 2 })
// POST /items with body
api.postItems({ name: 'Item name' })
// api.foobar is not a function
api.foobar()
上面例子中使用Proxy实现了一个小型请求库,是不是非常有趣~