Javascript装饰器原理

本文详细介绍了JavaScript装饰器的概念、类型(类装饰器、方法装饰器、存取器装饰器、属性装饰器和参数装饰器),以及如何在实际项目中使用它们,包括装饰器的工作原理和应用示例。此外,还提到了如何借助Babel等工具实现装饰器功能在早期JavaScript环境中的应用。
摘要由CSDN通过智能技术生成

有经验的大家肯定第一时间想到了,把那些重复语句封装为一个方法,并统一调用。是的,这样可以解决大部分问题,但是还不够“优雅”。同时还有另外一个问题,如果“房间”特别多,又或者只有大楼奇数号房间要验证偶数不验证,那岂不是很“变态”?如果使用装饰器模式来做,代码会如下面这样的:

@verify(who)class Building { @verify(who) A101(){//} @verify(who) A102(){//} //…}

verify是验证的装饰器,而其本质就是一组函数。

JavaScript装饰器


正如先前的那个例子,装饰器其实本身就是一个函数,它在执行被装饰的对象之前先被执行。

在JavaScript中,装饰器的类型有:

  • 存取方法(属性的get和set)

  • 字段

  • 方法

  • 参数

由于目前装饰器概念还处于提案阶段,不是一个正式可用的js功能,所以想要使用这个功能,不得不借助翻译器工具,例如Babel工具或者TypeScript编译JS代码转后才能被执行。我们需要先搭建运行环境,配置一些参数。(以下过程,假设已经正确安装了NodeJS开发环境以及包管理工具)

cd project && npm initnpm i -D @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env babel-plugin-parameter-decorator

创建一个.babelrc配置文件,如下:

{ “presets”: [“@babel/preset-env”], “plugins”: [ [“@babel/plugin-proposal-decorators”, { “legacy”: true }], [“@babel/plugin-proposal-class-properties”, { “loose”: true }], “babel-plugin-parameter-decorator” ]}

利用下面的转换命令,我们可以得到ES5的转换程序:

npx babel source.js --out-file target.js

类装饰器

创建一个使用装饰器的JS程序decorate-class.js

@classDecoratorclass Building { constructor() { this.name = “company”; }}

const building = new Building();

function classDecorator(target) { console.log(“target”, target);}

以上是最最简单的装饰器程序,我们利用babel将其“翻译”为ES5的程序,然后再美化一下后得到如下程序。

“use strict”;

var _class;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}

var Building = classDecorator( (_class = function Building() { _classCallCheck(this, Building);

this.name = “company”; }) ) || _class;

var building = new Building();

function classDecorator(target) { console.log(“target”, target);}

第12行就是在类生成过程中,调用函数形态的装饰器,并将构造函数(类本身)送入其中。同样揭示了装饰器的第一个参数是类的构造函数的由来。

方法 (method)装饰器

稍微修改一下代码,依旧是尽量保持最简单:

class Building { constructor() { this.name = “company”; } @methodDecorator openDoor() { console.log(“The door being open”); }}

const building = new Building();

function methodDecorator(target, property, descriptor) { console.log(“target”, target); if (property) { console.log(“property”, property); } if (descriptor) { console.log(“descriptor”, descriptor); } console.log(“=end of decorator=====”);}

然后转换代码,可以发现,这次代码量突然增大了很多。排除掉_classCallCheck、_defineProperties和_createClass三个函数,关注_applyDecoratedDescriptor函数:

function _applyDecoratedDescriptor( target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if (“value” in desc || desc.initializer) { desc.writable = true; } desc = decorators .slice() .reverse() .reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc;}

它在生成构造函数之后,执行了这个函数,特别注意,这个装饰器函数是以数组形式的参数传递的。然后到上述代码的17~22行,将装饰器逐个应用,其中对装饰器的调用就在第21行。

它发送了3个参数,target指类本身。property指方法名(或者属性名),desc是可能被先前装饰器被处理过的descriptor,如果是第一次循环或只有一个装饰器,那么就是方法或属性本身的descriptor。

存取器(accessor)装饰

JS关于类的定义中,支持get和set关键字针对设置某个字段的读写操作逻辑,装饰器也同样支持这类方法的操作。

class Building { constructor() { this.name = “company”; } @propertyDecorator get roomNumber() { return this._roomNumber; }

_roomNumber = “”; openDoor() { console.log(“The door being open”); }}

有心的读者可能已经发现了,存取器装饰的代码与上面的方法装饰代码非常接近。关于属性 get和set方法,其本身也是一种方法的特殊形态。所以他们之间的代码就非常接近了。

属性装饰器

继续修改源代码:

class Building { constructor() { this.name = “company”; } @propertyDecorator roomNumber = “”;}

const building = new Building();

function propertyDecorator(target, property, descriptor) { console.log(“target”, target); if (property) { console.log(“property”, property); } if (descriptor) { console.log(“descriptor”, descriptor); } console.log(“=end of decorator=====”);}

转换后的代码,还是与上述属性、存取器的代码非常接近。但除了_applyDecoratedDescriptor外,还多了一个_initializerDefineProperty函数。这个函数在生成构造函数时,将声明的各种字段绑定给对象。

参数装饰器

参数装饰器的使用位置较之前集中装饰器略有不同,它被使用在行内。

class Building { constructor() { this.name = “company”; } openDoor(@parameterDecorator num, @parameterDecorator zoz) { console.log(${num} door being open); }}

const building = new Building();

function parameterDecorator(target, property, key) { console.log(“target”, target); if (property) { console.log(“property”, property); } if (key) { console.log(“key”, key); } console.log(“=end of decorator=====”);}

转换后的代码区别就比较明显了,babel并没有对其生成一个特定的函数对其进行特有的操作,而只在创建完类(构造函数)以及相关属性、方法后直接调用了开发者自己编写的装饰器函数:

var Building = /#PURE/function () { function Building() { _classCallCheck(this, Building);

this.name = “company”; }

