JS裝飾器函數(shù)用法總結(jié)
在 ES6 中增加了對(duì)類對(duì)象的相關(guān)定義和操作(比如 class 和 extends ),這就使得我們?cè)诙鄠€(gè)不同類之間共享或者擴(kuò)展一些方法或者行為的時(shí)候,變得并不是那么優(yōu)雅。這個(gè)時(shí)候,我們就需要一種更優(yōu)雅的方法來(lái)幫助我們完成這些事情。
什么是裝飾器
Python 的裝飾器
在面向?qū)ο螅∣OP)的設(shè)計(jì)模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來(lái)實(shí)現(xiàn),而Python除了能支持 OOP 的 decorator 外,直接從語(yǔ)法層次支持 decorator。
如果你熟悉 python 的話,對(duì)它一定不會(huì)陌生。那么我們先來(lái)看一下 python 里的裝飾器是什么樣子的吧:
def decorator(f): print "my decorator" return f @decorator def myfunc(): print "my function" myfunc() # my decorator # my function
這里的 @decorator 就是我們說的裝飾器。在上面的代碼中,我們利用裝飾器給我們的目標(biāo)方法執(zhí)行前打印出了一行文本,并且并沒有對(duì)原方法做任何的修改。代碼基本等同于:
def decorator(f):
def wrapper():
print "my decorator"
return f()
return wrapper
def myfunc():
print "my function"
myfunc = decorator(myfuc)
通過代碼我們也不難看出,裝飾器 decorator 接收一個(gè)參數(shù),也就是我們被裝飾的目標(biāo)方法,處理完擴(kuò)展的內(nèi)容以后再返回一個(gè)方法,供以后調(diào)用,同時(shí)也失去了對(duì)原方法對(duì)象的訪問。當(dāng)我們對(duì)某個(gè)應(yīng)用了裝飾以后,其實(shí)就改變了被裝飾方法的入口引用,使其重新指向了裝飾器返回的方法的入口點(diǎn),從而來(lái)實(shí)現(xiàn)我們對(duì)原函數(shù)的擴(kuò)展、修改等操作。
ES7 的裝飾器
ES7 中的 decorator 同樣借鑒了這個(gè)語(yǔ)法糖,不過依賴于 ES5 的 Object.defineProperty 方法 。
Object.defineProperty
Object.defineProperty() 方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性, 并返回這個(gè)對(duì)象。
該方法允許精確添加或修改對(duì)象的屬性。通過賦值來(lái)添加的普通屬性會(huì)創(chuàng)建在屬性枚舉期間顯示的屬性(for...in 或 Object.keys 方法), 這些值可以被改變,也可以被刪除。這種方法允許這些額外的細(xì)節(jié)從默認(rèn)值改變。默認(rèn)情況下,使用 Object.defineProperty() 添加的屬性值是不可變的。
語(yǔ)法
Object.defineProperty(obj, prop, descriptor)
- obj:要在其上定義屬性的對(duì)象。
- prop:要定義或修改的屬性的名稱。
- descriptor:將被定義或修改的屬性描述符。
- 返回值:被傳遞給函數(shù)的對(duì)象。
在ES6中,由于 Symbol類型 的特殊性,用 Symbol類型 的值來(lái)做對(duì)象的key與常規(guī)的定義或修改不同,而Object.defineProperty 是定義 key為 Symbol 的屬性的方法之一。
屬性描述符
對(duì)象里目前存在的屬性描述符有兩種主要形式:數(shù)據(jù)描述符和存取描述符。
數(shù)據(jù)描述符是一個(gè)具有值的屬性,該值可能是可寫的,也可能不是可寫的。
- 存取描述符是由 getter-setter 函數(shù)對(duì)描述的屬性。
- 描述符必須是這兩種形式之一;不能同時(shí)是兩者。
數(shù)據(jù)描述符和存取描述符均具有以下可選鍵值:
configurable
當(dāng)且僅當(dāng)該屬性的 configurable 為 true 時(shí),該屬性描述符才能夠被改變,同時(shí)該屬性也能從對(duì)應(yīng)的對(duì)象上被刪除。默認(rèn)為 false。
enumerable
enumerable定義了對(duì)象的屬性是否可以在 for...in 循環(huán)和 Object.keys() 中被枚舉。
當(dāng)且僅當(dāng)該屬性的 enumerable 為 true 時(shí),該屬性才能夠出現(xiàn)在對(duì)象的枚舉屬性中。默認(rèn)為 false。
數(shù)據(jù)描述符同時(shí)具有以下可選鍵值:
value
該屬性對(duì)應(yīng)的值。可以是任何有效的 JavaScript 值(數(shù)值,對(duì)象,函數(shù)等)。默認(rèn)為 undefined。
writable
當(dāng)且僅當(dāng)該屬性的 writable 為 true 時(shí),value 才能被賦值運(yùn)算符改變。默認(rèn)為 false。
存取描述符同時(shí)具有以下可選鍵值:
get
一個(gè)給屬性提供 getter 的方法,如果沒有 getter 則為 undefined。該方法返回值被用作屬性值。默認(rèn)為 undefined。
set
一個(gè)給屬性提供 setter 的方法,如果沒有 setter 則為 undefined。該方法將接受唯一參數(shù),并將該參數(shù)的新值分配給該屬性。默認(rèn)為 undefined。
如果一個(gè)描述符不具有value,writable,get 和 set 任意一個(gè)關(guān)鍵字,那么它將被認(rèn)為是一個(gè)數(shù)據(jù)描述符。如果一個(gè)描述符同時(shí)有(value或writable)和(get或set)關(guān)鍵字,將會(huì)產(chǎn)生一個(gè)異常。
用法
類的裝飾
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
上面代碼中,@testable 就是一個(gè)裝飾器。它修改了 MyTestableClass這 個(gè)類的行為,為它加上了靜態(tài)屬性isTestable。testable 函數(shù)的參數(shù) target 是 MyTestableClass 類本身。
基本上,裝飾器的行為就是下面這樣。
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
也就是說,裝飾器是一個(gè)對(duì)類進(jìn)行處理的函數(shù)。裝飾器函數(shù)的第一個(gè)參數(shù),就是所要裝飾的目標(biāo)類。
如果覺得一個(gè)參數(shù)不夠用,可以在裝飾器外面再封裝一層函數(shù)。
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false
上面代碼中,裝飾器 testable 可以接受參數(shù),這就等于可以修改裝飾器的行為。
注意,裝飾器對(duì)類的行為的改變,是代碼編譯時(shí)發(fā)生的,而不是在運(yùn)行時(shí)。這意味著,裝飾器能在編譯階段運(yùn)行代碼。也就是說,裝飾器本質(zhì)就是編譯時(shí)執(zhí)行的函數(shù)。
前面的例子是為類添加一個(gè)靜態(tài)屬性,如果想添加實(shí)例屬性,可以通過目標(biāo)類的 prototype 對(duì)象操作。
下面是另外一個(gè)例子。
// 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對(duì)象的方法添加到了 MyClass 的實(shí)例上面。
方法的裝飾
裝飾器不僅可以裝飾類,還可以裝飾類的屬性。
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
上面代碼中,裝飾器 readonly 用來(lái)裝飾“類”的name方法。
裝飾器函數(shù) readonly 一共可以接受三個(gè)參數(shù)。
function readonly(target, name, descriptor){
// descriptor對(duì)象原來(lái)的值如下
// {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor;
}
readonly(Person.prototype, 'name', descriptor);
// 類似于
Object.defineProperty(Person.prototype, 'name', descriptor);
- 裝飾器第一個(gè)參數(shù)是 類的原型對(duì)象,上例是 Person.prototype,裝飾器的本意是要“裝飾”類的實(shí)例,但是這個(gè)時(shí)候?qū)嵗€沒生成,所以只能去裝飾原型(這不同于類的裝飾,那種情況時(shí)target參數(shù)指的是類本身);
- 第二個(gè)參數(shù)是 所要裝飾的屬性名
- 第三個(gè)參數(shù)是 該屬性的描述對(duì)象
另外,上面代碼說明,裝飾器(readonly)會(huì)修改屬性的 描述對(duì)象(descriptor),然后被修改的描述對(duì)象再用來(lái)定義屬性。
函數(shù)方法的裝飾
裝飾器只能用于類和類的方法,不能用于函數(shù),因?yàn)榇嬖诤瘮?shù)提升。
另一方面,如果一定要裝飾函數(shù),可以采用高階函數(shù)的形式直接執(zhí)行。
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是一個(gè)第三方模塊,提供了幾個(gè)常見的裝飾器,通過它可以更好地理解裝飾器。
@autobind
autobind 裝飾器使得方法中的this對(duì)象,綁定原始對(duì)象。
@readonly
readonly 裝飾器使得屬性或方法不可寫。
@override
override 裝飾器檢查子類的方法,是否正確覆蓋了父類的同名方法,如果不正確會(huì)報(bào)錯(cuò)。
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 裝飾器在控制臺(tái)顯示一條警告,表示該方法將廢除。
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 裝飾器導(dǎo)致的 console.warn() 調(diào)用。但是,異步代碼發(fā)出的調(diào)用除外。
使用場(chǎng)景
裝飾器有注釋的作用
@testable
class Person {
@readonly
@nonenumerable
name() { return `${this.first} ${this.last}` }
}
有了裝飾器,就可以改寫上面的代碼。裝飾
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
相對(duì)來(lái)說,后一種寫法看上去更容易理解。
新功能提醒或權(quán)限
菜單點(diǎn)擊時(shí),進(jìn)行事件攔截,若該菜單有新功能更新,則彈窗顯示。
/**
* @description 在點(diǎn)擊時(shí),如果有新功能提醒,則彈窗顯示
* @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 項(xiàng)目中,我們可能需要在向后臺(tái)請(qǐng)求數(shù)據(jù)時(shí),頁(yè)面出現(xiàn) loading 動(dòng)畫。這個(gè)時(shí)候,你就可以使用裝飾器,優(yōu)雅地實(shí)現(xiàn)功能。
@autobind
@loadingWrap(true)
async handleSelect(params) {
await this.props.dispatch({
type: 'product_list/setQuerypParams',
querypParams: params
});
}
loadingWrap 函數(shù)如下:、
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;
};
}
問題:這里大家可以想想看,如果我們不希望每次請(qǐng)求數(shù)據(jù)時(shí)都出現(xiàn) loading,而是要求只要后臺(tái)請(qǐng)求時(shí)間大于 300ms 時(shí),才顯示loading,這里需要怎么改?
以上就是本次小編整理的關(guān)于JS裝飾器的相關(guān)知識(shí)點(diǎn),感謝大家對(duì)腳本之家的支持。
相關(guān)文章
JavaScript表達(dá)式:URL 協(xié)議介紹
javascript:后面可以是表達(dá)式或者使用分號(hào)分隔的表達(dá)式集合如javascript: 5 + 4,感興趣的朋友可以參考下或許可以幫助到你2013-03-03
javascript中神奇的 Date對(duì)象小結(jié)
日常生活中,各種形式的時(shí)間字符到處都是。時(shí)間觀念的產(chǎn)生,時(shí)間單位、計(jì)時(shí)工具的發(fā)明,給人類帶來(lái)的變化實(shí)在一言難盡。今天就來(lái)談?wù)勅掌谀切┦聝?。一起?lái)看看 JavaScript 中的日期對(duì)象 Date。2017-10-10
基于JavaScript實(shí)現(xiàn)繼承機(jī)制之調(diào)用call()與apply()的方法詳解
本文將介紹兩種很類似于對(duì)象冒充的繼承方式,即使用call()和apply()方法2013-05-05
使用?Next.js?Cli?快速搭建和運(yùn)行?Web?應(yīng)用
這篇文章主要介紹了使用?Next.js?Cli?快速搭建和運(yùn)行?Web?應(yīng)用,需要的朋友可以參考下2024-04-04
JavaScript中的Object對(duì)象學(xué)習(xí)教程
這篇文章主要介紹了JavaScript中的Object對(duì)象學(xué)習(xí)教程,是JavaScript入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2016-05-05
JavaScript substr() 字符串截取函數(shù)使用詳解
substr() 方法可在字符串中抽取從 start 下標(biāo)開始的指定數(shù)目的字符,文中配有大量實(shí)例代碼2013-04-04
ES6學(xué)習(xí)總結(jié)之Set和Map的使用
這篇博客介紹了 ES6 中的 Set 和 Map 數(shù)據(jù)結(jié)構(gòu),Set 是一個(gè)存儲(chǔ)唯一值的集合,支持添加、刪除、檢查元素的方法,Map 則是用于存儲(chǔ)鍵值對(duì)的集合,鍵和值都可以是任何類型,文章詳細(xì)講解了兩者的主要方法和用法,并與傳統(tǒng)的數(shù)組和對(duì)象進(jìn)行了對(duì)比,突出 Set 和 Map 的獨(dú)特優(yōu)勢(shì)2024-08-08

