裝飾器是一種與類相關的語法,用來註釋或修改類和類方法。es6
// target 就是構造函數 Foo
function print(target){
console.log(target)
}
// 編譯前,注意,在這裏咱們的使用方式是 `@print` 而不是 `@print()`。
@print
class Foo(){}
// 編譯後
var Foo =
print(
(_class = function Foo() {})
) || _class;
複製代碼
因爲 print
函數返回 undefined
,因此 Foo
在這裏還是 Foo
。那當咱們裝飾器寫成 @print()
時,又會發生什麼?app
// 編譯後
var Foo = ((_dec = print()),
_dec(
(_class = ((_temp = function Foo() {
_classCallCheck(this, Foo);
this.a = 1;
this.b = 2;
}),
_temp))
) || _class);
複製代碼
能夠發現首先執行 print
函數,可是因爲 print
返回 undefined
,因此當執行 dec()
直接報錯。函數
從這裏,能夠發現裝飾器會在編譯階段就開始執行,你能夠在裝飾器對應的函數中對原型作些修改。ui
若是裝飾器函數又返回一個函數,那這時怎麼處理?應用場景又會是什麼?this
// 編譯前
function print(mixin){
return function(name){
this.name = name;
}
}
@print
class Foo{}
new Foo('張三')
// 編譯後
function print(target) {
return function(name) {
this.name = name;
};
}
var Foo =
print(
(_class = function Foo() {
_classCallCheck(this, Foo);
})
) || _class;
var foo = new Foo('張三');
console.log(foo.name); // 張三
console.log(foo.hasOwnProperty('name')); // true
複製代碼
能夠看到,Foo
的構造函數變成 print
函數內所返回的函數,且實例上有 name
屬性。spa
看了 @decorator
方式,接下來看下 @decorator()
方式。prototype
function mixin(...listFn){
return function(target){
Object.assign(target.prototype, { ...listFn });
}
}
function print(){
console.log(this.name)
}
@mixin(print)
class Foo{
name = '張三';
}
var foo = new Foo();
foo.print(); // 張三
複製代碼
咱們能夠看出 foo
的原型上有 print
方法。借鑑阮一峯老師的文章來講。有了裝飾器,就能夠把這樣的代碼code
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
複製代碼
改爲這樣的代碼對象
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
複製代碼
是否是後一種更容易理解。ip
// 編譯前
function print(target){
console.log(target)
}
class Foo{
@print name;
}
new Foo();
// 編譯後部分代碼
var Foo = ((_class = ((_temp = function Foo() {
_initializerDefineProperty(this, 'name', _descriptor, this);
}),
_temp)),
(_descriptor = _applyDecoratedDescriptor(_class.prototype, 'name', [print], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
})),
_class);
複製代碼
能夠看到編譯後 decorator
會做爲 _applyDecoratedDescriptor
第三個參數傳遞進去,_applyDecoratedDescriptor
內部會先拷貝原有屬性的屬性描述符,緊接着調用 decorator
對應的函數,最終返回屬性描述符。
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;
}
// 由內往外執行 decorator 對應函數
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;
}
複製代碼
// 編譯前
function print() {}
class Foo {
@print
getName() {
return this.name;
}
}
複製代碼
decorator
裝飾方法與裝飾屬性和類編譯後的代碼有很大差別。其一, 把構造函數的原型對象做爲參數 context
傳入_applyDecoratedDescriptor
函數中。其二,屬性描述符是經過 Object.getOwnPropertyDescriptor
方法獲取。其三,默認調用 _createClass
爲構造函數 Foo
建立屬性,其屬性描述符默認爲不可枚舉。
// 編譯後 部分代碼
var Foo = (
(_class = (function() {
function Foo() {}
_createClass(Foo, [
{
key: 'getName',
value: function getName() {
console.log('getName');
}
}
]);
return Foo;
})()),
...,
_class
);
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
複製代碼