ES6之代理与反射应用

22 阅读3分钟

ES6之代理与反射应用

//创建一个观察者
function observer(target) {
    const div = document.getElementById("container")
    // const obj = {}
    // const props = Object.keys(target)
    // for (const prop of props) {
    //     Object.defineProperty(obj, prop, {
    //         get() {
    //             console.log(`get ${prop}`)
    //             return target[prop]
    //         },
    //         set(value) {
    //             console.log(`set ${prop}`)
    //             target[prop] = value
    //             render()
    //         },
    //         enumerable: true,
    //     })
    // }
    const proxy = new Proxy(target, {
        get(target, prop) {
            console.log(`get ${prop}`)
            return Reflect.get(target, prop)
            console.log(Reflect.get(target, prop),'get')
        },
        set(target, prop, value) {
            console.log(`set ${prop}`)
            Reflect.set(target, prop, value)
            render()
        },
        enumerable: true,
    })
    function render() {
        let html = "";
        // for (const prop in obj) {
        //     html += `<p><span>${prop}:</span>
        //     <span>${obj[prop]}</span></p>`;
        // }
        for (const prop in target) {
            html += `<p><span>${prop}:</span>
            <span>${target[prop]}</span></p>`;
        }
        div.innerHTML = html;
    }
    // return obj;
    return proxy;
}
const obj = observer({
    a: 1,
    b: 2
})
// obj.a = 3;

这是观察者模式,对象内的属性发生改变,对应的元素也会改变。
一种是通过 for-of 循环拿到属性去进行 get 和 set 设置,为了进行实时显示渲染,必须在存取器属性中去操作然后渲染出来。在第一种方法中,必须使用两个对象,第二个对象会增加内存占用,并且当我们进行属性增加时出现问题,因为一旦设置好就不可以随意增加。
另一种是通过代理的方式。这样只需要一个对象就够了,而且由于是在 js 底层操作,任何赋值和读取的操作都会触发代理,触发里面的反射。所以可以随意增加属性,不会收到限制。

class User {

}
//构造函数代理函数
function ConstructorProxy(target, ...propNames) {
    //返回一个代理
    return new Proxy(target, {
        construct(target, argumentsList) {
            //调用代理传入的参数
            const obj = Reflect.construct(target, argumentsList)
            //创建代理的参数
            propNames.forEach((name, i) => {
                console.log(name, argumentsList[i])
                //创建代理的参数作为属性名,调用代理的参数作为属性值
                obj[name] = argumentsList[i];
            })
            return obj;
        }
    })
}
const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age")
const obj = new UserProxy("ming", "xiao", 18)
console.log(obj, 'obj')
class Monster {

}
const MonsterProxy = ConstructorProxy(Monster, "attack", "defence", "hp", "rate", "name")
const m = new MonsterProxy(10, 20, 100, 30, "怪物")
console.log(m);

我们可以通过代理的方式控制函数调用的过程,从而控制一个类中属性的创建。
首先创建一个代理函数,调用这个函数就可以返回一个代理。在代理的内部通过 construct 控制调用,只有在返回的代理调用时,它才会起作用。
这时会有两个参数,第一个参数是创建代理时传入的参数,第二个参数是调用这个代理时传入的参数。代理调用的时候返回一个对象。我们将前面的参数作为对象的属性名,后面的参数作为对象的属性值。通过返回这个对象,我们就拿到了添加属性后的对象。
我们可以任意添加参数数量,只要前后参数对应就可以。这种方式是一种通用模型,可以用在多种函数上。

function sum(a, b) {
    return a + b;
}
//创建一个可以验证参数的函数
function validatorFunction(func, ...types){
    return new Proxy(func, {
        apply(target, thisArgument, argumentsList){
            types.forEach((t, i) => {
                const arg = argumentsList[i]
                if(typeof arg !== t){
                    throw new TypeError(`第${i+1}个参数${argumentsList[i]}不匹配类型${t}`)
                }
            })
            return Reflect.apply(target, thisArgument, argumentsList)
        }
    })
}

//使用传统方法高阶函数实现
// function validatorFunction(func, ...types) {
//     return function (...argumentsList) {
//         types.forEach((t, i) => {
//             const arg = argumentsList[i]
//             if (typeof arg !== t) {
//                 throw new TypeError(`第${i + 1}个参数${argumentsList[i]}不匹配类型${t}`)
//             }
//         })
//         return func(...argumentsList)
//     }
// }
const sumProxy = validatorFunction(sum, "number", "number")
console.log(sumProxy(1, '2'))

有时候我们想验证一个函数传入的参数是否符合规定的类型,可以使用代理的方式实现。返回一个代理,当代理被调用的时候,会进行创建代理时的设定类型和调用代理时的传入参数的类型进行比较。如果不一致,就会抛出一个对应的错误。最后返回调用函数的返回值。
代理就是对反射的再次重写。