Jak dynamicznie uzyskiwać nazwy / wartości parametrów funkcji?

300

Czy istnieje sposób na dynamiczne uzyskiwanie nazw parametrów funkcji?

Powiedzmy, że moja funkcja wygląda następująco:

function doSomething(param1, param2, .... paramN){
   // fill an array with the parameter name and value
   // some other code 
}

W jaki sposób mógłbym uzyskać listę nazw parametrów i ich wartości do tablicy z wnętrza funkcji?

Vikasde
źródło
Dziękuję wszystkim. Po przeszukaniu znalazłem rozwiązanie na SO: stackoverflow.com/questions/914968/... Używa wyrażenia regularnego, aby uzyskać nazwę parametru. To chyba nie najlepsze rozwiązanie, jednak dla mnie działa.
vikasde
8
chodźmy naprzód i zaznaczmy to jako odpowiedź kolego. nie nadchodzą nowe odpowiedzi.
Matthew Graves
function doSomething(...args) { /*use args*/}
kaub

Odpowiedzi:

322

Poniższa funkcja zwróci tablicę nazw parametrów każdej przekazanej funkcji.

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func) {
  var fnStr = func.toString().replace(STRIP_COMMENTS, '');
  var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
  if(result === null)
     result = [];
  return result;
}

Przykładowe użycie:

getParamNames(getParamNames) // returns ['func']
getParamNames(function (a,b,c,d){}) // returns ['a','b','c','d']
getParamNames(function (a,/*b,c,*/d){}) // returns ['a','d']
getParamNames(function (){}) // returns []

Edytuj :

Dzięki wynalezieniu ES6 funkcja ta może zostać uruchomiona przy domyślnych parametrach. Oto szybki hack, który powinien działać w większości przypadków:

var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

Mówię większość przypadków, ponieważ są pewne rzeczy, które mogą to potknąć

function (a=4*(5/3), b) {} // returns ['a']

Edycja : Zauważam również, że vikasde chce również wartości parametrów w tablicy. Jest to już zapewnione w lokalnej zmiennej o nazwie argumenty.

fragment https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments :

Obiekt argumentów nie jest tablicą. Jest podobny do tablicy, ale nie ma żadnych właściwości tablicy poza długością. Na przykład nie ma metody pop. Można go jednak przekonwertować na prawdziwą macierz:

var args = Array.prototype.slice.call(arguments);

Jeśli dostępne są ogólne rodzaje tablic, można zamiast tego użyć następujących:

var args = Array.slice(arguments);
Jack Allan
źródło
12
Pamiętaj, że to rozwiązanie może się nie powieść z powodu komentarzy i spacji - na przykład: var fn = function(a /* fooled you)*/,b){};spowoduje ["a", "/*", "fooled", "you"]
bubersson
1
Zmodyfikowałem funkcję, aby zwracała pustą tablicę (zamiast null), gdy nie ma żadnych argumentów
BT
2
Kompilacja wyrażenia regularnego wiąże się z pewnym kosztem, dlatego chcesz uniknąć kompilowania wyrażeń regularnych więcej niż jeden raz. Dlatego odbywa się to poza funkcją
Jack Allan
2
KOREKCJA: Zamierzałem zmodyfikować wyrażenie regularne za pomocą modyfikatora A / S, na który pozwala perl, więc „. może również pasować do nowej linii. Jest to konieczne w przypadku komentarzy wieloliniowych wewnątrz / * * /. Okazuje się, że regex Javascript nie pozwala na modyfikator / s. Oryginalne wyrażenie regularne przy użyciu [/ s / S] pasuje do znaków nowej linii. SOOO, zignoruj ​​poprzedni komentarz.
tgoneil
1
@ andes Uwaga: włączasz kompilację wyrażeń regularnych do swoich testów. Należy to zrobić tylko raz. Twoje wyniki mogą być inne, jeśli przeniesiesz kompilację wyrażeń regularnych do kroku konfiguracji zamiast testu
Jack Allan
123

Poniżej znajduje się kod pobrany z AngularJS, który wykorzystuje tę technikę dla mechanizmu wstrzykiwania zależności.

A oto wyjaśnienie tego zaczerpnięte z http://docs.angularjs.org/tutorial/step_05

Wtryskiwacz zależności Angulara zapewnia obsługę kontrolera podczas jego konstruowania. Wtryskiwacz zależności zajmuje się także tworzeniem wszelkich przechodnich zależności, które może mieć usługa (usługi często zależą od innych usług).

Zauważ, że nazwy argumentów są znaczące, ponieważ inżektor używa ich do wyszukiwania zależności.

