我的编程空间,编程开发者的网络收藏夹
学习永远不晚

JavaScript装饰器的实现原理详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

JavaScript装饰器的实现原理详解

最近在使用TS+Vue的开发模式,发现项目中大量使用了装饰器,看得我手足无措,今天特意研究一下实现原理,方便自己理解这块知识点。

装饰器的常见作用

  • 装饰一个类的属性
  • 装饰一个类

装饰器只能针对类和类的属性,不能直接作用于函数,因为存在函数提升。

下面我们针对这两种情况进行举例阐述。

装饰类的属性

function readonly(target, name, descriptor) {
    discriptor.writable = false;
    return discriptor;
}
class Cat {
    @readonly
    say() {
        console.log("meow ~");
    }
}
var kitty = new Cat();
kitty.say = function() {
    console.log("woof !");
}
kitty.say()    // meow ~

ES6中的类实际上就是一个语法糖,本质上是构造函数,类的属性的定义使用的是 Object.defineProperty() 类的代码可以改写为下面代码:

class Cat {
    say() {
        console.log("meow ~");
    }
}

// 上面等价于下面代码

function Cat() {}

Object.defineProperty(Cat.prototype, "say", {
    value: function() { console.log("meow ~"); },
    enumerable: false,
    configurable: true,
    writable: true
});

say方法前面加入@readonly后,整个代码等价于下面代码:

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

function Cat() {}

let descriptor = {
    value: function() { console.log("meow ~"); },
    enumerable: false,
    configurable: true,
    writable: true
};

descriptor = readonly(Cat.prototype, 'say', descriptor) || descriptor;

Object.defineProperty(Cat.prototype, "say", descriptor);

装饰类属性的大致实现原理,就是上面的语法糖实现过程。

这里注意下装饰器的用法,是在属性前面加入@readonly,不是@readonly()。如果是后一种写法的话,装饰器函数需要返回一个函数来表示装饰器。这种写法主要是通过传参来扩展装饰器函数的功能,使装饰器函数复用性更强。

如通过传参决定是否设置只读,代码如下:

function readonly(flag) {
    return function (target, name, descriptor) {
        if (flag) {
            discriptor.writable = false;
        } else {
            discriptor.writable = true;
        }
        return discriptor;
    }
}
class Cat {
    @readonly(true)
    say() {
        console.log("meow ~");
    }
}
var kitty = new Cat();
kitty.say = function() {
    console.log("woof !");
}
kitty.say()

装饰类

装饰一个类的时候类本身本质上是一个函数,没有descriptor,target是这个函数本身。

function isAnimal(target) {
    target.isAnimal = true;
  	return target;
}
@isAnimal
class Cat {
    ...
}
console.log(Cat.isAnimal);    // true

也就是说,上面的@isAnimal其实就是做了下面这件事

Cat = isAnimal(function Cat() { ... });

注意

作用在方法上的 decorator 接收的第一个参数(target )是类的 prototype;如果把一个 decorator 作用到类上,则它的第一个参数 target 是 类本身。

装饰器执行的时间是在属性定义的时候,也就是被装饰的属性在定义后就是已经被装饰器处理过的不一样的属性了。

下面举例看下装饰器的执行时间:

function log(message) {
    return function() {
        console.log(message);
    }
}
console.log('before class');
@log('class Bar')
class Bar {
    @log('class method bar');
    bar() {}
    @log('class getter alice');
    get alice() {}
    @log('class property bob');
    bob = 1;
}
console.log('after class');
let bar = {
    @log('object method bar')
    bar() {}
};

输出结果:

before class
class method bar
class getter alice
class property bob
class Bar
after class
object method bar

可以看出装饰器在类和类的属性定义的时候就对它们进行了"装饰"。

实例应用

我们的示例场景是这样的

  • 首先创建一个普通的Man类,它的抵御值 2,攻击力为 3,血量为 3;
  • 然后我们让其带上钢铁侠的盔甲,这样他的抵御力增加 100,变成 102;
  • 让其带上光束手套,攻击力增加 50,变成 53;
  • 最后让他增加“飞行”能力

1. 创建 Man 类:

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  toString(){
    return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
  }
}

var tony = new Man();

console.log(`当前状态 ===> ${tony}`); 

// 输出:当前状态 ===> 防御力:2,攻击力:3,血量:3

2. 装饰器为钢铁侠装配盔甲:

