最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 源代码解析——core-decorators(decorate)

    正文概述 掘金(鹤中云)   2021-03-08   643

    前言

    这次要分析的源代码是 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装饰器的相关知识。

    当一个类中的方法被装饰器修饰时,会进行如下的步骤:

    1. 先对装饰器表达式本身求值
    2. 求值的结果会被当作函数,然后再进行调用。

    我们还是以文章最开头的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 之前执行。简单的讲会发生下面这些事情:

    1. 先执行 core_decorators_1.decorate(lodash_1.memoize) 并返回一个新的函数,我们假设这个新函数为 midFunc.

    其中midFunc 其实就是:

    function() {
        return handleDescriptor(
            ...Array.prototype.slice.call(arguments),
            entryArgs
        );
    };
    
    1. 然后将在执行上述的函数midFunc(target, key, descriptor),会得到一个 Descriptor 对象,再使用 Object.defineProperty 重新为该target重新设置 keyDescriptor

    即:

    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" 属性的时候,会直接进入该Descriptorget部分。其中的意思大概是:

    1. 先获取到要修饰的方法 fn
    2. 使用我们要装饰该函数的高阶函数decorator去修饰fn,返回的value也应该是一个函数。
    3. 重新给"test"属性设置Descriptor,这样我们再以下次访问"test" 属性时,则会直接访问到value这个函数了。

    我们以@decorate(memoize) 为例,流程如下

    源代码解析——core-decorators(decorate)

    总结

    大概总结一下其中比较精髓的点:

    1. 直接返回了一个descriptor,在其中设置了getter。
    2. 在上述的getter中去执行我们要应用的高阶函数,然后重新给该属性设置(defineProperty)。
    3. 由于在上一步我们重新定义了descriptor,所以我们不会再进入到上面的getter中,而是进入到我们使用的高阶函数返回的函数中。

    以上,大概就是decorate函数的执行流程了。如果你觉得文章对你有帮助,点个赞再走哦。


    起源地下载网 » 源代码解析——core-decorators(decorate)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元