JS装饰器(Decorators)用法-Stage3(新)

521 阅读3分钟

装饰器 API

type Decorator = (value: Input, context: {
    kind: string;
    name: string | symbol;
    access: {
        get?(): unknown;
        set?(value: unknown): void;
    };
    private?: boolean;
    static?: boolean;
    addInitializer?(initializer: () => void): void;
}) => Output | void;

当调用 decorator 时,它们会收到两个参数:

  • value: 所要装饰的值, 当装饰类属性时为 undefined
  • context: 上下文对象,包含有关要装饰的值的信息
    • kind:表示装饰类型,可能的取值有 classmethodgettersetterfieldaccessor
    • name:值的名称(被装饰的值的名称)
    • access: 访问这个值的方法,即存值器(set)和取值器(get)。
    • static: 是否是 static(静态) 类元素。仅适用于类元素。
    • private: 和 static 类似, 是否为 private(私有) 类元素
    • addInitializer: 添加额外的初始化逻辑。适用于所有按类操作的装饰器

装饰器语法

类(class)装饰器

API 定义

可以看到比最开始的 装饰器 API 少了一些字段, 是因为具体的装饰器有些字段是不需要的

type ClassDecorator = (value: Function, context: {
  kind: "class";
  name: string | undefined;
  addInitializer(initializer: () => void): void;
}) => Function | void;

用法

@logged
class C {}

// or (等价写法)
class C {}
C = logged(C, {
    kind: "class",
    name: "C",
}) ?? C;

示例

function logged(value, { kind, name }) {
    return class extends value {
        constructor(...args) {
            super(...args);
            console.log(`constructing an instance of ${name} with arguments ${args.join(", ")}`);
        }
    }
}

@logged
class C {}

new C(1);
// constructing an instance of C with arguments 1

类方法装饰器

API 定义

type ClassMethodDecorator = (value: Function, context: {
    kind: "method";
    name: string | symbol;
    access: { get(): unknown };
    static: boolean;
    private: boolean;
    addInitializer(initializer: () => void): void;
}) => Function | void;

用法

class C {
    @logged
    m(arg) {}
}

// or (等价写法)
class C {
    m(arg) {}
}

C.prototype.m = logged(C.prototype.m, {
    kind: "method",
    name: "m",
    static: false,
    private: false,
}) ?? C.prototype.m;

示例

function logged(value, { kind, name }) {
    if (kind === "method") {
        return function (...args) {
            console.log(`starting ${name} with arguments ${args.join(", ")}`);
            const ret = value.call(this, ...args);
            console.log(`ending ${name}`);
            return ret;
        };
    }
}

class C {
  @logged
  m(arg) {}
}

new C().m(1);
// starting m with arguments 1
// ending m

存取器(set/get)装饰器

API 定义

// get
type ClassGetterDecorator = (value: Function, context: {
    kind: "getter";
    name: string | symbol;
    access: { get(): unknown };
    static: boolean;
    private: boolean;
    addInitializer(initializer: () => void): void;
}) => Function | void;
// set
type ClassSetterDecorator = (value: Function, context: {
    kind: "setter";
    name: string | symbol;
    access: { set(value: unknown): void };
    static: boolean;
    private: boolean;
    addInitializer(initializer: () => void): void;
}) => Function | void;
  • 可以选择返回一个新的 getter/setter 函数来替换它
  • 如果返回任何其他类型的值(undefined除外),则会引发错误。

用法

class C {
    @foo
    get x() {}

    set x(val) {}
}

在以上示例中,@foo仅应用于get x()set x()未装饰

// 等价写法
class C {
    get x() {}
    set x(val) {}
}

let { get } = Object.getOwnPropertyDescriptor(C.prototype, "x");
get = foo(get, {
  kind: "getter",
  name: "x",
  static: false,
  private: false,
}) ?? get;

Object.defineProperty(C.prototype, "x", { get });

示例

我们对 @logged 装饰器稍加修改,就可以用在存取装饰器上