_createClass(Building, [{ key: “openDoor”, value: function openDoor(num, zoz) { console.log(“”.concat(num, " door being open")); } }]);

parameterDecorator(Building.prototype, “openDoor”, 1); parameterDecorator(Building.prototype, “openDoor”, 0); return Building;}();

装饰器应用


使用参数——闭包

以上所有的案例,装饰器本身均没有使用任何参数。然实际应用中,经常会需要有特定的参数需求。我们再回到一开头的例子中verify(who),其中需要传入一个身份变量。哪又怎么做?我们少许改变一下类装饰器的代码:

const who = “Django”;@classDecorator(who)class Building { constructor() { this.name = “company”; }}

转换后得到

// …var who = “Django”;var Building = ((_dec = classDecorator(who)), _dec( (_class = function Building() { _classCallCheck(this, Building);

this.name = “company”; }) ) || _class);// …

请注意第4第5行,它先执行了装饰器,然后再用返回值将类(构造函数)送入。相对应的,我们就应该将构造函数写成下面这样:

function classDecorator(people) { console.log(hi~ ${people}); return function (target) { console.log(“target”, target); };}

同样的,方法、存取器、属性和参数装饰器均是如此。

装饰器包裹方法

到此,我们已经可以将装饰器参数与目标对象结合起来,进行一些逻辑类的操作。那么再回到文章的开头的例子中:需求中要先验证来访者权限,然后记录,最后在来访者离开时再做一次记录。此时需要监管对象方法被调用的整个过程。

请大家留意那个方法装饰器的descriptor,我们可以利用这个对象来“重写”这个方法。

class Building { constructor() { this.name = “company”; }

@methodDecorator(“Gate”) openDoor(firstName, lastName) { return The door will be open, when ${firstName} ${lastName} is walking into the ${this.name}.; }}

let building = new Building();console.log(building.openDoor(“django”, “xiang”));

function methodDecorator(door) { return function (target, property, descriptor) { let fn = descriptor.value; descriptor.value = function (…args) { let [firstName, lastName] = args; console.log(log: ${firstName}, who are comming.); // verify(firstName,lastName) let result = Reflect.apply(fn, this, [firstName, lastName]); console.log(log: ${result}); console.log(log: ${firstName}, who are leaving.); return result; }; return descriptor; };}

代码第17行,将原方法暂存;18行定义一个新的方法,20~25行,记录、验证和记录离开的动作。

log: Django, who are comming.log: The door will be open, when Django Xiang is walking in to the company.log: Django, who are leaving.The door will be open, when Django Xiang is walking in to the company

装饰顺序


自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

最后

前15.PNG

前16.PNG

由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处即可获取!!!

节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(资料价值较高,非无偿)

最后

[外链图片转存中…(img-XPyFuWwR-1711676592170)]

[外链图片转存中…(img-oUTUkKPm-1711676592170)]

由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处即可获取!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值