JavaScript 反射(Reflect)和代理(Proxy)简单介绍

1 代理(Proxy)

1.1 代理基本定义

        MDN定义:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。其基本语法如下,target就是要创建代理的对象,handler就是拦截代理操作的对象,里面定义函数拦截:

const p = new Proxy(target, handler)

        在我看来代理就是在对对象进行操作的时候进行拦截,做一些自己想做的事情,包括一些副作用,例如操作虚拟DOM。

1.2 代理基本示例与应用

1.2.1 基本示例(set get拦截)

        定义obj对象并创建其代理对象,拦截对对象值的访问以及赋值操作,set拦截赋值操作,传入三个参数(不关注receiver参数),分别是目标对象(我们代理的原始对象),prop属性值,以及要赋值的值(=操作符右侧的值)。get方法拦截对对象属性的值,(target, prop)参数,target为我们代理的原始对象,prop是要求访问的对象,代码如下所示:

const obj = {
    name: 'billy',
    age: 30
}

const objProxy = new Proxy(obj, {
    // set传入参数:目标对象、属性名、属性值
    set(target, prop, val){
        console.log(`Setting value ${val} to ${prop}`)
        target[prop] = val
    },
    // get传入参数:目标对象、属性名
    get(target, prop){
        console.log(`Getting value ${prop}`)
        return target[prop]
    }
})

objProxy.name = 'billy2'
objProxy.age
// output:
// Setting value billy2 to name
// Getting value age

1.2.2 无操作的转发代理

        在创建代理对象的时候,设置任何拦截的函数时,会自动帮助我们进行无任何操作的转发,相当于操作原始对象,代码如下所示:

// 无拦截操作的代理对象

const obj = {
    name: "Billy"
}

// 不进行如何拦截操作
const p = new Proxy(obj, {})

p.name = "Jack"
p.age = 11
console.log(obj) //{ name: 'Jack', age: 11 }

1.2.3 代理set拦截-进行副作用操作-操作DOM节点

        场景如下:将obj对象的str属性渲染到div标签下,要求就是在改变属性值的时候,div标签下的值也同时改变,实现一个按钮,点击后显示文字内容“Hello World”。

        实现分析:使用set方法进行拦截,同时进行DOM操作,index.html代码如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="text"></div>
    <button id="btn">Click</button>
    <script src="./15_01_proxy_reflect_.js"></script>
</body>
</html>

        实现代理对象的代码如下所示:

const obj = {
    str: "Hello World"
}

const divNode = document.getElementById('text')
const btn = document.getElementById('btn')

const p = new Proxy(obj, {
    set(target, prop, val){
        // 进行副作用操作
        divNode.innerText = val
        target[prop] = va;
    }
})

btn.addEventListener('click', () => {
    p.str = "Hello World"
})

1.2.4 代理set拦截-赋值类型正确性验证

        要求如下:要求obj对象的age属性始终为number属性,如果不正确抛出异常,实现代码如下所示:

// 代理操作-验证赋值类型正确性
const obj = {
    name: "Bill",
    age: 11
}


const p = new Proxy(obj, {
    set(target, prop, val){
        if(typeof val !== "number"){
            throw new TypeError("obj.name can't assigned with not a number")
        }

        if(val <= 0){
            throw new RangeError("age range error")
        }

        target[prop] = val
    }
})


p.age = 124
// p.age = -199
p.age = "afaf"

2 反射(Reflect)

        MDN定义及描述:Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。与大多数全局对象不同 Reflect 并非一个构造函数,所以不能通过 new 运算符对其进行调用,或者将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。

        我的理解:Reflect实际上就是将JavaScript一些全局函数实现的局部元方法暴露出来,如果使用js提供的全局函数,除了执行Reflect的元操作意外还会有其它操作及判断,Reflect其实就是将某写js操作的核心部分给暴露出来,总结就是以下两点:

  • 通过Reflect方法可以轻松简洁的调用默认行为(赋值、删除、取值等),没有副作用。
  • 与Proxy配合保持一致性

        例如Reflect.get和Reflect.set函数就是js在赋值和取值过程中的核心方法暴露出来(还有一些其他处理没有包括)。

        让我们看以下下面这个例子:

