给对象的某个方法添加mixins

106 阅读1分钟

在mobx-react中看到的代码,记录下来,以学习之

采用了侵入式的方法,给对象新增一个mixins对象,用于保存mixins,然后更改对象的方法为访问器属性,在访问器属性getter中完成mixins逻辑

const mixinKey = Symbol('mixin')
const patchMixinKey = Symbol('patchedMixin')

function getMixins(target, methodName) {
    const mixins = (target[mixinKey] = target[mixinKey] || {})
    const methodMixins = (mixins[methodName] = mixins[methodName] || {})
    methodMixins.locks = methodMixins.locks || 0
    methodMixins.methods = methodMixins.methods || []
    return methodMixins
}

function wrapper(realMethod, mixins, ...args) {
    mixins.locks++

    try {
        let retVal
        if (realMethod !== undefined && realMethod !== null) {
            retVal = realMethod.apply(this, args)
        }

        return retVal
    } finally {
        mixins.locks--

        if (mixins.locks === 0) {
            mixins.methods.forEach(mx => {
                mx.apply(this, args)
            })
        }
    }
}

function wrapFunction(realMethod, mixins) {
    const fn = function (...args) {
        wrapper.call(this, realMethod, mixins, ...args)
    }

    return fn
}


function createDefinition(
    target,
    methodName,
    enumerable,
    mixins,
    originalMethod
) {
   let wrappedFunc = wrapFunction(originalMethod, mixins)

    return {
       [patchMixinKey]: true,
        enumerable: enumerable,
        configurable: true,
        get: function () {
            return wrappedFunc
        },
        set: function (value) {
           if (this === target) {
               wrappedFunc = wrapFunction(value, mixins)
           } else {
               const newDefinition = createDefinition(this, methodName, enumerable, mixins, value)
               Object.defineProperty(this, methodName, newDefinition)
           }
        }
    }
}

function patch(target, methodName, mixinMethod) {
   const mixins = getMixins(target, methodName)

   if (mixins.methods.indexOf(mixinMethod) < 0) {
       mixins.methods.push(mixinMethod)
   }

   const oldDefinition = Object.getOwnPropertyDescriptor(target, methodName)

   if (oldDefinition && oldDefinition[patchMixinKey]) {
     return
   }

   const originalMethod = target[methodName]

   const newDefinition = createDefinition(
       target,
       methodName,
       oldDefinition ? oldDefinition.enumerable : undefined,
       mixins,
       originalMethod
   )

   Object.defineProperty(target, methodName, newDefinition)
}

const testObj = {
    foo() {
        console.log('original foo')
    }
}

patch(testObj, 'foo', (testArg) => {
    console.log('mixin1', testArg)
})

patch(testObj, 'foo', (testArg) => {
    console.log('mixin2', testArg)
})

testObj.foo('bar')