[javascript] 함수 매개 변수 이름 / 값을 동적으로 얻는 방법?

함수의 함수 매개 변수 이름을 동적으로 얻는 방법이 있습니까?

내 기능이 다음과 같다고 가정 해 봅시다.

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

이제 함수 내부에서 매개 변수 이름과 값 목록을 배열로 가져 오는 방법은 무엇입니까?



답변

다음 함수는 전달 된 함수의 매개 변수 이름 배열을 반환합니다.

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;
}

사용법 예 :

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 []

편집 :

ES6의 발명으로이 기능은 기본 매개 변수에 의해 넘어 질 수 있습니다. 다음은 대부분의 경우 작동해야하는 빠른 해킹입니다.

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

나는 그것을 트립 할 것들이 있기 때문에 대부분의 경우를 말한다

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

편집 : 또한 vikasde는 배열의 매개 변수 값도 원한다는 점에 유의하십시오. 이것은 이미 arguments라는 로컬 변수로 제공됩니다.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments 에서 발췌 :

arguments 객체는 Array가 아닙니다. Array와 비슷하지만 length를 제외한 Array 속성이 없습니다. 예를 들어, pop 메소드가 없습니다. 그러나 실제 배열로 변환 할 수 있습니다.

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

배열 제네릭을 사용할 수있는 경우 다음을 대신 사용할 수 있습니다.

var args = Array.slice(arguments);


답변

다음은 의존성 주입 메커니즘에 대한 기술을 사용하는 AngularJS에서 가져온 코드입니다.

다음은 http://docs.angularjs.org/tutorial/step_05 에서 가져온 설명입니다.

Angular의 의존성 인젝터는 컨트롤러가 구성 될 때 컨트롤러에 서비스를 제공합니다. 의존성 인젝터는 또한 서비스가 가질 수있는 전이 의존성을 생성하는 것을 관리합니다 (서비스는 종종 다른 서비스에 의존합니다).

인젝터는이를 사용하여 종속성을 조회하므로 인수 이름이 중요합니다.

