preface

The source code for this analysis is a function in the core-decorator library: decorate. Core-decorators is a decorator library written in accordance with the JavaScript state-0 decorators proposal. Decorator standards are subject to change. It is now convenient to use this library in typescript using decorator syntax. Such as:

class Test {
    @decorate(memoize)
    public complicatedCalcs(): number {
        // do some complicated caculates
        return 123; }}Copy the code

Functions decorated with decorate(memoize) are cacheable and return results without passing through the internal logic of a function with its parameters unchanged. This is a common way to optimize performance.

Note: The memoize here is not provided in the Core-decorators library, but comes from Lodash, where we use the decorate function to turn Memoize into a decorator. And many other decorators in the core-decorators library depend on this function as well. The decorate function turns all higher-order functions into decorators. Today we’re going to explain the secrets of the class function.

The whole

Like most libraries, the body of the exposed function is simple:

export default function decorate(. args) {
    return _decorate(handleDescriptor, args);
}
Copy the code

_decorate is another basic function:


export function decorate(handleDescriptor, entryArgs) {
    if (isDescriptor(entryArgs[entryArgs.length - 1]) {returnhandleDescriptor(... entryArgs, []); }else {
        // Method decorator
        return function() {
            returnhandleDescriptor( ... Array.prototype.slice.call(arguments), entryArgs ); }; }}Copy the code

To reduce complexity, we’ll just look at the statements in the else branch. The else branch is exactly the method decorator used to modify methods in the class.

So, the decorate function itself is equivalent to:

export default function decorate(. args) {
    return function() {
        returnhandleDescriptor( ... Array.prototype.slice.call(arguments),
            entryArgs
        );
    };
}
Copy the code

Some knowledge about typescript decorators needs to be inserted here.

When a method in a class is decorated by a decorator, the following steps occur:

  1. The decorator expression itself is evaluated first
  2. The result of the evaluation is treated as a function and then called.

Let’s use the Test class at the beginning of this article as an example:

Typescript is compiled:

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
    );
    returnTest; }) ();Copy the code

From the code above we can see that core_decorators_1.decorate(lodash_1.memoize) is executed before __decorate. In a nutshell, this is what happens:

  1. To perform firstcore_decorators_1.decorate(lodash_1.memoize)And return a new function, let’s saymidFunc.

MidFunc is:

function() {
    returnhandleDescriptor( ... Array.prototype.slice.call(arguments),
        entryArgs
    );
};
Copy the code
  1. The above function will then be executedmidFunc(target, key, descriptor)I’m going to get oneDescriptorObject, and then useObject.definePropertyBack to thetargetTo resetkeytheDescriptor

That is:

Object.defineProperty(Test.prototype, "test", midFunc(Test.prototype, "test".null));
Copy the code

So the important thing is the handleDescriptor function. The source code is as follows:


function handleDescriptor(target, key, descriptor, [decorator, ...args]) {
    const { configurable, enumerable, writable } = descriptor;
    const originalGet = descriptor.get;
    const originalSet = descriptor.set;
    const originalValue = descriptor.value;
    constisGetter = !! 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);

                returnvalue; }},set: isGetter ? originalSet : createDefaultSetter()
    };
}
Copy the code

So we see that the handleDescriptor directly returns an object, and that object is a Descriptor. So the first time we access the “test” property, we’re going to go straight to the get part of that Descriptor. Which means something like:

  1. Get the method fn to decorate
  2. Use the higher-order function that we want to decorate the functiondecoratorTo modify the return of fnvalueIt should also be a function.
  3. Reset the “test” propertyDescriptor, so that the next time we access the “test” property, it will be directly accessedvalueThat’s the function.

Let’s take @decorate(memoize) as an example

conclusion

To summarise some of the finer points:

  1. It just returns onedescriptor, where the getter is set.
  2. Execute the higher-order function we want to apply in the getter above and then reset the attribute (defineProperty).
  3. Because we redefined it in the last stepdescriptorSo instead of going into the getter above, we’re going into the function returned by the higher-order function we’re using.

So that’s pretty much how you execute the decorate function. If you find the article helpful, give it a “like” before you leave.