/**
 * @ngdoc overview
 * @name AUTO
 * @description
 *
 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
 */

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
        arg.replace(FN_ARG, function(all, underscore, name){
          $inject.push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn')
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}
Lambder
źródło
40
@apaidnerd z krwią demonów i spawn szatana, najwyraźniej. Regex ?! Byłoby fajnie, gdyby w JS był wbudowany sposób, prawda?
Aditya, poseł
6
@apaidnerd, to prawda! Pomyślałem - jak do diabła jest to realizowane? Właściwie myślałem o użyciu funkcji functionName.toString (), ale liczyłem na coś bardziej eleganckiego (i być może szybszego)
sasha.sochka
2
@ sasha.sochka, przyszedł tutaj, zastanawiając się dokładnie nad tym samym, po uświadomieniu sobie, że nie ma wbudowanego sposobu na uzyskanie nazw parametrów za pomocą javascript
Hart Simha
14
aby zaoszczędzić czas ludzi, możesz uzyskać tę funkcję od kąta przez annotate = angular.injector.$$annotate
Nick
3
Dosłownie zacząłem szukać w Internecie tego tematu, ponieważ byłem ciekawy, jak to zrobił Angular ... teraz wiem, a także wiem za dużo!
Steven Hunt
40

Oto zaktualizowane rozwiązanie, które próbuje rozwiązać wszystkie wyżej wymienione przypadki krawędzi w kompaktowy sposób:

function $args(func) {  
    return (func + '')
      .replace(/[/][/].*$/mg,'') // strip single-line comments
      .replace(/\s+/g, '') // strip white space
      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
      .replace(/=[^,]+/g, '') // strip any ES6 defaults  
      .split(',').filter(Boolean); // split & filter [""]
}  

Skrócone wyjście testowe (pełne przypadki testowe załączono poniżej):

'function (a,b,c)...' // returns ["a","b","c"]
'function ()...' // returns []
'function named(a, b, c) ...' // returns ["a","b","c"]
'function (a /* = 1 */, b /* = true */) ...' // returns ["a","b"]
'function fprintf(handle, fmt /*, ...*/) ...' // returns ["handle","fmt"]
'function( a, b = 1, c )...' // returns ["a","b","c"]
'function (a=4*(5/3), b) ...' // returns ["a","b"]
'function (a, // single-line comment xjunk) ...' // returns ["a","b"]
'function (a /* fooled you...' // returns ["a","b"]
'function (a /* function() yes */, \n /* no, */b)/* omg! */...' // returns ["a","b"]
'function ( A, b \n,c ,d \n ) \n ...' // returns ["A","b","c","d"]
'function (a,b)...' // returns ["a","b"]
'function $args(func) ...' // returns ["func"]
'null...' // returns ["null"]
'function Object() ...' // returns []

humbletim
źródło
Dzieje się tak, gdy pojawiają się komentarze jednowierszowe. Spróbuj tego: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham
4
Powinieneś wymienić func + ''z Function.toString.call(func)obrony przed przypadku, gdy funkcja ma zwyczaj .ToString () realizację.
Paul Go
1
grube strzały =>.split(/\)[\{=]/, 1)[0]
Matt
1
to dzieli zniszczone obiekty (jak ({ a, b, c })) na wszystkie parametry wewnątrz destrukcji. aby utrzymać nienaruszone obiekty w stanie nienaruszonym, zmień ostatni .splitna: .split(/,(?![^{]*})/g)
Michael Auderer
1
To również nie zadziała, jeśli istnieją domyślne wartości ciągu zawierające „//” lub „/ *”
skerit
23

Rozwiązaniem, które jest mniej podatne na błędy i spacje, byłoby:

var fn = function(/* whoa) */ hi, you){};

fn.toString()
  .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
  .match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
  .split(/,/)

["hi", "you"]
bubersson
źródło
1
@AlexMills Jedną z rzeczy, które zauważyłem, jest to, że specyfikacja funkcji strzałek mówi, że nie należy ich traktować jako „funkcji”. Oznacza to, że nie byłoby odpowiednie dopasowanie funkcji tablicowych. „To” nie jest ustawione w ten sam sposób i nie należy też wywoływać ich jako funkcji. Tego nauczyłem się na własnej skórze. ($ myService) => $ myService.doSomething () wygląda fajnie, ale jest to niewłaściwe użycie funkcji tablicy.
Andrew T Finnell,
20

Wiele odpowiedzi tutaj zawiera wyrażenia regularne, jest w porządku, ale nie obsługuje zbyt dobrze nowych dodatków do języka (takich jak funkcje strzałek i klasy). Warto również zauważyć, że jeśli użyjesz którejkolwiek z tych funkcji na zminimalizowanym kodzie, to pójdzie 🔥. Będzie używać jakiejkolwiek zminimalizowanej nazwy. Angular omija ten problem, umożliwiając przekazywanie uporządkowanej tablicy ciągów, która odpowiada kolejności argumentów podczas rejestrowania ich w kontenerze DI. Tak dalej z rozwiązaniem:

var esprima = require('esprima');
var _ = require('lodash');

const parseFunctionArguments = (func) => {
    // allows us to access properties that may or may not exist without throwing 
    // TypeError: Cannot set property 'x' of undefined
    const maybe = (x) => (x || {});

    // handle conversion to string and then to JSON AST
    const functionAsString = func.toString();
    const tree = esprima.parse(functionAsString);
    console.log(JSON.stringify(tree, null, 4))
    // We need to figure out where the main params are. Stupid arrow functions 👊
    const isArrowExpression = (maybe(_.first(tree.body)).type == 'ExpressionStatement');
    const params = isArrowExpression ? maybe(maybe(_.first(tree.body)).expression).params 
                                     : maybe(_.first(tree.body)).params;

    // extract out the param names from the JSON AST
    return _.map(params, 'name');
};

To obsługuje oryginalny problem analizy i kilka innych typów funkcji (np. Funkcje strzałek). Oto pomysł na to, co może, a czego nie może obsłużyć w następujący sposób:

// I usually use mocha as the test runner and chai as the assertion library
describe('Extracts argument names from function signature. 💪', () => {
    const test = (func) => {
        const expectation = ['it', 'parses', 'me'];
        const result = parseFunctionArguments(toBeParsed);
        result.should.equal(expectation);
    } 

    it('Parses a function declaration.', () => {
        function toBeParsed(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses a functional expression.', () => {
        const toBeParsed = function(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses an arrow function', () => {
        const toBeParsed = (it, parses, me) => {};
        test(toBeParsed);
    });

    // ================= cases not currently handled ========================

    // It blows up on this type of messing. TBH if you do this it deserves to 
    // fail 😋 On a tech note the params are pulled down in the function similar 
    // to how destructuring is handled by the ast.
    it('Parses complex default params', () => {
        function toBeParsed(it=4*(5/3), parses, me) {}
        test(toBeParsed);
    });

    // This passes back ['_ref'] as the params of the function. The _ref is a 
    // pointer to an VariableDeclarator where the ✨🦄 happens.
    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ({it, parses, me}){}
        test(toBeParsed);
    });

    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ([it, parses, me]){}
        test(toBeParsed);
    });

    // Classes while similar from an end result point of view to function
    // declarations are handled completely differently in the JS AST. 
    it('Parses a class constructor when passed through', () => {
        class ToBeParsed {
            constructor(it, parses, me) {}
        }
        test(ToBeParsed);
    });
});

W zależności od tego, czego chcesz użyć do serwerów proxy ES6, najlepszym rozwiązaniem może być destrukcja. Na przykład, jeśli chcesz użyć go do wstrzykiwania zależności (używając nazw parametrów), możesz to zrobić w następujący sposób:

class GuiceJs {
    constructor() {
        this.modules = {}
    }
    resolve(name) {
        return this.getInjector()(this.modules[name]);
    }
    addModule(name, module) {
        this.modules[name] = module;
    }
    getInjector() {
        var container = this;

        return (klass) => {
            console.log(klass);
            var paramParser = new Proxy({}, {
                // The `get` handler is invoked whenever a get-call for
                // `injector.*` is made. We make a call to an external service
                // to actually hand back in the configured service. The proxy
                // allows us to bypass parsing the function params using
                // taditional regex or even the newer parser.
                get: (target, name) => container.resolve(name),

                // You shouldn't be able to set values on the injector.
                set: (target, name, value) => {
                    throw new Error(`Don't try to set ${name}! 😑`);
                }
            })
            return new klass(paramParser);
        }
    }
}

