Reflect是ECMAScript 6(ES6)引入的一个内置对象,它提供了封装普通JavaScript对象原生行为的方法。与普通的函数不同,Reflect方法通常与Proxy对象一起使用,以实现对对象操作的拦截和定制。

Reflect基本介绍

Reflect 对象提供了多种方法来封装和操作JavaScript对象的行为。详见源码,下面给出使用示例。

  1. Reflect.get(target, propertyKey[, receiver])获取目标对象的属性值。

    const obj = { a: 1, b: 2 }
    const value = Reflect.get(obj, 'a')
    console.log(value) // 输出 1
    
  2. Reflect.set(target, propertyKey, value[, receiver])设置目标对象的属性值。

    const obj = { a: 1 }
    const result = Reflect.set(obj, 'a', 2)
    console.log(result) // 输出 true
    console.log(obj.a)  // 输出 2
    
  3. Reflect.has(target, propertyKey)检查目标对象是否有某个属性。

    const obj = { a: 1 }
    const hasProp = Reflect.has(obj, 'a')
    console.log(hasProp) // 输出 true
    
  4. Reflect.deleteProperty(target, propertyKey)删除目标对象的属性。

    const obj = { a: 1 }
    const result = Reflect.deleteProperty(obj, 'a')
    console.log(result) // 输出 true
    console.log(obj.a)  // 输出 undefined
    
  5. Reflect.ownKeys(target)返回目标对象的所有自身属性键。

    const obj = { a: 1, b: 2 }
    const keys = Reflect.ownKeys(obj)
    console.log(keys) // 输出 ['a', 'b']
    
  6. Reflect.getPrototypeOf(target)获取目标对象的原型。

    const obj = Object.create({ a: 1 })
    const proto = Reflect.getPrototypeOf(obj)
    console.log(proto) // 输出 { a: 1 }
    
  7. Reflect.setPrototypeOf(target, prototype)设置目标对象的原型。

    const obj = {}
    const proto = { a: 1 }
    const result = Reflect.setPrototypeOf(obj, proto)
    console.log(result) // 输出 true
    console.log(obj.a)  // 输出 1
    
  8. Reflect.isExtensible(target)检查目标对象是否可扩展。

    const obj = {}
    const extensible = Reflect.isExtensible(obj)
    console.log(extensible) // 输出 true
    
  9. Reflect.preventExtensions(target)阻止目标对象扩展。

    const obj = {}
    const result = Reflect.preventExtensions(obj)
    console.log(result) // 输出 true
    try {
      Reflect.defineProperty(obj, 'a', { value: 1 })
    } catch (e) {
      console.log(e) // 抛出错误
    }
    
  10. Reflect.getOwnPropertyDescriptor(target, propertyKey)获取目标对象的属性描述符。

    const obj = { a: 1 }
    const desc = Reflect.getOwnPropertyDescriptor(obj, 'a')
    console.log(desc) // 输出 { value: 1, writable: true, enumerable: true, configurable: true }
    
  11. Reflect.defineProperty(target, propertyKey, attributes)定义或修改目标对象的属性。

    const obj = {}
    const result = Reflect.defineProperty(obj, 'a', { value: 1, writable: false })
    console.log(result) // 输出 true
    console.log(obj.a)  // 输出 
    
  12. Reflect.apply(function, thisArg, argArray)调用函数,传入特定的 this 值和参数。

    const add = (a, b) => a + b
    const result = Reflect.apply(add, null, [1, 2])
    console.log(result) // 输出 3
    
  13. Reflect.construct(constructor, argumentsList[, newTarget])创建并初始化一个新对象。

    function Person(name) {
      this.name = name
    }
    const person = Reflect.construct(Person, ['John'])
    console.log(person.name) // 输出 "John"
    
  14. Reflect.hasOwn(target, propertyKey)检查目标对象是否具有指定的自身属性。

    const obj = { a: 1 }
    const hasOwnProp = Reflect.hasOwn(obj, 'a')
    console.log(hasOwnProp) // 输出 true
    

Reflect与Proxy

Proxy和Reflect 经常配合使用,以实现对对象操作的拦截和自定义行为。Proxy允许你创建一个对象的代理,这个代理可以拦截并控制对目标对象的各种操作,如属性读取、赋值、枚举、函数调用等。而Reflect提供了一系列与Proxy的陷阱(traps)相对应的静态方法,使得我们可以直接使用标准的语法来执行这些操作。

class Data{
    name: string
    age: number
    
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
}

class DataProxyHandler implements ProxyHandler<Data>{
    get(target: Data, p: PropertyKey, receiver: any): any {
        console.log(`getting ${String(p)}`)
        return Reflect.get(target, p, receiver)
    }
    set(target: Data, p: PropertyKey, value: any, receiver: any): boolean {
        console.log(`setting ${String(p)} = ${value}`)
        return Reflect.set(target, p, value, receiver)
    }
}

let proxy = new Proxy(new Data('zhangsan', 18), new DataProxyHandler())
proxy.age = 20
console.log(proxy.age)

Reflect 和Metadata

Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+的版本已经支持它,你只需要:

  • npm i reflect-metadata --save
  • tsconfig.json 里配置 emitDecoratorMetadata 选项。

