美文网首页日常刻书
一些常用的代理陷阱函数(四)

一些常用的代理陷阱函数(四)

作者: F_wind | 来源:发表于2021-02-17 09:58 被阅读0次

    《深入理解ES6》阅读随笔

    函数代理中的 apply 和 construct 陷阱

    JavaScript 中函数有两个内部方法 [[call]] 和 [[construct]],[[call]] 方法用来直接调用函数,在代理中对应 Reflect.apply,可接收三个参数:

    1. trapTargat: 目标函数;
    2. thisArg:目标函数中的 this;
    3. argumentsList:目标函数携带的参数;

    而 [[construct]] 方法则需要使用 new 来创建构造函数,对应代理中的 Reflect.construct,可接收两个必选参数和一个可选参数:

    1. trapTargat: 目标函数;
    2. argumentsList:目标函数携带的参数;
    3. newTargat:指定目标函数的 new.targat 值(可选);

    下面是一个默认的函数代理转发例子:

    const targat = function () { return 100 }
    
    const proxy = new Proxy(targat, {
        apply(trapTargat, thisArg, argumentsList) {
            return Reflect.apply(trapTargat, thisArg, argumentsList)
        },
        construct(trapTargat, argumentsList) {
            return Reflect.construct(trapTargat, argumentsList)
        }
    })
    
    console.log(proxy()) // 100 (直接调用)
    
    const t1 = new proxy() // 用构造函数的方式新建实例
    console.log(t1 instanceof targat) // true (t1 是 targat 的实例)
    console.log(t1 instanceof proxy) // true(t1 是 proxy 的实例)
    
    验证函数参数

    在创建函数代理时,可以在代理中验证参数类型,正常则通过,如果发现类型不对,则抛出异常:

    const sum = function (a, b) {
        return a + b;
    }
    
    const proxy = new Proxy(sum, {
        apply(trapTargat, thisArg, argumentsList) {
            for (const i of argumentsList) {
                if ((typeof i) !== 'number') {
                    throw 'arguments is not number'
                }
            }
            return Reflect.apply(trapTargat, thisArg, argumentsList)
        },
        construct(trapTargat, argumentsList) {
            return Reflect.construct(trapTargat, argumentsList)
        }
    })
    
    console.log(sum(1,2)) // 3 
    console.log(sum(1,'2')) // 12 
    console.log(proxy(1,2)) // 3
    console.log(proxy(1,'2')) // err :arguments is not number
    

    此处使用代理来验证可以提高程序的安全性,如没有代理,那么在上面例子中,当出现数值类型与字符串类型相加时,数值 1 将被强制转换为字符串 1,因此结果会变为异常的字符串 12。

    是否使用 new 来调用构造函数

    在使用声明函数时,还可以通过代理的方式,来决定是否需要 new 来创建构造函数。
    下面是代理中禁止用 new 的例子:

    const Sum = function (a, b) {
        this.result = a + b;
        return this.result
    }
    
    const proxy = new Proxy(Sum, {
        apply(trapTargat, thisArg, argumentsList) {
            return Reflect.apply(trapTargat, thisArg, argumentsList)
        },
        construct(trapTargat, argumentsList) {
            throw 'can not be constructed'
        }
    })
    
    const s1 = new Sum(1, 2)
    console.log(s1.result) // 3
    
    const s2 = proxy(1, 2)
    console.log(s2) // 3
    
    const s3 = new proxy(1, 2)
    console.log(s3.result) // err:can not be constructed
    

    下面是代理中只能使用 new 的例子:

    const Sum = function (a, b) {
        this.result = a + b;
        return this.result
    }
    
    const proxy = new Proxy(Sum, {
        apply(trapTargat, thisArg, argumentsList) {
            throw 'can not be called'
    
        },
        construct(trapTargat, argumentsList) {
            return Reflect.construct(trapTargat, argumentsList)
        }
    })
    
    const s1 = new Sum(1, 2)
    console.log(s1.result) // 3
    
    const s2 = new proxy(1, 2)
    console.log(s2.result) //3 
    
    const s3 = proxy(1, 2)
    console.log(s3.result) // err: can not be called
    

    实际上,在声明函数时,也可以在其内部定义是否可用 new :

    const Sum = function (a, b) {
        if (new.target === undefined) {
            throw 'there is no new'
        }
        this.result = a + b;
        return this.result
    }
    
    const s1 = new Sum(1, 2)
    console.log(s1.result) // 3
    
    const s2 = Sum(1, 2)
    console.log(s1) // err:there is no new
    

    上面是一个必须使用 new 的例子,如果需要禁用 new 则改判 new.targat 的状态即可;如此看来,通过函数内部 new.targat 特性来做这件事也可以,并且更简洁一些;但是当无法直接操作目标函数时(比如调用了已封装好的第三方类库中的函数),代理的作用才真正体现出来:

    const Sum = function (a, b) {
        if (new.target === undefined) {
            throw 'there is no new'
        }
        this.result = a + b;
        return this.result
    }
    
    const proxy = new Proxy(Sum, {
        apply(trapTargat, thisArg, argumentsList) {
            return Reflect.construct(trapTargat, argumentsList)
        }
    })
    
    const s1 = new Sum(1,2)
    console.log(s1.result) // 3
    
    const s2 = proxy(1,2)
    console.log(s2.result) // 3
    

    上面的例子绕过目标函数必须使用 new 来构造的限制,通过在 Reflect.apply 中直接返回 Reflect.construct 的方式完美解决了问题。

    覆写基于抽象类的构造函数

    下面是一个只能用来继承的类:

    class Sum {
        constructor(a, b) {
            if (new.target === Sum) {
                throw 'only child'
            }
            this.result = a + b
        }
    }
    
    class SumChild extends Sum {}
    const c1 = new SumChild(1,2)
    console.log(c1.result) // 3 
    
    const s1 = new Sum(1,2)
    console.log(s1.result) // err:only child
    

    此时可以利用 Reflect.construct 的第三个参数来覆写该抽象类,通过配置 newTarget 的方式,可以顺利绕过目标函数无法直接构建实例的限制:

    class Sum {
        constructor(a, b) {
            if (new.target === Sum) {
                throw 'only child'
            }
            this.result = a + b
        }
    }
    
    const proxy = new Proxy(Sum, {
        apply(trapTargat, thisArg, argumentsList) {
            return Reflect.apply(trapTargat, thisArg, argumentsList)
        },
        construct(trapTargat, argumentsList) {
            return Reflect.construct(trapTargat, argumentsList, function () { })
        }
    })
    
    const p1 = new proxy(1, 2)
    console.log(p1.result) // 3
    
    可调用的类构造函数

    在使用类构造函数时,本来是不可以直接调用的,但是可以通过函数代理的方式,在 Reflect.apply 环节内部完成实例化,然后在外部直接调用即可:

    class Sum {
        constructor(a, b) {
            this.result = a + b
        }
    }
    
    const proxy = new Proxy(Sum, {
        apply(trapTargat, thisArg, argumentsList) {
            return new trapTargat(...argumentsList)
        }
    })
    
    const p1 = proxy(1, 2)
    console.log(p1.result) // 3
    

    相关文章

      网友评论

        本文标题:一些常用的代理陷阱函数(四)

        本文链接:https://www.haomeiwen.com/subject/xhrixltx.html