Nie jest to najbardziej zaawansowany resolver na rynku, ale daje wyobrażenie o tym, jak użyć proxy do obsługi go, jeśli chcesz użyć parsera args do prostej DI. Jest jednak jedno niewielkie zastrzeżenie w tym podejściu. Musimy użyć zadań restrukturyzacji zamiast normalnych parametrów. Kiedy mijamy proxy wtryskiwacza, destrukcja jest taka sama jak wywoływanie gettera na obiekcie.

class App {
   constructor({tweeter, timeline}) {
        this.tweeter = tweeter;
        this.timeline = timeline;
    }
}

class HttpClient {}

class TwitterApi {
    constructor({client}) {
        this.client = client;
    }
}

class Timeline {
    constructor({api}) {
        this.api = api;
    }
}

class Tweeter {
    constructor({api}) {
        this.api = api;
    }
}

// Ok so now for the business end of the injector!
const di = new GuiceJs();

di.addModule('client', HttpClient);
di.addModule('api', TwitterApi);
di.addModule('tweeter', Tweeter);
di.addModule('timeline', Timeline);
di.addModule('app', App);

var app = di.resolve('app');
console.log(JSON.stringify(app, null, 4));

To powoduje, że:

{
    "tweeter": {
        "api": {
            "client": {}
        }
    },
    "timeline": {
        "api": {
            "client": {}
        }
    }
}

Jest podłączony do całej aplikacji. Najlepsze jest to, że aplikacja jest łatwa do przetestowania (możesz po prostu utworzyć instancję każdej klasy i przekazać makiety / odcinki / etc). Również jeśli chcesz zamienić implementacje, możesz to zrobić z jednego miejsca. Wszystko to jest możliwe dzięki obiektom JS Proxy.

Uwaga: Jest dużo pracy, którą należałoby wykonać, zanim będzie gotowy do użytku produkcyjnego, ale daje wyobrażenie o tym, jak by to wyglądało.

Trochę późno na odpowiedź, ale może pomóc innym, którzy myślą o tym samym. 👍

James Drew
źródło
13

Wiem, że to stare pytanie, ale początkujący kopiowali to tak, jakby to była dobra praktyka w dowolnym kodzie. W większości przypadków konieczność przeanalizowania reprezentacji ciągu funkcji w celu użycia nazw parametrów po prostu ukrywa lukę w logice kodu.

Parametry funkcji są w rzeczywistości przechowywane w obiekcie tablicowym o nazwie arguments, gdzie pierwszy argument to arguments[0]drugi, arguments[1]i tak dalej. Wpisywanie nazw parametrów w nawiasach można traktować jako skróconą składnię. To:

function doSomething(foo, bar) {
    console.log("does something");
}

...jest taki sam jak:

function doSomething() {
    var foo = arguments[0];
    var bar = arguments[1];

    console.log("does something");
}

Same zmienne są przechowywane w zakresie funkcji, a nie jako właściwości w obiekcie. Nie ma możliwości odzyskania nazwy parametru za pomocą kodu, ponieważ jest to jedynie symbol reprezentujący zmienną w języku ludzkim.

Zawsze rozważałem reprezentację ciągu funkcji jako narzędzia do celów debugowania, szczególnie ze względu na ten argumentsobiekt podobny do tablicy. W pierwszej kolejności nie musisz podawać nazw argumentom. Jeśli spróbujesz parsować funkcję łańcuchową, tak naprawdę nie powie ci ona o dodatkowych parametrach nienazwanych, które może wziąć.

Oto jeszcze gorsza i bardziej powszechna sytuacja. Jeśli funkcja ma więcej niż 3 lub 4 argumenty, logiczne może być przekazanie jej zamiast tego obiektu, co jest łatwiejsze w obsłudze.

function saySomething(obj) {
  if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
}

saySomething({sender: "user123", message: "Hello world"});

W takim przypadku sama funkcja będzie w stanie odczytać otrzymany obiekt i poszukać jej właściwości oraz uzyskać zarówno ich nazwy, jak i wartości, ale próba przetworzenia reprezentacji ciągu funkcji dałaby tylko „obj” dla parametrów, co wcale nie jest przydatne.

Domino
źródło
1
Myślę, że w przypadku czegoś takiego jest to zwykle: debugowanie / logowanie, jakiś dekorator, który robi fajne rzeczy (termin techniczny 😁), lub budowanie struktury wstrzykiwania zależności dla aplikacji, które wstrzykują się automatycznie na podstawie nazwy argumentu (tak to jest kątowe Pracuje). Innym naprawdę interesującym przypadkiem użycia jest węzeł promisify (jest to biblioteka, która przyjmuje funkcję, która normalnie przyjmuje wywołanie zwrotne, a następnie przekształca ją w obietnicę). Używają tego do znajdowania wspólnych nazw dla wywołań zwrotnych (takich jak cb / callback / etc), a następnie mogą sprawdzić, czy funkcja jest asynchroniczna lub synchronizowana przed jej opakowaniem.
James Drew
Zobacz ten plik dla ich parsera . Jest to trochę naiwne, ale obsługuje większość przypadków.
James Drew
Co ciekawe, jestem zaskoczony, że taka biblioteka wzbudziła jakiekolwiek zainteresowanie. Cóż, istnieje wiele otwartych kwestii dotyczących problematycznych sytuacji, takich jak te, które opisałem. Jak powiedziałem, jeśli jest to potrzebne do debugowania, jest w porządku, ale poleganie na konwersji ciągów funkcji w środowiskach produkcyjnych jest zbyt ryzykowne.
Domino
Na „śmiesznym” sidenocie możesz sprawić, by wszystkie funkcje działały tak, jakby były anonimowymi wbudowanymi funkcjami, uruchamiając to:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino
1
Zgadzam się, trochę niechlujne jest obchodzenie się z tym w ten sposób. Najlepszym i najprostszym zastosowaniem jest wstrzykiwanie zależności. W takim przypadku jesteś właścicielem kodu i możesz obsługiwać nazewnictwo i inne obszary kodu. Myślę, że przydałoby mu się to najwięcej. Obecnie używam esprima (zamiast wyrażenia regularnego) i ES6 Proxy( pułapka konstruktora i pułapka zastosuj ) i Reflectiondo obsługi DI dla niektórych moich modułów. Jest dość solidny.
James Drew
10

Ponieważ JavaScript jest językiem skryptowym, uważam, że jego introspekcja powinna obsługiwać uzyskiwanie nazw parametrów funkcji. Pakowanie tej funkcjonalności stanowi naruszenie pierwszych zasad, dlatego postanowiłem zbadać ten problem dalej.

Doprowadziło mnie to do tego pytania, ale nie ma wbudowanych rozwiązań. Co doprowadziło mnie do tej odpowiedzi, która wyjaśnia, że argumentsjest ona przestarzała poza funkcją, więc nie możemy już używać myFunction.argumentslub otrzymujemy:

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