Reflect允许在类或其成员上附加元数据,这可以通过装饰器实现。

  1. Reflect.metadata(metadataKey, metadataValue):这是一个装饰器工厂,可以用来自定义元数据。

  2. Reflect.getMetadata(metadataKey, target[, propertyKey]):获取继承链上的元数据。

    function metadata(metadataKey: any, metadataValue: any): {
                (target: Function): void
                (target: Object, propertyKey: string | symbol): void
    }
    import "reflect-metadata"
    
    @Reflect.metadata('inClass', 'A')
    class Test {
      @Reflect.metadata('inMethod', 'B')
      public hello(): string {
        return 'hello world'
      }
    }
    
    console.log(Reflect.getMetadata('inClass', Test)) // 'A'
    console.log(Reflect.getMetadata('inMethod', new Test(), 'hello')) // 'B'
    
    function Prop(): PropertyDecorator {
        return (target, key: string | symbol) => {
            const type = Reflect.getMetadata('design:type', target, key)
            console.log(`type: ${type.name}`)  // type: string
      }
    }
    
    class SomeClass {
      @Prop()
      public Aprop!: string;
    }
    
  3. Reflect.defineMetadata(metadataKey, metadataValue, target[, propertyKey]):定义元数据。

    function classDecorator(): ClassDecorator{
        return (target) => {
            Reflect.defineMetadata("classMetadata", "a", target)
        }
    }
    
    function methodDecorator(): MethodDecorator{
        return (target, key: string | symbol, descriptor: PropertyDescriptor) => {
            Reflect.defineMetadata("methodMetadata", "b", target, key)
        }
    }
    
    @classDecorator()
    class SomeClass {
        @methodDecorator()
        someMethod() { }
    }
    
  4. Reflect.getOwnMetadata(metadataKey, target[, propertyKey]):获取指定的元数据。

  5. Reflect.deleteMetadata(metadataKey, target[, propertyKey]):删除元数据。

控制反转与依赖注入

const Injectable = (): ClassDecorator => target => {}
  1. const Injectable =:声明一个常量 Injectable。
  2. (): ClassDecorator:这部分表示 Injectable 是一个函数,它不接受参数(空括号),并返回一个 ClassDecorator 类型。
  3. => target => {}:这是一个箭头函数,它返回另一个箭头函数。
    • 外层箭头函数 () => 对应 Injectable 函数本身。
    • 内层箭头函数 target => {} 是实际的类装饰器。
  4. target:这是类装饰器的参数,表示被装饰的类的构造函数。
  5. {}:装饰器的函数体,目前是空的。

完整解释:Injectable 是一个函数,当被调用时(如 @Injectable()),它会返回一个类装饰器。这个类装饰器是一个接受 target(被装饰的类)作为参数的函数。

type Constructor<T = any> = new (...args: any[]) => T

class OtherServie{
    a = 1
}

@Inject()
class TestService {
    constructor(public readonly other: OtherServie) { }

    testMethod() {
        console.log(this.other.a)
    }
}

const Factory = <T>(target: Constructor<T>): T => {
    // 获取所有注入的服务
    const providers = Reflect.getMetadata('design:paramtypes', target)
    const args = providers.map((provider: Constructor) => new provider())
    return new target(...args)
}

Factory(TestService).testMethod()

Controller与Get

const METHOD_METADATA = 'method'
const PATH_METADATA = 'path'

const Controller = (path: string): ClassDecorator => {
    return target => {
        Reflect.defineMetadata(PATH_METADATA, path, target)
    }
}

const createMappingDecorator = (method: string) => (path: string): MethodDecorator => {
    return (target, key, descriptor) => {
        if (descriptor.value) {
            Reflect.defineMetadata(PATH_METADATA, path, descriptor.value)
            Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value)
        }
    }
}

const Get = createMappingDecorator('GET')
const Post = createMappingDecorator('POST')

function mapRoute(instance: Object) {
    const prototype = Object.getPrototypeOf(instance)
    // 筛选出类的 methodName
    const methodsNames = Object.getOwnPropertyNames(prototype).filter(item => item !== 'constructor' && typeof prototype[item] === 'function')
    return methodsNames.map(methodName => {
        const fn = prototype[methodName]
        // 取出定义的 metadata
        const path = Reflect.getMetadata(PATH_METADATA, fn)
        const method = Reflect.getMetadata(METHOD_METADATA, fn)
        return {
            fn,
            path,
            method,
            methodName
        }
    })
    
}

@Controller('/test')
class SomeClass {
  @Get('/a')
  someGetMethod() {
    return 'hello world'
  }

  @Post('/b')
  somePostMethod() {}
}

Reflect.getMetadata(PATH_METADATA, SomeClass) // '/test'

console.log(mapRoute(new SomeClass()))

/**
 * [
  {
    fn: [Function: someGetMethod],
    path: '/a',
    method: 'GET',
    methodName: 'someGetMethod'
  },
  {
    fn: [Function: somePostMethod],
    path: '/b',
    method: 'POST',
    methodName: 'somePostMethod'
  }
]
 */
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