12.代理和反射

178 阅读4分钟

Reflect 反射

  1. Reflect是什么?

Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层功能

由于它类似于其他语言的反射,因此取名为Reflect

  1. 它可以做什么?

使用Reflect可以实现诸如 属性的赋值与取值、调用普通函数、调用构造函数、判断属性是否存在与对象中 等等功能

  1. 这些功能不是已经存在了吗?为什么还需要用Reflect实现一次?

有一个重要的理念,在ES5就被提出:减少魔法、让代码更加纯粹

这种理念很大程度上是受到函数式编程的影响

ES6进一步贯彻了这种理念,它认为,对属性内存的控制、原型链的修改、函数的调用等等,这些都属于底层实现,属于一种魔法,因此,需要将它们提取出来,形成一个正常的API,并高度聚合到某个对象中,于是,就造就了Reflect对象

因此,你可以看到Reflect对象中有很多的API都可以使用过去的某种语法或其他API实现。

  1. 它里面到底提供了哪些API呢?
  • Reflect.set(target, propertyKey, value): 设置对象target的属性propertyKey的值为value,等同于给对象的属性赋值
  • Reflect.get(target, propertyKey): 读取对象target的属性propertyKey,等同于读取对象的属性值
  • Reflect.apply(target, thisArgument, argumentsList):调用一个指定的函数,并绑定this和参数列表。等同于函数调用
  • Reflect.deleteProperty(target, propertyKey):删除一个对象的属性
  • Reflect.defineProperty(target, propertyKey, attributes):类似于Object.defineProperty,不同的是如果配置出现问题,返回false而不是报错
  • Reflect.construct(target, argumentsList):用构造函数的方式创建一个对象
  • Reflect.has(target, propertyKey): 判断一个对象是否拥有一个属性
  • 其他API:developer.mozilla.org/zh-CN/docs/…
    <script>
        const obj = {
            a: 1,
            b: 2
        }

        // obj.a = 10;

        Reflect.set(obj, "a", 10);

        console.log(Reflect.get(obj, "a"))
    </script>

set方法最简单使用,这里用赋值这个简单操作来说明反射的作用。

 <script>
        function method(a, b){
            console.log("method", a, b);
        }

        // method(3, 4);
        
        // Reflect.apply 简单的方法调用,null 说明不需要更改this的绑定
        Reflect.apply(method, null, [3, 4])

        const obj = {
            a: 1,
            b: 2
        }

        // delete obj.a;
        
        // Reflect.deleteProperty  删除某个对象的property.
        Reflect.deleteProperty(obj, "a");

        console.log(obj);

        function Test(a, b) {
            this.a = a;
            this.b = b;
        }

        // const t = new Test(1, 3);
        // 下面是使用  Reflect.construct 来同样实现一个new这个关键字是干了什么
        const t = Reflect.construct(Test, [1, 3]);
        console.log(t)

        const obj1 = {
            a: 1,
            b: 2
        }

        // console.log("a" in obj);
        // 下面是使用 Reflect.has 来实现 属性 in是干什么用的
        console.log(Reflect.has(obj1, "a"));
    </script>

Proxy 代理

代理:提供了修改底层实现的方式


//代理一个目标对象
//target:目标对象
//handler:是一个普通对象,其中可以重写底层实现
//返回一个代理对象
new Proxy(target, handler)
<script>
        const obj = {
            a: 1,
            b: 2
        }

        const proxy = new Proxy(obj, {
            set(target, propertyKey, value) {
                // console.log(target, propertyKey, value);
                // target[propertyKey] = value;
                Reflect.set(target, propertyKey, value);
            },
            get(target, propertyKey) {
                if (Reflect.has(target, propertyKey)) {
                    return Reflect.get(target, propertyKey);
                } else {
                    return -1;
                }
            },
            has(target, propertyKey) {
                return false;
            }
        });
        // console.log(proxy);
        // proxy.a = 10;
        // console.log(proxy.a);

        console.log(proxy.d);
        console.log("a" in proxy);
    </script>

