JSアクセサリー、1編で十分です


詳細は、Github blogでご覧ください
クラスオブジェクトに関する定義と操作(classやextendsなど)がES 6に追加され、複数の異なるクラス間でいくつかの方法や動作を共有したり拡張したりするときに、それほど優雅ではありません.この時、私たちはこれらのことを完成させるためにもっと優雅な方法が必要です.
デコレーションとは
Pythonのアクセサリー
オブジェクト(OOP)向けのデザインモードではdecoratorを装飾モードと呼ぶ.OOPの装飾モードは継承と組合せで実現する必要があるが,PythonはOOPのdecoratorをサポートできるほか,直接文法階層からdecoratorをサポートする.
pythonに詳しいなら、きっと見慣れないでしょう.では、pythonのアクセサリーがどんなものか見てみましょう.

def decorator(f):
    print "my decorator"
    return f
@decorator
def myfunc():
    print "my function"
myfunc()
# my decorator
# my function

ここの@decoratorは私たちが言った装飾器です.上記のコードでは、デザイナを使用してターゲットメソッドを実行する前にテキストを1行印刷し、元のメソッドを変更しませんでした.コードは基本的に次のとおりです.

def decorator(f):
    def wrapper():
        print "my decorator"
        return f()
    return wrapper
def myfunc():
    print "my function"
myfunc = decorator(myfuc)

コードからも分かるように、デコレーションdecoratorはパラメータ、すなわちデコレーションされたターゲットメソッドを受信し、拡張されたコンテンツを処理した後、後で呼び出すためのメソッドを返し、元のメソッドオブジェクトへのアクセスを失う.ある装飾を適用すると,実際には被装飾方法の入口参照を変更し,装飾器が返す方法の入口点を再指向させ,元の関数の拡張,修正などの操作を実現する.
ES 7の装飾器
ES 7のdecoratorも同様にこの文法糖を参考にしているが、ES 5のObject.defineProperty法に依存している.
Object.defineProperty Object.defineProperty()メソッドは、オブジェクトに直接新しいプロパティを定義するか、オブジェクトの既存のプロパティを変更してオブジェクトに戻ります.
この方法では、オブジェクトのプロパティを正確に追加または変更できます.値を割り当てて追加する通常の属性は、属性列挙中に表示される属性(for...inまたはObject.keysメソッド)を作成します.これらの値は変更したり削除したりできます.この方法では、これらの追加の詳細をデフォルト値から変更できます.デフォルトでは、Object.defineProperty()を使用して追加された属性値は可変ではありません.
構文

