У меня есть в основном рабочее решение, но вам все еще нужно решить одну небольшую, но раздражающую проблему (см. Предостережение 3). В основном это сделано, поэтому я все равно это здесь выложу.
Я думаю, это то, что вы ищете:
function app(selector) {
const retArr = document.querySelectorAll(selector); // The array to return
// Add proxies for all prototype methods of all elements
for (let e of retArr) {
let methods = getProtoMethods(e);
for (let mKey in methods) {
// Skip if the proxy method already exists in retArr
if (retArr[mKey] !== undefined) continue;
// Otherwise set proxy method
Object.defineProperty(retArr, mKey, {
value: function(...args) {
// Loop through all elements in selection
retArr.forEach(el => {
// Call method if it exists
if (el[mKey] !== undefined) el[mKey](...args);
});
}
});
}
}
return retArr;
// Gets all prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
return methods;
}
}
Идея состоит в том, чтобы поместить все выбранные объекты в массив, затем определите дополнительные методы в массиве. Он должен иметь все имена методов выбранных объектов, но эти методы на самом деле являются прокси этих оригинальных методов. Когда вызывается один из этих прокси-методов, он вызывает оригинальный метод для всех (см. Предостережение 1) выбранных объектов в массиве. Но в противном случае возвращенный объект можно просто использовать как обычный массив (или, точнее, NodeList
в данном случае).
Однако стоит упомянуть, что с этой конкретной реализацией есть несколько предостережений.
- Список созданных прокси-методов представляет собой объединение методов всех выбранных объектов, а не пересечение . Предположим, вы выбрали два элемента -
A
и B
. A
имеет метод doA()
, а B
имеет метод doB()
. Тогда массив, возвращаемый app()
, будет иметь как прокси-методы doA()
, так и doB()
. Однако, когда вы вызываете, например, doA()
, будет вызван только A.doA()
, потому что, очевидно, B
не имеет метода doA()
. - Если выбранные объекты не имеют одно и то же определение для того же имя метода, прокси-метод будет использовать свои индивидуальные определения. Это обычно желаемое поведение при полиморфизме, но все же это что-то, что нужно иметь в виду.
- Эта реализация не пересекает цепочку прототипов, что на самом деле является серьезной проблемой. Он смотрит только на прототипы выбранных элементов, но не на прототипы прототипов. Поэтому эта реализация не работает хорошо с любым наследованием. Я попытался заставить это работать, сделав
getProtoMethods()
рекурсивным, и он работает с обычными JS объектами, но выполнение этого с элементами DOM выдает странные ошибки (TypeError: Illegal Invocation
) (см. здесь ) , Если вы можете как-то решить эту проблему, то это будет полностью рабочее решение.
Это рекурсивный код c:
// Recursively gets all nested prototype methods for one object
function getProtoMethods(obj) {
let methods = {};
// Loop through all prototype properties of obj and add all functions
for (let pKey of Object.getOwnPropertyNames(Object.getPrototypeOf(obj))) {
// Skip properties that aren't functions and constructor
// obj[pKey] throws error when obj is already a prototype object
if (pKey !== "constructor" && typeof obj[pKey] === "function") {
methods[pKey] = obj[pKey];
}
}
// If obj's prototype has its own prototype then recurse.
if (Object.getPrototypeOf(Object.getPrototypeOf(obj)) == null) {
return methods;
} else {
return {...methods, ...getProtoMethods(Object.getPrototypeOf(obj))};
}
}
Извините, я не могу решить вашу проблему на 100%, но, надеюсь, это по крайней мере несколько полезно.