在不实际扩展原型的情况下拥有扩展原型的优点

Have the niceties of extended prototype without actually extending the prototype

我想做的是有一个接口,您可以在其中将元素包装到具有扩展原型的实例中,例如。

const wrappedButton = new WrappedElem(document.getElementById('some-button'))
// we have access to our custom added method:
console.log(`I am the ${wrappedButton.siblingIndex()}th button`)

// but we still have access to the original methods
wrappedButton.addEventListener('click', function(e){
  console.log('I clicked on', e.target)
  // right here we won't have access to siblingIndex, because the prototype of the element itself was not changed.
})

我可以像这样扩展原始原型

HTMLElement.prototype.siblingIndex = function() {
    if(this.parentNode == null) {
        return -1
    }
    
    return Array.prototype.indexOf.call(this.parentNode.children, this)
}

但是扩展原型是不好的做法,而且性能很差。

所以可以做这样的事情吗?

通过使用代理,我们可以在不更改原型的情况下添加新方法:

const siblingIndex = () => {}

const handler = {
    get(target, property) {
        
        if(property === 'siblingIndex') {
            return siblingIndex;
        }

        return target[property];
    }
}

const proxyButton = new Proxy(button, handler);

// we can also call the siblingIndex function
proxyButton.siblingIndex();
// we can access the properties of the underlying object
proxyButton.tagName;

e.target 但是不会 return 代理而是原始对象, 但你可以只使用 proxyButton 而不是 e.target

如果需要,您还可以在调用回调时将 addEventListener 方法重写为 return 代理版本

这似乎工作正常。非常感谢Lk77。

var wrappedElMethods = {
    siblingIndex() {
        if(this.parentNode == null) {
            return -1
        }
        
        return Array.prototype.indexOf.call(this.parentNode.children, this)
    }
}

function wrappedEl(el) {
    return proxyInherit(el, wrappedElMethods)
}

function proxyInherit(item, overwrites) {
    const handler = {
        get(target, property) {
            
            let value
            const overwriteVal = overwrites[property]
            
            if(overwriteVal != undefined) {
                value = overwriteVal
            } else {
                value = target[property]
            }
            
            if(value instanceof Function) {
                return value.bind(item)
            }
            
            return value
        },
        set(target, property, value) {
            target[property] = value
        }
    }
    
    return new Proxy(item, handler)
}


// Usage:
var button = wrappedEl(e.target)
button.onclick = function() {
  console.log(button.siblingIndex())
}