이 기사의 내용은 ES6의 데코레이터에 대해 간략하게 설명하는 내용입니다. 참고할 가치가 있는 친구들이 참고할 수 있기를 바랍니다.
Decorator
데코레이터는 주로 다음 용도로 사용됩니다.
Decoration 클래스
Decoration 메서드 또는 속성
@annotation class MyClass { } function annotation(target) { target.annotated = true; }
class MyClass { @readonly method() { } } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; }
Babel 공식 홈페이지의 Try it out 에서 Babel 컴파일 코드를 확인하실 수 있습니다.
그러나 로컬에서 컴파일하도록 선택할 수도 있습니다.
npm init npm install --save-dev @babel/core @babel/cli npm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
새 .babelrc 파일 만들기
{ "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", {"loose": true}] ] }
지정된 파일을 컴파일합니다
babel decorator.js --out-file decorator-compiled.js
컴파일 전:
@annotation class MyClass { } function annotation(target) { target.annotated = true; }
컴파일 후:
var _class; let MyClass = annotation(_class = class MyClass {}) || _class; function annotation(target) { target.annotated = true; }
할 수 있습니다. 클래스 데코레이션의 원칙은 다음과 같습니다.
@decorator class A {} // 等同于 class A {} A = decorator(A) || A;
컴파일 전:
class MyClass { @unenumerable @readonly method() { } } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; } function unenumerable(target, name, descriptor) { descriptor.enumerable = false; return descriptor; }
컴파일 후:
var _class; function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) { /** * 第一部分 * 拷贝属性 */ var desc = {}; Object["ke" + "ys"](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; } /** * 第二部分 * 应用多个 decorators */ desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc); /** * 第三部分 * 设置要 decorators 的属性 */ 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["define" + "Property"](target, property, desc); desc = null; } return desc; } let MyClass = ((_class = class MyClass { method() {} }), _applyDecoratedDescriptor( _class.prototype, "method", [readonly], Object.getOwnPropertyDescriptor(_class.prototype, "method"), _class.prototype ), _class); function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; }
Babel이 _applyDecoratedDescriptor 함수를 구축한 것을 볼 수 있습니다. 메서드를 장식하는 데 사용됩니다.
매개변수를 전달할 때 Object.getOwnPropertyDescriptor() 메소드를 사용합니다.
Object.getOwnPropertyDescriptor() 메소드는 지정된 객체에 대한 자체 속성을 반환합니다. 속성에 해당하는 설명자입니다. (자체 속성은 객체에 직접 할당된 속성을 말하며, 프로토타입 체인에서 조회할 필요가 없습니다.)
그런데, 이는 ES5 방식이라는 점 참고하시기 바랍니다.
예:
const foo = { value: 1 }; const bar = Object.getOwnPropertyDescriptor(foo, "value"); // bar { // value: 1, // writable: true // enumerable: true, // configurable: true, // } const foo = { get value() { return 1; } }; const bar = Object.getOwnPropertyDescriptor(foo, "value"); // bar { // get: /*the getter function*/, // set: undefined // enumerable: true, // configurable: true, // }
_applyDecoratedDescriptor 함수 내에서 먼저 Object.getOwnPropertyDescriptor()가 반환한 속성 설명자 개체의 복사본을 만듭니다.
// 拷贝一份 descriptor var desc = {}; Object["ke" + "ys"](descriptor).forEach(function(key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; // 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setter if ("value" in desc || desc.initializer) { desc.writable = true; }
그럼 초기화 속성은 무엇인가요? ? Object.getOwnPropertyDescriptor()에 의해 반환된 객체에는 이 속성이 없습니다. 실제로 이것은 데코레이터와 협력하기 위해 Babel의 클래스에서 생성된 속성입니다. 예를 들어 다음 코드의 경우:
class MyClass { @readonly born = Date.now(); } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; } var foo = new MyClass(); console.log(foo.born);
Babel은 다음과 같이 컴파일됩니다.
// ... (_descriptor = _applyDecoratedDescriptor(_class.prototype, "born", [readonly], { configurable: true, enumerable: true, writable: true, initializer: function() { return Date.now(); } })) // ...이때 _applyDecoratedDescriptor 함수에 전달된 디스크립터에는 초기화 속성이 있습니다. 소스 코드 분석의 두 번째 부분다음 단계는 여러 데코레이터를 적용하는 것입니다.
/** * 第二部分 * @type {[type]} */ desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc);메서드의 경우 다음과 같이 여러 데코레이터가 적용됩니다.
class MyClass { @unenumerable @readonly method() { } }Babel은 다음과 같이 컴파일됩니다.
_applyDecoratedDescriptor( _class.prototype, "method", [unenumerable, readonly], Object.getOwnPropertyDescriptor(_class.prototype, "method"), _class.prototype )소스에서 두 번째 부분의 코드에서는 reverse() 및 Reduce() 작업이 수행되었습니다. 이를 통해 동일한 메서드에 여러 개의 데코레이터가 있는 경우 내부에서 실행된다는 것도 알 수 있습니다. 3부 소스 코드 분석
/** * 第三部分 * 设置要 decorators 的属性 */ 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["define" + "Property"](target, property, desc); desc = null; } return desc;
desc.initializer.call(context)그리고 context의 값은
입니다. _class.prototype
, call(context)
를 하는 이유도 가능하기 때문에 이해하기 쉽습니다
class MyClass { @readonly value = this.getNum() + 1; getNum() { return 1; } }
_class.prototype
,之所以要 call(context)
,这也很好理解,因为有可能Object["define" + "Property"](target, property, desc);
最后无论是装饰方法还是属性,都会执行:
class Math { @log add(a, b) { return a + b; } } function log(target, name, descriptor) { var oldValue = descriptor.value; descriptor.value = function(...args) { console.log(`Calling ${name} with`, args); return oldValue.apply(this, args); }; return descriptor; } const math = new Math(); // Calling add with [2, 4] math.add(2, 4);
由此可见,装饰方法本质上还是使用 Object.defineProperty()
결국 장식적인 방법인지 속성인지는
let log = (type) => { return (target, name, descriptor) => { const method = descriptor.value; descriptor.value = (...args) => { console.info(`(${type}) 正在执行: ${name}(${args}) = ?`); let ret; try { ret = method.apply(target, args); console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`); } catch (error) { console.error(`(${type}) 失败: ${name}(${args}) => ${error}`); } return ret; } } };
Object.defineProperty()
를 사용하여 구현되는 것을 볼 수 있습니다. 1.log
메서드에 로그 함수를 추가하고 입력 매개변수를 확인하세요.
class Person { @autobind getPerson() { return this; } } let person = new Person(); let { getPerson } = person; getPerson() === person; // true
class Toggle extends React.Component { @autobind handleClick() { console.log(this) } render() { return ( <button onClick={this.handleClick}> button </button> ); } }
2.autobind
const { defineProperty, getPrototypeOf} = Object; function bind(fn, context) { if (fn.bind) { return fn.bind(context); } else { return function __autobind__() { return fn.apply(context, arguments); }; } } function createDefaultSetter(key) { return function set(newValue) { Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: newValue }); return newValue; }; } function autobind(target, key, { value: fn, configurable, enumerable }) { if (typeof fn !== 'function') { throw new SyntaxError(`@autobind can only be used on functions, not: ${fn}`); } const { constructor } = target; return { configurable, enumerable, get() { /** * 使用这种方式相当于替换了这个函数,所以当比如 * Class.prototype.hasOwnProperty(key) 的时候,为了正确返回 * 所以这里做了 this 的判断 */ if (this === target) { return fn; } const boundFn = bind(fn, this); defineProperty(this, key, { configurable: true, writable: true, enumerable: false, value: boundFn }); return boundFn; }, set: createDefaultSetter(key) }; }
우리가 쉽게 생각할 수 있는 시나리오는 React 바인딩 이벤트입니다. :
class Toggle extends React.Component { @debounce(500, true) handleClick() { console.log('toggle') } render() { return ( <button onClick={this.handleClick}> button </button> ); } }
function _debounce(func, wait, immediate) { var timeout; return function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } } } function debounce(wait, immediate) { return function handleDescriptor(target, key, descriptor) { const callback = descriptor.value; if (typeof callback !== 'function') { throw new SyntaxError('Only functions can be debounced'); } var fn = _debounce(callback, wait, immediate) return { ...descriptor, value() { fn() } }; } }
3.debounce
때로는 실행 방법을 디바운스해야 합니다.
function time(prefix) { let count = 0; return function handleDescriptor(target, key, descriptor) { const fn = descriptor.value; if (prefix == null) { prefix = `${target.constructor.name}.${key}`; } if (typeof fn !== 'function') { throw new SyntaxError(`@time can only be used on functions, not: ${fn}`); } return { ...descriptor, value() { const label = `${prefix}-${count}`; count++; console.time(label); try { return fn.apply(this, arguments); } finally { console.timeEnd(label); } } } } }
const SingerMixin = { sing(sound) { alert(sound); } }; const FlyMixin = { // All types of property descriptors are supported get speed() {}, fly() {}, land() {} }; @mixin(SingerMixin, FlyMixin) class Bird { singMatingCall() { this.sing('tweet tweet'); } } var bird = new Bird(); bird.singMatingCall(); // alerts "tweet tweet"
4 time
function mixin(...mixins) { return target => { if (!mixins.length) { throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`); } for (let i = 0, l = mixins.length; i < l; i++) { const descs = Object.getOwnPropertyDescriptors(mixins[i]); const keys = Object.getOwnPropertyNames(descs); for (let j = 0, k = keys.length; j < k; j++) { const key = keys[j]; if (!target.prototype.hasOwnProperty(key)) { Object.defineProperty(target.prototype, key, descs[key]); } } } }; }
5.mixin
은 클래스에 객체 메소드를 혼합하는 데 사용됩니다.
class MyReactComponent extends React.Component {} export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
@connect(mapStateToProps, mapDispatchToProps) export default class MyReactComponent extends React.Component {};
6.redux
실제 개발에서 React가 함께 사용되는 경우 Redux 라이브러리를 사용하면 다음과 같이 작성해야 하는 경우가 많습니다.
const method = descriptor.value;
데코레이터를 사용하면 위 코드를 다시 작성할 수 있습니다.
const value = descriptor.initializer && descriptor.initializer();
7. 참고
위 내용은 모두 클래스 메소드를 수정하는 데 사용됩니다. 값을 얻는 방법은 다음과 같습니다.
rrreee하지만 클래스의 인스턴스 속성을 수정하면 Babel을 통해 값을 얻을 수 없습니다. value 속성을 사용하면 다음과 같이 쓸 수 있습니다:
rrreee🎜🎜위 내용은 ES6의 데코레이터에 대한 간략한 토론의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!