Czas zakasać rękawy i zabrać się do pracy:

Parameters Pobieranie parametrów funkcji wymaga analizatora składni, ponieważ złożone wyrażenia, takie jak, 4*(5/3)mogą być użyte jako wartości domyślne. Zatem odpowiedź Gaafara lub odpowiedź Jamesa Drew są jak dotąd najlepszymi podejściami.

Próbowałem parserów babylon i esprima, ale niestety nie mogą one parsować samodzielnych anonimowych funkcji, jak wskazano w odpowiedzi Mateusza Charytoniuka . Znalazłem inne obejście, otaczając kod w nawiasach, aby nie zmieniać logiki:

const ast = parser.parse("(\n" + func.toString() + "\n)")

Nowe wiersze zapobiegają problemom z //(komentarze jednowierszowe).

⭐ Jeśli analizator składni nie jest dostępny, następną najlepszą opcją jest użycie sprawdzonej techniki, takiej jak wyrażenia regularne wtryskiwacza zależności Angular.js. Połączyłem funkcjonalną wersję odpowiedzi Lambder jest z odpowiedzią humbletim męska i dodano opcjonalny ARROWlogiczną do kontrolowania, czy funkcje ES6 tłuszcz strzałek są akceptowane przez wyrażeń regularnych.


Oto dwa rozwiązania, które zestawiłem. Zauważ, że nie mają one logiki do wykrycia, czy funkcja ma poprawną składnię, wyodrębniają tylko argumenty. Jest to na ogół w porządku, ponieważ zwykle przekazujemy przeanalizowane funkcje, getArguments()więc ich składnia jest już poprawna.

Spróbuję dobrać te rozwiązania najlepiej jak potrafię, ale bez wysiłku opiekunów JavaScript, pozostanie to otwarty problem.

Wersja Node.js (nie działa, dopóki StackOverflow nie obsługuje Node.js):

const parserName = 'babylon';
// const parserName = 'esprima';
const parser = require(parserName);

function getArguments(func) {
    const maybe = function (x) {
        return x || {}; // optionals support
    }

    try {
        const ast = parser.parse("(\n" + func.toString() + "\n)");
        const program = parserName == 'babylon' ? ast.program : ast;

        return program
            .body[0]
            .expression
            .params
            .map(function(node) {
                return node.name || maybe(node.left).name || '...' + maybe(node.argument).name;
            });
    } catch (e) {
        return []; // could also return null
    }
};

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});

console.log('');
console.log('////////// STRINGS //////////');

logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');

Pełny przykład działania:

https://repl.it/repls/SandybrownPhonyAngles

Wersja przeglądarki (pamiętaj, że zatrzymuje się na pierwszej złożonej wartości domyślnej):

function getArguments(func) {
    const ARROW = true;
    const FUNC_ARGS = ARROW ? /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m : /^(function)\s*[^\(]*\(\s*([^\)]*)\)/m;
    const FUNC_ARG_SPLIT = /,/;
    const FUNC_ARG = /^\s*(_?)(.+?)\1\s*$/;
    const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    return ((func || '').toString().replace(STRIP_COMMENTS, '').match(FUNC_ARGS) || ['', '', ''])[2]
        .split(FUNC_ARG_SPLIT)
        .map(function(arg) {
            return arg.replace(FUNC_ARG, function(all, underscore, name) {
                return name.split('=')[0].trim();
            });
        })
        .filter(String);
}

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});

console.log('');
console.log('////////// STRINGS //////////');

logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');

Pełny przykład działania:

https://repl.it/repls/StupendousShowyOffices

Zack Morris
źródło
jeśli się nie mylę, w wersji przeglądarki pierwszy warunek w FUNC_ARGS będzie działał zarówno dla funkcji strzałek, jak i tradycyjnych, więc nie potrzebujesz drugiej części i możesz pozbyć się zależności od ARROW.
pwilcox
To jest świetne! Szukałem takiego rozwiązania, które wykorzystuje analizator składni do pokrycia składni ES6. Planuję użyć tego do stworzenia dopasowywania „jest implementuje interfejs”, ponieważ po prostu użycie function.length ma ograniczenia dotyczące parametrów domyślnych i chciałem móc zapewnić parametry spoczynku.
cue8chalk
Warto zauważyć, że piąty przypadek testowy, który zawiera nawiasy w wartości domyślnej, obecnie zawodzi. Chciałbym, żeby mój regex-fu był wystarczająco silny, by to naprawić, przepraszam!
Dale Anderson
8

Przeczytałem większość odpowiedzi tutaj i chciałbym dodać moją linijkę.

new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)').exec(Function.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '')

lub

function getParameters(func) {
  return new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');
}

lub dla funkcji jednowierszowej w ECMA6

var getParameters = func => new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');

__

Powiedzmy, że masz funkcję

function foo(abc, def, ghi, jkl) {
  //code
}

Poniższy kod powróci "abc,def,ghi,jkl"

Ten kod będzie również działał z ustawieniem funkcji, którą dał Camilo Martin :

function  (  A,  b
,c      ,d
){}

Również z komentarzem Buberssona do odpowiedzi Jacka Allana :

function(a /* fooled you)*/,b){}

__

Wyjaśnienie

new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')

Tworzy to wyrażenie regularne z new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). Muszę użyć, new RegExpponieważ wstrzykuję zmienną ( Function.namenazwę funkcji docelowej) do RegExp.

Przykład Jeśli nazwa funkcji to „foo” ( function foo()), RegExp będzie /foo\s*\((.*?)\)/.

Function.toString().replace(/\n/g, '')

Następnie konwertuje całą funkcję na ciąg znaków i usuwa wszystkie znaki nowej linii. Usunięcie nowej linii pomaga w konfiguracji funkcji, którą podarował Camilo Martin .

.exec(...)[1]

To jest RegExp.prototype.execfunkcja. Zasadniczo dopasowuje regularny wykładnik ( new RegExp()) do String ( Function.toString()). Następnie [1]zwróci pierwszą grupę przechwytywania znalezioną w zwykłym wykładniku ( (.*?)).

.replace(/\/\*.*?\*\//g, '').replace(/ /g, '')

Spowoduje to usunięcie wszelkich komentarz wnętrze /*i */, i usunąć wszystkie spacje.


To także obsługuje teraz czytanie i rozumienie funkcji arrow ( =>), takich jak f = (a, b) => void 0;, w których Function.toString()zwracane byłyby (a, b) => void 0zamiast funkcji normalnych function f(a, b) { return void 0; }. Oryginalne wyrażenie regularne spowodowałoby błąd w jego pomieszaniu, ale teraz jest rozliczane.

Zmieniono z new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) na new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


