首頁 > web前端 > js教程 > 在專案中怎樣使用JS裝飾器函數

在專案中怎樣使用JS裝飾器函數

php中世界最好的语言
發布: 2018-06-07 10:04:59
原創
1517 人瀏覽過

這次帶給大家怎樣在專案中使用JS裝飾器函數,怎麼能在專案中使用JS裝飾器函數的注意事項有哪些,以下就是實戰案例,一起來看一下。

在ES6 中增加了對類別物件的相關定義和操作(例如class 和extends ),這使得我們在多個不同類別之間共享或擴展一些方法或行為的時候,變得併不是那麼優雅。這時候,我們需要一種更優雅的方法來幫助我們完成這些事情。

什麼是裝飾器

Python 的裝飾器

在物件導向(OOP)的設計模式中,decorator被稱為裝飾模式。 OOP的裝飾模式需要透過繼承和組合來實現,而Python除了能支援 OOP 的 decorator 外,直接從語法層次支援 decorator。

如果你熟悉 python 的話,對它一定不會陌生。那我們先來看看 python 裡的裝飾器是什麼樣子的吧:

1

2

3

4

5

6

7

8

9

def decorator(f):

  print "my decorator"

  return f

@decorator

def myfunc():

  print "my function"

myfunc()

# my decorator

# my function

登入後複製

這裡的 @decorator 就是我們說的裝飾器。在上面的程式碼中,我們利用裝飾器給我們的目標方法執行前打印出了一行文本,並且並沒有對原始方法做任何的修改。程式碼基本上等同於:

1

2

3

4

5

6

7

8

def decorator(f):

  def wrapper():

    print "my decorator"

    return f()

  return wrapper

def myfunc():

  print "my function"

myfunc = decorator(myfuc)

登入後複製

透過程式碼我們也不難看出,裝飾器decorator 接收一個參數,也就是我們被裝飾的目標方法,處理完擴展的內容以後再返回一個方法,供以後調用,同時也失去了對原方法對象的存取。當我們對某個應用了裝飾以後,其實就改變了被裝飾方法的入口引用,使其重新指向了裝飾器返回的方法的入口點,從而來實現我們對原函數的擴展、修改等操作。

ES7 的裝飾器

ES7 中的 decorator 同樣借鑒了這個語法糖,不過依賴 ES5 的 Object.defineProperty 方法 。

Object.defineProperty

Object.defineProperty() 方法會直接在一個物件上定義一個新屬性,或修改一個物件的現有屬性, 並傳回這個對象。

此方法允許精確新增或修改物件的屬性。透過賦值來新增的普通屬性會建立在屬性枚舉期間顯示的屬性(for...in 或 Object.keys 方法), 這些值可以被改變,也可以被刪除。這種方法允許這些額外的細節從預設值改變。預設情況下,使用 Object.defineProperty() 新增的屬性值是不可變的。

語法

1

Object.defineProperty(obj, prop, descriptor)

登入後複製
  1. obj:要在其上定義屬性的物件。

  2. prop:要定義或修改的屬性的名稱。

  3. descriptor:將被定義或修改的屬性描述符。

  4. 傳回值:傳遞給函數的物件。

在ES6中,由於Symbol類型的特殊性,用Symbol類型的值來做物件的key與常規的定義或修改不同,而Object.defineProperty 是定義key為Symbol的屬性的方法之一。

屬性描述符

物件裡目前存在的屬性描述符有兩種主要形式:資料描述符和存取描述符。

資料描述子是一個具有值的屬性,該值可能是可寫的,也可能不是可寫的。

  • 存取描述子是由 getter-setter 函數對描述的屬性。

  • 描述子必須是這兩種形式之一;不能同時是兩者。

資料描述子和存取描述子都具有以下可選鍵值:

#configurable

當且僅當該屬性的configurable 為true 時,該屬性描述子才能夠被改變,同時該屬性也能從對應的物件上被刪除。預設為 false。

enumerable

enumerable定義了物件的屬性是否可以在 for...in 迴圈和 Object.keys() 中被列舉。

當且僅當該屬性的 enumerable 為 true 時,該屬性才能夠出現在物件的列舉屬性中。預設為 false。
資料描述子同時具有下列可選鍵值:

value

#該屬性對應的值。可以是任何有效的 JavaScript 值(數值,對象,函數等)。預設為 undefined。

writable

当且仅当该属性的 writable 为 true 时,value 才能被赋值运算符改变。默认为 false。

存取描述符同时具有以下可选键值:

get

一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。

set

一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。

如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
用法

类的装饰

1

2

3

4

5

6

7

8

@testable

class MyTestableClass {

 // ...

}

function testable(target) {

 target.isTestable = true;

}

MyTestableClass.isTestable // true

登入後複製

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

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

1

2

3

4

5

@decorator

class A {}

// 等同于

class A {}

A = decorator(A) || A;

登入後複製

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

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

1

2

3

4

5

6

7

8

9

10

11

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 对象操作。

下面是另外一个例子。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// 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 的实例上面。

方法的装饰

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

1

2

3

4

class Person {

 @readonly

 name() { return `${this.first} ${this.last}` }

}

登入後複製

上面代码中,装饰器 readonly 用来装饰“类”的name方法。

装饰器函数 readonly 一共可以接受三个参数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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),然后被修改的描述对象再用来定义属性。

函数方法的装饰

装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。

另一方面,如果一定要装饰函数,可以采用高阶函数的形式直接执行。

1

2

3

4

5

6

7

8

9

10

11

12

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 装饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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 装饰器在控制台显示一条警告,表示该方法将废除。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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() 调用。但是,异步代码发出的调用除外。

使用场景

装饰器有注释的作用

1

2

3

4

5

6

@testable

class Person {

 @readonly

 @nonenumerable

 name() { return `${this.first} ${this.last}` }

}

登入後複製

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

1

2

@connect(mapStateToProps, mapDispatchToProps)

export default class MyReactComponent extends React.Component {}

登入後複製

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

新功能提醒或权限

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

/**

 * @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 动画。这个时候,你就可以使用装饰器,优雅地实现功能。

1

2

3

4

5

6

7

8

@autobind

@loadingWrap(true)

async handleSelect(params) {

 await this.props.dispatch({

  type: 'product_list/setQuerypParams',

  querypParams: params

 });

}

登入後複製

loadingWrap 函数如下:、

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

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('finally')(res)) {

     res.finally(() => {

      Toast.hide();

     });

    else {

     Toast.hide();

    }

   }

  };

  return descriptor;

 };

}

登入後複製

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

express + mock如何操作前后台并行开发

字符串+数组去重实战案例解析

以上是在專案中怎樣使用JS裝飾器函數的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板