Object.defineProperty(obj, prop, descriptor)
  • obj:プロパティを定義するオブジェクト.
  • prop:定義または変更する属性の名前.
  • descriptor:定義または変更される属性記述子.
  • 戻り値:関数に渡されたオブジェクト.

  • ES 6では、Symbolタイプの特殊性のため、Symbolタイプの値でオブジェクトを作成するkeyは、従来の定義または変更とは異なり、Object.definePropertyは、keyがSymbolの属性であることを定義する方法の1つである.
    属性記述子
    オブジェクトに現在存在するプロパティ記述子には、データ記述子とアクセス記述子の2つの主要な形式があります.
  • データ記述子は、書き込み可能であるか、書き込み可能でないかのいずれかの値を有する属性である.
  • アクセス記述子はgetter-setter関数ペアによって記述される属性である.

  • 記述子はこの2つの形式の1つでなければならない.同時に両者ではいけない.
    データ記述子とアクセス記述子には、次のオプションキー値があります.
    configurable
    属性記述子は、属性のconfigurableがtrueである場合にのみ変更され、対応するオブジェクトからも削除される.デフォルトはfalseです.
    enumerable
    Enumerableは、オブジェクトのプロパティがfor...InサイクルとObject.keys()に列挙される.
    このプロパティは、プロパティのenumerableがtrueである場合にのみ、オブジェクトの列挙プロパティに表示されます.デフォルトはfalseです.データ記述子には、次のオプションキー値があります.
    value
    この属性に対応する値.有効なJavaScript値(数値、オブジェクト、関数など)でもかまいません.デフォルトはundefinedです.
    writable
    valueは、属性のwritableがtrueである場合にのみ、付与演算子によって変更されます.デフォルトはfalseです.
    アクセス記述子には、次のオプションキー値があります.
    get
    属性にgetterを提供する方法で、getterがなければundefinedです.このメソッドの戻り値は、属性値として使用されます.デフォルトはundefinedです.
    set
    属性にsetterを提供する方法で、setterがなければundefinedです.このメソッドは、一意のパラメータを受け入れ、そのパラメータの新しい値を属性に割り当てます.デフォルトはundefinedです.
    記述子がvalue,writable,get,setのいずれかのキーワードを持たない場合、データ記述子とみなされます.記述子に(valueまたはwritable)キーと(getまたはset)キーが同時にある場合、例外が発生します.
    使用法
    類の装飾
    
    @testable
    class MyTestableClass {
      // ...
    }
    
    function testable(target) {
      target.isTestable = true;
    }
    
    MyTestableClass.isTestable // true
    

    上記のコードでは、@testableが装飾器です.MyTestableClassというクラスの動作を修正し、静的属性isTestableを追加しました.testable関数のパラメータtargetはMyTestableClassクラスそのものである.
    基本的に、装飾器の振る舞いは以下の通りです.
    
    @decorator
    class A {}
    
    //    
    
    class A {}
    A = decorator(A) || A;
    

    すなわち,アクセラレータはクラスを処理する関数である.装飾関数の最初のパラメータは、装飾するターゲットクラスです.
    1つのパラメータが足りないと思ったら、装飾器の外にもう1つの関数をカプセル化することができます.
    
    function testable(isTestable) {
      return function(target) {
        target.isTestable = isTestable;
      }
    }
    
    @testable(true)
    class MyTestableClass {}
    MyTestableClass.isTestable // true
    
    @testable(false)
    class MyClass {}
    MyClass.isTestable // false
    

    上記のコードでは、アクセサリーtestableはパラメータを受け入れることができ、これはアクセサリーの動作を変更できることに等しい.
    注意:クラスの動作に対する装飾器の変更は、実行時ではなくコードコンパイル時に発生します.これは、装飾器がコンパイル段階でコードを実行できることを意味します.すなわち,装飾器の本質はコンパイル時に実行される関数である.
    前の例では、クラスに静的プロパティを追加し、インスタンスプロパティを追加する場合は、ターゲットクラスのprototypeオブジェクトで操作できます.
    次は別の例です.
    
    // mixins.js
    export function mixins(...list) {
      return function (target) {
        Object.assign(target.prototype, ...list)
      }
    }
    
    // main.js
    import { mixins } from './mixins'
    
    const Foo = {
      foo() { console.log('foo') }
    };
    
    @mixins(Foo)
    class MyClass {}
    
    let obj = new MyClass();
    obj.foo() // 'foo'
    

    上のコードは,アクセラレータmixinsにより,FooオブジェクトのメソッドをMyClassのインスタンスに追加した.
    方法の装飾
    アクセサリーはクラスを飾るだけでなく、クラスの属性を飾ることもできます.
    
    class Person {
      @readonly
      name() { return `${this.first} ${this.last}` }
    }
    

    上のコードでは、アクセサリーreadonlyは「クラス」のnameメソッドを飾るために使用されます.
    アクセサリー関数readonlyは全部で3つのパラメータを受け入れることができます.
    
    function readonly(target, name, descriptor){
      // descriptor        
      // {
      //   value: specifiedFunction,
      //   enumerable: false,
      //   configurable: true,
      //   writable: true
      // };
      descriptor.writable = false;
      return descriptor;
    }
    
    readonly(Person.prototype, 'name', descriptor);
    //    
    Object.defineProperty(Person.prototype, 'name', descriptor);
    
  • 装飾器の最初のパラメータはクラスの原型オブジェクトであり、前例はPersonである.prototypeは、装飾器の本意は「装飾」クラスのインスタンスであるが、このときインスタンスはまだ生成されていないので、原型を装飾するしかない(これはクラスの装飾とは異なり、その場合targetパラメータはクラス自体を指す).
  • の2番目のパラメータは、装飾する属性名
  • である.
  • の3番目のパラメータは、属性の記述オブジェクト
  • である.
    また、上記のコードは、 (readonly)が属性の (descriptor)を変更し、変更された記述オブジェクトが属性を定義するために使用されることを示している.
    関数メソッドの装飾
    アクセラレータはクラスとクラスのメソッドにのみ使用でき、関数のアップグレードがあるため、関数には使用できません.
    一方,関数を装飾する必要がある場合は,高次関数の形式で直接実行することができる.
    
    function doSomething(name) {
      console.log('Hello, ' + name);
    }
    
    function loggingDecorator(wrapped) {
      return function() {
        console.log('Starting');
        const result = wrapped.apply(this, arguments);
        console.log('Finished');
        return result;
      }
    }
    
    const wrapped = loggingDecorator(doSomething);
    

    core-decorators.js
    core-decorators.jsはサードパーティモジュールであり、いくつかの一般的な装飾器を提供し、それによって装飾器をよりよく理解することができる.
    @autobind
    Autobind装飾器は、メソッド内のthisオブジェクトを元のオブジェクトにバインドします.
    @readonly
    readonly装飾器は属性や方法を書くことができません.
    @override
    overrideアクセラレータは、子クラスのメソッドが親クラスの同名メソッドを正しく上書きしているかどうかをチェックし、正しくない場合はエラーを報告します.
    
    import { override } from 'core-decorators';
    
    class Parent {
      speak(first, second) {}
    }
    
    class Child extends Parent {
      @override
      speak() {}
      // SyntaxError: Child#speak() does not properly override Parent#speak(first, second)
    }
    
    // or
    
    class Child extends Parent {
      @override
      speaks() {}
      // SyntaxError: No descriptor matching Child#speaks() was found on the prototype chain.
      //
      //   Did you mean "speak"?
    }
    

    @deprecate(別名@deprecated)
    deprecateまたはdeprecated装飾器は、コンソールに警告を表示し、この方法が廃止されることを示します.
    
    import { deprecate } from 'core-decorators';
    
    class Person {
      @deprecate
      facepalm() {}
    
      @deprecate('We stopped facepalming')
      facepalmHard() {}
    
      @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
      facepalmHarder() {}
    }
    
    let person = new Person();
    
    person.facepalm();
    // DEPRECATION Person#facepalm: This function will be removed in future versions.
    
    person.facepalmHard();
    // DEPRECATION Person#facepalmHard: We stopped facepalming
    
    person.facepalmHarder();
    // DEPRECATION Person#facepalmHarder: We stopped facepalming
    //
    //     See http://knowyourmeme.com/memes/facepalm for more details.
    //
    

    @suppressWarnings
    suppressWarnings装飾器はdeprecated装飾器によるconsoleを抑制する.warn()呼び出し.ただし、非同期コードが発行する呼び出しは除外されます.
    シーンの操作
    アクセサリーには注釈の役割があります
    
    @testable
    class Person {
      @readonly
      @nonenumerable
      name() { return `${this.first} ${this.last}` }
    }
    

    上記のコードから,Personクラスはテスト可能であり,nameメソッドは読み取り専用で枚挙にいとまがないことが一目で分かる.
    Reactの接続
    実際の開発では,ReactとReduxライブラリを組み合わせて使用する場合,次のように書くことがしばしば必要である.
    
    class MyReactComponent extends React.Component {}
    
    export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
    

    アクセサリーがあれば、上のコードを書き換えることができます.に飾りを付ける
    
    @connect(mapStateToProps, mapDispatchToProps)
    export default class MyReactComponent extends React.Component {}
    

    対照的に、後者の書き方は理解しやすいように見えます.
    新しい機能のアラートまたは権限
    メニューがクリックされるとイベントブロックが行われ、そのメニューに新しい機能が更新されるとポップアップ表示されます.
    
    /**
     * @description     ,        ,     
     * @param code     code
     * @returns {function(*, *, *)}
     */
     const checkRecommandFunc = (code) => (target, property, descriptor) => {
        let desF = descriptor.value; 
        descriptor.value = function (...args) {
          let recommandFuncModalData = SYSTEM.recommandFuncCodeMap[code];
    
          if (recommandFuncModalData && recommandFuncModalData.id) {
            setTimeout(() => {
              this.props.dispatch({type: 'global/setRecommandFuncModalData', recommandFuncModalData});
            }, 1000);
          }
          desF.apply(this, args);
        };
        return descriptor;
      };
    
    

    loading
    Reactプロジェクトでは、バックグラウンドにデータを要求するときに、ページにloadingアニメーションを表示する必要がある場合があります.このとき、アクセサリーを使って、優雅に機能を実現することができます.
    
    @autobind
    @loadingWrap(true)
    async handleSelect(params) {
      await this.props.dispatch({
        type: 'product_list/setQuerypParams',
        querypParams: params
      });
    }
    

    loadingWrap関数は次のとおりです.
    
    export function loadingWrap(needHide) {
    
      const defaultLoading = (
        <div className="toast-loading">
          <Loading className="loading-icon"/>
          <div>   ...</div>
        </div>
      );
    
      return function (target, property, descriptor) {
        const raw = descriptor.value;
        
        descriptor.value = function (...args) {
          Toast.info(text || defaultLoading, 0, null, true);
          const res = raw.apply(this, args);
          
          if (needHide) {
            if (get('finally')(res)) {
              res.finally(() => {
                Toast.hide();
              });
            } else {
              Toast.hide();
            }
          }
        };
        return descriptor;
      };
    }
    

    質問:ここで皆さんは考えてみてください.もし私たちがデータを要求するたびにloadingが現れることを望んでいないならば、バックグラウンドの要求時間が300 msより大きい限り、loadingを表示することを要求します.ここはどのように変更する必要がありますか?
    リファレンス
  • Object.defineProperty()
  • JavaScript Decorators: What They Are and When to Use Them
  • ECMAScript 6入門
  • 原文住所:https://segmentfault.com/a/1190000014495089
    転入先https://www.cnblogs.com/datiangou/p/9759792.html