new Proxy(target, handler) 可以实现对目标对象的同样的方法实现外边再包一层的目的。调用的时候直接调用Proxy的方法,Proxy底层会调用目标对象的相关方法。

下面实现的是set方法包的一层

set(target, propertyKey, value) {
                // console.log(target, propertyKey, value);
                // target[propertyKey] = value;
                Reflect.set(target, propertyKey, value);
            }

下面是get方法包了一层

get(target, propertyKey) {
                if (Reflect.has(target, propertyKey)) {
                    return Reflect.get(target, propertyKey);
                } else {
                    return -1;
                }
            },

下面是has方法包了一层

 has(target, propertyKey) {
                return false;
            }

观察者模式

有一个对象,是观察者,它用于观察另外一个对象的属性值变化,当属性值变化后会收到一个通知,可能会做一些事。 其实本质都是监听属性的set方法。

方法1 使用 defineProperty 这个方法去做

下面是具体代码

 <script>
        //创建一个观察者
        function observer(target) {
            const div = document.getElementById("container");
            const ob = {};
            const props = Object.keys(target);
            for (const prop of props) {
                Object.defineProperty(ob, prop, {
                    get() {
                        return target[prop];
                    },
                    set(val) {
                        target[prop] = val;
                        render();
                    },
                    enumerable: true
                })
            }
            render();

            function render() {
                let html = "";
                for (const prop of Object.keys(ob)) {
                    html += `
                        <p><span>${prop}:</span><span>${ob[prop]}</span></p>
                    `;
                }
                div.innerHTML = html;
            }

            return ob;
        }
        const target = {
            a: 1,
            b: 2
        }
        const obj = observer(target)
    </script>

方法2 使用 Proxy 这个方法去做

下面是具体的代码

<script>
        //创建一个观察者
        function observer(target) {
            const div = document.getElementById("container");
            const proxy = new Proxy(target, {
                set(target, prop, value) {
                    Reflect.set(target, prop, value);
                    render();
                },
                get(target, prop){
                    return Reflect.get(target, prop);
                }
            })
            render();

            function render() {
                let html = "";
                for (const prop of Object.keys(target)) {
                    html += `
                        <p><span>${prop}:</span><span>${target[prop]}</span></p>
                    `;
                }
                div.innerHTML = html;
            }

            return proxy;
        }
        const target = {
            a: 1,
            b: 2
        }
        const obj = observer(target)
    </script>

偷懒的构造函数

<script>
        class User {

        }

        function ConstructorProxy(Class, ...propNames) {
            return new Proxy(Class, {
                construct(target, argumentsList) {
                    const obj = Reflect.construct(target, argumentsList)
                    propNames.forEach((name, i) => {
                        obj[name] = argumentsList[i];
                    })
                    return obj;
                }
            })
        }

        const UserProxy = ConstructorProxy(User, "firstName", "lastName", "age")

        const obj = new UserProxy("袁", "进", 18);
        console.log(obj)

        class Monster {

        }

        const MonsterProxy = ConstructorProxy(Monster, "attack", "defence", "hp", "rate", "name")

        const m = new MonsterProxy(10, 20, 100, 30, "怪物")
        console.log(m);
    </script>

可验证的函数参数

 <script>
        function sum(a, b) {
            return a + b;
        }

        function validatorFunction(func, ...types) {
            const proxy = 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);
                }
            })
            return proxy;
        }

        const sumProxy = validatorFunction(sum, "number", "number")
        console.log(sumProxy(1, 2))
    </script>

通用的写法

 <script>
        function sum(a, b) {
            return a + b;
        }

        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)
            }
            return proxy;
        }

        const sumProxy = validatorFunction(sum, "number", "number")
        console.log(sumProxy(1, 2))
    </script>