Jeśli chcesz przekształcić wszystkie parametry w tablicę zamiast ciągu oddzielonego przecinkami, na końcu po prostu dodaj .split(',').

Jaketr00
źródło
całkiem miło. jedna linia, obsługuje funkcje strzałek, brak zależności zewnętrznych i obejmuje więcej niż wystarczającą liczbę przypadków krawędzi, przynajmniej dla mojego zamierzonego zastosowania. jeśli możesz przyjąć rozsądne założenia dotyczące podpisów funkcji, będziesz sobie z tym radzić, to wszystko, czego potrzebujesz. dzięki!
charlie roberts
Nie działa z tej prostej funkcji strzałką: f = (a, b) => void 0; na getParameters(f)otrzymujęTypeError: Cannot read property '1' of null
AT
@AT Właśnie zaktualizowałem odpowiedź, aby naprawić obsługę Twojego problemu
Jaketr00,
Dzięki… ale pamiętaj, że nawiasy nie są już potrzebne, więc możesz robić takie rzeczy getParameters(a => b => c => d => a*b*c*d), które dzięki Twojemu kodowi nadal to dają TypeError: Cannot read property '1' of null… podczas gdy ten działa stackoverflow.com/a/29123804
AT
Nie działa, gdy funkcja ma wartości domyślne (rola, nazwa = „bob”) Wyodrębniony parametr to name = „bob” zamiast oczekiwanego „name”
Dmitri
7
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

=> [„a”, „b”, „c”]

Będzie
źródło
7

Możesz także użyć parsera „esprima”, aby uniknąć wielu problemów z komentarzami, białymi znakami i innymi rzeczami na liście parametrów.

function getParameters(yourFunction) {
    var i,
        // safetyValve is necessary, because sole "function () {...}"
        // is not a valid syntax
        parsed = esprima.parse("safetyValve = " + yourFunction.toString()),
        params = parsed.body[0].expression.right.params,
        ret = [];

    for (i = 0; i < params.length; i += 1) {
        // Handle default params. Exe: function defaults(a = 0,b = 2,c = 3){}
        if (params[i].type == 'AssignmentPattern') {
            ret.push(params[i].left.name)
        } else {
            ret.push(params[i].name);
        }
    }

    return ret;
}

Działa nawet z takim kodem:

getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]
Mateusz Charytoniuk
źródło
6

Próbowałem to zrobić wcześniej, ale nigdy nie znalazłem praktycznego sposobu, aby to zrobić. Skończyło się na tym, że podałem przedmiot, a potem go zapętliłem.

//define like
function test(args) {
    for(var item in args) {
        alert(item);
        alert(args[item]);
    }
}

//then used like
test({
    name:"Joe",
    age:40,
    admin:bool
});
Hugoware
źródło
Mam wiele wcześniej zdefiniowanych funkcji, które są wywoływane za pomocą standardowych parametrów zamiast jednego obiektu. Zmiana wszystkiego zajęłaby dużo czasu.
vikasde
2
Ta odpowiedź nie jest odpowiedzią na pierwotnie zdefiniowane pytanie. Pokazuje rozwiązanie zupełnie innego problemu. Pierwotne pytanie odnosi się do techniki, którą AngularJS wykorzystuje do wstrzykiwania zależności. Nazwy argumentów są znaczące, ponieważ odpowiadają zależnościom modułu, które DI automatycznie zapewnia.
Lambder
4

Nie wiem, czy to rozwiązanie pasuje do twojego problemu, ale pozwala ci na nowo zdefiniować dowolną funkcję, bez konieczności zmiany kodu, który z niej korzysta. Istniejące wywołania wykorzystują parametry pozycjonowane, podczas gdy implementacja funkcji może używać „nazwanych parametrów” (pojedynczy parametr skrótu).

Pomyślałem, że i tak zmodyfikujesz istniejące definicje funkcji, więc dlaczego nie mieć funkcji fabrycznej, która robi dokładnie to, czego chcesz:

<!DOCTYPE html>

<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
var withNamedParams = function(params, lambda) {
    return function() {
        var named = {};
        var max   = arguments.length;

        for (var i=0; i<max; i++) {
            named[params[i]] = arguments[i];
        }

        return lambda(named);
    };
};

var foo = withNamedParams(["a", "b", "c"], function(params) {
    for (var param in params) {
        alert(param + ": " + params[param]);
    }
});

foo(1, 2, 3);
</script>
</head>
<body>

</body>
</html>

Mam nadzieję, że to pomoże.

Ionuț G. Stan
źródło
4

Właściwym sposobem na to jest użycie parsera JS. Oto przykład z użyciem żołędzi .

const acorn = require('acorn');    

function f(a, b, c) {
   // ...
}

const argNames = acorn.parse(f).body[0].params.map(x => x.name);
console.log(argNames);  // Output: [ 'a', 'b', 'c' ]

Tutaj kod znajduje nazwy trzech (formalnych) parametrów funkcji f. Czyni to poprzez karmienie fsię acorn.parse().

Itay Maman
źródło
co z wartościami?
eran otzap
2

Nie wiem, jak uzyskać listę parametrów, ale możesz to zrobić, aby uzyskać ich liczbę.

alert(doSomething.length);
Lalafur Waage
źródło
function gotcha (a, b = false, c) {}; alert(gotcha.length)
balupton,
2

Biorąc odpowiedź od @ jack-allan zmodyfikowałem nieco funkcję, aby umożliwić domyślne właściwości ES6, takie jak:

function( a, b = 1, c ){};

wciąż wracać [ 'a', 'b' ]

/**
 * Get the keys of the paramaters of a function.
 *
 * @param {function} method  Function to get parameter keys for
 * @return {array}
 */
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /(?:^|,)\s*([^\s,=]+)/g;
function getFunctionParameters ( func ) {
    var fnStr = func.toString().replace(STRIP_COMMENTS, '');
    var argsList = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));
    var result = argsList.match( ARGUMENT_NAMES );

    if(result === null) {
        return [];
    }
    else {
        var stripped = [];
        for ( var i = 0; i < result.length; i++  ) {
            stripped.push( result[i].replace(/[\s,]/g, '') );
        }
        return stripped;
    }
}
thelastshadow
źródło
1
Dzięki, twój jest jedynym w tym wątku, który dla mnie zadziałał.
AT
1

Jak zwykle to robię:

function name(arg1, arg2){
    var args = arguments; // array: [arg1, arg2]
    var objecArgOne = args[0].one;
}
name({one: "1", two: "2"}, "string");

Możesz nawet odwoływać argumenty według nazw funkcji takich jak:

