Evalternative? - Funktion referenzieren

Mat

Aktives Mitglied
Moin,

ich lerne grad JavaScript und komme nicht drauf, wie ich eine Funktion referenzieren kann, ohne eval benutzen zu müssen.

So in der Art würde ich das gerne machen, aber ich weiß nicht, ob JS das kann:
Javascript:
// Pseudocode
const mitLog = function (funktion, ...args) {
    console.log("%10s(%d, %d) => %d", funktion.name, args[0], args[1], funktion(args[0], args[1]));
};

function addiere(x, y) {
    return x + y;
}

function multipliziere(x, y) {
    return x * y;
}

mitLog(multipliziere, x, y);
Wenn keiner helfen kann, weiß dann vielleicht sonst jemand, unter welchem Suchbegriff ich etwas dazu finden könnte?
 

alinnert

Mitglied
Das dürfte (fast) 1:1 so funktionieren. Hast du das schon ausprobiert? Du kannst Funktionen also wie in deinem Pseudocode direkt über ihren Handle referenzieren.

Javascript:
const foo = function bar() {};
const baz = function () {};
function baz() {}
In der ersten Zeile kannst du die Funktion über foo referenzieren. foo.name ergibt aber "bar". In der zweiten und dritten Zeile ist der Handle und der Name baz.

Das einzige Problem, das ich sehe ist %10s. %s geht aber. Wenn du den String kürzen möchtest, müsstest du das in den Argumenten hinten direkt machen mit funktion.name.substr(0, 10). Du könntest sogar beide Vorkommen von args[0], args[1] mit ...args abkürzen, um die Funktion weiter zu verallgemeinern.
 
  • Like
Reaktionen: Mat

Mat

Aktives Mitglied
Das dürfte (fast) 1:1 so funktionieren. Hast du das schon ausprobiert?
Tatsächlich, das geht ja doch. Im Original hatte ich noch funktion.arguments[0] usw., was ja gar nicht klappen konnte.

Du könntest sogar beide Vorkommen von args[0], args[1] mit ...args abkürzen, um die Funktion weiter zu verallgemeinern.
Ahh.. stimmt.. Javascript-Methoden nehmen einfach alle Parameter, die man ihnen gibt.


Hab daraus erstmal das hier gemacht:
Javascript:
const mitLog = function (funktion, ...args) {
    let muster = "%s(";
    for (let i = 0; i < args.length - 1; i++) {
        muster += "%d, ";
    }
    muster += "%d) => %d";
    console.log(muster, funktion.name, ...args, funktion(...args));
};

function addiere(x, y) {
    return x + y;
}

function multipliziere(x, y) {
    return x * y;
}

function summiere(...nummern) {
    let summe = 0;
    for (let i = 0; i < nummern.length; i++) {
        summe += nummern[i];
    }
    return summe;
}

mitLog(addiere, 10, 5); // addiere(10, 5) => 15
mitLog(multipliziere, 10, 5); // multipliziere(10, 5) => 50
mitLog(summiere, 1, 2, 3); // summiere(1, 2, 3) => 6
In der ersten Zeile kannst du die Funktion über foo referenzieren. foo.name ergibt aber "bar". In der zweiten und dritten Zeile ist der Handle und der Name baz.
Komisch, bei const baz = function () {}; hätte ich baz.name == undefined erwartet. Gut zu wissen
 
Zuletzt bearbeitet:

alinnert

Mitglied
Ahh.. stimmt.. Javascript-Methoden nehmen einfach alle Parameter, die man ihnen gibt.
Nicht mal deswegen. Mit function (funktion, ...args) { } nimmst du sowieso explizit alle übergebenen Parameter (außer dem ersten) an und packst sie in das Array args.

Komisch, bei const baz = function () {}; hätte ich baz.name == undefined erwartet. Gut zu wissen
Dachte ich zuerst auch, aber habs vorsichtshalber überprüft. Das wurde auch erst mit ES6 eingeführt. IE11 unterstützt das auch noch nicht. Der Name von anonymen Funktionen ist aber ein Leerstring statt undefined

Zu deinem Code-Beispiel:

for (let i = 0; i < args.length - 1; i++) wird das letzte Argument aber überspringen. Entweder i < length oder i <= length - 1.

Und kennst du Template Strings? Die würde ich in dem Falle gegenüber der Schleife bevorzugen, weil das jetzt doch etwas ausartet:

Javascript:
console.log(`${funktion.name}(${args.join(", ")}) => ${funktion(...args)}`);
 
  • Like
Reaktionen: Mat

Mat

