搜索
首页web前端js教程详解及案例介绍Javascript装饰器原理

本篇文章给大家带来了关于javascript装饰器原理的相关知识,JavaScript的装饰器可能是借鉴自Python也或许是Java,较为明显的不同的是大部分语言的装饰器必须是一行行分开,而js的装饰器可以在一行中,希望对大家有帮助。

详解及案例介绍Javascript装饰器原理

一个以@开头的描述性词语。英语的decorator动词是decorate,装饰的意思。其中词根dek(dec发音)原始印欧语系中意思是“接受”。即,原来的某个事物接受一些新东西(而变得更好)。

从另外一个角度描述,装饰器主要是在被装饰对象的外部起作用,而非入侵其内部发生什么改变。装饰器模式同时也是一种开发模式,其地位虽然弱于MVC、IoC等,但不失为一种优秀的模式。

JavaScript的装饰器可能是借鉴自Python也或许是Java。较为明显的不同的是大部分语言的装饰器必须是一行行分开,而js的装饰器可以在一行中。

装饰器存在的意义

举个例子:我拿着员工卡进入公司总部大楼。因为每个员工所属的部门、级别不同,并不能进入大楼的任何房间。每个房间都有一扇门;那么,公司需要安排每个办公室里至少一个人关于验证来访者的工作:

  1. 先登记来访者

  2. 验证是否有权限进入,如果没有则要求其离开

  3. 记录其离开时间

还有一个选择方式,就是安装电子门锁,门锁只是将员工卡的信息传输给机房,由特定的程序验证。

前者暂且称之为笨模式,代码如下:

function A101(who){  
record(who,new Date(),'enter');  
if (!permission(who)) {    
record(who,new Date(),'no permission')    
return void;  
}  // 继续执行  
doSomeWork();  
record(who,new Date(),'leave')}
function A102(who){record(who,new Date(),'enter');  
if (!permission(who)) {    
record(who,new Date(),'no permission')    
return void;  }  
// 继续执行  
doSomeWork();  
record(who,new Date(),'leave')}
// ...

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

@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

装饰顺序

通过阅读转换后的代码,我们知道装饰器工作的时刻是在类被实例化之前,在生成之中完成装饰函数的动作。那么,如果不同类型的多个装饰器同时作用,其过程是怎样的?我们将先前的案例全部整合到一起看看:

const who = "Django";@classDecorator(who)class Building {  
constructor() {    
this.name = "company";  
}
  @propertyDecorator  roomNumber = "";
  @methodDecorator  openDoor(@parameterDecorator num) {    
  console.log(`${num} door being open`);  
  }
  @accessorDecorator  get roomNumber() {    
  return this._roomNumber;  
  }}
const building = new Building();
function classDecorator(people) {  
console.log(`class decorator`);  
return function (target) {    
console.log("target", target);  
};}
function methodDecorator(target, property, descriptor) {  
console.log("method decorator");}
function accessorDecorator(target, property, descriptor) {  
console.log("accessor decorator");}
function propertyDecorator(target, property, descriptor) {  
console.log("property decoator");}
function parameterDecorator(target, property, key) {  
console.log("parameter decorator");}
  1. class decorator

  2. parameter decorator

  3. property decoator

  4. method decorator

  5. accessor decorator

还可以通过阅读转换后的源代码得到执行顺序:

  1. 类装饰器(在最外层)

  2. 参数装饰器(在生成构造函数最里层)

  3. 按照出现的先后顺序的:属性、方法和存取器

总结

装饰器是一种优雅的开发模式,极大的方便了开发者编码过程,同时提升了代码的可读性。我们在使用装饰器开发时,还是非常有必要了解其运行机理。

相关推荐:javascript学习教程

以上是详解及案例介绍Javascript装饰器原理的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:CSDN。如有侵权,请联系admin@php.cn删除
JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

了解JavaScript引擎:实施详细信息了解JavaScript引擎:实施详细信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python vs. JavaScript:学习曲线和易用性Python vs. JavaScript:学习曲线和易用性Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

Python vs. JavaScript:社区,图书馆和资源Python vs. JavaScript:社区,图书馆和资源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

从C/C到JavaScript:所有工作方式从C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript引擎:比较实施JavaScript引擎:比较实施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

超越浏览器:现实世界中的JavaScript超越浏览器:现实世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在现实世界中的应用包括服务器端编程、移动应用开发和物联网控制:1.通过Node.js实现服务器端编程,适用于高并发请求处理。2.通过ReactNative进行移动应用开发,支持跨平台部署。3.通过Johnny-Five库用于物联网设备控制,适用于硬件交互。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境