Home > Web Front-end > JS Tutorial > body text

Summary of JavaScript decorator function usage

青灯夜游
Release: 2018-10-09 14:55:56
forward
1577 people have browsed it

This article summarizes the relevant usage and knowledge points about JS decorator functions. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

In ES6, related definitions and operations of class objects (such as class and extends) have been added, which makes it easier for us to share or extend some methods or behaviors between multiple different classes. Not so elegant. At this time, we need a more elegant method to help us accomplish these things.

What is a decorator

Python’s decorator

In object-oriented (OOP) Among the design patterns, decorator is called the decoration pattern. OOP's decoration mode needs to be implemented through inheritance and combination, and Python, in addition to supporting OOP's decorators, also supports decorators directly from the syntax level.

If you are familiar with python, you will be familiar with it. So let’s first take a look at what a decorator in python looks like:

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

The @decorator here is what we call the decorator. In the above code, we use the decorator to print a line of text before executing our target method, and do not make any modifications to the original method. The code is basically equivalent to:

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

It is not difficult to see from the code that the decorator receives a parameter, which is the target method we are decorated. After processing the extended content, it returns a method for future calls. , and also loses access to the original method object. When we apply decoration to a function, we actually change the entry reference of the decorated method so that it points back to the entry point of the method returned by the decorator, thereby allowing us to extend and modify the original function.

ES7 decorator

The decorator in ES7 also draws on this syntax sugar, but relies on the ES5 Object.defineProperty method.

Object.defineProperty

The Object.defineProperty() method directly defines a new property on an object, or modifies an existing property of an object. and returns this object.

This method allows precise addition or modification of the object's properties. Ordinary properties added by assignment create properties that are exposed during property enumeration (for...in or Object.keys methods), and these values ​​can be changed or deleted. This approach allows these additional details to be changed from the default values. By default, property values ​​added using Object.defineProperty() are immutable.

Syntax

Object.defineProperty(obj, prop, descriptor)
Copy after login
  1. obj: The object on which the properties are to be defined.

  2. prop: The name of the property to be defined or modified.

  3. descriptor: The attribute descriptor to be defined or modified.

  4. Return value: the object passed to the function.

In ES6, due to the particularity of the Symbol type, using the Symbol type value as the key of the object is different from the conventional definition or modification, and Object.defineProperty defines the key as Symbol One of the attribute methods.

Attribute descriptor

There are two main forms of attribute descriptors currently existing in objects: data descriptors and access descriptors.

A data descriptor is a property with a value that may or may not be writable.

  • #Access descriptors are properties described by getter-setter function pairs.

  • The descriptor must be one of these two forms; not both.

Both the data descriptor and the access descriptor have the following optional key values:

configurable

if and only When the property's configurable is true, the property descriptor can be changed, and the property can also be deleted from the corresponding object. Default is false.

enumerable

enumerable defines whether the properties of an object can be enumerated in for...in loops and Object.keys().

If and only if the enumerable of the property is true, the property can appear in the enumeration property of the object. Default is false.
The data descriptor also has the following optional key values:

value

The value corresponding to this attribute. Can be any valid JavaScript value (number, object, function, etc.). Default is undefined.

writable

The value can be changed by the assignment operator if and only if the writable of the property is true. Default is false.

The access descriptor also has the following optional key values:

get

#A method that provides a getter for the property, or if there is no getter. undefined. The return value of this method is used as the attribute value. Default is undefined.

set

A method that provides a setter for a property. If there is no setter, it will be undefined. This method will accept a unique parameter and assign the parameter's new value to the property. Default is undefined.

If a descriptor does not have any of the value, writable, get and set keywords, then it will be considered a data descriptor. If a descriptor has both the (value or writable) and (get or set) keywords, an exception will be generated.
usage

类的装饰

@testable
class MyTestableClass {
 // ...
}

function testable(target) {
 target.isTestable = true;
}

MyTestableClass.isTestable // true
Copy after login

上面代码中,@testable 就是一个装饰器。它修改了 MyTestableClass这 个类的行为,为它加上了静态属性isTestable。testable 函数的参数 target 是 MyTestableClass 类本身。

基本上,装饰器的行为就是下面这样。

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;
Copy after login

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

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

function testable(isTestable) {
 return function(target) {
  target.isTestable = isTestable;
 }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false
Copy after login

上面代码中,装饰器 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'
Copy after login

上面代码通过装饰器 mixins,把Foo对象的方法添加到了 MyClass 的实例上面。

方法的装饰

装饰器不仅可以装饰类,还可以装饰类的属性。

class Person {
 @readonly
 name() { return `${this.first} ${this.last}` }
}
Copy after login

上面代码中,装饰器 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);
Copy after login
  • 装饰器第一个参数是 类的原型对象,上例是 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);
Copy after login

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"?
}
Copy after login

@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.
//
Copy after login

@suppressWarnings

suppressWarnings 装饰器抑制 deprecated 装饰器导致的 console.warn() 调用。但是,异步代码发出的调用除外。

使用场景

装饰器有注释的作用

@testable
class Person {
 @readonly
 @nonenumerable
 name() { return `${this.first} ${this.last}` }
}
Copy after login

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

@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
Copy after login

相对来说,后一种写法看上去更容易理解。

新功能提醒或权限

菜单点击时,进行事件拦截,若该菜单有新功能更新,则弹窗显示。

/**
 * @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;
 };
Copy after login

loading

在 React 项目中,我们可能需要在向后台请求数据时,页面出现 loading 动画。这个时候,你就可以使用装饰器,优雅地实现功能。

@autobind
@loadingWrap(true)
async handleSelect(params) {
 await this.props.dispatch({
  type: 'product_list/setQuerypParams',
  querypParams: params
 });
}
Copy after login

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;
 };
}
Copy after login

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

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

相关推荐:

php公益培训视频教程

JavaScript图文教程

JavaScript在线手册

The above is the detailed content of Summary of JavaScript decorator function usage. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:jb51.net
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template