// Reflect的使用
const obj = {
    a: 1,
    b: 2,
    get c(){
        return this.a + this.b
    }
}


const p = new Proxy(obj, {
    get(target, prop){
        console.log(`Getting value ${prop}`)
        return target[prop]
    }
})

console.log(p.c)
// output:
// Getting value c
// 3

        发现在访问obj.c的时候访问a和b属性没有经过拦截器,接下来使用拦截器进行拦截,代码及结果如下所示(get还有第三个变量reiceiver,其是代理对象本身的引用):

// Reflect的使用
const obj = {
    a: 1,
    b: 2,
    get c(){
        return this.a + this.b
    }
}


const p = new Proxy(obj, {
    // receiver为代理对象的引用
    get(target, prop, receiver){
        console.log(`Getting value ${prop}`)
        // 原始使用target[c]无法触发getter
        // Reflect.get(target, prop, receiver)中的receiver可以保证调用函数的this指向代理对象
        return Reflect.get(target, prop, receiver)
    }
})

console.log(p.c)
// output:
// Getting value c
// Getting value a
// Getting value b
// 3

        Reflect对象提供的方法包括:

  • Reflect.apply(target, thisArgument, argumentsList)
  • Reflect.construct(target, argumentsList[, newTarget])
  • Reflect.get(target, propertyKey[, receiver])
  • Reflect.set(target, propertyKey, value[, receiver])
  • Reflect.has(target, propertyKey)
  • Reflect.deleteProperty(target, propertyKey)
  • Reflect.defineProperty(target, propertyKey, attributes)
  • Reflect.getOwnPropertyDescriptor(target, propertyKey)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype) Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.ownKeys(target)

2.1 反射基本示例

2.1.1 获取和设置对象属性-Reflect.set-Reflect.get

const obj = {
    name: "Bill",
    age: 11
}

Reflect.set(obj, "name", "Jack")
console.log(obj.name) // Jack
const res = Reflect.get(obj, "name") // Jack
console.log(res)

2.1.2 检查属性是否存在-Reflect.has

const obj = {
    name: "Bill",
    age: 11
}

console.log(Reflect.has(obj, "name")) // true
console.log(Reflect.has(obj, "number")) // false

2.1.3 删除属性-Reflect.deleteProperty

const obj = {
    name: "Bill",
    age: 11
}

Reflect.deleteProperty(obj, "name") 
console.log(obj) // { age: 11 }

3 反射和代理配合使用

        以属性访问日志为例子(即访问属性或者对对象属性进行赋值操作时进行打印),代码如下:

const loggingHandler = {
  get(target, property) {
    console.log(`正在访问属性:${property}`);
    // 调用默认的获取属性行为
    return Reflect.get(target, property);
  },
  set(target, property, value) {
    console.log(`正在设置属性:${property} = ${value}`);
    // 调用默认的设置属性行为
    return Reflect.set(target, property, value);
  }
};

const obj = { a: 1 };
const proxyObj = new Proxy(obj, loggingHandler);

console.log(proxyObj.a); // 输出日志并返回值
proxyObj.b = 2; // 输出日志并设置属性

         这里我就产生了疑问,那么这和我直接操作target对象有什么区别吗?有区别,区别如下所示:

  • 返回值的不一致:例如Reflect.set 返回一个布尔值,表示属性是否成功设置。直接操作target设置属性会导致没有明确的返回值,隐式返回被赋的值。
  • 处理this上下文:Reflect.get 接受第三个参数 receiver,用于正确绑定 this 上下文,特别是在访问继承的属性或访问器属性时。直接访问 target[property],不支持传递 receiver,可能导致 this 上下文不正确,尤其是在使用继承或访问器属性时。
  • 遵循 Proxy 捕获器的规则:Proxy和Reflect对象方法一致,确保整个Proxy代理的行为一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值