JS修饰器(Decorators)用法-Stage1

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

简介

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

先看一个 mobx4 文档上的例子

ts 复制代码
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 的功能
    • 高阶函数: 一个函数接收另一个函数作为参数,这种函数就称之为高阶函数
js 复制代码
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 定义

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

用法

js 复制代码
@decorator
class A {}

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

示例一

js 复制代码
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
js 复制代码
Object.defineProperties(target.prototype, {
    name: {
        value: '张三',
        writable: false,
    },
    logName: {
        value: function() {
            console.log(this.name);
        }
    }
})

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

示例二

js 复制代码
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 定义

ts 复制代码
function decorator(
    target: Class,
    name: PropertyKey,
    descriptor: PropertyDescriptor
): PropertyDescriptor {
    // do something

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

用法

js 复制代码
class A {
    @decorator1
    name = '';
    
    @decorator2

    name2() {
    
    }
}

示例一(类方法装饰器)

js 复制代码
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 方法已经被重写了, 他第一步是打印信息, 第二步是实现原函数的处理逻辑

示例二(类属性装饰器)

js 复制代码
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 的值没有发生变化

示例三(访问器装饰器)

js 复制代码
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

装饰器执行顺序

js 复制代码
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...

相关推荐
不会算法的小灰9 分钟前
JavaScript基础详解
开发语言·javascript·udp
十一吖i5 小时前
vue3表格显示隐藏列全屏拖动功能
前端·javascript·vue.js
徐同保6 小时前
tailwindcss暗色主题切换
开发语言·前端·javascript
生莫甲鲁浪戴6 小时前
Android Studio新手开发第二十七天
前端·javascript·android studio
细节控菜鸡8 小时前
【2025最新】ArcGIS for JS 实现随着时间变化而变化的热力图
开发语言·javascript·arcgis
拉不动的猪10 小时前
h5后台切换检测利用visibilitychange的缺点分析
前端·javascript·面试
桃子不吃李子10 小时前
nextTick的使用
前端·javascript·vue.js
Devil枫11 小时前
HarmonyOS鸿蒙应用:仓颉语言与JavaScript核心差异深度解析
开发语言·javascript·ecmascript
惺忪979812 小时前
回调函数的概念
开发语言·前端·javascript
前端 贾公子12 小时前
Element Plus组件v-loading在el-dialog组件上使用无效
前端·javascript·vue.js