Good decorator source code to learn the third play

This is the 28th day of my participation in Gwen Challenge

preface

Through the first two articles “good decorator source code learning (a) : Time”, “good decorator source code learning (two)” study

@ Time,@deprecate, @ReadOnly, @Enumerable, @Non64x and other basic decorators

Here’s a look at a few slightly more complex decorators:

  • @mixin: Blends methods into classes
  • @lazyInitialize: Initializes target properties only when they are used

@mixin

Mixing methods from objects into classes

Use the sample

Through @ mixins (obj1 obj2, obj3,..) You can mount properties from the object onto the prototype of the target class

Just mix some common methods with mixins in Vue

import { mixin } from '.. /index'
const obj1 = {
    logA() {
        console.log(this.a); }}const obj2 = {
    printKeys() {
        console.log(Object.keys(this)); }}@mixin(obj1, obj2)
class Test {
    public a = 1
}

const t: any = new Test()

t.logA() / / 1
t.printKeys() // ['a']
Copy the code

The function structure

Passing in parameters:

  • Objs: The rest parameter, which supports passing in multiple objects for mixing
function handleClass(target, mixins) {
    if(! mixins.length) {throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`); }}export default function mixin(. objs) {
    return function (target) {
        return handleClass(target, objs)
    }
}
Copy the code

Realize the principle of

  • Class decorator first argumenttargetClass for identifying decorations
  • Prototype Identifies the prototype of the class
  • Iterates over the incoming Object, passing thegetOwnPropertyNameswithgetOwnPropertyDescriptorMethod gets the target object’s own property (excluding the Symbol property) and the descriptor for the specified property, respectively
  • Through theObject.definePropertyImplementation inClass.prototypeTo expand

A complete implementation of handleClass

/** * gets the descriptor */ for each attribute on the object
function getOwnPropertyDescriptors(obj) {
    const descs = {};
    Object.getOwnPropertyNames(obj).forEach(key= > {
        descs[key] = Object.getOwnPropertyDescriptor(obj, key)
    })
    return descs;
}

function handleClass(target, mixins) {
    if(! mixins.length) {throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`);
    }
    for (let i = 0; i < mixins.length; i++) {
        const descs = getOwnPropertyDescriptors(mixins[i])
        const keys = Object.getOwnPropertyNames(mixins[i])

        for (let j = 0; j < keys.length; j++) {
            const key = keys[j];
            if(! target.prototype.hasOwnProperty(key)) {Object.defineProperty(target.prototype, key, descs[key])
            }
        }
    }
}
Copy the code

lazyInitialize

Lazy loading of specified properties, that is, the target property is initialized at use time

Use the sample

Put lazy logic into @lazyInitialize

import { lazyInitialize } from "..";

function getMaxArray(str=' '){
    console.log(str,'init huge array');
    return new Array(100)}class Test{
    @lazyInitialize(() = >getMaxArray('a'))
    public a
    public b = getMaxArray('b')}const t = new Test()
const k = new Test()
k.a
Copy the code

The results

b init huge array
b init huge array
a init huge array
Copy the code

Realize the principle of

  • Functions that are initialized using closures to store values
  • Modify the property’s GET behavior so that initialization logic is performed when GET is called
  • The initialized content is temporarily stored using intermediate variables
  • The next time get calls the property, it returns the temporary contents directly

function

function createDefaultSetter(key) {
    return function set(newValue) {
        Object.defineProperty(this, key, {
            configurable: true.writable: true.enumerable: true.value: newValue
        });
        return newValue;
    };
}

export default function lazyInitialize(initializer) :any {
    let t
    return function (target, key) {
        return {
            get() {
                t = t === undefined? initializer.call(this) : t
                return t;
            },
            set: createDefaultSetter(key)
        }
    }
}
Copy the code

To be continued

The next one will learn:

  • @debounce: if you
  • @throttle: the throttle