>  기사  >  웹 프론트엔드  >  Javascript 데코레이터 원리에 대한 자세한 설명 및 사례 소개

Javascript 데코레이터 원리에 대한 자세한 설명 및 사례 소개

WBOY
WBOY앞으로
2022-02-21 17:35:291712검색

이 기사는 javascript의 데코레이터 원칙에 대한 관련 지식을 제공합니다. JavaScript 데코레이터는 Python 또는 Java에서 빌릴 수 있습니다. 가장 분명한 차이점은 대부분의 언어에서 데코레이터가 한 줄이어야 한다는 것입니다. js 데코레이터는 한 줄에 있을 수 있지만 모든 사람에게 도움이 되기를 바랍니다.

Javascript 데코레이터 원리에 대한 자세한 설명 및 사례 소개

@으로 시작하는 설명어입니다. 영어 동사 decorator는 장식하다, 꾸미다라는 뜻입니다. 루트 데크(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 Decorator

이전 예제와 마찬가지로 데코레이터 자체는 실제로 데코레이팅된 개체를 실행하기 전에 실행되는 함수입니다.

JavaScript에서 데코레이터 유형은 다음과 같습니다.

  • Class

  • 액세스 메서드(속성 가져오기 및 설정)

  • Fields

  • Methods

  • 매개변수

데코레이터부터 개념은 아직 제안 단계이고 공식적으로 사용 가능한 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 프로그램을 만듭니다. Decorator-class.js

@classDecoratorclass Building {  
constructor() {    
this.name = "company";  
}}
const building = new Building();
function classDecorator(target) {  
console.log("target", target);}

위는 가장 간단한 데코레이터 프로그램입니다. 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. 按照出现的先后顺序的:属性、方法和存取器

요약

Decorator는 개발자의 코딩 프로세스를 크게 촉진하고 코드의 가독성을 향상시키는 우아한 개발 모델입니다. 데코레이터를 사용하여 개발할 때 작동 메커니즘을 이해하는 것이 여전히 매우 필요합니다.

관련 권장 사항: javascript 학습 튜토리얼

위 내용은 Javascript 데코레이터 원리에 대한 자세한 설명 및 사례 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제