許多面向對象都有decorator(裝飾器)函數,好比python中也能夠用decorator函數來強化代碼,decorator至關於一個高階函數,接收一個函數,返回一個被裝飾後的函數。javascript
注: javascript中也有decorator
相關的提案,只是目前node以及各瀏覽器中均不支持。只能經過安裝babel插件來轉換代碼,插件名叫這個:transform-decorators-legacy
。也有在線試用](babeljs.io/repl/),安裝好transform-decorators-legacy
以後,就能看到轉義後的代碼了前端
在vscode裏打開設置=>用戶設置裏面加入(tips:打開設置後也能夠直接點擊右上角的'{}'進行用戶設置)java
"javascript.implicitProjectConfig.experimentalDecorators": true 複製代碼
就能夠了;node
因爲目前瀏覽器和node暫時不支持裝飾器,因此咱們能夠配置一個webpack來使用裝飾器python
全局安裝webpack
cnpm install webpack webpack-cli webpack-dev-server -g
複製代碼
啓動配置 建立一個webpack.dev.jsgit
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 方法 )github
npm install -D babel-loader @babel/core @babel/preset-env
複製代碼
配置.babelrcweb
{
"presets": [ "@babel/preset-env" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ], ] } 複製代碼
{
"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" } } 複製代碼
許多面向對象的語言都有修飾器(Decorator)函數,用來修改類的行爲。目前,有一個提案將這項功能,引入了 ECMAScript。 下面咱們採用一個鋼鐵俠的例子來展開npm
@transform
class IronMan { // ... } function transform(target) { target.weapon = laser } console.log(IronMan.weapon) // laser 複製代碼
上面代碼中,@transform就是一個修飾器。它修改了IronMan
這個類的行爲,爲它加上了武器屬性weapon
。transform
函數的參數target
是IronMan
類自己。
實際開發中,React 與 Redux 庫結合使用時,經常須要寫成下面這樣。
class MyReactComponent extends React.Component {} export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent); 複製代碼
有了裝飾器,就能夠改寫上面的代碼。
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {} 複製代碼
修飾器不只能夠修飾類,還能夠修飾類的屬性。
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
參數指的是類自己);第二個參數是所要修飾的屬性名,第三個參數是該屬性的描述對象。
本來做者設計的時候 是可使用這種方式的 好比修飾一個函數這麼寫
@RunOnce
function expensiveOperation() { return 1 + 1; } //語法糖以後的效果是這樣的 var expensiveOperation = RunOnce(function expensiveOperation() { return 1 + 1; }); 複製代碼
這意味着裝飾器能夠用於任何任務,可是做者認爲這樣可能有點複雜 而且還有一個潛在的問題 裝飾器和跟隨變量一塊提高 使得裝飾器語法函數過早執行而致使由於位置的緣由產生的一些錯誤 好比:
var readOnly = require("some-decorator"); // 是否提高求值? // 若是是這樣的話 `readOnly` 那麼就是未定義 @readOnly function foo() { } 複製代碼
總而言之,做者不但願產生這樣的複雜性,因此去除了修飾函數,詳情能夠參考這篇做者參與討論的帖子
至於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(); 複製代碼
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 複製代碼
在修飾器的基礎上,能夠實現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"