JS修饰器(Decorators)用法-Stage1

  • 这里就不介绍修饰器了, 感兴趣的可以去看 juejin.cn/editor/draf…
  • 本文我们介绍一下 修饰器 Stage1 的用法
  • 本文的所有示例都可以通过 Babel在线工具 进行编译后直接运行
    注意: 在 Decorators version 的配置要选用 Legacy 因为我们使用的语法是比较旧的
    JS修饰器(Decorators)用法-Stage1

简介

在 JavaScript 中,装饰器有一种特殊的语法。它们以 @ 符号为前缀,放置在我们需要装饰的代码之前。(可参考: 难道你还不知道 JS修饰器(Decorators)
)。另外,可以一次使用多个装饰器。

先看一个 mobx4 文档上的例子

import {observer} from "mobx-react"
import {observable} from "mobx"

@observer
class Timer extends React.Component {
    @observable secondsPassed = 0

    componentWillMount() {
        setInterval(() => {
            this.secondsPassed++
        }, 1000)
    }

    render() {
        return (<span>Seconds passed: { this.secondsPassed } </span> )
    }
}

ReactDOM.render(<Timer />, document.body);
  • observerobservable 就是修饰器的用法
  • 用法就是示例的样子, 那 observerobservable 是什么呢? 他们都干了什么呢?
  • 其实他们都是 Function, 我们按照修饰器的语法写一个 Function 也是可以当作修饰器使用的

装饰器类型

函数装饰器

  • 你可能会注意到有的文章说到函数是没有修饰器的, 这个主要看自己的理解
  • 我们通过 @ 符号是没办法修饰一个 Function 的, 语法上不支持
  • 但我们可以通过使用高阶函数的方式, 实现修饰 Function 的功能
    • 高阶函数: 一个函数接收另一个函数作为参数,这种函数就称之为高阶函数
function logMessage(message) {
    console.log(message);
}

function record(fun) {
    let count = 0;

    return function(...args) {
        console.log(`Function: ${fun.name || ''}, 运行次数: ${++count}`);
        return fun.apply(this, args);
    }
}

const newLogMessage = record(logMessage);
newLogMessage('message 0');
// Function: logMessage, 运行次数: 1
// message 0

newLogMessage('message 1');
// Function: logMessage, 运行次数: 2
// message 1

newLogMessage('message 2');
// Function: logMessage, 运行次数: 3
// message 2

类(class)装饰器

API 定义

function decorator(target: Class) {
  // do something
}
  • 装饰器本质上是一个 Function, 他有一个参数即 class
  • 我们可以根据实际需求实现装饰器内部的处理逻辑

用法

@decorator
class A {}

// 等同于
class A {}
A = decorator(A) || A;

示例一

function classDecorator(target) {
    target.color = 'red';
    target.logMsg = function(msg) {
        console.log(msg);
    }

    Object.assign(target.prototype, {
        name: '张三',
        logName() {
            console.log(this.name);
        }
    });  
}

@classDecorator
class A {}

console.log(A.color); // red
A.logMsg('msg'); // msg

const a = new A();
console.log(a.name); // 张三
a.logName(); // 张三

我们对 classDecorator 装饰器做个简单的分析

  • 在第 2~5 行, 我们对被装饰的 class 新增了一个静态属性 color 和一个静态方法 logMsg
  • 在第 7~12 行, 我们对被装饰的 class 新增了类属性 name 和类方法 logName
Object.defineProperties(target.prototype, {
    name: {
        value: '张三',
        writable: false,
    },
    logName: {
        value: function() {
            console.log(this.name);
        }
    }
})

我们也可以使用这种方式代替上面的处理, 这样我们就可以设置一些属性的描述符

示例二

function recordClassDecorator(recorder) {
    return function(Target) {
        return function(...args) {
            const target = new Target(...args);
            recorder.push(target);
            return target;
        }
    }
}

const recorder = [];
@recordClassDecorator(recorder)
class User {
    name;
    constructor(name) {
        this.name = name;
    }
}

cons = zhangsan = new User('张三');
cons = lisi = new User('李四');
console.log(recorder); // [User, User]

