decorator 裝飾器
許多面向對象都有decorator(裝飾器)函數,比如python中也可以用decorator函數來強化代碼,decorator相當於一個高階函數,接收一個函數,返回一個被裝飾后的函數。
注: javascript中也有decorator
相關的提案,只是目前node以及各瀏覽器中均不支持。只能通過安裝babel插件來轉換代碼,插件名叫這個:transform-decorators-legacy
。也有在線試用](babeljs.io/repl/),安裝好transform-decorators-legacy
之后,就能看到轉義后的代碼了
2.1 使用decorator的前期配置
1.vscode里面去除裝飾器報錯的方法
在vscode里打開設置=>用戶設置里面加入(tips:打開設置后也可以直接點擊右上角的'{}'進行用戶設置)
"javascript.implicitProjectConfig.experimentalDecorators": true 復制代碼
就可以了;
2.搭建一個簡單的webpack 來使用裝飾器
由於目前瀏覽器和node暫時不支持裝飾器,所以我們可以配置一個webpack來使用裝飾器
全局安裝
cnpm install webpack webpack-cli webpack-dev-server -g
復制代碼
啟動配置 創建一個webpack.dev.js
var path = require('path') module.exports = { mode: "development", entry: { main: "./test.js" }, output: { path: path.resolve(__dirname, "./dist"), filename: "test.js" }, module: { rules: [ //webpack 4.X寫法 { test: /.js$/, use: ['babel-loader'] } ] } } 復制代碼
下載依賴(webpack4.x 方法 )
npm install -D babel-loader @babel/core @babel/preset-env
復制代碼
配置.babelrc
{
"presets": [ "@babel/preset-env" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ], ] } 復制代碼
創建好webpack的目錄結構是這樣的

package.json的配置
{
"name": "decorator", "version": "1.0.0", "description": "", "main": "test.js", "scripts": { "build": "webpack --config=webpack.dev.js", "start": "node ./dist/bound.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@babel/core": "^7.4.4", "@babel/plugin-proposal-class-properties": "^7.4.4", "@babel/plugin-proposal-decorators": "^7.4.4", "@babel/preset-env": "^7.4.4", "babel-loader": "^8.0.5", "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-preset-env": "^1.7.0", "core-decorators": "^0.20.0" } } 復制代碼
2.2 開始使用decorator
1.類的修飾
許多面向對象的語言都有修飾器(Decorator)函數,用來修改類的行為。目前,有一個提案將這項功能,引入了 ECMAScript。 下面我們采用一個鋼鐵俠的例子來展開
@transform
class IronMan { // ... } function transform(target) { target.weapon = laser } console.log(IronMan.weapon) // laser 復制代碼
上面代碼中,@transform就是一個修飾器。它修改了IronMan
這個類的行為,為它加上了武器屬性weapon
。transform
函數的參數target
是IronMan
類本身。
2.結合redux庫使用
實際開發中,React 與 Redux 庫結合使用時,常常需要寫成下面這樣。
class MyReactComponent extends React.Component {} export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent); 復制代碼
有了裝飾器,就可以改寫上面的代碼。
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {} 復制代碼
3.方法的修飾
修飾器不僅可以修飾類,還可以修飾類的屬性。
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
參數指的是類本身);第二個參數是所要修飾的屬性名,第三個參數是該屬性的描述對象。
4.裝飾器不能用於修飾函數
原本作者設計的時候 是可以使用這種方式的 比如修飾一個函數這么寫
@RunOnce
function expensiveOperation() { return 1 + 1; } //語法糖之后的效果是這樣的 var expensiveOperation = RunOnce(function expensiveOperation() { return 1 + 1; }); 復制代碼
這意味着裝飾器可以用於任何任務,但是作者認為這樣可能有點復雜 並且還有一個潛在的問題 裝飾器和跟隨變量一塊提升 使得裝飾器語法函數過早執行而導致因為位置的原因產生的一些錯誤 比如:
var readOnly = require("some-decorator"); // 是否提升求值? // 如果是這樣的話 `readOnly` 那么就是未定義 @readOnly function foo() { } 復制代碼
總而言之,作者不希望產生這樣的復雜性,所以去除了修飾函數,詳情可以參考這篇作者參與討論的帖子
5.應用
至於decorator的應用場景在哪里?應該大部分AOP的場景都可以用,例如日志系統。 這里就手動來實現一個簡單的日志系統。
const log = (type) => { return (target, name, descriptor) => { let method = descriptor.value ; // 具體三個方法 descriptor.value = (...args) => { console.log(`${type}`); let result ; try { result = method.apply(target,args); console.log(`${type} ${result} 成功`) } catch (error) { console.log(`${type} ${result} 失敗`) } return result ; } } } class Man { @log('正在洗漱') wash() { return "洗漱"; } @log('正在吃飯') eat() { return "吃飯"; } @log('正在跑步') run() { return "跑步"; } } let m = new Man() ; m.wash(); m.eat(); m.run(); 復制代碼
6.core-decorators.js
core-decorators.js是一個第三方模塊,提供了幾個常見的修飾器,通過它可以更好地理解修飾器。
(1)@readonly
readonly
修飾器使得屬性或方法不可寫。
import { readonly } from 'core-decorators'; class Meal { @readonly entree (){ console.log(111) }; } var dinner = new Meal(); dinner.entree = 'salmon'; // Cannot assign to read only property 'entree' of [object Object] 復制代碼
(2)@override
override
修飾器檢查子類的方法,是否正確覆蓋了父類的同名方法,如果不正確會報錯。
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"? } 復制代碼
(3)@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. // 復制代碼
(4)@suppressWarnings
suppressWarnings
修飾器抑制deprecated
修飾器導致的console.warn()
調用。但是,異步代碼發出的調用除外。
import { suppressWarnings } from 'core-decorators'; class Person { @deprecated facepalm() {} @suppressWarnings facepalmWithoutWarning() { this.facepalm(); } } let person = new Person(); person.facepalmWithoutWarning(); // no warning is logged 復制代碼
7.Mixin
在修飾器的基礎上,可以實現Mixin
模式。所謂Mixin
模式,就是對象繼承的一種替代方案,中文譯為“混入”(mix in),意為在一個對象之中混入另外一個對象的方法。
const Foo = { foo() { console.log('foo') } }; class MyClass {} Object.assign(MyClass.prototype, Foo); let obj = new MyClass(); obj.foo() // 'foo' 復制代碼
可以使用裝飾器改寫為
function mixins(...list) { return function (target) { Object.assign(target.prototype, ...list); }; } const Foo = { foo() { console.log('foo') } }; @mixins(Foo) class MyClass {} let obj = new MyClass(); obj.foo() // "foo" 復制代碼
這中方法會改寫Myclass的 prototype,所以也可以使用繼承的方法混入
let MyMixin = (superclass) => class extends superclass { foo() { console.log('foo from MyMixin'); } }; class MyClass extends MyMixin(MyBaseClass) { /* ... */ } let c = new MyClass(); c.foo(); // "foo from MyMixin"
作者:前端小菜鳥qwq
鏈接:https://juejin.im/post/5cdc1bbce51d45379a164342
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。