Aktives Mitglied
for (let i = 0; i < args.length - 1; i++) wird das letzte Argument aber überspringen.
Soll es auch. Aber join ist schöner.. das wurde bei mir bei der Autovervollständigung nicht angezeigt, also war ich fälschlicherweise davon ausgegangen, dass so ein varargs-Objekt die Methode nicht hat.

Und kennst du Template Strings?
Ne, cool, das machts viel einfacher, danke.

PS:
Grad gesehen, dass JS-Arrays auch reduce haben, cool
Javascript:
function summiere(...nummern) {
    return nummern.reduce((summe, n) => summe + n);
}
 
Zuletzt bearbeitet:

alinnert

Mitglied
Soll es auch. Aber join ist schöner.. das wurde bei mir bei der Autovervollständigung nicht angezeigt, also war ich fälschlicherweise davon ausgegangen, dass so ein varargs-Objekt die Methode nicht hat.
Ach so, dann hab ich nichts gesagt.

Das kommt drauf an, welchen Editor du verwendest und ob dieser weiß, dass Rest-Parameter immer Arrays sind (also, ja. Ist ein ganz normales Array). Wenn nicht gerade TypeScript zumindest als Code-Analyzer läuft, ist Code-Vervollständigung für JS nicht sehr zuverlässig, da das schwierig umzusetzen ist.

Grad gesehen, dass JS-Arrays auch reduce haben, cool
Ja, Array-Methoden gibt es so einige. Und es werden immer mehr.
 

Mat

Aktives Mitglied
Hab noch weiter rumprobiert und halbwegs passend zum Thema wieder eine Frage:

Spricht irgendwas gegen diesen Aufbau, wenn der Operator aus einer Benutzereingabe stammt (im Bezug auf Sicherheit oder Entwurf)?
Javascript:
const OP = {
    calc: (op, ...args) => {
        if (typeof OP[op] !== 'function') throw new Error('Unknown Operator');
        return OP[op].apply(null, args);
    },
    '+': (a, b) => a + b,
    '-': (a, b) => a - b,
    '/': (a, b) => a / b,
    '*': (a, b) => a * b,
    '^': (a, b) => Math.pow(a, b),
    '!': (n) => {
        let x = Math.abs(n);
        if (x <= 2) return n;
        let sum = 2;
        for (let i = x - 1; i > 2; i--) {
            sum *= i;
        }
        return sum * n;
    },
};

function testOPs() {
    console.log(OP['+'].apply(null, [5, 9])); // 14 <-- hier wird wohl eine lokale Kopie der Funktion erzeugt
    console.log(OP['-'].call(null, 5, 9)); // -4 <-- auch hier wird wohl eine lokale Kopie der Funktion erzeugt
    console.log(OP['*'](5, 9)); // 45
    console.log(OP.calc('/', 5, 9)); // 0.5555555555555556
    console.log(OP.calc('!', 6)); // 720
    console.log(OP['!'](-6)); // -720
    console.log(OP['^'](2, 16)); // 65536
    console.log(OP.calc('^', 2, 10)); // 1024

    try {
        console.log(OP.calc('__proto__.constructor', 1, 2)); // Unknown Operator <-- warum konnte ich das nicht "exploiten"?
    } catch (err) {
        console.error(err.message);
    }
}
 

asc

Mitglied
devCommunity-Experte
Du kannst mit [] nur eine direkte Eigenschaft abfragen. JavaScript behandelt den . nicht.

Was aber zum Beispiel geht:
OP.calc('toString', 1, 2) liefert "[object Null]"

Abhilfe schafft ein "blankes" Objekt:
const OPS = Object.assign(Object.create(null), { '+' : (a, b) => ... })

Damit hast du schonmal alle vererbten Methoden weg.

Was aber weiterhin funktioniert:
OP.calc('calc', '+', 1, 2) liefert "3"

Das lässt sich auch weiter spinnen:
OP.calc('calc', 'calc', 'calc', '+', 1, 2) liefert ebenfalls "3"

D.h. die calc-Methode sollte noch aus dem Objekt raus.
Du kannst daraus ja eine eigene Funktion bauen.
 
  • Like
Reaktionen: Mat

Mat

Aktives Mitglied
Das lässt sich auch weiter spinnen:
OP.calc('calc', 'calc', 'calc', '+', 1, 2) liefert ebenfalls "3"
Ups, gut gesehen.. da ist DOS vorprogrammiert :poop: :

Javascript:
try {
    let blargs = [];
    for (let i = 0; i < 1000; i++) {
        blargs.push('calc');
    }
    blargs.push('+', 1, 2);
    console.log(OP.calc.apply(null, blargs));
} catch (err) {
    console.error(err.message); // Maximum call stack size exceeded
}
 
Oben Unten