简单的说一下这个示例的知识点

  • recordClassDecorator 本身不是一个装饰器方法, 但他执行后返回的值是一个装饰器, 这样也是可以的. 通过这种方式可以支持传递参数
  • 这个示例告诉我们怎么在实例化的时候做一些处理

类成员(属性/方法)

API 定义

function decorator(
    target: Class,
    name: PropertyKey,
    descriptor: PropertyDescriptor
): PropertyDescriptor {
    // do something

    return descriptor;
}
  • target: 类的原型对象
  • name: 要装饰的属性名(string | number | symbol)
  • descriptor: 属性的描述对象

用法

class A {
    @decorator1
    name = '';
    
    @decorator2

    name2() {
    
    }
}

示例一(类方法装饰器)

function log(target, name, descriptor) {
    const oldValue = descriptor.value;

    descriptor.value = function() {
        console.log(`Calling ${name} with`, arguments);
        return oldValue.apply(this, arguments);
    };

    return descriptor;
}

class Math {
    @log
    add(a, b) {
        return a + b;
    }
}

const math = new Math();

// passed parameters should get logged now
math.add(2, 4);
  • 可以看到 add 方法已经被重写了, 他第一步是打印信息, 第二步是实现原函数的处理逻辑

示例二(类属性装饰器)

function readonly(target, name, descriptor) {
    descriptor.writable = false;

    return descriptor;
}

class User {
    @readonly
    name = '张三'
}

const user = new User();
user.name = '李四';
console.log(user.name); // 张三
  • name 属性被 readonly 修饰后, 去修改 name 后, name 的值没有发生变化

示例三(访问器装饰器)

function counter() {
    let count = 0;

    return function (target, name, descriptor) {
        const oldGet = descriptor.get;

        descriptor.get = function(...args) {
            console.log(`class: ${target.constructor.name}, 属性: ${name}, 计算值次数: ${++count}`);
            const value = oldGet.apply(this, args);
            return value;
        };

        return descriptor;
    }
}

class A {
    @counter()
    get c() {
        return 'tset';
    }
}

const a = new A();
a.c;
console.log(a.c);

// class: A, 属性: c, 计算值次数: 1
// class: A, 属性: c, 计算值次数: 2
// test

装饰器执行顺序

function a(value) {
    console.log('a-outer');
    return function(target) {
        console.log('a-inner');
        target.a = value;
    }
}

function b(value) {
    console.log('b-outer');
    return function(target) {
        console.log('b-inner');
        target.b = value;
    }
}

@a('a')
@b('b')
class A {}
// or: @a('a') @b('b') class A {}

const aa = new A();

// a-outer
// b-outer
// b-inner
// a-inner
  • 可以看到创建修饰器时, 是从上到下的顺序执行了代码
  • 真正调用修饰器对内容进行修饰, 是从下到上的顺序执行的(也可以理解为由内到外)

小结

  • 装饰器的种类及使用方法
    • 函数: decorator(fun)
    • 类(class): @decorator class or decorator(class)
    • 类属性(包括类方法和类属性): class User { @readonly name = '张三'}
  • 如何实现装饰器: 装饰器的本质是一个函数, 通过实现函数的行为完成对 方法 类 的装饰
  • 那我们如何实现装饰器? 它有哪些特征?
    • 函数: 入参-被修饰的函数, 出参-新函数
    • 类: 入参-是 class, 出参-可以有也可以没有
      • 当需要在 new 的阶段加入一些我们的处理时, 需要有返回参数, 参数是一个函数(注意: 不要用箭头函数)
      • 我们要在这个函数里面完成 new 操作并返回实例;
    • 类属性: 入参targetclass, name字段key, descriptor属性的描述对象, 出参-属性的描述对象

相关文档

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

参考文档

es6.ruanyifeng.com/#docs/decor…
www.tslang.cn/docs/handbo…
juejin.cn/post/705999…

原文链接:https://juejin.cn/post/7261555752584921146 作者:洲_

(0)
上一篇 2023年7月31日 上午10:10
下一篇 2023年7月31日 上午10:21

相关推荐

发表回复

登录后才能评论