摘要:的裝飾器中的同樣借鑒了這個語法糖,不過依賴于的方法。等同于也就是說,裝飾器是一個對類進行處理的函數。別名或裝飾器在控制臺顯示一條警告,表示該方法將廢除。有了裝飾器,就可以改寫上面的代碼。
更多文章,請在Github blog查看
在 ES6 中增加了對類對象的相關定義和操作(比如 class 和 extends ),這就使得我們在多個不同類之間共享或者擴展一些方法或者行為的時候,變得并不是那么優雅。這個時候,我們就需要一種更優雅的方法來幫助我們完成這些事情。
什么是裝飾器 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 就是我們說的裝飾器。在上面的代碼中,我們利用裝飾器給我們的目標方法執行前打印出了一行文本,并且并沒有對原方法做任何的修改。代碼基本等同于:
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.definePropertyObject.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 并返回這個對象。
該方法允許精確添加或修改對象的屬性。通過賦值來添加的普通屬性會創建在屬性枚舉期間顯示的屬性(for...in 或 Object.keys 方法), 這些值可以被改變,也可以被刪除。這種方法允許這些額外的細節從默認值改變。默認情況下,使用 Object.defineProperty() 添加的屬性值是不可變的。
語法Object.defineProperty(obj, prop, descriptor)
obj:要在其上定義屬性的對象。
prop:要定義或修改的屬性的名稱。
descriptor:將被定義或修改的屬性描述符。
返回值:被傳遞給函數的對象。
在ES6中,由于 Symbol類型 的特殊性,用 Symbol類型 的值來做對象的key與常規的定義或修改不同,而Object.defineProperty 是定義 key為 Symbol 的屬性的方法之一。
屬性描述符對象里目前存在的屬性描述符有兩種主要形式:數據描述符和存取描述符。
數據描述符是一個具有值的屬性,該值可能是可寫的,也可能不是可寫的。
存取描述符是由 getter-setter 函數對描述的屬性。
描述符必須是這兩種形式之一;不能同時是兩者。
數據描述符和存取描述符均具有以下可選鍵值:
configurable當且僅當該屬性的 configurable 為 true 時,該屬性描述符才能夠被改變,同時該屬性也能從對應的對象上被刪除。默認為 false。
enumerableenumerable定義了對象的屬性是否可以在 for...in 循環和 Object.keys() 中被枚舉。
當且僅當該屬性的 enumerable 為 true 時,該屬性才能夠出現在對象的枚舉屬性中。默認為 false。
數據描述符同時具有以下可選鍵值:
該屬性對應的值。可以是任何有效的 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)關鍵字,將會產生一個異常。用法 類的裝飾
@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是一個第三方模塊,提供了幾個常見的裝飾器,通過它可以更好地理解裝飾器。
@autobindautobind 裝飾器使得方法中的this對象,綁定原始對象。
@readonlyreadonly 裝飾器使得屬性或方法不可寫。
@overrideoverride 裝飾器檢查子類的方法,是否正確覆蓋了父類的同名方法,如果不正確會報錯。
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 的 connect實際開發中,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 = (); 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,而是要求只要后臺請求時間大于 300ms 時,才顯示loading,這里需要怎么改?
參考Object.defineProperty()
JavaScript Decorators: What They Are and When to Use Them
ECMAScript 6 入門
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94430.html
摘要:小明小明兒子,可以看到組件顯示了父組件的。小明受控組件和非受控組件受控組件和非受控組件這些都是指的表單組件,當一個表單的值是通過改變的而不是通過是受控組件,否則就是非受控組件。 react眾所周知的前端3大主流框架之一,由于出色的性能,完善的周邊設施風頭一時無兩。本文就帶大家一起掌握react。 jsx語法 前端MVVM主流框架都有一套自己的模板處理方法,react則使用它獨特的jsx...
摘要:如果不使用裝飾器的話,普通的做法可能是在中寫一堆校驗代碼來判斷用戶是否登錄,然后決定后面的執行邏輯,這樣比較麻煩。 前言 裝飾器是程序開發中經常會用到的一個功能,也是python語言開發的基礎知識,如果能夠在程序中合理的使用裝飾器,不僅可以提高開發效率,而且可以讓寫的代碼看上去顯的高大上^_^ 使用場景 可以用到裝飾器的地方有很多,簡單的舉例如以下場景 引入日志 函數執行時間統計 執...
摘要:摘自簡介選擇器是規則的一部分且位于聲明塊前。選擇器選擇器由哈希磅符號組成,后面是給定元素的名稱。通用選擇器通用選擇是最終的王牌。子選擇器允許您選擇一個元素,該元素是另一個元素的直接子元素。該選擇器僅選擇屬性被賦值為的所有元素。 摘自 MDN web docs 簡介 選擇器是 CSS 規則的一部分且位于 CSS 聲明塊前。 showImg(https://segmentfault.com...
摘要:的裝飾器是用來裝飾函數的。簡單裝飾器裝飾器的語法糖是使用符號表示,裝飾器本身也是一個函數,只不過參數是函數而已。保留函數的元信息被修飾之后的函數,它的元信息都消失,被替換的函數代替。中提供了來保存函數的元信息。 python的裝飾器是用來裝飾函數的。這是什么意思呢?假如我們有一個函數,這個函數的功能不能滿足我們現有的需求,那么我們可以通過裝飾器在這個函數執行前執行后做一些我們需要的操作...
摘要:動態地代理,可以猜測一下它的含義,在運行時動態地對某些東西代理,代理它做了其他事情。所以動態代理的內容重點就是這個。所以下一篇我們來細致了解下的到底是怎么使用動態代理的。 之前講了《零基礎帶你看Spring源碼——IOC控制反轉》,本來打算下一篇講講Srping的AOP的,但是其中會涉及到Java的動態代理,所以先單獨一篇來了解下Java的動態代理到底是什么,Java是怎么實現它的。 ...
閱讀 2320·2021-09-29 09:42
閱讀 556·2021-09-06 15:02
閱讀 2595·2021-09-02 15:40
閱讀 2111·2019-08-30 14:23
閱讀 1860·2019-08-30 13:48
閱讀 1289·2019-08-26 12:01
閱讀 957·2019-08-26 11:53
閱讀 2141·2019-08-23 18:31