0

Let's say that I'm making a JS library that has some functions for DOM manipulation. I have a few functions that are added to elements, for example myElement.empty();. I would like the same functionality for NodeLists (the objects returned from, for example, querySelectorAll). To dynamically add the functions, I have done the below (and please note that the below does work):

var funcs=["text","html","attr","remove","empty","css"];
var n=funcs.length;
while(~--n){
    var f=function(){
        var i,l=this.length;
        for(i=0;i<l;i++){
            this[i][ funcs[arguments.callee.n] ].apply(this[i], arguments);
        }
        return this;
    };
    f.n=n;
    NodeList.prototype[funcs[n]]=f;
}

This works, but I have heard that arguments.callee doesn't work in "strict" mode.

Someone said to give the function a name, but I can't, although I tried:

var funcs=["text","html","attr","remove","empty","css"];
var n=funcs.length;
while(~--n){
    this[funcs[n]]=function(){
        var i,l=this.length;
        for(i=0;i<l;i++){
            this[i][ funcs[name] ].apply(this[i], arguments);
            // In the above it has 'name' which references the function name
        }
        return this;
    };
    NodeList.prototype[funcs[n]]=this[funcs[n]];
}

That didn't work. I was able to do it using eval. I decided not to use eval, although it worked (by putting n into the string and making the function out of that). I figured that arguments.callee is probably better than eval, if I had to use one of them.

How can I make my function work without using anything that is suggested against (such as arguments.callee and eval)?

Edit for more details:

Let's say I define an empty function (and once again for the purpose of the question let's assume that modifying the prototype is OK):

Element.prototype.empty=function(){
    while(this.childNodes[0])
        this.childNodes[0].remove();
};

This works for one element. What if the user wants to do something similar to:

document.querySelectorAll("button .myclass").empty();

So I want to make a script that dynamically creates functions for NodeLists that call the corresponding functions for each element, for example:

NodeList.prototype.empty=function(){
    var i,l=this.length;
    for(i=0;i<l;i++)
        this[i].empty();
    return this;
};

If I want to do the above I will need to create many functions that do very similar things. So I want to write some code that dynamically creates functions for NodeList. As you probably read above, I did it with arguments.callee (and eval), but I want to use something that is standard and considered OK to use.

How would I do this?

If you need any more information then please ask in the comments, and I will try to respond as soon as possible.

Lakshya Raj
  • 1,669
  • 3
  • 10
  • 34
  • Seems like an [X-Y problem](https://meta.stackexchange.com/a/233676/399876). I'd suggest writing wrappers instead of trying to add prototype functions, which is [generally considered poor practice these days](https://flaviocopes.com/javascript-why-not-modify-object-prototype/). – ggorlen Apr 05 '21 at 17:16
  • @ggorlen: OK, let's assume I wrote a wrapper or some normal library function. How would I automatically generate the functions then? (My question was about creating functions that can access their own names, and I really didn't want to go off topic to not editing prototypes, although I do know that usually that's not OK). – Lakshya Raj Apr 05 '21 at 17:31
  • Is the idea to forward calls to the wrapped Node when the function is native, and let your library handle calls that aren't native, like `empty()`? Maybe a [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), but if it were me, I'd probably do it explicitly to avoid confusing magic. It also seems like rewriting jQuery here which makes me a bit uneasy. – ggorlen Apr 05 '21 at 17:36
  • @ggorlen: See my updated question. – Lakshya Raj Apr 05 '21 at 17:47

1 Answers1

0

Don't use that weird iteration style, but an idiomatic for … of loop that allows for a block-scoped variable:

const funcNames = ["text","html","attr","remove","empty","css"];
for (const name of funcNames) {
    NodeList.prototype[name] = function(...args) {
        for (let i=0; i<this.length; i++) {
            this[i][name](...args);
        }
        return this;
    };
}

No reason to access the current function or store the name on its .n property, just access it through closure.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Hmmm... I wonder how I didn't think of that! Thanks a lot, I couldn't find any way to get it to work the way I wanted! Just a question - what is the difference between the `...args` way you're using now and the `.apply` way you were using before? – Lakshya Raj Apr 05 '21 at 17:53
  • It's more modern (available since ES6) and seems simpler (shorter) and clearer, but otherwise it works exactly the same. – Bergi Apr 05 '21 at 17:57
  • And about the "weird iteration style" - `~--variable` just means keep decreasing it until it's complement is 0. The complement of 0 is -1, so when it reaches -1 then stop. Since it is decrementing with a prefix, it loops over the correct numbers (just backwards). – Lakshya Raj Apr 05 '21 at 18:29
  • @LakshyaRaj Yes, I could figure out what it does, but it's still weird. Even when using `while` loops for iteration with a counter, the normal approach for backwards iteration would be `while (i--)` – Bergi Apr 05 '21 at 19:54
  • I guess it would only be useful in statements like `if`, when you use it as `if(~string.indexOf(otherString))` because that runs if `otherString` is found in `string`, so I probably only use it in loops because it looks nice :) I'll try using the normal `variable--` though. – Lakshya Raj Apr 05 '21 at 20:25