本篇文章為大家帶來了關於javascript裝飾器原理的相關知識,JavaScript的裝飾器可能是藉鑑自Python也或許是Java,較為明顯的不同的是大部分語言的裝飾者必須是一行行分開,而js的裝飾器可以在一行中,希望對大家有幫助。
一個以@開頭的描述性字詞。英文的decorator動詞是decorate,裝飾的意思。其中字根dek(dec發音)原始印歐語系中意思是「接受」。即,原來的某個事物接受一些新東西(而變得更好)。
從另一個角度描述,裝飾器主要是在被裝飾物件的外部起作用,而非入侵其內部發生什麼改變。裝飾器模式同時也是一種開發模式,其地位雖然弱於MVC、IoC等,但不失為優秀的模式。
JavaScript的裝飾器可能是藉鏡自Python或許也是Java。較明顯的不同的是大部分語言的裝飾器必須是一行行分開,而js的裝飾器可以在一行中。
裝飾器存在的意義
舉例:我拿著員工卡進入公司總部大樓。因為每個員工所屬的部門、階級不同,並不能進入大樓的任何房間。每個房間都有一扇門;那麼,公司需要安排每個辦公室裡至少一個人關於驗證來訪者的工作:
#先登記來訪者
驗證是否有權限進入,如果沒有則要求其離開
記錄其離開時間
還有一個選擇方式,就是安裝電子門鎖,門鎖只是將員工卡的資訊傳送給機房,由特定的程序驗證。
前者暫且稱之為笨模式,程式碼如下:
function A101(who){ record(who,new Date(),'enter'); if (!permission(who)) { record(who,new Date(),'no permission') return void; } // 继续执行 doSomeWork(); record(who,new Date(),'leave')} function A102(who){record(who,new Date(),'enter'); if (!permission(who)) { record(who,new Date(),'no permission') return void; } // 继续执行 doSomeWork(); record(who,new Date(),'leave')} // ...
有經驗的大家肯定第一時間想到了,把那些重複語句封裝為一個方法,並統一呼叫。是的,這樣可以解決大部分問題,但是還不夠「優雅」。同時還有另一個問題,如果「房間」特別多,又或者只有大樓奇數號房間要驗證偶數不驗證,那豈不是很「變態」?如果使用裝飾器模式來做,程式碼會如下面這樣的:
@verify(who)class Building { @verify(who) A101(){/*...*/} @verify(who) A102(){/*...*/} //...}
verify是驗證的裝飾器,而其本質就是一組函數。
JavaScript裝飾器
正如先前的那個例子,裝飾器其實本身就是一個函數,它在執行被裝飾的物件之前先被執行。
在JavaScript中,裝飾器的類型有:
類別
訪問方法(屬性的get和set )
欄位
方法
#參數
由於目前裝飾器概念還處於提案階段,不是一個正式可用的js功能,所以想要使用這個功能,不得不藉助翻譯器工具,例如Babel工具或TypeScript編譯JS程式碼轉後才能執行。我們需要先搭建運作環境,設定一些參數。 (以下過程,假設已經正確安裝了NodeJS開發環境以及套件管理工具)
cd project && npm initnpm i -D @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env babel-plugin-parameter-decorator
建立一個.babelrc設定文件,如下:
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }], "babel-plugin-parameter-decorator" ]}
利用下面的轉換命令,我們可以得到ES5的轉換程式:
npx babel source.js --out-file target.js
#類別裝飾器
建立一個使用裝飾器的JS程式decorate-class.js
@classDecoratorclass Building { constructor() { this.name = "company"; }} const building = new Building(); function classDecorator(target) { console.log("target", target);}
以上是最最簡單的裝飾器程序,我們利用babel將其「翻譯」為ES5的程序,然後再美化一下後得到如下程序。
"use strict"; var _class; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }} var Building = classDecorator( (_class = function Building() { _classCallCheck(this, Building); this.name = "company"; }) ) || _class; var building = new Building(); function classDecorator(target) { console.log("target", target);}
第12行就是在类生成过程中,调用函数形态的装饰器,并将构造函数(类本身)送入其中。同样揭示了装饰器的第一个参数是类的构造函数的由来。
方法 (method)装饰器
稍微修改一下代码,依旧是尽量保持最简单:
class Building { constructor() { this.name = "company"; } @methodDecorator openDoor() { console.log("The door being open"); }} const building = new Building(); function methodDecorator(target, property, descriptor) { console.log("target", target); if (property) { console.log("property", property); } if (descriptor) { console.log("descriptor", descriptor); } console.log("=====end of decorator========="); }
然后转换代码,可以发现,这次代码量突然增大了很多。排除掉_classCallCheck、_defineProperties和_createClass三个函数,关注_applyDecoratedDescriptor函数:
function _applyDecoratedDescriptor( target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ("value" in desc || desc.initializer) { desc.writable = true; } desc = decorators .slice() .reverse() .reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc;}
它在生成构造函数之后,执行了这个函数,特别注意,这个装饰器函数是以数组形式的参数传递的。然后到上述代码的17~22行,将装饰器逐个应用,其中对装饰器的调用就在第21行。
它发送了3个参数,target指类本身。property指方法名(或者属性名),desc是可能被先前装饰器被处理过的descriptor,如果是第一次循环或只有一个装饰器,那么就是方法或属性本身的descriptor。
存取器(accessor)装饰
JS关于类的定义中,支持get和set关键字针对设置某个字段的读写操作逻辑,装饰器也同样支持这类方法的操作。
class Building { constructor() { this.name = "company"; } @propertyDecorator get roomNumber() { return this._roomNumber; } _roomNumber = ""; openDoor() { console.log("The door being open"); }}
有心的读者可能已经发现了,存取器装饰的代码与上面的方法装饰代码非常接近。关于属性 get和set方法,其本身也是一种方法的特殊形态。所以他们之间的代码就非常接近了。
属性装饰器
继续修改源代码:
class Building { constructor() { this.name = "company"; } @propertyDecorator roomNumber = ""; } const building = new Building(); function propertyDecorator(target, property, descriptor) { console.log("target", target); if (property) { console.log("property", property); } if (descriptor) { console.log("descriptor", descriptor); } console.log("=====end of decorator========="); }
转换后的代码,还是与上述属性、存取器的代码非常接近。但除了_applyDecoratedDescriptor外,还多了一个_initializerDefineProperty函数。这个函数在生成构造函数时,将声明的各种字段绑定给对象。
参数装饰器
参数装饰器的使用位置较之前集中装饰器略有不同,它被使用在行内。
class Building { constructor() { this.name = "company"; } openDoor(@parameterDecorator num, @parameterDecorator zoz) { console.log(`${num} door being open`); }} const building = new Building(); function parameterDecorator(target, property, key) { console.log("target", target); if (property) { console.log("property", property); } if (key) { console.log("key", key); } console.log("=====end of decorator========="); }
转换后的代码区别就比较明显了,babel并没有对其生成一个特定的函数对其进行特有的操作,而只在创建完类(构造函数)以及相关属性、方法后直接调用了开发者自己编写的装饰器函数:
var Building = /*#__PURE__*/function () { function Building() { _classCallCheck(this, Building); this.name = "company"; } _createClass(Building, [{ key: "openDoor", value: function openDoor(num, zoz) { console.log("".concat(num, " door being open")); } }]); parameterDecorator(Building.prototype, "openDoor", 1); parameterDecorator(Building.prototype, "openDoor", 0); return Building;}();
装饰器应用
使用参数——闭包
以上所有的案例,装饰器本身均没有使用任何参数。然实际应用中,经常会需要有特定的参数需求。我们再回到一开头的例子中verify(who),其中需要传入一个身份变量。哪又怎么做?我们少许改变一下类装饰器的代码:
const who = "Django";@classDecorator(who)class Building { constructor() { this.name = "company"; }}
转换后得到
// ...var who = "Django";var Building = ((_dec = classDecorator(who)), _dec( (_class = function Building() { _classCallCheck(this, Building); this.name = "company"; }) ) || _class); // ...
请注意第4第5行,它先执行了装饰器,然后再用返回值将类(构造函数)送入。相对应的,我们就应该将构造函数写成下面这样:
function classDecorator(people) { console.log(`hi~ ${people}`); return function (target) { console.log("target", target); }; }
同样的,方法、存取器、属性和参数装饰器均是如此。
装饰器包裹方法
到此,我们已经可以将装饰器参数与目标对象结合起来,进行一些逻辑类的操作。那么再回到文章的开头的例子中:需求中要先验证来访者权限,然后记录,最后在来访者离开时再做一次记录。此时需要监管对象方法被调用的整个过程。
请大家留意那个方法装饰器的descriptor,我们可以利用这个对象来“重写”这个方法。
class Building { constructor() { this.name = "company"; } @methodDecorator("Gate") openDoor(firstName, lastName) { return `The door will be open, when ${firstName} ${lastName} is walking into the ${this.name}.`; }} let building = new Building();console.log(building.openDoor("django", "xiang")); function methodDecorator(door) { return function (target, property, descriptor) { let fn = descriptor.value; descriptor.value = function (...args) { let [firstName, lastName] = args; console.log(`log: ${firstName}, who are comming.`); // verify(firstName,lastName) let result = Reflect.apply(fn, this, [firstName, lastName]); console.log(`log: ${result}`); console.log(`log: ${firstName}, who are leaving.`); return result; }; return descriptor; };}
代码第17行,将原方法暂存;18行定义一个新的方法,20~25行,记录、验证和记录离开的动作。
log: Django, who are comming.log: The door will be open, when Django Xiang is walking in to the company.log: Django, who are leaving.The door will be open, when Django Xiang is walking in to the company
装饰顺序
通过阅读转换后的代码,我们知道装饰器工作的时刻是在类被实例化之前,在生成之中完成装饰函数的动作。那么,如果不同类型的多个装饰器同时作用,其过程是怎样的?我们将先前的案例全部整合到一起看看:
const who = "Django";@classDecorator(who)class Building { constructor() { this.name = "company"; } @propertyDecorator roomNumber = ""; @methodDecorator openDoor(@parameterDecorator num) { console.log(`${num} door being open`); } @accessorDecorator get roomNumber() { return this._roomNumber; }} const building = new Building(); function classDecorator(people) { console.log(`class decorator`); return function (target) { console.log("target", target); };} function methodDecorator(target, property, descriptor) { console.log("method decorator");} function accessorDecorator(target, property, descriptor) { console.log("accessor decorator");} function propertyDecorator(target, property, descriptor) { console.log("property decoator");} function parameterDecorator(target, property, key) { console.log("parameter decorator");}
class decorator
parameter decorator
property decoator
method decorator
accessor decorator
还可以通过阅读转换后的源代码得到执行顺序:
类装饰器(在最外层)
参数装饰器(在生成构造函数最里层)
按照出现的先后顺序的:属性、方法和存取器
總結
裝飾器是一種優雅的開發模式,極大的方便了開發者編碼過程,同時提升了程式碼的可讀性。我們在使用裝飾器開發時,還是非常有必要了解其運作機制。
相關推薦:javascript學習教學
以上是詳解及案例介紹Javascript裝飾器原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

Dreamweaver Mac版
視覺化網頁開發工具

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

Atom編輯器mac版下載
最受歡迎的的開源編輯器

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。