function logged(value, { kind, name }) {
    if (kind === "method" || kind === "getter" || kind === "setter") {
        return function (...args) {
            console.log(`starting ${name} with arguments ${args.join(", ")}`);
            const ret = value.call(this, ...args);
            console.log(`ending ${name}`);
            return ret;
        };
    }
}

class C {
  @logged
  set x(arg) {}
}

new C().x = 1
// starting x with arguments 1
// ending x

类属性装饰器

API 定义

type ClassFieldDecorator = (value: undefined, context: {
    kind: "field";
    name: string | symbol;
    access: { get(): unknown, set(value: unknown): void };
    static: boolean;
    private: boolean;
}) => (initialValue: unknown) => unknown | void;

属性装饰器的第一个参数是undefined,即不输入值。

用法

class C {
  @logged x = 1;
}

// or (等价写)
let initializeX = logged(undefined, {
  kind: "field",
  name: "x",
  static: false,
  private: false,
}) ?? (initialValue) => initialValue;

class C {
  x = initializeX.call(this, 1);
}

示例

function logged(value, { kind, name }) {
    if (kind === "field") {
        return function (initialValue) {
            console.log(`initializing ${name} with value ${initialValue}`);
            return initialValue;
        };
    }
}

class C {
  @logged x = 1;
}

new C();
// initializing x with value 1

addInitializer 初始化方法

addInitializer 该方法除类字段之外都可以使用, 可以调用此方法完成一些初始化操作

  • 类装饰器:在类完全定义之后以及分配类静态字段之后运行。
  • 方法装饰器:在类构造期间运行,在属性初始化之前。
  • 静态方法装饰器:在类定义期间运行,早于静态属性定义,但晚于类方法的定义。

addInitializer 的执行时机测试

function test(type) {
    return function test(value, { addInitializer }) {
        console.log(`run: ${type}-test`);
        addInitializer(function() {
            console.log(`run: ${type}-initializer`);
        });
    }
}

@test('class')
class MyClass {
    @test('static_method')
    static staticLog() {
        console.log('run: static log');
    }
    
    @test('class_method')
    classLog() {
        console.log('run: class log');
    }
}

console.log('run: new before');
const myClass = new MyClass();
console.log('run: new after');

// run: static_method-test
// run: class_method-test
// run: class-test
// run: static_method-initializer
// run: class_initializer

// run: new before
// run: class_method-initializer
// run: new after

accessor 命令

类装饰器引入了一个新命令accessor, 通过在类字段前面添加关键字的方式使用

class C {
    accessor x = 1;
}

它是一种简写形式,相当于声明属性x是私有属性#x的存取接口。上面的代码等同于下面的代码。

class C {
    #x = 1;

    get x() {
        return this.#x;
    }

    set x(val) {
        this.#x = val;
    }
}

accessor 可以被装饰,装饰器具有以下特性(签名)

type ClassAutoAccessorDecorator = (
    value: {
        get: () => unknown;
        set(value: unknown) => void;
    },
    context: {
        kind: "accessor";
        name: string | symbol;
        access: { get(): unknown, set(value: unknown): void };
        static: boolean;
        private: boolean;
        addInitializer(initializer: () => void): void;
    }
) => {
    get?: () => unknown;
    set?: (value: unknown) => void;
    init?: (initialValue: unknown) => unknown;
} | void;

下面我们来一个简单的示例

function logged(value, { kind, name }) {
    let { get, set } = value;

    return {
        get() {
            console.log(`getting ${name}`);

            return get.call(this);
        },

        set(val) {
            console.log(`setting ${name} to ${val}`);

            return set.call(this, val);
        },

        init(initialValue) {
            console.log(`initializing ${name} with value ${initialValue}`);
            return initialValue;
        }
    };
}

class C {
    @logged accessor x = 1;
}

const c = new C();
// initializing x with value 1
c.x;
// getting x
c.x = 123;
// setting x to 123

相关文档

难道你还不知道 JS装饰器(Decorators)

JS装饰器(Decorators)用法-Stage1(旧)

参考文档

github.com/tc39/propos…

es6.ruanyifeng.com/#docs/decor…