Decorator: 원리 부터 실천 까지 나 는 조금도 헛 되 지 않 아 ~

14917 단어 decorator자바 script
머리말
원문 링크: [Nealyang / personal Blog] ()
ES6 는 더 이상 소개 할 필요 가 없습니다. ES6 이전에 장식 기 는 그다지 중요 하지 않 았 을 수도 있 습 니 다. wrapper 를 한 층 만 넣 으 면 되 기 때 문 입 니 다. 하지만 지금 은 문법 사탕 class 의 등장 으로 여러 가지 유형 에서 공유 하거나 확장 하려 고 할 때 코드 가 복잡 해 지고 유지 하기 어렵 습 니 다.본 격 적 으로 우리 Decorator 의 용도 도
Object.defineProperty
Object. defineProperty 에 대해 간단히 말 하면 이 방법 은 대상 의 속성 을 정확하게 추가 하고 수정 할 수 있다 는 것 이다.
문법 Object.defineProperty(obj,prop,descriptor)
  • ojb: 그 위 에 속성 을 정의 할 대상
  • prop: 정의 하거나 수정 할 속성의 이름
  • descriptor: 정의 되 거나 수 정 된 속성 설명자
  • 이 방법 은 함수 에 전 달 된 대상 을 되 돌려 줍 니 다.
    ES6 에 서 는 Symbol 형식의 특수성 때문에 Symbol 형식의 값 으로 대상 을 만 드 는 key 는 일반적인 정의 나 수정 과 다 르 며, Object. defineProperty 는 key 를 Symbol 의 속성 으로 정의 하 는 방법 중 하나 입 니 다.
    할당 작업 을 통 해 추 가 된 일반 속성 은 매 거 질 수 있 습 니 다. 속성 매 거 진 기간 에 나타 날 수 있 습 니 다 (for... in 또는 Object. keys 방법). 이 속성의 값 은 변경 할 수도 있 고 삭제 할 수도 있 습 니 다.이 방법 은 기본 추가 옵션 (또는 설정) 을 수정 할 수 있 습 니 다.기본적으로 Object. defineProperty () 를 사용 하여 추 가 된 속성 값 은 수정 할 수 없습니다.
    띠 설명자
    대상 에 현재 존재 하 는 속성 설명 자 는 두 가지 주요 형식 이 있 습 니 다. 데이터 설명자 와 액세스 설명자 입 니 다.데이터 설명 자 는 값 을 가 진 속성 입 니 다. 이 값 은 쓸 수도 있 고 쓸 수도 있 습 니 다.액세스 설명 자 는 getter - setter 함수 가 설명 하 는 속성 입 니 다.설명 자 는 반드시 이 두 가지 형식의 하나 여야 한다.둘 다 는 아니 야.
    데이터 설명자 와 액세스 설명 자 는 모두 다음 과 같은 선택 가능 한 키 값 을 가지 고 있 습 니 다.
    configurable
    또한 이 속성의 configurable 이 true 일 때 만 이 속성 설명 자 는 변 경 될 수 있 으 며 해당 하 는 대상 에서 도 삭 제 될 수 있 습 니 다.기본 값 false
    enumerable
    이 속성의 enumerable 이 true 일 때 만 이 속성 은 대상 의 매 거 진 속성 에 나타 날 수 있 습 니 다.기본 값 은 false 입 니 다.
    데이터 설명 자 는 다음 과 같은 선택 가능 한 키 값 을 동시에 가지 고 있 습 니 다:
    value
    이 속성 에 대응 하 는 값 입 니 다.유효한 자 바스 크 립 트 값 (수치, 대상, 함수 등) 일 수 있 습 니 다.기본 값 은 undefined 입 니 다.
    writable
    이 속성의 writable 이 true 일 때 만 value 는 할당 연산 자 에 의 해 변 경 됩 니 다.기본 값 false
    액세스 설명 자 는 다음 과 같은 선택 가능 한 키 를 가지 고 있 습 니 다:
    get
    속성 에 getter 를 제공 하 는 방법 입 니 다. getter 가 없 으 면 undefined 입 니 다.이 속성 에 접근 할 때 이 방법 은 실 행 됩 니 다. 방법 이 실 행 될 때 매개 변수 가 들 어 오지 않 지만 this 대상 에 들 어 갑 니 다 (계승 관계 로 인해 여기 this 가 반드시 이 속성 을 정의 하 는 대상 은 아 닙 니 다).기본 값 은 undefined 입 니 다.
    set
    속성 에 setter 를 제공 하 는 방법 입 니 다. setter 가 없 으 면 undefined 입 니 다.속성 값 이 수정 되면 이 방법 을 실행 합 니 다.이 방법 은 이 속성의 새로운 매개 변수 값 인 유일한 매개 변 수 를 받 아들 일 것 입 니 다.기본 값 은 undefined 입 니 다.
    하나의 설명자 가 value, writable, get, set 의 임의의 키 워드 를 가지 고 있 지 않 으 면 데이터 설명자 로 여 겨 집 니 다.설명자 에 (value 또는 writable) 와 (get 또는 set) 키워드 가 동시에 있 으 면 이상 이 발생 합 니 다.
    인 스 턴 스 와 소 개 를 더 많이 사용 합 니 다. 참조: MDN
    장식 자 모드
    Decorator 를 보기 전에 장식 자 모델 의 사용 을 살 펴 보 자. 장식 자 모델 은 대상 자 체 를 바 꾸 지 않 고 프로그램 이 실행 되 는 동안 대상 에 게 지적 을 할 수 있다 는 것 을 잘 알 고 있다.이전 대상 의 특성 에 영향 을 주지 않 고 추가 적 인 직책 기능 을 추가 하 는 것 이 특징 이다.
    like...this:
    이 단락 은 비교적 간단 하 니 코드 를 직접 보 세 요.
    let Monkey = function () {}
    Monkey.prototype.say = function () {
      console.log('         ');
    }
    let TensionMonkey = function (monkey) {
      this.monkey = monkey;
    }
    TensionMonkey.prototype.say = function () {
      this.monkey.say();
      console.log('     ,         !');
    }
    let monkey = new TensionMonkey(new Monkey());
    monkey.say();

    실행 결과:
    Decorator
    Decorator 는 문법 사탕 입 니 다. 그 뒤에 es5 Object.defineProperty(target,name,descriptor) 를 이용 하여 Object. defineProperty 를 알 고 있 습 니 다. 이 링크 를 옮 겨 주세요: MDN 문서
    그 배후 의 원 리 는 대체로 다음 과 같다.
    class Monkey{
      say(){
        console.log('  ,       ');
      }
    }

    위의 코드 를 실행 합 니 다. 대체적으로 코드 는 다음 과 같 습 니 다.
    Object.defineProperty(Monkey.prototype,'say',{
      value:function(){console.log('  ,       ')},
      enumerable:false,
      configurable:true,
      writable:true
    })

    하면, 만약, 만약...
    class Monkey{
    @readonly
    say(){console.log('        ')}
    }

    이 장식 기의 속성 은 Object. defineProperty 가 Monkey. prototype 으로 say 속성 을 등록 하기 전에 다음 코드 를 실행 합 니 다.
    let descriptor = {
      value:specifiedFunction,
      enumerable:false,
      configurable:true,
      writeable:true
    };
    
    descriptor = readonly(Monkey.prototype,'say',descriptor)||descriptor;
    Object.defineProperty(Monkey.prototype,'say',descriptor);

    위의 위조 코드 를 통 해 알 수 있 듯 이 Decorator 는 Object. defineProperty 가 Monkey. prototype 등록 속성 이 되 기 전에 장식 함 수 를 실 행 했 는데 이것 은 Object. defineProperty 에 대한 차단 에 속 합 니 다.그래서 Object. defineProperty 와 일치 하 는 형 삼 을 가지 고 있 습 니 다.
  • obj: 역할 의 대상
  • prop: 역할 의 속성 명
  • descriptor: 이 속성 에 대한 설명자
  • 다음은 간단하게 사용 할 게 요.
    class 에서 의 사용
  • 기 존의 클 라 스 를 계승 하 는 새로운 클 라 스 를 만 들 고 속성 을 추가 합 니 다
  • @name
    class Person{
      sayHello(){
        console.log(`hello ,my name is ${this.name}`)
      }
    }
    
    function name(constructor) {  
      return class extends constructor{
        name="Nealyang"
      }
    }
    
    new Person().sayHello()
    //hello ,my name is Nealyang
  • 현재 class 에 대한 수정 (mixin 유사)
  • @name
    @seal
    class Person {
      sayHello() {
        console.log(`hello ,my name is ${this.name}`)
      }
    }
    
    function name(constructor) {
      Object.defineProperty(constructor.prototype,'name',{
        value:'  '
      })
    }
    new Person().sayHello()
    
    //       
    
    function seal(constructor) {
      let descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, 'sayHello')
      Object.defineProperty(constructor.prototype, 'sayHello', {
        ...descriptor,
        writable: false
      })
    }
    
    new Person().sayHello = 1;// Cannot assign to read only property 'sayHello' of object '#'

    위 에서 mixin 하면 제 가 mixin 을 모 의 해 볼 게 요.
    class A {
      run() {
        console.log('    !')
      }
    }
    
    class B {
      jump() {
        console.log('   !')
      }
    }
    
    @mixin(A, B)
    class C {}
    
    function mixin(...args) {
      return function (constructor) {
        for (const arg of args) {
          for (let key of Object.getOwnPropertyNames(arg.prototype)) {
            if (key === 'constructor') continue;
            Object.defineProperty(constructor.prototype, key, Object.getOwnPropertyDescriptor(arg.prototype, key));
          }
        }
      }
    }
    
    let c = new C();
    c.jump();
    c.run();
    //    !
    //     !

    지금까지 우 리 는 매우 많은 코드 를 쓴 것 같 습 니 다. 맞습니다.이 편 은 철저하게 Decorator 를 투입 하기 위해 서...그냥 시작...
    class 멤버 에서 의 사용
    이런 장식 기의 쓰 기 는 우리 가 가장 잘 알 고 있 을 것 이다. 세 개의 인 자 를 받 아들 일 것 이다.
  • 장식 기 가 정적 구성원 에 마 운 트 되면 구조 함 수 를 되 돌려 주 고 인 스 턴 스 구성원 에 마 운 트 되면 클래스 의 원형
  • 을 되 돌려 줍 니 다.
  • 인 테 리 어 마 운 트 된 멤버 이름
  • Object. getOwnProperty Descriptor 의 반환 값
  • 우선, 우 리 는 정적 구성원 과 인 스 턴 스 구성원 의 차 이 를 명 확 히 한다.
    class Model{
      //    
      method1(){}
      method2 = ()=>{}
      
      //     
      static method3(){}
      static method4 = ()=>{}
    }

    method 1 과 method 2 는 인 스 턴 스 구성원 이지 만 method 1 은 prototype 에 존재 합 니 다. method 2 는 예화 대상 이후 에 만 있 습 니 다.
    method 3 과 method 4 는 정적 구성원 입 니 다. 이들 의 차 이 는 설명자 의 설정 을 매 거 할 수 있 는 지 여부 입 니 다. 우 리 는 babel 코드 를 통 해 볼 수 있 습 니 다.
    상기 코드 는 비교적 복잡 하고 간단 한 것 은 다음 과 같다.
    function Model () {
      //           
      this.method2 = function () {}
    }
    
    //           
    Object.defineProperty(Model.prototype, 'method1', {
      value: function () {}, 
      writable: true, 
      enumerable: false,  //        
      configurable: true
    })
    
    //            ,         
    Model.method4 = function () {}
    
    //            
    Object.defineProperty(Model, 'method3', {
      value: function () {}, 
      writable: true, 
      enumerable: false,  //        
      configurable: true
    })

    이 를 통 해 알 수 있 듯 이 method 2 만 예화 할 때 값 을 부여 합 니 다. 존재 하지 않 는 속성 은 descriptor 가 없 기 때문에 이것 이 바로 Property Decorator 에 대해 세 번 째 매개 변 수 를 전달 하지 않 는 이유 입 니 다. 왜 정적 구성원 도 descriptor 를 전달 하지 않 았 는 지 에 대해 합 리 적 인 설명 을 찾 지 못 했 지만 명확 하 게 사용 하려 면수 동 으로 구 할 수 있 습 니 다.
    상기 예제 와 같이 우 리 는 네 명의 구성원 에 게 장식 기 를 추가 한 후에 method 1 과 method 2 의 첫 번 째 매개 변 수 는 Model. prototype 이 고 method 3 과 method 4 의 첫 번 째 매개 변 수 는 Model 이다.
    class Model {
      //     
      @instance
      method1 () {}
      @instance
      method2 = () => {}
    
      //     
      @static
      static method3 () {}
      @static
      static method4 = () => {}
    }
    
    function instance(target) {
      console.log(target.constructor === Model)
    }
    
    function static(target) {
      console.log(target === Model)
    }

    함수, 접근 기, 속성 3 자 장식 기 사용
  • 함수 장식 기의 반환 값 은 기본적으로 속성의 value 설명자 로 존재 합 니 다. undefined 로 되 돌아 가면 무시 합 니 다
  • class Model {
      @log1
      getData1() {}
      @log2
      getData2() {}
    }
    
    //    ,    value   
    function log1(tag, name, descriptor) {
      return {
        ...descriptor,
        value(...args) {
          let start = new Date().valueOf()
          try {
            return descriptor.value.apply(this, args)
          } finally {
            let end = new Date().valueOf()
            console.log(`start: ${start} end: ${end} consume: ${end - start}`)
          }
        }
      }
    }
    
    //    、       
    function log2(tag, name, descriptor) {
      let func = descriptor.value //         
    
      //      value
      descriptor.value = function (...args) {
        let start = new Date().valueOf()
        try {
          return func.apply(this, args)
        } finally {
          let end = new Date().valueOf()
          console.log(`start: ${start} end: ${end} consume: ${end - start}`)
        }
      }
    }
  • 접근 기의 Decorator 는 get set 접두사 함수 입 니 다. 속성 을 제어 하 는 할당, 수치 추출 작업 에 사용 되 며 사용 에 있어 함수 장식 기와 차이 가 없습니다
  • 
    class Modal {
      _name = 'Niko'
    
      @prefix
      get name() { return this._name }
    }
    
    function prefix(target, name, descriptor) {
      return {
        ...descriptor,
        get () {
          return `wrap_${this._name}`
        }
      }
    }
    
    console.log(new Modal().name) // wrap_Niko
  • 속성 장식 기 는 descriptor 가 되 돌아 오지 않 고 장식 기 함수 의 반환 값 도 무 시 됩 니 다. 만약 에 우리 가 특정한 정적 속성 을 수정 하려 면 스스로 descriptor
  • 를 가 져 와 야 합 니 다.
    class Modal {
      @prefix
      static name1 = 'Niko'
    }
    
    function prefix(target, name) {
      let descriptor = Object.getOwnPropertyDescriptor(target, name)
    
      Object.defineProperty(target, name, {
        ...descriptor,
        value: `wrap_${descriptor.value}`
      })
    }
    
    console.log(Modal.name1) // wrap_Niko

    하나의 사례 의 속성 에 대해 서 는 직접 수정 하 는 방안 이 없 지만 우 리 는 다른 장식 기 를 결합 하여 곡선 으로 나 라 를 구 할 수 있다.
    예 를 들 어, 우 리 는 이름 과 나 이 를 초기 화 하 는 매개 변수 로 입력 한 다음 에 이 두 매개 변수 에 대응 하 는 형식 검 사 를 설정 해 야 합 니 다.
    const validateConf = {} //       
    
    @validator
    class Person {
      @validate('string')
      name
      @validate('number')
      age
    
      constructor(name, age) {
        this.name = name
        this.age = age
      }
    }
    
    function validator(constructor) {
      return class extends constructor {
        constructor(...args) {
          super(...args)
    
          //              
          for (let [key, type] of Object.entries(validateConf)) {
            if (typeof this[key] !== type) throw new Error(`${key} must be ${type}`)
          }
        }
      }
    }
    
    function validate(type) {
      return function (target, name, descriptor) {
        //                   
        validateConf[name] = type
      }
    }
    
    new Person('Niko', '18')  // throw new error: [age must be number]

    함수 매개 변수 장식 기
    const parseConf = {}
    class Modal {
      @parseFunc
      addOne(@parse('number') num) {
        return num + 1
      }
    }
    
    //              
    function parseFunc (target, name, descriptor) {
      return {
        ...descriptor,
        value (...arg) {
          //        
          for (let [index, type] of parseConf) {
            switch (type) {
              case 'number':  arg[index] = Number(arg[index])             break
              case 'string':  arg[index] = String(arg[index])             break
              case 'boolean': arg[index] = String(arg[index]) === 'true'  break
            }
    
            return descriptor.value.apply(this, arg)
          }
        }
      }
    }
    
    //                 
    function parse(type) {
      return function (target, name, index) {
        parseConf[index] = type
      }
    }
    
    console.log(new Modal().addOne('10')) // 11

    장식 용 용례
    log
    하나의 방법 에 log 함 수 를 추가 하고 입력 한 인 자 를 검사 합 니 다.
        let log = type => {
          return (target,name,decorator) => {
            const method = decorator.value;
            console.log(method);
    
            decorator.value = (...args) => {
              console.info(`${type}     :${name}(${args}) = ?`);
              let result;
              try{
                result = method.apply(target,args);
                console.info(`(${type})    : ${name}(${args}) => ${result}`);
              }catch(err){
                console.error(`(${type})   : ${name}(${args}) => ${err}`);
              }
              return result;
            }
          }
        }
    
        class Math {
          @log('add')
          add(a, b) {
            return a + b;
          }
        }
    
        const math = new Math();
    
        // (add)    : add(2,4) => 6
        math.add(2, 4);

    time
    통계 방법 실행 시간:
    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);
            }
          }
        }
      }
    }

    debounce
    실행 방법 에 대해 떨 림 방지 처 리 를 하 다.
    class Toggle extends React.Component {
    
      @debounce(500, true)
      handleClick() {
        console.log('toggle')
      }
    
      render() {
        return (
          
        );
      }
    }
    
    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()
          }
        };
      }
    }

    더 많은 코어 - decorators 의 예 뒤에 Nealyang / Personal Blog 에 보충 하고 주석 설명 을 추가 합 니 다.
    레 퍼 런 스
  • 자 바스 크 립 트 장식 기의 묘 용
  • ES7 장식 장식 자 모드
  • ES7 의 Decorators 는 AOP 예제
  • 를 실현 한다.
  • 상세 설명 ES7 JavaScript Decorators
  • Using ES.later Decorators as Mixins
  • core-decorators
  • 좋은 웹페이지 즐겨찾기