EdwardZZZ / articles

工作点滴记录
2 stars 0 forks source link

decorator方法中如何判断是在修饰class还是class属性 #3

Open EdwardZZZ opened 7 years ago

EdwardZZZ commented 7 years ago

当修饰器带参数时,

修饰器函数获取到的参数类型都是string,值为修饰器参数

当修饰器不带参数时,

类的修饰器函数获取到的参数是类,typeof返回为function,
类属性的修饰器函数获取的参数是对象,typeof返回为object

类属性的修饰器不带参数时,少一层函数嵌套
// 属性修饰器
export default function(...args) {
    if (args.length === 3 && args[2].value && typeof args[2].value === 'function') {
        // 不带参
        return handle(args);
    } else {
        //带参数
        return function() {
            return handle(arguments, args);
        };
    }
}

function handle(...args) {
    console.log(args);
}
// 类修饰器
export default function(...args) {
    return function(...args1) {
        if (args1.length === 1) {
            return function() {
                return handle(args1[0], args)
            }
        }
        return handle(args[0])
    }
}

// 虽然都会调用此方法,但是源类已经被改写。。。
// 原因是多嵌套了一层 return
// 可以使用 return new clazz() ,但是会产生新的问题
function handle(clazz, ...args) {
    console.log(clazz, args);
}
// 全判断
export default function({ handleMethod, handleClass }) {
    const _checkMethod = (args) => {
        return args.length === 3 && args[0].constructor && typeof args[1] === 'string' && args[2].value && typeof args[2].value === 'function'
    }

    const _handleMethod = (method, ...params) => {
        // console.log('method', method, params);
    }

    const _handleClass = (clazz, ...params) => {
        // console.log('clazz', clazz, params);
    }

    return function decorator(...args) {
        if (_checkMethod(args)) {
            return _handleMethod(args);
        } else if (args.length === 1 && typeof args[0] === 'function' && Object.getOwnPropertyNames(args[0].prototype).length !== 1) {
            // 此判断条件是为排除修饰器的参数为一个function时(非最优,待解决)
            return _handleClass(args[0]);
        } else {
            return function(...args1) {
                if (_checkMethod(args1)) {
                    return _handleMethod(args1, ...args);
                }
                if (args1.length === 1) {
                    return _handleClass(args1[0], ...args);
                }
            };
        }
    }
}
const { defineProperty, getPrototypeOf, getOwnPropertyNames,
    getOwnPropertyDescriptor, getOwnPropertySymbols } = Object;

// 类修饰器
export default function(...args) {
    if (args.length === 3 && typeof args[2].value === 'function') {
        return handle(args);
    } else {
        return function () {
            return handle(arguments);
        };
    }
}

const getOwnKeys = getOwnPropertySymbols
    ? function (object) {
        return getOwnPropertyNames(object)
          .concat(getOwnPropertySymbols(object));
      }
    : getOwnPropertyNames;

function getOwnPropertyDescriptors(obj) {
    const descs = {};
    getOwnKeys(obj).forEach(
        key => (descs[key] = getOwnPropertyDescriptor(obj, key))
    );
    return descs;
}

function createDefaultSetter(key) {
    return function set(newValue) {
        Object.defineProperty(this, key, {
            configurable: true,
            writable: true,
            // IS enumerable when reassigned by the outside word
            enumerable: true,
            value: newValue
        });

        return newValue;
    };
}

function autobindMethod(target, key, { value: fn, configurable, enumerable }) {
    if (typeof fn !== 'function') {
        throw new SyntaxError(`@autobind can only be used on functions, not: ${fn}`);
    }

    const { constructor } = target;

    return {
        configurable,
        enumerable,

        get() {
            if (this === target) {
                return fn;
            }

            if (this.constructor !== constructor && getPrototypeOf(this).constructor === constructor) {
                return fn;
            }

            if (this.constructor !== constructor && key in this.constructor.prototype) {
                return getBoundSuper(this, fn);
            }

            const boundFn = bind(fn, this);

            defineProperty(this, key, {
                configurable: true,
                writable: true,
                enumerable: false,
                value: boundFn
            });

            return boundFn;
        },
        set: createDefaultSetter(key)
    };
}

// 虽然都会调用此方法,但是源类已经被改写。。。
// 原因是多嵌套了一层 return
// 可以使用 return new clazz() ,但是会产生新的问题
function autobindClass(clazz, ...args) {
    const descs = getOwnPropertyDescriptors(clazz.prototype);
    const keys = getOwnKeys(descs);

    for (let i = 0, l = keys.length; i < l; i++) {
        const key = keys[i];
        const desc = descs[key];

        if (typeof desc.value !== 'function' || key === 'constructor') {
            continue;
        }

      defineProperty(clazz.prototype, key, autobindMethod(clazz.prototype, key, desc));
    }
}

function handle(args) {
    if (args.length === 1) {
        return autobindClass(...args);
    }
}
EdwardZZZ commented 5 years ago

装饰器 顺序 解释顺序,执行倒序,先方法再类

class C {
    @f()
    @g()
    method() {}
}

f(): evaluated
g(): evaluated
g(): called
f(): called

autobind

function d(target: any, propertyKey: string, { value, configurable, enumerable }: PropertyDescriptor) {
    return {
        configurable,
        enumerable,
        get() {
            return function (...props: any[]) {
                console.log('before');
                value.apply(this, props);
                console.log('after');
            }.bind(this);
        },
    };
}
EdwardZZZ commented 4 years ago

function D(target, key, desc) {
    const { value, configurable, enumerable, writable } = desc;

    return {
        configurable,
        enumerable,
        writable,
        value: 2
    }
}

function D1(target, key, desc) {
    desc.value = function () {
        console.log('call b');
    }

    Reflect.defineProperty(target, key, desc);
}

class Test {
    @D
    a = 1

    @D1
    b() {
        console.log('bbbbbb');
    }
}

const t = new Test();
console.log(t.a);
t.b();