/**
 * @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;
}


답변

다음은 위에서 언급 한 모든 주요 사례를 간결하게 해결하려는 업데이트 된 솔루션입니다.

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 [""]
}  

약식 테스트 결과 (전체 테스트 사례는 아래에 첨부 됨) :

'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 []

코드 스 니펫 표시


답변

공백과 주석이 발생하기 쉬운 오류가 적은 솔루션은 다음과 같습니다.

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

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

["hi", "you"]


답변

여기에 대한 많은 대답은 정규 표현식을 사용합니다. 이것은 훌륭하지만 화살표 기능 및 클래스와 같이 언어에 대한 새로운 추가 기능을 너무 잘 처리하지 못합니다. 또한 축소 된 코드에서 이러한 함수를 사용하면 ?으로 이동합니다. 축소 된 이름이 무엇이든 사용합니다. Angular는 DI 컨테이너에 등록 할 때 인수의 순서와 일치하는 정렬 된 문자열 배열을 전달할 수 있도록하여이 문제를 해결합니다. 솔루션과 마찬가지로 :

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');
};

원래 구문 분석 문제와 몇 가지 추가 기능 유형 (예 : 화살표 기능)을 처리합니다. 다음과 같이 처리 할 수있는 것과 처리 할 수없는 것에 대한 아이디어가 있습니다.

// 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);
    });
});

ES6 프록시에 사용하려는 것에 따라 파괴하는 것이 가장 좋습니다. 예를 들어, 매개 변수 이름을 사용하여 종속성 주입에 사용하려는 경우 다음과 같이 할 수 있습니다.

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);
        }
    }
}

그것은 가장 진보 된 리졸버는 아니지만 간단한 DI에 args 파서를 사용하려는 경우 프록시를 사용하여 프록시를 처리하는 방법에 대한 아이디어를 제공합니다. 그러나이 방법에는 약간의 경고가 있습니다. 일반적인 매개 변수 대신 파괴적인 할당을 사용해야합니다. 인젝터 프록시를 전달할 때, 파괴는 객체에서 getter를 호출하는 것과 같습니다.

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));

이것은 다음을 출력합니다 :

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

전체 응용 프로그램을 연결했습니다. 가장 좋은 점은 응용 프로그램을 테스트하기 쉽다는 것입니다 (각 클래스를 인스턴스화하고 mocks / stubs 등을 전달할 수 있습니다). 또한 구현을 교체해야하는 경우 한 곳에서 구현할 수 있습니다. JS 프록시 객체로 인해이 모든 것이 가능합니다.

참고 : 프로덕션 용도로 사용하기 전에이 작업을 수행해야하는 작업이 많이 있지만 모양에 대한 아이디어를 제공합니다.

답이 늦었지만 같은 것을 생각하는 다른 사람들에게 도움이 될 수 있습니다. ?


답변

나는 이것이 오래된 질문이라는 것을 알고 있지만 초보자는 이것이 어떤 코드에서나 좋은 습관 인 것처럼 이것을 복사하여 붙여 넣었습니다. 대부분의 경우 매개 변수 이름을 사용하기 위해 함수의 문자열 표현을 구문 분석해야 코드 논리의 결함이 숨겨집니다.

함수의 매개 변수는 실제로라는 배열 형 객체에 저장됩니다. arguments여기서 첫 번째 인수는 arguments[0]이고 두 번째 인수는 arguments[1]입니다. 괄호 안에 매개 변수 이름을 쓰는 것은 간단한 구문으로 볼 수 있습니다. 이:

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

…와 같다:

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

    console.log("does something");
}

변수 자체는 객체의 속성이 아니라 함수의 범위에 저장됩니다. 사람 언어에서 변수를 나타내는 기호 일 뿐이므로 코드를 통해 매개 변수 이름을 검색 할 방법이 없습니다.

나는 항상이 arguments배열과 같은 객체 때문에 디버깅 목적을위한 도구로 함수의 문자열 표현을 고려했습니다 . 처음에는 인수 이름을 지정할 필요가 없습니다. 문자열 화 된 함수를 구문 분석하려고하면 실제로 이름이 지정되지 않은 추가 매개 변수에 대해 알려주지 않습니다.

더 악화되고 더 일반적인 상황이 있습니다. 함수에 인수가 3 개 또는 4 개를 초과하는 경우 객체를 대신 전달하는 것이 논리적 일 수 있으므로 작업하기가 더 쉽습니다.

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

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

이 경우 함수 자체는 수신 한 객체를 통해 읽고 속성을 찾고 이름과 값을 모두 얻을 수 있지만 함수의 문자열 표현을 구문 분석하려고하면 매개 변수에 “obj”만 표시됩니다. 전혀 유용하지 않습니다.


답변

JavaScript는 스크립팅 언어이기 때문에, 그 내성 (introspection)이 함수 매개 변수 이름을 얻는 것을 지원해야한다고 생각합니다. 이 기능을 사용하는 것은 첫 번째 원칙을 위반하는 것이기 때문에 문제를 더 조사하기로 결정했습니다.

그로 인해이 질문으로 이어 졌지만 기본 제공 솔루션은 없습니다. 저를지도 한 이 답 이라고 설명 arguments에만 사용되지 않습니다 외부 그래서 우리는 더 이상 사용할 수있는 함수 myFunction.arguments또는 우리가 얻을 :

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

소매를 감아 일할 시간 :

⭐ 함수 매개 변수를 검색하려면 구문 분석기와 같은 복잡한 표현식 4*(5/3)을 기본값으로 사용할 수 있으므로 구문 분석기가 필요 합니다. 그래서 Gaafar의 대답 이나 제임스 드류의 답변 이 지금까지 가장 좋은 방법입니다.

나는 바빌론에스프리 마를 시도 파서를 했지만 불행히도 Mateusz Charytoniuk의 대답 에서 지적했듯이 독립형 익명 함수를 구문 분석 할 수 없습니다 . 논리를 변경하지 않도록 코드를 괄호로 묶어 다른 해결 방법을 찾았습니다.

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

//줄 바꿈은 (한 줄 주석) 문제를 방지합니다 .

⭐ 파서를 사용할 수 없다면, 차선책은 Angular.js의 의존성 인젝터 정규 표현식과 같은 검증 된 기술을 사용하는 것입니다. 나는의 기능 버전 결합 Lambder의 답변을 함께 humbletim의 대답 하고 옵션 추가 ARROWES6 지방 화살표 기능은 정규 표현식으로 사용할 수 있는지 여부를 제어하는 부울.


다음은 두 가지 솔루션입니다. 이것에는 함수에 유효한 구문이 있는지 여부를 감지하는 논리가 없으며 인수 만 추출합니다. 일반적으로 파싱 된 함수를 전달하기 때문에 일반적으로 괜찮습니다.getArguments() 하여 구문이 이미 유효 때문에 .

최선을 다해 이러한 솔루션을 선별하려고 노력하지만, JavaScript 관리자의 노력 없이는 공개적인 문제로 남아있을 것입니다.

Node.js 버전 (StackOverflow가 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() {}');

전체 작업 예 :

https://repl.it/repls/SandybrownPhonyAngles

브라우저 버전 (첫 번째 복잡한 기본값에서 중지됨) :

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() {}');

전체 작업 예 :

https://repl.it/repls/StupendousShowyOffices