摘要:第二部分源碼解析接下是應用多個第二部分對于一個方法應用了多個,比如會編譯為在第二部分的源碼中,執行了和操作,由此我們也可以發現,如果同一個方法有多個裝飾器,會由內向外執行。有了裝飾器,就可以改寫上面的代碼。
Decorator
裝飾器主要用于:
裝飾類
裝飾方法或屬性
裝飾類@annotation class MyClass { } function annotation(target) { target.annotated = true; }裝飾方法或屬性
class MyClass { @readonly method() { } } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; }Babel 安裝編譯
我們可以在 Babel 官網的 Try it out,查看 Babel 編譯后的代碼。
不過我們也可以選擇本地編譯:
npm init npm install --save-dev @babel/core @babel/cli npm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
新建 .babelrc 文件
{ "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", {"loose": true}] ] }
再編譯指定的文件
babel decorator.js --out-file decorator-compiled.js裝飾類的編譯
編譯前:
@annotation class MyClass { } function annotation(target) { target.annotated = true; }
編譯后:
var _class; let MyClass = annotation(_class = class MyClass {}) || _class; function annotation(target) { target.annotated = true; }
我們可以看到對于類的裝飾,其原理就是:
@decorator class A {} // 等同于 class A {} A = decorator(A) || A;裝飾方法的編譯
編譯前:
class MyClass { @unenumerable @readonly method() { } } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; } function unenumerable(target, name, descriptor) { descriptor.enumerable = false; return descriptor; }
編譯后:
var _class; function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) { /** * 第一部分 * 拷貝屬性 */ var desc = {}; Object["ke" + "ys"](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; } /** * 第二部分 * 應用多個 decorators */ desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc); /** * 第三部分 * 設置要 decorators 的屬性 */ 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["define" + "Property"](target, property, desc); desc = null; } return desc; } let MyClass = ((_class = class MyClass { method() {} }), _applyDecoratedDescriptor( _class.prototype, "method", [readonly], Object.getOwnPropertyDescriptor(_class.prototype, "method"), _class.prototype ), _class); function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; }裝飾方法的編譯源碼解析
我們可以看到 Babel 構建了一個 _applyDecoratedDescriptor 函數,用于給方法裝飾。
Object.getOwnPropertyDescriptor()在傳入參數的時候,我們使用了一個 Object.getOwnPropertyDescriptor() 方法,我們來看下這個方法:
Object.getOwnPropertyDescriptor() 方法返回指定對象上的一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性,不需要從原型鏈上進行查找的屬性)
順便注意這是一個 ES5 的方法。
舉個例子:
const foo = { value: 1 }; const bar = Object.getOwnPropertyDescriptor(foo, "value"); // bar { // value: 1, // writable: true // enumerable: true, // configurable: true, // } const foo = { get value() { return 1; } }; const bar = Object.getOwnPropertyDescriptor(foo, "value"); // bar { // get: /*the getter function*/, // set: undefined // enumerable: true, // configurable: true, // }第一部分源碼解析
在 _applyDecoratedDescriptor 函數內部,我們首先將 Object.getOwnPropertyDescriptor() 返回的屬性描述符對象做了一份拷貝:
// 拷貝一份 descriptor var desc = {}; Object["ke" + "ys"](descriptor).forEach(function(key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; // 如果沒有 value 屬性或者沒有 initializer 屬性,表明是 getter 和 setter if ("value" in desc || desc.initializer) { desc.writable = true; }
那么 initializer 屬性是什么呢?Object.getOwnPropertyDescriptor() 返回的對象并不具有這個屬性呀,確實,這是 Babel 的 Class 為了與 decorator 配合而產生的一個屬性,比如說對于下面這種代碼:
class MyClass { @readonly born = Date.now(); } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; } var foo = new MyClass(); console.log(foo.born);
Babel 就會編譯為:
// ... (_descriptor = _applyDecoratedDescriptor(_class.prototype, "born", [readonly], { configurable: true, enumerable: true, writable: true, initializer: function() { return Date.now(); } })) // ...
此時傳入 _applyDecoratedDescriptor 函數的 descriptor 就具有 initializer 屬性。
第二部分源碼解析接下是應用多個 decorators:
/** * 第二部分 * @type {[type]} */ desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc);
對于一個方法應用了多個 decorator,比如:
class MyClass { @unenumerable @readonly method() { } }
Babel 會編譯為:
_applyDecoratedDescriptor( _class.prototype, "method", [unenumerable, readonly], Object.getOwnPropertyDescriptor(_class.prototype, "method"), _class.prototype )
在第二部分的源碼中,執行了 reverse() 和 reduce() 操作,由此我們也可以發現,如果同一個方法有多個裝飾器,會由內向外執行。
第三部分源碼解析/** * 第三部分 * 設置要 decorators 的屬性 */ 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["define" + "Property"](target, property, desc); desc = null; } return desc;
如果 desc 有 initializer 屬性,意味著當裝飾的是類的屬性時,會將 value 的值設置為:
desc.initializer.call(context)
而 context 的值為 _class.prototype,之所以要 call(context),這也很好理解,因為有可能
class MyClass { @readonly value = this.getNum() + 1; getNum() { return 1; } }
最后無論是裝飾方法還是屬性,都會執行:
Object["define" + "Property"](target, property, desc);
由此可見,裝飾方法本質上還是使用 Object.defineProperty() 來實現的。
應用 1.log為一個方法添加 log 函數,檢查輸入的參數:
class Math { @log add(a, b) { return a + b; } } function log(target, name, descriptor) { var oldValue = descriptor.value; descriptor.value = function(...args) { console.log(`Calling ${name} with`, args); return oldValue.apply(this, args); }; return descriptor; } const math = new Math(); // Calling add with [2, 4] math.add(2, 4);
再完善點:
let log = (type) => { return (target, name, descriptor) => { const method = descriptor.value; descriptor.value = (...args) => { console.info(`(${type}) 正在執行: ${name}(${args}) = ?`); let ret; try { ret = method.apply(target, args); console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`); } catch (error) { console.error(`(${type}) 失敗: ${name}(${args}) => ${error}`); } return ret; } } };2.autobind
class Person { @autobind getPerson() { return this; } } let person = new Person(); let { getPerson } = person; getPerson() === person; // true
我們很容易想到的一個場景是 React 綁定事件的時候:
class Toggle extends React.Component { @autobind handleClick() { console.log(this) } render() { return ( ); } }
我們來寫這樣一個 autobind 函數:
const { defineProperty, getPrototypeOf} = Object; function bind(fn, context) { if (fn.bind) { return fn.bind(context); } else { return function __autobind__() { return fn.apply(context, arguments); }; } } function createDefaultSetter(key) { return function set(newValue) { Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: newValue }); return newValue; }; } function autobind(target, key, { value: fn, configurable, enumerable }) { if (typeof fn !== "function") { throw new SyntaxError(`@autobind can only be used on functions, not: ${fn}`); } const { constructor } = target; return { configurable, enumerable, get() { /** * 使用這種方式相當于替換了這個函數,所以當比如 * Class.prototype.hasOwnProperty(key) 的時候,為了正確返回 * 所以這里做了 this 的判斷 */ if (this === target) { return fn; } const boundFn = bind(fn, this); defineProperty(this, key, { configurable: true, writable: true, enumerable: false, value: boundFn }); return boundFn; }, set: createDefaultSetter(key) }; }3.debounce
有的時候,我們需要對執行的方法進行防抖處理:
class Toggle extends React.Component { @debounce(500, true) handleClick() { console.log("toggle") } render() { return ( ); } }
我們來實現一下:
function _debounce(func, wait, immediate) { var timeout; return function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } } } function debounce(wait, immediate) { return function handleDescriptor(target, key, descriptor) { const callback = descriptor.value; if (typeof callback !== "function") { throw new SyntaxError("Only functions can be debounced"); } var fn = _debounce(callback, wait, immediate) return { ...descriptor, value() { fn() } }; } }4.time
用于統計方法執行的時間:
function time(prefix) { let count = 0; return function handleDescriptor(target, key, descriptor) { const fn = descriptor.value; if (prefix == null) { prefix = `${target.constructor.name}.${key}`; } if (typeof fn !== "function") { throw new SyntaxError(`@time can only be used on functions, not: ${fn}`); } return { ...descriptor, value() { const label = `${prefix}-${count}`; count++; console.time(label); try { return fn.apply(this, arguments); } finally { console.timeEnd(label); } } } } }5.mixin
用于將對象的方法混入 Class 中:
const SingerMixin = { sing(sound) { alert(sound); } }; const FlyMixin = { // All types of property descriptors are supported get speed() {}, fly() {}, land() {} }; @mixin(SingerMixin, FlyMixin) class Bird { singMatingCall() { this.sing("tweet tweet"); } } var bird = new Bird(); bird.singMatingCall(); // alerts "tweet tweet"
mixin 的一個簡單實現如下:
function mixin(...mixins) { return target => { if (!mixins.length) { throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`); } for (let i = 0, l = mixins.length; i < l; i++) { const descs = Object.getOwnPropertyDescriptors(mixins[i]); const keys = Object.getOwnPropertyNames(descs); for (let j = 0, k = keys.length; j < k; j++) { const key = keys[j]; if (!target.prototype.hasOwnProperty(key)) { Object.defineProperty(target.prototype, key, descs[key]); } } } }; }6.redux
實際開發中,React 與 Redux 庫結合使用時,常常需要寫成下面這樣。
class MyReactComponent extends React.Component {} export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
有了裝飾器,就可以改寫上面的代碼。
@connect(mapStateToProps, mapDispatchToProps) export default class MyReactComponent extends React.Component {};
相對來說,后一種寫法看上去更容易理解。
7.注意以上我們都是用于修飾類方法,我們獲取值的方式為:
const method = descriptor.value;
但是如果我們修飾的是類的實例屬性,因為 Babel 的緣故,通過 value 屬性并不能獲取值,我們可以寫成:
const value = descriptor.initializer && descriptor.initializer();參考
ECMAScript 6 入門
core-decorators
ES7 Decorator 裝飾者模式
JS 裝飾器(Decorator)場景實戰
ES6 系列ES6 系列目錄地址:https://github.com/mqyqingfeng/Blog
ES6 系列預計寫二十篇左右,旨在加深 ES6 部分知識點的理解,重點講解塊級作用域、標簽模板、箭頭函數、Symbol、Set、Map 以及 Promise 的模擬實現、模塊加載方案、異步處理等內容。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99295.html
摘要:前言這里的泛指之后的新語法這里的完全是指本文會不斷更新這里的使用是指本文會展示很多的使用場景這里的手冊是指你可以參照本文將項目更多的重構為語法此外還要注意這里不一定就是正式進入規范的語法。 前言 這里的 ES6 泛指 ES5 之后的新語法 這里的 完全 是指本文會不斷更新 這里的 使用 是指本文會展示很多 ES6 的使用場景 這里的 手冊 是指你可以參照本文將項目更多的重構為 ES6...
摘要:本文從裝飾模式出發,聊聊中的裝飾器和注解。該函數的函數名。不提供元數據的支持。中的元數據操作可以通過包來實現對于元數據的操作。 ??隨著Typescript的普及,在KOA2和nestjs等nodejs框架中經??吹筋愃朴趈ava spring中注解的寫法。本文從裝飾模式出發,聊聊Typescipt中的裝飾器和注解。 什么是裝飾者模式 Typescript中的裝飾器 Typescr...
摘要:裝飾器顧名思義就是裝飾某種東西的方法,可以用來裝飾屬性變量函數類實例方法本質上是個函數。以符開頭,函數名稱自擬。愛吃蘋果裝飾器裝飾類愛吃蘋果結果是這個類本身就可以通過修改類的屬性增加屬性被裝飾的對象可以使用多個裝飾器。 @Decorator 裝飾器是es7的語法,這個方法對于面向切面編程有了更好的詮釋,在一些情境中可以使用,比如路人A的代碼實現了一需求,路人B希望用A的方法來實現一個新...
摘要:我們今天也來做一個萬能遙控器設計模式適配器模式將一個類的接口轉換成客戶希望的另外一個接口。今天要介紹的仍然是創建型設計模式的一種建造者模式。設計模式的理論知識固然重要,但 計算機程序的思維邏輯 (54) - 剖析 Collections - 設計模式 上節我們提到,類 Collections 中大概有兩類功能,第一類是對容器接口對象進行操作,第二類是返回一個容器接口對象,上節我們介紹了...
閱讀 1438·2021-09-28 09:44
閱讀 2501·2021-09-28 09:36
閱讀 1144·2021-09-08 09:35
閱讀 1982·2019-08-29 13:50
閱讀 810·2019-08-29 13:29
閱讀 1130·2019-08-29 13:15
閱讀 1724·2019-08-29 13:00
閱讀 2988·2019-08-26 16:16