function decorateArmour(target, key, descriptor) {
  const method = descriptor.value;
  let moreDef = 100;
  let ret;
  descriptor.value = (...args)=>{
    args[0] += moreDef;
    ret = method.apply(target, args);
    return ret;
  }
  return descriptor;
}

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  toString(){
    return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
  }
}

var tony = new Man();

console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:3,血量:3

3. 装饰器增加光束手套

function decorateLight(target, key, descriptor) {
  const method = descriptor.value;
  let moreAtk = 50;
  let ret;
  descriptor.value = (...args)=>{
    args[1] += moreAtk;
    ret = method.apply(target, args);
    return ret;
  }
  return descriptor;
}

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour
  @decorateLight
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
...
}
var tony = new Man();
console.log(`当前状态 ===> ${tony}`);
//输出:当前状态 ===> 防御力:102,攻击力:53,血量:3

在这里你就能看出装饰模式的优势了,它可以对某个方法进行叠加使用,对原类的侵入性非常小,只是增加一行@decorateLight而已,可以方便地增删;(同时还可以复用)

4. 增加飞行能力

function addFly(canFly){
  return function(target){
    target.prototype.canFly = canFly;
    let extra = canFly ? '(技能加成:飞行能力)' : '';
    let method = target.prototype.toString;
    target.prototype.toString = (...args)=>{
      return method.apply(target.prototype,args) + extra;
    }
    return target;
  }
}

@addFly(true)
class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour
  @decorateLight
  init(def,atk,hp){
    this.def = def; // 防御值
    this.atk = atk;  // 攻击力
    this.hp = hp;  // 血量
  }
  ...
}
...

console.log(`当前状态 ===> ${tony}`);
// 输出:当前状态 ===> 防御力:102,攻击力:53,血量:3(技能加成:飞行能力)

到此这篇关于JavaScript装饰器的实现原理详解的文章就介绍到这了,更多相关JavaScript装饰器内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

JavaScript装饰器的实现原理详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

JavaScript装饰器的实现原理详解

最近在使用TS+Vue的开发模式,发现项目中大量使用了装饰器,看得我手足无措,今天特意研究一下实现原理,方便自己理解这块知识点,有需要的小伙伴也可以参考一下
2022-11-13

JavaScript 装饰器模式用法详解

装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许动态地向一个对象添加新的行为,本文将带大家详细了解一下JavaScript装饰器模式,文中有相关的示例代码供大家参考,感兴趣的同学可以跟着小编一起学习
2023-05-19

python中装饰器的原理

装饰器这玩意挺有用,当时感觉各种绕,现在终于绕明白了,俺滴个大爷,还是要慢慢思考才能买明白各种的真谛,没事就来绕一绕 def outer(func): def inner(): print("认证成功")
2023-01-30

Python 装饰器工作原理解析

#!/usr/bin/env python#coding:utf-8"""装饰器实例拆解"""def login00(func):    print('00请通过验证用户!')    return funcdef tv00(name):  
2023-01-31

详解如何在JavaScript中使用装饰器

Decorator装饰器是ES7的时候提案的特性,目前处于Stage 3候选阶段(2022年10月)。装饰器简单来说就是修改类和类方法的语法糖,很多面向对象语言都有装饰器这一特性。本文就来说说如何在JavaScript中使用装饰器,需要的可以参考一下
2022-11-13

详解Python中最难理解的点-装饰器

本文将带领大家由浅入深的去窥探一下,这个装饰器到底是何方神圣,看完本篇,装饰器就再也不是难点了. 一、什么是装饰器 网上有人是这么评价装饰器的,我觉得写的很有趣,比喻的很形象每个人都有的内裤主要是用来遮羞,但是到了冬天它没法为我们防风御寒,
2022-06-04

Python装饰器与类的装饰器怎么实现

这篇“Python装饰器与类的装饰器怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python装饰器与类的装饰器怎么
2023-06-29

JavaScript中new操作符的原理与实现详解

你知道new吗?你知道new的实现原理吗?你能手写new方法吗?不要担心,这篇文件就来带大家深入了解一下JavaScript中的new操作符,感兴趣的小伙伴可以学习一下
2022-11-13

JavaAQS的实现原理详解

这篇文章主要借助了ReentrantLock来带大家搞清楚AQS的实现原理,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以了解一下
2023-05-14

编程热搜

目录