ES6 学习笔记(十二)代理器Proxy的简单使用

发布于 2021-06-15  327 次阅读


1、前言

以前在学习react时做了个仿手机端的QQ音乐项目。当时的数据是通过proxy代理的QQ音乐数据接口,直接写在package.json里面。Proxy 对象(Proxy)是 ES6的特性,只是不太常用。

2、基本内容

2.1 含义

正如MDN上所说,Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
通俗的讲,Proxy是目标对象的一个代理器。即:在目标对象之前架设一层“拦截”,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写

2.2 语法

// 直接通过new Proxy实例一个代理对象
const p = new Proxy(target, handler)

其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来表示拦截行为。

常见的拦截项属性:

属性 作用
has 拦截 in 操作。比如,你可以用它来隐藏对象上某些属性。
get 来拦截读取操作。比如当试图读取不存在的属性时,你可以用它来返回默认值
set 用来拦截赋值操作。比如给属性赋值的时候你可以增加验证的逻辑,如果验证不通过可以抛出错误。
apply 用来拦截函数调用操作。比如,你可以把所有的函数调用都包裹在 try/catch 语句块中
ownKeys 用于拦截Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法

更多的拦截项见MDN

举个例子:

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;  // 对空对象的count属性进行操作,调用的set方法进行赋值
++obj.count;
console.log(obj);

输出结果:
setting count
getting count
setting count
{ count: 2 }

注意:
要使Proxy起作用,就要对Proxy实例进行操作而不是针对目的对象进行操作,如:

// 代理包围需要代理的对象
let p = new Proxy(
    {},
    {
        get(target, propKey) {
            return 22
        }
    }
)
console.log(p.name);
console.log(p.age);

输出结果:
22
22

// 需要代理的对象的反包围(委托)
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 对象属性的拦截

同一个拦截器对象,可以设置拦截多个操作,如:

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 实现数组或对象通过负索引值获取相应值

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

<!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

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/Iamflame" }, "here"),
    ". I like:",
    dom.ul(
        {},
        dom.li({}, "html5"),
        dom.li({}, "css3"),
        dom.li({}, "js"),
        dom.li({}, "vue"),
        dom.li({}, "node")
    )
);
document.body.appendChild(el);

运行结果:
在这里插入图片描述


活的像诗一样