JavaScript裝飾器的實(shí)現(xiàn)原理詳解
最近在使用TS+Vue的開發(fā)模式,發(fā)現(xiàn)項(xiàng)目中大量使用了裝飾器,看得我手足無措,今天特意研究一下實(shí)現(xiàn)原理,方便自己理解這塊知識(shí)點(diǎn)。
裝飾器的常見作用
- 裝飾一個(gè)類的屬性
- 裝飾一個(gè)類
裝飾器只能針對(duì)類和類的屬性,不能直接作用于函數(shù),因?yàn)榇嬖诤瘮?shù)提升。
下面我們針對(duì)這兩種情況進(jìn)行舉例闡述。
裝飾類的屬性
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í)際上就是一個(gè)語法糖,本質(zhì)上是構(gòu)造函數(shù),類的屬性的定義使用的是 Object.defineProperty() 類的代碼可以改寫為下面代碼:
class Cat {
say() {
console.log("meow ~");
}
}
// 上面等價(jià)于下面代碼
function Cat() {}
Object.defineProperty(Cat.prototype, "say", {
value: function() { console.log("meow ~"); },
enumerable: false,
configurable: true,
writable: true
});在say方法前面加入@readonly后,整個(gè)代碼等價(jià)于下面代碼:
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);
裝飾類屬性的大致實(shí)現(xiàn)原理,就是上面的語法糖實(shí)現(xiàn)過程。
這里注意下裝飾器的用法,是在屬性前面加入@readonly,不是@readonly()。如果是后一種寫法的話,裝飾器函數(shù)需要返回一個(gè)函數(shù)來表示裝飾器。這種寫法主要是通過傳參來擴(kuò)展裝飾器函數(shù)的功能,使裝飾器函數(shù)復(fù)用性更強(qiáng)。
如通過傳參決定是否設(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()
裝飾類
裝飾一個(gè)類的時(shí)候類本身本質(zhì)上是一個(gè)函數(shù),沒有descriptor,target是這個(gè)函數(shù)本身。
function isAnimal(target) {
target.isAnimal = true;
return target;
}
@isAnimal
class Cat {
...
}
console.log(Cat.isAnimal); // true
也就是說,上面的@isAnimal其實(shí)就是做了下面這件事
Cat = isAnimal(function Cat() { ... });
注意
作用在方法上的 decorator 接收的第一個(gè)參數(shù)(target )是類的 prototype;如果把一個(gè) decorator 作用到類上,則它的第一個(gè)參數(shù) target 是 類本身。
裝飾器執(zhí)行的時(shí)間是在屬性定義的時(shí)候,也就是被裝飾的屬性在定義后就是已經(jīng)被裝飾器處理過的不一樣的屬性了。
下面舉例看下裝飾器的執(zhí)行時(shí)間:
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() {}
};輸出結(jié)果:
before class
class method bar
class getter alice
class property bob
class Bar
after class
object method bar
可以看出裝飾器在類和類的屬性定義的時(shí)候就對(duì)它們進(jìn)行了"裝飾"。
實(shí)例應(yīng)用
我們的示例場景是這樣的
- 首先創(chuàng)建一個(gè)普通的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(`當(dāng)前狀態(tài) ===> ${tony}`);
// 輸出:當(dāng)前狀態(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(`當(dāng)前狀態(tài) ===> ${tony}`);
// 輸出:當(dāng)前狀態(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(`當(dāng)前狀態(tài) ===> ${tony}`);
//輸出:當(dāng)前狀態(tài) ===> 防御力:102,攻擊力:53,血量:3
在這里你就能看出裝飾模式的優(yōu)勢了,它可以對(duì)某個(gè)方法進(jìn)行疊加使用,對(duì)原類的侵入性非常小,只是增加一行@decorateLight而已,可以方便地增刪;(同時(shí)還可以復(fù)用)
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(`當(dāng)前狀態(tài) ===> ${tony}`);
// 輸出:當(dāng)前狀態(tài) ===> 防御力:102,攻擊力:53,血量:3(技能加成:飛行能力)到此這篇關(guān)于JavaScript裝飾器的實(shí)現(xiàn)原理詳解的文章就介紹到這了,更多相關(guān)JavaScript裝飾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript實(shí)現(xiàn)通過表格繪制顏色填充矩形的方法
這篇文章主要介紹了javascript實(shí)現(xiàn)通過表格繪制顏色填充矩形的方法,涉及javascript操作表格與樣式的相關(guān)技巧,需要的朋友可以參考下2015-04-04
javascript使用switch case實(shí)現(xiàn)動(dòng)態(tài)改變超級(jí)鏈接文字及地址
這篇文章主要介紹了javascript使用switch case實(shí)現(xiàn)動(dòng)態(tài)改變超級(jí)鏈接文字及地址,需要的朋友可以參考下2014-12-12
JS實(shí)現(xiàn)頁面跳轉(zhuǎn)與刷新的方法匯總
這篇文章主要給大家介紹了關(guān)于JS實(shí)現(xiàn)頁面跳轉(zhuǎn)與刷新的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用JS具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
js之input[type=file]選擇重復(fù)的文件,無法觸發(fā)change事件問題
這篇文章主要介紹了js之input[type=file]選擇重復(fù)的文件,無法觸發(fā)change事件問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05