name.arguments;

Mam nadzieję że to pomoże!

Cody
źródło
4
A gdzie są nazwy parametrów funkcji?
Andrej
Ach ... Masz na myśli, że chcesz to w formie skrótu? Jakby: var args = name.arguments; console.log('I WANNa SEE', args);wypisał coś w stylu „{arg1: {...}, arg2: 'string'}”? To może wyczyścić rzeczy: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Argumenty funkcji są obiektami podobnymi do „macierzy” i mają wyliczalne indeksy. Nie sądzę, aby mapowanie skrótów było możliwe, ale możesz po prostu przekazać literał obiektowy, w którym jest to dobra praktyka, jeśli i tak masz więcej niż 4 parametry.
Cody,
1
//See this:


// global var, naming bB
var bB = 5;

//  Dependency Injection cokntroller
var a = function(str, fn) {
  //stringify function body
  var fnStr = fn.toString();

  // Key: get form args to string
  var args = fnStr.match(/function\s*\((.*?)\)/);
  // 
  console.log(args);
  // if the form arg is 'bB', then exec it, otherwise, do nothing
  for (var i = 0; i < args.length; i++) {
    if(args[i] == 'bB') {
      fn(bB);
    }
  }
}
// will do nothing
a('sdfdfdfs,', function(some){
alert(some)
});
// will alert 5

a('sdfdsdsfdfsdfdsf,', function(bB){
alert(bB)
});

// see, this shows you how to get function args in string
Paul Lan
źródło
1

Odpowiedź na to wymaga 3 kroków:

  1. Aby uzyskać wartości rzeczywistych parametrów przekazywanych do funkcji (nazwijmy ją argValues). Jest to proste, ponieważ będzie dostępne jako argumentsfunkcja.
  2. Aby uzyskać nazwy parametrów z podpisu funkcji (nazwijmy to argNames). Nie jest to tak łatwe i wymaga analizy funkcji. Zamiast samodzielnie wykonywać złożone wyrażenia regularne i martwić się przypadkami krawędzi (parametry domyślne, komentarze, ...), możesz użyć biblioteki takiej jak Babilon, która przekształci funkcję w abstrakcyjne drzewo składni, z którego można uzyskać nazwy parametrów.
  3. Ostatnim krokiem jest połączenie 2 tablic razem w 1 tablicę, która ma nazwę i wartość wszystkich parametrów.

Kod będzie taki

const babylon = require("babylon")
function doSomething(a, b, c) {
    // get the values of passed argumenst
    const argValues = arguments

    // get the names of the arguments by parsing the function
    const ast = babylon.parse(doSomething.toString())
    const argNames =  ast.program.body[0].params.map(node => node.name)

    // join the 2 arrays, by looping over the longest of 2 arrays
    const maxLen = Math.max(argNames.length, argValues.length)
    const args = []
    for (i = 0; i < maxLen; i++) { 
       args.push({name: argNames[i], value: argValues[i]})
    }
    console.log(args)

    // implement the actual function here
}

doSomething(1, 2, 3, 4)

i zalogowany obiekt będzie

[
  {
    "name": "a",
    "value": 1
  },
  {
    "name": "c",
    "value": 3
  },
  {
    "value": 4
  }
]

A oto działający przykład https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a

gafi
źródło
1
function getArgs(args) {
    var argsObj = {};

    var argList = /\(([^)]*)/.exec(args.callee)[1];
    var argCnt = 0;
    var tokens;

    while (tokens = /\s*([^,]+)/g.exec(argList)) {
        argsObj[tokens[1]] = args[argCnt++];
    }

    return argsObj;
}
użytkownik3018868
źródło
1

Wow, już tyle odpowiedzi. Jestem pewien, że to zostanie pochowane. Mimo to pomyślałem, że może to być przydatne dla niektórych.

Nie byłem w pełni zadowolony z wybranych odpowiedzi, ponieważ w ES6 nie działa dobrze z wartościami domyślnymi. I nie zapewnia również informacji o wartości domyślnej. Chciałem też mieć lekką funkcję, która nie zależy od zewnętrznej biblioteki.

Ta funkcja jest bardzo przydatna do celów debugowania, na przykład: rejestrowanie wywoływanej funkcji za pomocą jej parametrów, domyślnych wartości parametrów i argumentów.

Wczoraj spędziłem trochę czasu na łamaniu odpowiedniego RegExp, aby rozwiązać ten problem i oto co wymyśliłem. Działa bardzo dobrze i jestem bardzo zadowolony z wyniku:

