前言
这次要分析的源代码是 core-decorator
库中的一个函数:decorate
。先简单介绍一下 core-decorators
这个库是干嘛的, core-decorators
是一个按照 JavaScript state-0 decorators 提案编写的一个装饰器库。(装饰器的标准可能会发生变化)。现目前在typescript中使用装饰器语法使用这个库是比较方便的。比如:
class Test {
@decorate(memoize)
public complicatedCalcs(): number {
// do some complicated caculates
return 123;
}
}
通过decorate(memoize)
装饰过的函数就有了缓存的功能,在函数参数不变的情况下不会经过函数体内的逻辑而直接返回结果,这是一种常见的性能优化手段。
需要说明的是:这里的memoize
不是 core-decorators
库中提供的,而是来自于lodash
中,我们使用了decorate
这个函数将memoize
变成了一个装饰器。 并且 core-decorators
函数库的中的其他很多装饰器也都是依赖于decorate
这个函数的。 decorate
函数可以将高阶函数都变为装饰器。今天我们就来解析一下 decorate
函数之中的秘密。
抽丝剥茧
和大多数的函数库类似,暴露出的函数的函数体很简单:
export default function decorate(...args) {
return _decorate(handleDescriptor, args);
}
_decorate
是另一个基础函数:
export function decorate(handleDescriptor, entryArgs) {
if (isDescriptor(entryArgs[entryArgs.length - 1])) {
return handleDescriptor(...entryArgs, []);
} else {
// 方法装饰器
return function() {
return handleDescriptor(
...Array.prototype.slice.call(arguments),
entryArgs
);
};
}
}
为了减少复杂度,这里我们只看else分支中的语句。else分支中正是用于修饰类中的方法使用的方法装饰器。
所以,decorate
函数本身等价于:
export default function decorate(...args) {
return function() {
return handleDescriptor(
...Array.prototype.slice.call(arguments),
entryArgs
);
};
}
这里需要插入一些关于typescript装饰器的相关知识。
当一个类中的方法被装饰器修饰时,会进行如下的步骤:
- 先对装饰器表达式本身求值
- 求值的结果会被当作函数,然后再进行调用。
我们还是以文章最开头的Test
类为例子说明:
Typescript经过编译后:
var Test = /** @class */ (function() {
function Test() {}
Test.prototype.test = function() {
console.log(123);
return 123;
};
__decorate(
[core_decorators_1.decorate(lodash_1.memoize)],
Test.prototype,
"test",
null
);
return Test;
})();
从上面的代码中我们可以看出:core_decorators_1.decorate(lodash_1.memoize)
会在 __decorate
之前执行。简单的讲会发生下面这些事情:
- 先执行
core_decorators_1.decorate(lodash_1.memoize)
并返回一个新的函数,我们假设这个新函数为midFunc
.
其中midFunc
其实就是:
function() {
return handleDescriptor(
...Array.prototype.slice.call(arguments),
entryArgs
);
};
- 然后将在执行上述的函数
midFunc(target, key, descriptor)
,会得到一个Descriptor
对象,再使用Object.defineProperty
重新为该target
重新设置key
的Descriptor
即:
Object.defineProperty(Test.prototype, "test", midFunc(Test.prototype, "test", null));
那么重点就在于 handleDescriptor
这个函数了。源码如下:
function handleDescriptor(target, key, descriptor, [decorator, ...args]) {
const { configurable, enumerable, writable } = descriptor;
const originalGet = descriptor.get;
const originalSet = descriptor.set;
const originalValue = descriptor.value;
const isGetter = !!originalGet;
return {
configurable,
enumerable,
get() {
const fn = isGetter ? originalGet.call(this) : originalValue;
const value = decorator.call(this, fn, ...args);
if (isGetter) {
return value;
} else {
const desc = {
configurable,
enumerable
};
desc.value = value;
desc.writable = writable;
defineProperty(this, key, desc);
return value;
}
},
set: isGetter ? originalSet : createDefaultSetter()
};
}
我们看到 handleDescriptor
直接返回了一个对象,该对象是一个Descriptor
。所以我们第一次访问 "test" 属性的时候,会直接进入该Descriptor
的 get
部分。其中的意思大概是:
- 先获取到要修饰的方法 fn
- 使用我们要装饰该函数的高阶函数
decorator
去修饰fn,返回的value
也应该是一个函数。 - 重新给"test"属性设置
Descriptor
,这样我们再以下次访问"test" 属性时,则会直接访问到value
这个函数了。
我们以@decorate(memoize)
为例,流程如下
总结
大概总结一下其中比较精髓的点:
- 直接返回了一个
descriptor
,在其中设置了getter。 - 在上述的getter中去执行我们要应用的高阶函数,然后重新给该属性设置(defineProperty)。
- 由于在上一步我们重新定义了
descriptor
,所以我们不会再进入到上面的getter中,而是进入到我们使用的高阶函数返回的函数中。
以上,大概就是decorate
函数的执行流程了。如果你觉得文章对你有帮助,点个赞再走哦。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!