1、前言
以前在学习react时做了个仿手机端的QQ音乐项目。当时的数据是通过proxy代理的QQ音乐数据接口,直接写在package.json里面。Proxy 对象(Proxy)是 ES6的特性,只是不太常用。
2、基本内容
2.1 含义
正如MDN上所说,Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
通俗的讲,Proxy是目标对象的一个代理器。即:在目标对象之前架设一层“拦截”,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写
2.2 语法
1 2
| const p = new Proxy(target, handler)
|
其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来表示拦截行为。
常见的拦截项属性:
| 属性 |
作用 |
| has |
拦截 in 操作。比如,你可以用它来隐藏对象上某些属性。 |
| get |
来拦截读取操作。比如当试图读取不存在的属性时,你可以用它来返回默认值 |
| set |
用来拦截赋值操作。比如给属性赋值的时候你可以增加验证的逻辑,如果验证不通过可以抛出错误。 |
| apply |
用来拦截函数调用操作。比如,你可以把所有的函数调用都包裹在 try/catch 语句块中 |
| ownKeys |
用于拦截Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法 |
更多的拦截项见MDN
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let obj = new Proxy( {}, { get: function (target, propKey, receiver) { console.log(`getting ${propKey}`); return Reflect.get(target, propKey, receiver) }, set: function (target, propKey, value, receiver) { console.log(`setting ${propKey}`); return Reflect.set(target, propKey, value, receiver) } } ) obj.count = 1; ++obj.count; console.log(obj);
|
输出结果:
setting count
getting count
setting count
{ count: 2 }
注意:
要使Proxy起作用,就要对Proxy实例进行操作而不是针对目的对象进行操作,如:
1 2 3 4 5 6 7 8 9 10 11
| let p = new Proxy( {}, { get(target, propKey) { return 22 } } ) console.log(p.name); console.log(p.age);
|
输出结果:
22
22
1 2 3 4 5 6 7 8 9 10 11
| let obj = { proxy: new Proxy(this, { get(target, prop) { console.log(`getting ${prop}`); return target[prop] ?? "Not Found" } }) } let x = obj.proxy.a console.log(x);
|
输出结果:
getting a
Not Found
2.3 实例
2.3.1 实例1 对象属性的拦截
同一个拦截器对象,可以设置拦截多个操作,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| let handler = { get(target, name) { if (name == "prototype") { return Object.prototype; } return `Hello,${name}` }, apply(target, thisBinding, args) { return args[0] }, construct(target, args) { let obj = new target(...args) obj.name = "foo" obj.value = args[1] return obj } } let fproxy = new Proxy(function (x, y) { return x + y }, handler) console.log(fproxy(1, 2)); console.log(new fproxy(1, 2)); console.log(fproxy.prototype == Object.prototype); console.log(fproxy.foo);
|
输出结果:
1
{ name: ‘foo’, value: 2 }
true
Hello,foo
2.3.2 实例2 实现数组或对象通过负索引值获取相应值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function createArray(...elements) { let handler = { get(target, propKey, receiver) { let index = Number(propKey) if (index < 0) { propKey = String(target.length + index) } return Reflect.get(target, propKey, receiver) } } let target = [] target.push(...elements) return new Proxy(target, handler) } let arr = createArray("a", "b", "c") let obj = createArray({ name: "Tom", age: 22 }) console.log(arr[-1]); console.log(obj[-1]);
|
输出结果:
c
{ name: ‘Tom’, age: 22 }
get的receiver参数,总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例
2.3.3 实例3 实现生成 DOM 节点的通用函数dom
dom.html
1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>dom</title> </head> <body> </body> <script src="dom.js"></script> </html>
|
dom.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const dom = new Proxy({}, { get(target, property) { return function (attrs = {}, ...children) { const el = document.createElement(property); for (let prop of Object.keys(attrs)) { el.setAttribute(prop, attrs[prop]); } for (let child of children) { if (typeof child === "string") { child = document.createTextNode(child); } el.appendChild(child); } return el; } } }) const el = dom.div({}, "Hello, my blog is ", dom.a({ href: "https://blog.csdn.net/likepoems" }, "here"), ". I like:", dom.ul( {}, dom.li({}, "html5"), dom.li({}, "css3"), dom.li({}, "js"), dom.li({}, "vue"), dom.li({}, "node") ) ); document.body.appendChild(el);
|
运行结果:
