ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScriptデコレータ関数の使い方まとめ

JavaScriptデコレータ関数の使い方まとめ

青灯夜游
青灯夜游転載
2018-10-09 14:51:331617ブラウズ

この記事は、JS デコレーター関数に関する関連する使用法と知識ポイントをまとめたものです。必要な方は参考にしていただければ幸いです。

ES6 では、クラス オブジェクト (class や extends など) の関連する定義と操作が追加され、複数の異なるクラス間で一部のメソッドや動作を共有または拡張することが容易になりました。現時点では、これらのことを達成するためのより洗練された方法が必要です。

デコレータとは

Python のデコレータ

オブジェクト指向 (OOP) の中でデザインパターン、デコレータは装飾パターンと呼ばれます。 OOP のデコレーション モードは、継承と組み合わせを通じて実装する必要があります。Python は、OOP のデコレーターのサポートに加えて、構文レベルから直接デコレーターもサポートします。

Python に精通していれば、よく知っているでしょう。それでは、まず Python のデコレータがどのようなものかを見てみましょう:

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

ここでの @decorator は、いわゆるデコレータです。上記のコードでは、ターゲット メソッドを実行する前にデコレータを使用してテキスト行を出力しており、元のメソッドには一切変更を加えていません。コードは基本的に次と同等です。

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

コードから、デコレータがパラメータを受け取ることは難しくありません。これは、拡張されたコンテンツを処理した後、デコレータのメソッドを返します。将来の呼び出しは無効になり、元のメソッド オブジェクトへのアクセスも失われます。関数に装飾を適用する場合、実際には装飾されたメソッドのエントリ参照を変更して、デコレータによって返されたメソッドのエントリ ポイントを指すようにします。これにより、元の関数を拡張および変更できるようになります。

ES7 デコレータ

ES7 のデコレータもこの構文シュガーを利用しますが、ES5 の Object.defineProperty メソッドに依存します。

Object.defineProperty

Object.defineProperty() メソッドは、オブジェクトの新しいプロパティを直接定義するか、オブジェクトの既存のプロパティを変更します。このオブジェクトを返します。

このメソッドを使用すると、オブジェクトのプロパティを正確に追加または変更できます。割り当てによって追加された通常のプロパティは、プロパティの列挙中に公開されるプロパティを作成し (for...in メソッドまたは Object.keys メソッド)、これらの値は変更または削除できます。このアプローチにより、これらの追加の詳細をデフォルト値から変更できるようになります。デフォルトでは、Object.defineProperty() を使用して追加されたプロパティ値は不変です。

構文

Object.defineProperty(obj, prop, descriptor)
  1. obj: プロパティを定義するオブジェクト。

  2. prop: 定義または変更するプロパティの名前。

  3. descriptor: 定義または変更する属性記述子。

  4. 戻り値: 関数に渡されるオブジェクト。

ES6 では、Symbol 型の特殊性により、Symbol 型の値をオブジェクトのキーとして使用することが従来の定義や変更とは異なり、Object.defineProperty によってシンボルとしてのキー 属性メソッドの 1 つ。

属性記述子

オブジェクト内に現在存在する属性記述子には、データ記述子とアクセス記述子の 2 つの主な形式があります。

データ記述子は、書き込み可能または書き込み不可能な値を持つプロパティです。

  • #アクセス記述子は、ゲッター関数とセッター関数のペアによって記述されるプロパティです。

  • 記述子は、これら 2 つの形式のいずれかでなければなりません。両方を指定することはできません。

データ記述子とアクセス記述子の両方に、次のオプションのキー値があります:

configurable

if および When のみプロパティの構成可能値が true の場合、プロパティ記述子は変更でき、対応するオブジェクトからプロパティを削除することもできます。デフォルトは false です。

enumerable

enumerable は、オブジェクトのプロパティを for...in ループおよび Object.keys() で列挙できるかどうかを定義します。

プロパティの列挙可能値が true の場合に限り、そのプロパティはオブジェクトの列挙プロパティに表示されます。デフォルトは false です。
データ記述子には、次のオプションのキー値もあります:

value

この属性に対応する値。任意の有効な JavaScript 値 (数値、オブジェクト、関数など) を指定できます。デフォルトは未定義です。

writable

プロパティの writable が true の場合にのみ、代入演算子によって値を変更できます。デフォルトは false です。

アクセス記述子には、次のオプションのキー値もあります。

get

#プロパティのゲッターを提供するメソッド、または存在する場合未定義のゲッターではありません。このメソッドの戻り値が属性値として使用されます。デフォルトは未定義です。

set

プロパティにセッターを提供するメソッド。セッターがない場合は未定義になります。このメソッドは一意のパラメータを受け入れ、パラメータの新しい値をプロパティに割り当てます。デフォルトは未定義です。

記述子に 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;

也就是说,装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。

如果觉得一个参数不够用,可以在装饰器外面再封装一层函数。

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 一共可以接受三个参数。

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参数指的是类本身);

  • 第二个参数是 所要装饰的属性名

  • 第三个参数是 该属性的描述对象

另外,上面代码说明,装饰器(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}` }
}

有了装饰器,就可以改写上面的代码。装饰

@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 = (
  <p className="toast-loading">
   <Loading className="loading-icon"/>
   <p>加载中...</p>
  </p>
 );

 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(&#39;finally&#39;)(res)) {
     res.finally(() => {
      Toast.hide();
     });
    } else {
     Toast.hide();
    }
   }
  };
  return descriptor;
 };
}

问题:这里大家可以想想看,如果我们不希望每次请求数据时都出现 loading,而是要求只要后台请求时间大于 300ms 时,才显示loading,这里需要怎么改?

总结:以上就是本篇文的全部内容,希望能对大家的学习有所帮助。更多相关教程请访问JavaScript视频教程

相关推荐:

php公益培训视频教程

JavaScript图文教程

JavaScript在线手册

以上がJavaScriptデコレータ関数の使い方まとめの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjb51.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。