likes
comments
collection
share

再来了解一下装饰器

作者站长头像
站长
· 阅读数 44

背景

最近 Typescript5 beta 版发布,支持了装饰器(Decorator) stage3,我们来看一下 JS 新特性的产生过程和装饰器 Stage3 的内容。

JS 新特性产生流程

首先看一下目前标准流程是 5 个阶段,Stage0 ~ Stage 4

  • Stage0:稻草人(Strawpersion),由TC39成员发起,通常是提出新想法或是对未纳入正式的提案进行修改。

  • Stage1:提案(Proposal),提出一些具体的问题和解决方案。

  • Stage2:草稿(Draft),用ES语法尽可能精确地描述提案的语法、语义和API,并提供实验性的实现。意味着提案会有很大概率出现在正式版本的中。

  • Stage3:候选人(Candidate),到了该阶段,提案基本已经定型,仅根据外部反馈针对关键问题进行更改。

  • Stage4:完成(Finish),该提案会出现在正式的规范文档中,并在下一个版本的ES中正式支持。

装饰器的三种能力

装饰器的三个能力

  1. 替换:将所修饰的元素替换成其他值(用其他方法替换所修饰的方法,用其他属性替换所修饰的属性等等) ;

  2. 访问:通过访问器来访问所修饰元素的能力;

  3. 初始化:初始化所修饰的元素。

装饰器的四种类型

类定义装饰器

定义

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

举例

function logged(value, { kind, name }) {
  if (kind === "class") {
    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

Desugar

class C {}

C = logged(C, {
  kind: "class",
  name: "C",
}) ?? C;

new C(1);

类属性装饰器

这种类型的装饰器不会接收到 value,但是可以通过初始化函数来拿到初始数据,并且还可以返回一个新的数据。

定义

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;

举例

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

Desugar

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

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

类方法装饰器

定义

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

举例

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

Desugar

class C {
  m(arg) {}
}

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

类访问器装饰器

定义

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

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;

举例

function logged(value, { kind, name }) {
  if (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

Desugar

class C {
  set x(arg) {}
}

let { set } = Object.getOwnPropertyDescriptor(C.prototype, "x");
set = logged(set, {
  kind: "setter",
  name: "x",
  static: false,
  private: false,
}) ?? set;

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

新元素

自动访问器

accessor 修饰,自动生成私有属性和关联的 getter/setter 方法


class C {
  accessor x = 1;
}

等同于

class C {
  #x = 1;

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

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

自动访问器装饰器

自动访问器也是可以被装饰的

定义

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 }) {
  if (kind === "accessor") {
    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;
}

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

Desugar

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

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

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

let { get: oldGet, set: oldSet } = Object.getOwnPropertyDescriptor(C.prototype, "x");

let {
  get: newGet = oldGet,
  set: newSet = oldSet,
  init: initializeX = (initialValue) => initialValue
} = logged(
  { get: oldGet, set: oldSet },
  {
    kind: "accessor",
    name: "x",
    static: false,
    private: false,
  }
) ?? {};

Object.defineProperty(C.prototype, "x", { get: newGet, set: newSet });

addInitializer

通过 addInitializer 可以添加额外的初始化逻辑,这些逻辑的执行时机取决于所修饰对象的类型:

  • 修饰类定义:类定义完成之后执行;

  • 修饰类元素(方法/属性):在执行类构造函数时执行;

  • 修饰静态元素:类定义的过程中,在普通字段定义完成后,并且在静态字段定义之前执行;

修饰类定义

举例 @customElement

function customElement(name) {
  return (value, { addInitializer }) => {
    addInitializer(function() {
      customElements.define(name, this);
    });
  }
}

@customElement('my-element')
class MyElement extends HTMLElement {
}

Desugar

class MyElement {
}

let initializersForMyElement = [];

MyElement = customElement('my-element')(MyElement, {
  kind: "class",
  name: "MyElement",
  addInitializer(fn) {
    initializersForMyElement.push(fn);
  },
}) ?? MyElement;

for (let initializer of initializersForMyElement) {
  initializer.call(MyElement);
}

修饰类元素

举例 @bound

function bound(value, { name, addInitializer }) {
  addInitializer(function () {
    this[name] = this[name].bind(this);
  });
}

class C {
  message = "hello!";

  @bound
  m() {
    console.log(this.message);
  }
}

let { m } = new C();

m(); // hello!

Desugar

class C {
  constructor() {
    for (let initializer of initializersForM) {
      initializer.call(this);
    }

    this.message = "hello!";
  }

  m() {}
}

let initializersForM = []

C.prototype.m = bound(
  C.prototype.m,
  {
    kind: "method",
    name: "m",
    static: false,
    private: false,
    addInitializer(fn) {
      initializersForM.push(fn);
    },
  }
) ?? C.prototype.m;

Access

到目前为止,我们看到例子都属于装饰器的替换初始化能力,还没看到装饰器的访问能力。下面的例子是依赖注入装饰器,可以通过向实例注入数据。

举例

const INJECTIONS = new WeakMap();

function createInjections() {
  const injections = [];

  function injectable(Class) {
    INJECTIONS.set(Class, injections);
  }

  function inject(injectionKey) {
    return function applyInjection(v, context) {
      injections.push({ injectionKey, set: context.access.set });
    };
  }

  return { injectable, inject };
}

class Container {
  registry = new Map();

  register(injectionKey, value) {
    this.registry.set(injectionKey, value);
  }

  lookup(injectionKey) {
    this.registry.get(injectionKey);
  }

  create(Class) {
    let instance = new Class();

    for (const { injectionKey, set } of INJECTIONS.get(Class) || []) {
      set.call(instance, this.lookup(injectionKey));
    }

    return instance;
  }
}

class Store {}

const { injectable, inject } = createInjections();

@injectable
class C {
  @inject('store') store;
}

let container = new Container();
let store = new Store();

container.register('store', store);

let c = container.create(C);

c.store === store; // true

常见装饰器

参考:github.com/jayphelps/c…

@autobind

类方法自动绑定 this

import { autobind } from 'core-decorators';

class Person {
  @autobind
  getPerson() {
     return this;
  }
}

let person = new Person();
let { getPerson } = person;

getPerson() === person;
// true

@readonly

只读

import { readonly } from 'core-decorators';

class Meal {
  @readonly
  entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';
// Cannot assign to read only property 'entree' of [object Object]

@deprecated

import { deprecate } from 'core-decorators';

class Person {
  @deprecate('We stopped facepalming')
  facepalmHard() {}
}

let person = new Person();

person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming

总结

装饰器可以在不修改原有元素的情况下,新增一些能力,可读性和维护性都很高。不过装饰器目前只对类相关的元素生效,不能用于普通函数,所以如果是面向普通函数的编程,就没办法用这个特性了。

参考

github.com/tc39/propos…

github.com/jayphelps/c…

mp.weixin.qq.com/s/vmh4rEjR0…

Allow decorators for functions as well

转载自:https://juejin.cn/post/7197608584690548795
评论
请登录