JavaScript裝飾器的實現(xiàn)原理詳解
最近在使用TS+Vue的開發(fā)模式,發(fā)現(xiàn)項目中大量使用了裝飾器,看得我手足無措,今天特意研究一下實現(xiàn)原理,方便自己理解這塊知識點。
裝飾器的常見作用
- 裝飾一個類的屬性
- 裝飾一個類
裝飾器只能針對類和類的屬性,不能直接作用于函數(shù),因為存在函數(shù)提升。
下面我們針對這兩種情況進行舉例闡述。
裝飾類的屬性
function readonly(target, name, descriptor) { discriptor.writable = false; return discriptor; } class Cat { @readonly say() { console.log("meow ~"); } } var kitty = new Cat(); kitty.say = function() { console.log("woof !"); } kitty.say() // meow ~
ES6中的類實際上就是一個語法糖,本質上是構造函數(shù),類的屬性的定義使用的是 Object.defineProperty()
類的代碼可以改寫為下面代碼:
class Cat { say() { console.log("meow ~"); } } // 上面等價于下面代碼 function Cat() {} Object.defineProperty(Cat.prototype, "say", { value: function() { console.log("meow ~"); }, enumerable: false, configurable: true, writable: true });
在say
方法前面加入@readonly后,整個代碼等價于下面代碼:
function readonly(target, name, descriptor) { discriptor.writable = false; return discriptor; } function Cat() {} let descriptor = { value: function() { console.log("meow ~"); }, enumerable: false, configurable: true, writable: true }; descriptor = readonly(Cat.prototype, 'say', descriptor) || descriptor; Object.defineProperty(Cat.prototype, "say", descriptor);
裝飾類屬性的大致實現(xiàn)原理,就是上面的語法糖實現(xiàn)過程。
這里注意下裝飾器的用法,是在屬性前面加入@readonly
,不是@readonly()
。如果是后一種寫法的話,裝飾器函數(shù)需要返回一個函數(shù)來表示裝飾器。這種寫法主要是通過傳參來擴展裝飾器函數(shù)的功能,使裝飾器函數(shù)復用性更強。
如通過傳參決定是否設置只讀,代碼如下:
function readonly(flag) { return function (target, name, descriptor) { if (flag) { discriptor.writable = false; } else { discriptor.writable = true; } return discriptor; } } class Cat { @readonly(true) say() { console.log("meow ~"); } } var kitty = new Cat(); kitty.say = function() { console.log("woof !"); } kitty.say()
裝飾類
裝飾一個類的時候類本身本質上是一個函數(shù),沒有descriptor,target是這個函數(shù)本身。
function isAnimal(target) { target.isAnimal = true; return target; } @isAnimal class Cat { ... } console.log(Cat.isAnimal); // true
也就是說,上面的@isAnimal
其實就是做了下面這件事
Cat = isAnimal(function Cat() { ... });
注意
作用在方法上的 decorator
接收的第一個參數(shù)(target )是類的 prototype
;如果把一個 decorator
作用到類上,則它的第一個參數(shù) target
是 類本身。
裝飾器執(zhí)行的時間是在屬性定義的時候,也就是被裝飾的屬性在定義后就是已經(jīng)被裝飾器處理過的不一樣的屬性了。
下面舉例看下裝飾器的執(zhí)行時間:
function log(message) { return function() { console.log(message); } } console.log('before class'); @log('class Bar') class Bar { @log('class method bar'); bar() {} @log('class getter alice'); get alice() {} @log('class property bob'); bob = 1; } console.log('after class'); let bar = { @log('object method bar') bar() {} };
輸出結果:
before class
class method bar
class getter alice
class property bob
class Bar
after class
object method bar
可以看出裝飾器在類和類的屬性定義的時候就對它們進行了"裝飾"。
實例應用
我們的示例場景是這樣的
- 首先創(chuàng)建一個普通的Man類,它的抵御值 2,攻擊力為 3,血量為 3;
- 然后我們讓其帶上鋼鐵俠的盔甲,這樣他的抵御力增加 100,變成 102;
- 讓其帶上光束手套,攻擊力增加 50,變成 53;
- 最后讓他增加“飛行”能力
1. 創(chuàng)建 Man 類:
class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } init(def,atk,hp){ this.def = def; // 防御值 this.atk = atk; // 攻擊力 this.hp = hp; // 血量 } toString(){ return `防御力:${this.def},攻擊力:${this.atk},血量:${this.hp}`; } } var tony = new Man(); console.log(`當前狀態(tài) ===> ${tony}`); // 輸出:當前狀態(tài) ===> 防御力:2,攻擊力:3,血量:3
2. 裝飾器為鋼鐵俠裝配盔甲:
function decorateArmour(target, key, descriptor) { const method = descriptor.value; let moreDef = 100; let ret; descriptor.value = (...args)=>{ args[0] += moreDef; ret = method.apply(target, args); return ret; } return descriptor; } class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour init(def,atk,hp){ this.def = def; // 防御值 this.atk = atk; // 攻擊力 this.hp = hp; // 血量 } toString(){ return `防御力:${this.def},攻擊力:${this.atk},血量:${this.hp}`; } } var tony = new Man(); console.log(`當前狀態(tài) ===> ${tony}`); // 輸出:當前狀態(tài) ===> 防御力:102,攻擊力:3,血量:3
3. 裝飾器增加光束手套
function decorateLight(target, key, descriptor) { const method = descriptor.value; let moreAtk = 50; let ret; descriptor.value = (...args)=>{ args[1] += moreAtk; ret = method.apply(target, args); return ret; } return descriptor; } class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour @decorateLight init(def,atk,hp){ this.def = def; // 防御值 this.atk = atk; // 攻擊力 this.hp = hp; // 血量 } ... } var tony = new Man(); console.log(`當前狀態(tài) ===> ${tony}`); //輸出:當前狀態(tài) ===> 防御力:102,攻擊力:53,血量:3
在這里你就能看出裝飾模式的優(yōu)勢了,它可以對某個方法進行疊加使用,對原類的侵入性非常小,只是增加一行@decorateLight
而已,可以方便地增刪;(同時還可以復用)
4. 增加飛行能力
function addFly(canFly){ return function(target){ target.prototype.canFly = canFly; let extra = canFly ? '(技能加成:飛行能力)' : ''; let method = target.prototype.toString; target.prototype.toString = (...args)=>{ return method.apply(target.prototype,args) + extra; } return target; } } @addFly(true) class Man{ constructor(def = 2,atk = 3,hp = 3){ this.init(def,atk,hp); } @decorateArmour @decorateLight init(def,atk,hp){ this.def = def; // 防御值 this.atk = atk; // 攻擊力 this.hp = hp; // 血量 } ... } ... console.log(`當前狀態(tài) ===> ${tony}`); // 輸出:當前狀態(tài) ===> 防御力:102,攻擊力:53,血量:3(技能加成:飛行能力)
到此這篇關于JavaScript裝飾器的實現(xiàn)原理詳解的文章就介紹到這了,更多相關JavaScript裝飾器內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
javascript實現(xiàn)通過表格繪制顏色填充矩形的方法
這篇文章主要介紹了javascript實現(xiàn)通過表格繪制顏色填充矩形的方法,涉及javascript操作表格與樣式的相關技巧,需要的朋友可以參考下2015-04-04javascript使用switch case實現(xiàn)動態(tài)改變超級鏈接文字及地址
這篇文章主要介紹了javascript使用switch case實現(xiàn)動態(tài)改變超級鏈接文字及地址,需要的朋友可以參考下2014-12-12js之input[type=file]選擇重復的文件,無法觸發(fā)change事件問題
這篇文章主要介紹了js之input[type=file]選擇重復的文件,無法觸發(fā)change事件問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05