const REGEX_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const REGEX_FUNCTION_PARAMS = /(?:\s*(?:function\s*[^(]*)?\s*)((?:[^'"]|(?:(?:(['"])(?:(?:.*?[^\\]\2)|\2))))*?)\s*(?=(?:=>)|{)/m
const REGEX_PARAMETERS_VALUES = /\s*(\w+)\s*(?:=\s*((?:(?:(['"])(?:\3|(?:.*?[^\\]\3)))((\s*\+\s*)(?:(?:(['"])(?:\6|(?:.*?[^\\]\6)))|(?:[\w$]*)))*)|.*?))?\s*(?:,|$)/gm

/**
 * Retrieve a function's parameter names and default values
 * Notes:
 *  - parameters with default values will not show up in transpiler code (Babel) because the parameter is removed from the function.
 *  - does NOT support inline arrow functions as default values
 *      to clarify: ( name = "string", add = defaultAddFunction )   - is ok
 *                  ( name = "string", add = ( a )=> a + 1 )        - is NOT ok
 *  - does NOT support default string value that are appended with a non-standard ( word characters or $ ) variable name
 *      to clarify: ( name = "string" + b )         - is ok
 *                  ( name = "string" + $b )        - is ok
 *                  ( name = "string" + b + "!" )   - is ok
 *                  ( name = "string" + λ )         - is NOT ok
 * @param {function} func
 * @returns {Array} - An array of the given function's parameter [key, default value] pairs.
 */
function getParams(func) {

  let functionAsString = func.toString()
  let params = []
  let match
  functionAsString = functionAsString.replace(REGEX_COMMENTS, '')
  functionAsString = functionAsString.match(REGEX_FUNCTION_PARAMS)[1]
  if (functionAsString.charAt(0) === '(') functionAsString = functionAsString.slice(1, -1)
  while (match = REGEX_PARAMETERS_VALUES.exec(functionAsString)) params.push([match[1], match[2]])
  return params

}



// Lets run some tests!

var defaultName = 'some name'

function test1(param1, param2, param3) { return (param1) => param1 + param2 + param3 }
function test2(param1, param2 = 4 * (5 / 3), param3) {}
function test3(param1, param2 = "/root/" + defaultName + ".jpeg", param3) {}
function test4(param1, param2 = (a) => a + 1) {}

console.log(getParams(test1)) 
console.log(getParams(test2))
console.log(getParams(test3))
console.log(getParams(test4))

// [ [ 'param1', undefined ], [ 'param2', undefined ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '4 * (5 / 3)' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '"/root/" + defaultName + ".jpeg"' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '( a' ] ]
// --> This last one fails because of the inlined arrow function!


var arrowTest1 = (a = 1) => a + 4
var arrowTest2 = a => b => a + b
var arrowTest3 = (param1 = "/" + defaultName) => { return param1 + '...' }
var arrowTest4 = (param1 = "/" + defaultName, param2 = 4, param3 = null) => { () => param3 ? param3 : param2 }

console.log(getParams(arrowTest1))
console.log(getParams(arrowTest2))
console.log(getParams(arrowTest3))
console.log(getParams(arrowTest4))

// [ [ 'a', '1' ] ]
// [ [ 'a', undefined ] ]
// [ [ 'param1', '"/" + defaultName' ] ]
// [ [ 'param1', '"/" + defaultName' ], [ 'param2', '4' ], [ 'param3', 'null' ] ]


console.log(getParams((param1) => param1 + 1))
console.log(getParams((param1 = 'default') => { return param1 + '.jpeg' }))

// [ [ 'param1', undefined ] ]
// [ [ 'param1', '\'default\'' ] ]

Jak można powiedzieć, niektóre nazwy parametrów znikają, ponieważ transpilator Babel usuwa je z funkcji. Jeśli uruchomisz to w najnowszym NodeJS, działa on zgodnie z oczekiwaniami (skomentowane wyniki pochodzą z NodeJS).

Inna uwaga, jak stwierdzono w komentarzu, jest taka, że ​​nie działa ona z wbudowanymi funkcjami strzałek jako wartością domyślną. To po prostu sprawia, że ​​wyciąganie wartości za pomocą RegExp jest zbyt skomplikowane.

Daj mi znać, jeśli było to dla Ciebie przydatne! Bardzo chciałbym usłyszeć opinie!

SnailCrusher
źródło
1

Dam ci krótki przykład poniżej:

function test(arg1,arg2){
    var funcStr = test.toString()
    var leftIndex = funcStr.indexOf('(');
    var rightIndex = funcStr.indexOf(')');
    var paramStr = funcStr.substr(leftIndex+1,rightIndex-leftIndex-1);
    var params = paramStr.split(',');
    for(param of params){
        console.log(param);   // arg1,arg2
    }
}

test();
myzhou
źródło
Ten przykład jest po prostu, aby uzyskać nazwę parametrów dla Ciebie.
myzhou
Ta odpowiedź jest przydatna, ale nie odpowiada na pytanie. Głosowałem, ponieważ rozwiązało to mój problem, ale myślę, że należy go przenieść w bardziej odpowiednie miejsce. Być może szukasz powiązanych pytań?
chharvey
1

Ten pakiet używa przekształcenia w celu utworzenia AST, a następnie nazwy parametrów są zbierane z nich, co pozwala na obsługę dopasowania wzorca, domyślnych argumentów, funkcji strzałek i innych funkcji ES6.

https://www.npmjs.com/package/es-arguments

użytkownik3436035
źródło
1

Zmodyfikowałem wersję pobraną z AngularJS, która implementuje mechanizm wstrzykiwania zależności do pracy bez Angulara. Zaktualizowałem również STRIP_COMMENTSwyrażenie regularne do pracy ECMA6, więc obsługuje takie rzeczy, jak wartości domyślne w podpisie.

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

function annotate(fn) {
  var $inject,
    fnText,
    argDecl,
    last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
        arg.replace(FN_ARG, function(all, underscore, name) {
          $inject.push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else {
    throw Error("not a function")
  }
  return $inject;
}

console.log("function(a, b)",annotate(function(a, b) {
  console.log(a, b, c, d)
}))
console.log("function(a, b = 0, /*c,*/ d)",annotate(function(a, b = 0, /*c,*/ d) {
  console.log(a, b, c, d)
}))
annotate({})

loretoparisi
źródło
0

Dostęp do wartości argumentów przekazywanych do funkcji można uzyskać za pomocą właściwości „arguments”.

    function doSomething()
    {
        var args = doSomething.arguments;
        var numArgs = args.length;
        for(var i = 0 ; i < numArgs ; i++)
        {
            console.log("arg " + (i+1) + " = " + args[i]);  
                    //console.log works with firefox + firebug
                    // you can use an alert to check in other browsers
        }
    }

    doSomething(1, '2', {A:2}, [1,2,3]);    
letronje
źródło
Czy argumenty nie są przestarzałe? Zobacz sugestię Ionut G. Stan powyżej.
Vikasde
Vikasde ma rację. Dostęp do argumentswłaściwości instancji funkcji jest przestarzały. Zobacz developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
Ionuț G. Stan
0

To całkiem proste.

Na początku jest przestarzałe arguments.callee- odniesienie do wywoływanej funkcji. Po drugie, jeśli masz odniesienie do swojej funkcji, możesz łatwo uzyskać ich reprezentację tekstową. Po trzecie, jeśli wywołujesz swoją funkcję jako konstruktor, możesz również mieć link poprzez yourObject.constructor. Uwaga: pierwsze rozwiązanie jest przestarzałe, więc jeśli nie możesz go nie używać, musisz także pomyśleć o architekturze aplikacji. Jeśli nie potrzebujesz dokładnych nazw zmiennych, po prostu użyj wewnątrz zmiennej wewnętrznej funkcji argumentsbez żadnej magii.

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee

Wszyscy będą dzwonić doString i zamieniać na re, abyśmy mogli stworzyć pomocnika:

// getting names of declared parameters
var getFunctionParams = function (func) {
    return String(func).replace(/[^\(]+\(([^\)]*)\).*/m, '$1');
}

Kilka przykładów:

// Solution 1. deprecated! don't use it!
var myPrivateFunction = function SomeFuncName (foo, bar, buz) {
    console.log(getFunctionParams(arguments.callee));
};
myPrivateFunction (1, 2);

// Solution 2.
var myFunction = function someFunc (foo, bar, buz) {
    // some code
};
var params = getFunctionParams(myFunction);
console.log(params);

// Solution 3.
var cls = function SuperKewlClass (foo, bar, buz) {
    // some code
};
var inst = new cls();
var params = getFunctionParams(inst.constructor);
console.log(params);

Ciesz się z JS!

UPD: Jack Allan otrzymał trochę lepsze rozwiązanie. GJ Jack!

Alex Yaroshevich
źródło
Może to być jeszcze prostsze, jeśli użyjesz go SomeFuncNamezamiast arguments.callee(oba wskazują na sam obiekt funkcji).
Raphael Schweikert
0

Niezależnie od rozwiązania, nie może działać na dziwne funkcje, których toString()wygląd jest równie dziwny:

function  (  A,  b
,c      ,d
){}

zrzut ekranu z konsoli

Ponadto, po co używać złożonych wyrażeń regularnych? Można to zrobić w następujący sposób:

function getArguments(f) {
    return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}

Działa to wszędzie z każdą funkcją, a jedynym wyrażeniem regularnym jest usuwanie białych znaków, które nawet nie przetwarzają całego łańcucha ze względu na .splitpodstęp.

Camilo Martin
źródło
0

Ok, więc stare pytanie z dużą ilością odpowiednich odpowiedzi. oto moja oferta, która nie używa wyrażenia regularnego, z wyjątkiem zwykłego zadania usuwania białych znaków. (Powinienem zauważyć, że funkcja „strips_comments” faktycznie rozdziela je, zamiast fizycznie je usuwać. To dlatego, że używam go gdzie indziej i z różnych powodów potrzebuję położenia oryginalnych tokenów bez komentarza, aby pozostały nienaruszone)

Jest to dość długi blok kodu, ponieważ wklejenie zawiera szkielet mini testu.

    function do_tests(func) {

    if (typeof func !== 'function') return true;
    switch (typeof func.tests) {
        case 'undefined' : return true;
        case 'object'    : 
            for (var k in func.tests) {

                var test = func.tests[k];
                if (typeof test==='function') {
                    var result = test(func);
                    if (result===false) {
                        console.log(test.name,'for',func.name,'failed');
                        return false;
                    }
                }

            }
            return true;
        case 'function'  : 
            return func.tests(func);
    }
    return true;
} 
function strip_comments(src) {

    var spaces=(s)=>{
        switch (s) {
            case 0 : return '';
            case 1 : return ' ';
            case 2 : return '  ';
        default : 
            return Array(s+1).join(' ');
        }
    };

    var c1 = src.indexOf ('/*'),
        c2 = src.indexOf ('//'),
        eol;

    var out = "";

    var killc2 = () => {
                out += src.substr(0,c2);
                eol =  src.indexOf('\n',c2);
                if (eol>=0) {
                    src = spaces(eol-c2)+'\n'+src.substr(eol+1);
                } else {
                    src = spaces(src.length-c2);
                    return true;
                }

             return false;
         };

    while ((c1>=0) || (c2>=0)) {
         if (c1>=0) {
             // c1 is a hit
             if ( (c1<c2) || (c2<0) )  {
                 // and it beats c2
                 out += src.substr(0,c1);
                 eol = src.indexOf('*/',c1+2);
                 if (eol>=0) {
                      src = spaces((eol-c1)+2)+src.substr(eol+2);
                 } else {
                      src = spaces(src.length-c1);
                      break;
                 }
             } else {

                 if (c2 >=0) {
                     // c2 is a hit and it beats c1
                     if (killc2()) break;
                 }
             }
         } else {
             if (c2>=0) {
                // c2 is a hit, c1 is a miss.
                if (killc2()) break;  
             } else {
                 // both c1 & c2 are a miss
                 break;
             }
         }

         c1 = src.indexOf ('/*');
         c2 = src.indexOf ('//');   
        }

    return out + src;
}

function function_args(fn) {
    var src = strip_comments(fn.toString());
    var names=src.split(')')[0].replace(/\s/g,'').split('(')[1].split(',');
    return names;
}

function_args.tests = [

     function test1 () {

            function/*al programmers will sometimes*/strip_comments_tester/* because some comments are annoying*/(
            /*see this---(((*/ src//)) it's an annoying comment does not help anyone understand if the 
            ,code,//really does
            /**/sucks ,much /*?*/)/*who would put "comment\" about a function like (this) { comment } here?*/{

            }


        var data = function_args(strip_comments_tester);

        return ( (data.length==4) &&
                 (data[0]=='src') &&
                 (data[1]=='code') &&
                 (data[2]=='sucks') &&
                 (data[3]=='much')  );

    }

];
do_tests(function_args);
niezsynchronizowany
źródło
0

Oto jeden ze sposobów:

// Utility function to extract arg name-value pairs
function getArgs(args) {
    var argsObj = {};

    var argList = /\(([^)]*)/.exec(args.callee)[1];
    var argCnt = 0;
    var tokens;
    var argRe = /\s*([^,]+)/g;

    while (tokens = argRe.exec(argList)) {
        argsObj[tokens[1]] = args[argCnt++];
    }

    return argsObj;
}

// Test subject
function add(number1, number2) {
    var args = getArgs(arguments);
    console.log(args); // ({ number1: 3, number2: 4 })
}

// Invoke test subject
add(3, 4);

Uwaga: Działa to tylko w przeglądarkach, które obsługują arguments.callee.

Ates Goral
źródło
2
Pętla while powoduje powstanie nieskończonej pętli z podanym kodem (stan na 20171121at1047EDT)
George 2.0 Hope
@ George2.0 Mam nadzieję, że dziękuję za zwrócenie na to uwagi. Zaktualizuję odpowiedź.
Ates Goral
args.toSource nie jest funkcją (wiersz 20) ... ale nawet jeśli zmienisz ją na: console.log (args.toString ()); ... dostajesz ... [obiekt Object] ... lepiej, jeśli to zrobisz: console.log (JSON.stringify (args));
George 2.0 Hope
Od 2009 roku wszystko się zmieniło!
Ates Goral
1
na wypadek, gdyby ktoś pojawił się tutaj dla React, ta niesamowita funkcja nie będzie działać w trybie ścisłym.
Indywidualny 11
0

Uwaga: jeśli chcesz użyć restrukturyzacji parametrów ES6 z najlepszym rozwiązaniem, dodaj następujący wiersz.

if (result[0] === '{' && result[result.length - 1 === '}']) result = result.slice(1, -1)
IanLancaster
źródło
-1

funkcja parametr łańcuch wartości obrazu dynamicznie z JSON . Ponieważ item.product_image2 jest ciągiem adresu URL, musisz umieścić go w cudzysłowach, gdy wywołasz parametr changeImage wewnątrz.

Moja funkcja Onclick

items+='<img src='+item.product_image1+' id="saleDetailDivGetImg">';
items+="<img src="+item.product_image2+"  onclick='changeImage(\""+item.product_image2+"\");'>";

Moja funkcja

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
KAUSHAL J. SATHWARA
źródło