วิธีรับชื่อฟังก์ชัน / ค่าพารามิเตอร์แบบไดนามิก?


300

มีวิธีรับชื่อพารามิเตอร์ฟังก์ชั่นของฟังก์ชั่นแบบไดนามิกหรือไม่?

สมมติว่าฟังก์ชั่นของฉันเป็นแบบนี้

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

ทีนี้ฉันจะดูรายการชื่อพารามิเตอร์และค่าของมันลงในอาร์เรย์จากภายในฟังก์ชันได้อย่างไร


ขอบคุณทุกคน หลังจากค้นหาไปรอบ ๆ ฉันพบวิธีแก้ปัญหาบน SO: stackoverflow.com/questions/914968/ ......มันใช้ regex เพื่อรับชื่อพารามิเตอร์ มันอาจไม่ใช่วิธีที่ดีที่สุด แต่ใช้ได้สำหรับฉัน
vikasde

8
ให้ไปข้างหน้าและทำเครื่องหมายว่าเป็นเพื่อนที่ได้รับคำตอบ ไม่มีคำตอบใหม่มา
Matthew Graves

function doSomething(...args) { /*use args*/}
caub

คำตอบ:


322

ฟังก์ชั่นต่อไปนี้จะส่งกลับอาร์เรย์ของชื่อพารามิเตอร์ของฟังก์ชั่นใด ๆ ที่ส่งผ่านมา

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 ต้องการค่าพารามิเตอร์ในอาร์เรย์ด้วย สิ่งนี้มีให้แล้วในตัวแปรโลคัลที่ชื่ออาร์กิวเมนต์

ตัดตอนมาจากhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments :

วัตถุอาร์กิวเมนต์ไม่ใช่ Array มันคล้ายกับ Array แต่ไม่มีคุณสมบัติ Array ใด ๆ ยกเว้นความยาว ตัวอย่างเช่นไม่มีวิธีการแบบป๊อป อย่างไรก็ตามมันสามารถแปลงเป็น Array จริง:

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

หากมีอาเรย์ทั่วไปอยู่หนึ่งสามารถใช้ต่อไปนี้แทน:

var args = Array.slice(arguments);

12
โปรดทราบว่าการแก้ปัญหานี้อาจล้มเหลวเนื่องจากความคิดเห็นและช่องว่าง - ตัวอย่างเช่น: var fn = function(a /* fooled you)*/,b){};จะส่งผลให้ ["a", "/*", "fooled", "you"]
bubersson

1
ฉันแก้ไขฟังก์ชั่นเพื่อคืนค่าอาเรย์ที่ว่างเปล่า (แทนที่จะเป็นโมฆะ) เมื่อไม่มีข้อโต้แย้งใด ๆ
BT

2
มีค่าใช้จ่ายในการรวบรวม regex ดังนั้นคุณต้องการหลีกเลี่ยงการรวบรวม regex ที่ซับซ้อนมากกว่าหนึ่งครั้ง นี่คือเหตุผลที่มันทำนอกฟังก์ชั่น
แจ็คอัลลัน

2
การแก้ไข: กำลังจะแก้ไข regex ด้วย / s แก้ไขที่ perl อนุญาตดังนั้น '.' ยังสามารถจับคู่ขึ้นบรรทัดใหม่ นี่เป็นสิ่งจำเป็นสำหรับความคิดเห็นหลายบรรทัดภายใน / * * / ปรากฎว่า Javascript regex ไม่อนุญาตให้ / s แก้ไข regex ดั้งเดิมที่ใช้ [/ s / S] ไม่ตรงกับอักขระขึ้นบรรทัดใหม่ SOOO โปรดละเว้นความคิดเห็นก่อนหน้า
tgoneil

1
@ และหมายเหตุคุณรวมถึงการรวบรวม regex ในการทดสอบของคุณ สิ่งเหล่านี้ควรทำเพียงครั้งเดียว ผลลัพธ์ของคุณอาจแตกต่างกันถ้าคุณย้ายการคอมไพล์ regex ไปยังขั้นตอนการตั้งค่าแทนการทดสอบ
Jack Allan

123

ด้านล่างเป็นรหัสที่นำมาจาก AngularJS ซึ่งใช้เทคนิคสำหรับกลไกการฉีดพึ่งพา

และนี่คือคำอธิบายที่นำมาจากhttp://docs.angularjs.org/tutorial/step_05

หัวฉีดพึ่งพาของแองกูลาร์ให้บริการแก่คอนโทรลเลอร์ของคุณเมื่อมีการสร้างคอนโทรลเลอร์ หัวฉีดพึ่งพายังดูแลการสร้างการพึ่งพาสกรรมกริยาใด ๆ ที่บริการอาจมี (บริการมักจะขึ้นอยู่กับบริการอื่น ๆ )

โปรดทราบว่าชื่อของอาร์กิวเมนต์มีความสำคัญเนื่องจากหัวฉีดใช้เพื่อค้นหาการอ้างอิง

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

40
@apaidnerd ด้วยเลือดของปีศาจและวางไข่ของซาตาน Regex ?! จะเจ๋งถ้ามีการสร้างใน JS ไม่ได้
Aditya MP

6
@apaidnerd จริงจริง ๆ ! แค่คิด - วิธีในนรกที่นำมาใช้อย่างไร อันที่จริงฉันคิดเกี่ยวกับการใช้ functionName.toString () แต่ฉันหวังว่าจะได้สิ่งที่หรูหรา (และอาจจะเร็วกว่า)
sasha.sochka

2
@ sasha.sochka มาที่นี่ด้วยความสงสัยเดียวกันหลังจากรู้ตัวว่าไม่มีการสร้างชื่อพารามิเตอร์ด้วย javascript
Hart Simha

14
เพื่อประหยัดเวลาผู้คนคุณสามารถรับฟังก์ชั่นนี้จากเชิงมุมผ่าน annotate = angular.injector.$$annotate
Nick

3
ฉันค้นหาหัวข้อนี้ในอินเทอร์เน็ตอย่างแท้จริงเพราะฉันอยากรู้ว่า Angular ทำมันอย่างไร ... ตอนนี้ฉันรู้แล้วและฉันก็รู้มากเกินไป!
สตีเวนฮันท์

40

นี่คือโซลูชันที่ได้รับการอัปเดตซึ่งพยายามที่จะจัดการปัญหาขอบทั้งหมดที่กล่าวถึงข้างต้นด้วยวิธีกะทัดรัด:

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


สิ่งนี้จะหยุดเมื่อมีการแสดงความคิดเห็นแบบหนึ่งบรรทัด ลองสิ่งนี้: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham

4
คุณควรแทนที่func + ''ด้วยFunction.toString.call(func)เพื่อป้องกันกรณีและปัญหาเมื่อฟังก์ชันมีการใช้. toString () ที่กำหนดเอง
Paul Go

1
ลูกศรอ้วน =>.split(/\)[\{=]/, 1)[0]
Matt

1
สิ่งนี้จะแยกวัตถุที่ถูกทำลาย (เช่น({ a, b, c })) ออกเป็นพารามิเตอร์ทั้งหมดภายในการทำลาย เพื่อให้วัตถุที่ถูกทำลายยังคงสภาพเดิมเปลี่ยนเป็น.split: .split(/,(?![^{]*})/g)
Michael Auderer

1
สิ่งนี้จะไม่ทำงานเมื่อมีค่าสตริงเริ่มต้นที่มี "//" หรือ "/ *"
skerit

23

โซลูชันที่มีข้อผิดพลาดน้อยมีแนวโน้มที่จะมีช่องว่างและความคิดเห็นจะเป็น:

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

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

["hi", "you"]

1
@AlexMills สิ่งหนึ่งที่ฉันสังเกตเห็นได้คือฟังก์ชั่น Spec สำหรับลูกศรบอกว่าพวกเขาไม่ควรถูกมองว่าเป็น 'ฟังก์ชั่น' ความหมายมันจะไม่เหมาะสมสำหรับสิ่งนี้เพื่อให้ตรงกับฟังก์ชั่นอาร์เรย์ 'นี้' ไม่ได้ตั้งค่าแบบเดียวกันและพวกเขาไม่ควรเรียกใช้เป็นฟังก์ชั่นอย่างใดอย่างหนึ่ง มันเป็นสิ่งที่ฉันเรียนรู้อย่างหนัก ($ myService) => $ myService.doSomething () ดูดี แต่มันเป็นการใช้ฟังก์ชัน Array ในทางที่ผิด
Andrew T Finnell

20

คำตอบมากมายในที่นี้ใช้ regexes นี่ใช้ได้ แต่มันไม่สามารถจัดการกับสิ่งใหม่ ๆ ในภาษาได้เป็นอย่างดี (เช่นฟังก์ชั่นลูกศรและคลาส) สิ่งที่ควรทราบอีกอย่างคือถ้าคุณใช้ฟังก์ชั่นใด ๆ เหล่านี้กับรหัสย่อส่วนมันจะไป go มันจะใช้ชื่อที่ย่อเล็กสุด Angular ได้รับสิ่งนี้โดยอนุญาตให้คุณส่งผ่านสตริงที่เรียงลำดับซึ่งตรงกับลำดับของอาร์กิวเมนต์เมื่อทำการลงทะเบียนกับ DI container ดังนั้นด้วยวิธีการแก้ปัญหา:

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 Proxies และการทำลายล้างอาจเป็นทางออกที่ดีที่สุดของคุณ ตัวอย่างเช่นหากคุณต้องการใช้สำหรับการฉีดพึ่งพา (ใช้ชื่อของ params) จากนั้นคุณสามารถทำได้ดังนี้:

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

มันไม่ใช่ตัวจำแนกขั้นสูงที่สุด แต่ให้แนวคิดว่าคุณสามารถใช้ Proxy เพื่อจัดการได้อย่างไรถ้าคุณต้องการใช้ args parser สำหรับ DI แบบง่าย อย่างไรก็ตามมีข้อแม้เล็กน้อยหนึ่งข้อในแนวทางนี้ เราจำเป็นต้องใช้การมอบหมายการทำลายล้างแทนที่จะเป็นพารามิเตอร์ปกติ เมื่อเราส่งผ่านพร็อกซีหัวฉีดการทำลายล้างจะเหมือนกับการเรียกตัวทะเยอทะยานบนวัตถุ

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 / etc) นอกจากนี้หากคุณต้องการสลับการใช้งานคุณสามารถทำได้จากที่เดียว ทั้งหมดนี้เป็นไปได้เพราะวัตถุ JS Proxy

หมายเหตุ: มีงานจำนวนมากที่ต้องทำก่อนที่มันจะพร้อมสำหรับการใช้งานจริง แต่ให้แนวคิดว่ามันจะเป็นอย่างไร

มันค่อนข้างช้าในการตอบ แต่มันอาจช่วยคนอื่นที่กำลังคิดถึงสิ่งเดียวกัน 👍


13

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ผู้เริ่มต้นได้คัดลอกสิ่งนี้ไปรอบ ๆ ราวกับว่านี่เป็นวิธีปฏิบัติที่ดีในรหัสใด ๆ ส่วนใหญ่แล้วการวิเคราะห์การแทนค่าสตริงของฟังก์ชั่นเพื่อใช้ชื่อพารามิเตอร์นั้นจะเป็นการซ่อนข้อบกพร่องในตรรกะของโค้ด

พารามิเตอร์ของฟังก์ชั่นถูกจัดเก็บจริงในวัตถุที่มีลักษณะคล้ายอาร์เรย์ที่เรียกว่า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" แก่คุณเท่านั้น ซึ่งไม่เป็นประโยชน์เลย


1
ฉันคิดว่ากรณีเช่นนี้มักจะ: การดีบัก / การบันทึก, มัณฑนากรบางประเภทที่ทำสิ่งที่ขี้ขลาด (ศัพท์ทางเทคนิค😁) หรือสร้างกรอบการฉีดพึ่งพาสำหรับแอพของคุณที่จะฉีดโดยอัตโนมัติตามชื่ออาร์กิวเมนต์ ผลงาน) กรณีการใช้งานที่น่าสนใจอีกอย่างหนึ่งอยู่ใน promisify-node (เป็นไลบรารีที่ใช้ฟังก์ชั่นที่ปกติจะใช้การเรียกกลับแล้วแปลงเป็น Promise) พวกเขาใช้สิ่งนี้เพื่อค้นหาชื่อสามัญสำหรับการเรียกกลับ (เช่น cb / callback / etc) จากนั้นพวกเขาสามารถตรวจสอบว่าฟังก์ชั่นนั้นเป็นแบบซิงค์หรือซิงค์ก่อนตัดคำ
James Drew

ดูแฟ้มนี้เพื่อแยกวิเคราะห์ของพวกเขา มันไร้เดียงสานิดหน่อย แต่ก็จัดการกับคดีส่วนใหญ่ได้
James Drew

น่าสนใจฉันแปลกใจที่ห้องสมุดได้รับความสนใจ มีปัญหาแบบเปิดหลายเรื่องเกี่ยวกับสถานการณ์ที่มีปัญหาอย่างที่ฉันอธิบายไว้ อย่างที่ฉันบอกไปว่าถ้ามันมีจุดประสงค์เพื่อการดีบั๊กมันก็ดี แต่การพึ่งพาการแปลงสตริงของฟังก์ชั่นในสภาพแวดล้อมการผลิตนั้นมีความเสี่ยงเกินไป
โดมิโน

ใน sidenote "ตลก" คุณสามารถทำให้ฟังก์ชั่นทั้งหมดทำหน้าที่เหมือนกับว่ามันเป็นบิวด์อินแบบไม่ระบุตัวตนโดยการรันสิ่งนี้:Function.prototype.toString = function () { return 'function () { [native code] }'; };
โดมิโน

1
ตกลงมันยุ่งเล็กน้อยที่จะจัดการกับเรื่องนี้ การใช้ที่ดีที่สุดและตรงไปตรงมาที่สุดคือการฉีดขึ้นอยู่กับ ในกรณีนั้นคุณเป็นเจ้าของรหัสและสามารถจัดการการตั้งชื่อและส่วนอื่น ๆ ของรหัส ฉันคิดว่านี่คือที่ที่มันจะได้เห็นการใช้งานมากที่สุด ฉันกำลังใช้esprima (แทนที่จะเป็น regex) และ ES6 Proxy( กับดักคอนสตรัคเตอร์และใช้กับดัก ) และReflectionจัดการ DI สำหรับโมดูลบางส่วนของฉัน มันแข็งพอสมควร
James Drew

10

เนื่องจาก JavaScript เป็นภาษาสคริปต์ฉันรู้สึกว่าวิปัสสนาควรสนับสนุนชื่อพารามิเตอร์ของฟังก์ชัน การใช้ฟังก์ชั่นนั้นเป็นการละเมิดหลักการข้อแรกดังนั้นฉันจึงตัดสินใจสำรวจปัญหาต่อไป

นั่นทำให้ฉันเกิดคำถามนี้แต่ไม่มีวิธีแก้ปัญหาในตัว ซึ่งนำฉันไปสู่คำตอบนี้ซึ่งอธิบายว่า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

ได้เวลาพับแขนเสื้อของเราแล้วไปทำงาน:

parameters การดึงพารามิเตอร์ฟังก์ชั่นต้องใช้ตัวแยกวิเคราะห์เนื่องจากการแสดงออกที่ซับซ้อนเช่น4*(5/3)สามารถใช้เป็นค่าเริ่มต้น ดังนั้นคำตอบของ GaafarหรือคำตอบของJames Drewจึงเป็นวิธีการที่ดีที่สุด

ฉันพยายามบาบิโลนและesprima parsers แต่โชคร้ายที่พวกเขาไม่สามารถฟังก์ชั่นที่ไม่ระบุชื่อแบบสแตนด์อโลนแยกเป็นแหลมออกมาในคำตอบของ Mateusz Charytoniuk ฉันคิดวิธีแก้ปัญหาอื่นโดยรอบรหัสในวงเล็บเพื่อไม่ให้เปลี่ยนตรรกะ:

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

การขึ้นบรรทัดใหม่ป้องกันปัญหาด้วย//(ความคิดเห็นบรรทัดเดียว)

⭐หากตัวแยกวิเคราะห์ไม่พร้อมใช้งานตัวเลือกที่ดีที่สุดถัดไปคือการใช้เทคนิคที่พยายามแล้วจริงเช่น Angular.js การแสดงผลปกติของหัวฉีดพึ่งพา ฉันรวมคำตอบของรุ่นLambder เข้ากับคำตอบของฮัมเบิลทิมและเพิ่มARROWบูลีนเสริมเพื่อควบคุมว่าจะอนุญาตให้ใช้ฟังก์ชั่นลูกศรอ้วน ES6 จากการแสดงออกปกติหรือไม่


นี่คือคำตอบสองข้อที่ฉันได้รวบรวมไว้ โปรดทราบว่าสิ่งเหล่านี้ไม่มีตรรกะในการตรวจสอบว่าฟังก์ชันมีไวยากรณ์ที่ถูกต้องหรือไม่ นี่เป็นเรื่องปกติเพราะเรามักจะผ่านฟังก์ชั่นการวิเคราะห์คำเพื่อgetArguments()ให้ไวยากรณ์ของพวกเขาถูกต้องแล้ว

ฉันจะพยายามแก้ไขปัญหาเหล่านี้ให้ดีที่สุดเท่าที่จะทำได้ แต่หากไม่มีความพยายามจากผู้ดูแลจาวาสคริปต์สิ่งนี้จะยังคงเป็นปัญหาที่เปิดอยู่

รุ่น 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


หากฉันไม่เข้าใจผิดในเวอร์ชันเบราว์เซอร์ของคุณเงื่อนไขแรกใน FUNC_ARGS จะใช้ได้ทั้งกับลูกศรและฟังก์ชั่นแบบดั้งเดิมดังนั้นคุณไม่จำเป็นต้องใช้ส่วนที่สองและคุณสามารถทำได้ด้วยการพึ่งพา ARROW
pwilcox

มันเยี่ยมมาก! ฉันกำลังมองหาวิธีแก้ปัญหาเช่นนี้ที่ใช้ parser เพื่อครอบคลุมไวยากรณ์ ES6 ฉันวางแผนที่จะใช้สิ่งนี้เพื่อสร้างตัวจับคู่“ ใช้ส่วนต่อประสาน” ที่ตลกขบขันเพราะเพียงแค่ใช้ function.length มีข้อ จำกัด กับพารามิเตอร์เริ่มต้นและฉันต้องการที่จะยืนยันพารามิเตอร์ส่วนที่เหลือ
cue8chalk

เป็นชี้ให้เห็นว่ากรณีทดสอบที่ห้าที่มีวงเล็บในค่าเริ่มต้นในปัจจุบันล้มเหลว ฉันหวังว่า regex-fu ของฉันแข็งแกร่งพอที่จะแก้ไขได้ขอโทษด้วย!
Dale Anderson

8

ฉันได้อ่านคำตอบส่วนใหญ่ที่นี่และฉันต้องการที่จะเพิ่มหนึ่งซับของฉัน

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

หรือ

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

หรือสำหรับฟังก์ชั่นหนึ่งซับใน ECMA6

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

__

สมมติว่าคุณมีฟังก์ชั่น

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

รหัสด้านล่างจะกลับมา "abc,def,ghi,jkl"

รหัสนั้นจะทำงานร่วมกับการตั้งค่าฟังก์ชั่นที่Camilo Martinมอบให้:

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

ด้วยความเห็นของ Bubersson ในคำตอบของ Jack Allan :

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

__

คำอธิบาย

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

นี้จะสร้างนิพจน์ปกติnew RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')ด้วย ฉันต้องใช้new RegExpเพราะฉันกำลังฉีดตัวแปร ( Function.nameชื่อของฟังก์ชั่นที่เป็นเป้าหมาย) ลงใน RegExp

ตัวอย่างเช่นถ้าชื่อฟังก์ชั่นคือ "foo" ( function foo()) /foo\s*\((.*?)\)/ที่นิพจน์ทั่วไปที่จะเป็น

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

จากนั้นจะแปลงฟังก์ชันทั้งหมดเป็นสตริงและลบบรรทัดใหม่ทั้งหมด การลบบรรทัดใหม่ช่วยด้วยการตั้งค่าฟังก์ชั่นที่Camilo Martinให้ไว้

.exec(...)[1]

นี่คือRegExp.prototype.execฟังก์ชั่น โดยพื้นฐานแล้วจะจับคู่ Exponent ปกติ ( new RegExp()) เข้ากับ String ( Function.toString()) จากนั้น[1]จะส่งคืนกลุ่มการจับภาพแรกที่พบใน Exponent ปกติ ( (.*?))

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

การดำเนินการนี้จะลบทุกความคิดเห็นภายใน/*และ*/และลบช่องว่างทั้งหมด


นี้ตอนนี้ยังสนับสนุนการอ่านและความเข้าใจลูกศร ( =>) ฟังก์ชั่นเช่นf = (a, b) => void 0;ในการที่Function.toString()จะกลับมาแทนของฟังก์ชั่นปกติ(a, b) => void 0 function f(a, b) { return void 0; }นิพจน์ปกติดั้งเดิมจะทำให้เกิดข้อผิดพลาดในความสับสน แต่ตอนนี้ได้รับการพิจารณาแล้ว

การเปลี่ยนแปลงมาจากnew RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) ถึงnew RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


.split(',')หากคุณต้องการที่จะทำให้ทุกพารามิเตอร์ลงในอาร์เรย์แทนสตริงคั่นด้วยเครื่องหมายจุลภาคที่สิ้นสุดเพียงแค่เพิ่ม


ค่อนข้างดี บรรทัดเดียวจัดการฟังก์ชั่นลูกศรไม่มีการพึ่งพาภายนอกและครอบคลุมมากกว่ากรณีขอบอย่างน้อยอย่างน้อยสำหรับการใช้งานที่ฉันตั้งใจ หากคุณสามารถตั้งสมมติฐานที่สมเหตุสมผลเกี่ยวกับลายเซ็นของฟังก์ชั่นที่คุณจะต้องเผชิญกับสิ่งนี้คุณต้อง ขอบคุณ!
charlie

ไม่ทำงานกับฟังก์ชันลูกศรง่าย ๆ : f = (a, b) => void 0; เมื่อวันที่getParameters(f)ฉันได้รับTypeError: Cannot read property '1' of null
AT

@ AT ฉันเพิ่งปรับปรุงคำตอบเพื่อแก้ไขปัญหาของคุณ
Jaketr00

ขอบคุณ ... แต่เก็บไว้ในใจว่าวงเล็บไม่จำเป็นต้องมีเพื่อให้คุณสามารถทำสิ่งที่ต้องการgetParameters(a => b => c => d => a*b*c*d)ซึ่งมีรหัสของคุณยังคงให้ว่าTypeError: Cannot read property '1' of null... ขณะนี้ผลงานstackoverflow.com/a/29123804
AT

ไม่ทำงานเมื่อฟังก์ชั่นมีค่าเริ่มต้น (บทบาท, ชื่อ = "bob") พารามิเตอร์ที่แยกออกมาคือ name = "bob" แทนที่จะเป็น "ชื่อ" ที่คาดไว้
Dmitri


7

นอกจากนี้คุณยังสามารถใช้ตัวแยกวิเคราะห์ "esprima" เพื่อหลีกเลี่ยงปัญหามากมายที่มีความคิดเห็นช่องว่างและสิ่งอื่น ๆ ภายในรายการพารามิเตอร์

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

มันทำงานได้แม้กับรหัสเช่นนี้:

getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]

6

ฉันเคยลองทำแบบนี้มาก่อน แต่ไม่เคยพบวิธีที่จะทำให้เป็นจริงได้ ฉันลงเอยด้วยวัตถุแทนแล้ววนไปเรื่อย ๆ

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

ฉันมีฟังก์ชั่นมากมายที่กำหนดไว้ล่วงหน้าแล้วซึ่งถูกเรียกด้วยพารามิเตอร์มาตรฐานแทนที่จะเป็นวัตถุเดียว การเปลี่ยนทุกอย่างจะใช้เวลามาก
vikasde

2
การตอบสนองนี้ไม่ใช่คำตอบของคำถามต้นฉบับที่ถูกกำหนดอย่างแม่นยำ มันแสดงวิธีแก้ปัญหาสำหรับปัญหาที่แตกต่างอย่างสิ้นเชิง คำถามเดิมหมายถึงเทคนิคที่ AngularJS ใช้จากการฉีดการพึ่งพา ชื่ออาร์กิวเมนต์มีความหมายเนื่องจากสอดคล้องกับการขึ้นต่อกันของโมดูลที่ DI จัดเตรียมโดยอัตโนมัติ
Lambder

4

ฉันไม่รู้ว่าโซลูชันนี้เหมาะสมกับปัญหาของคุณหรือไม่ แต่มันช่วยให้คุณกำหนดฟังก์ชันที่คุณต้องการได้ใหม่โดยไม่ต้องเปลี่ยนรหัสที่ใช้ การโทรที่มีอยู่จะใช้ params ที่มีตำแหน่งในขณะที่การใช้งานฟังก์ชั่นอาจใช้ "params ที่มีชื่อ" (hash param เดียว)

ฉันคิดว่าคุณจะแก้ไขข้อกำหนดฟังก์ชันที่มีอยู่แล้วดังนั้นทำไมไม่มีฟังก์ชั่นจากโรงงานที่ทำสิ่งที่คุณต้องการ:

<!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>

หวังว่ามันจะช่วย


4

วิธีที่เหมาะสมในการทำเช่นนี้คือการใช้ตัวแยกวิเคราะห์ JS นี่คือตัวอย่างการใช้โอ๊ก

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

รหัสที่นี่พบว่าชื่อของสาม (อย่างเป็นทางการ) fพารามิเตอร์ของฟังก์ชัน มันไม่ได้โดยการให้อาหารเข้าไปfacorn.parse()


สิ่งที่เกี่ยวกับค่าหรือไม่
eran otzap

2

ฉันไม่รู้วิธีรับรายการพารามิเตอร์ แต่คุณสามารถทำได้เพื่อให้ได้จำนวนที่ต้องการ

alert(doSomething.length);

function gotcha (a, b = false, c) {}; alert(gotcha.length)
balupton

2

การรับคำตอบจาก @ jack-allan ฉันแก้ไขฟังก์ชันเล็กน้อยเพื่อให้คุณสมบัติเริ่มต้นของ ES6 เช่น:

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

ยังคงกลับมา [ '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;
    }
}

1
ขอบคุณคุณเป็นคนเดียวในหัวข้อนี้ที่เหมาะกับฉัน
AT

1

ฉันจะทำมันอย่างไร:

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

คุณสามารถอ้างอิง args ด้วยชื่อฟังก์ชั่นเช่น:

name.arguments;

หวังว่านี่จะช่วยได้!


4
และชื่อของพารามิเตอร์ฟังก์ชั่นอยู่ที่ไหน
Andrej

อ่า ... คุณหมายถึงคุณต้องการมันในรูปแบบแฮชเหรอ? ราวกับว่า: var args = name.arguments; console.log('I WANNa SEE', args);ส่งออกบางอย่างเช่น "{arg1: {... }, arg2: 'string'}"? สิ่งนี้อาจช่วยล้างสิ่งต่างๆ: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. อาร์กิวเมนต์ของฟังก์ชันเป็นวัตถุที่มีลักษณะคล้าย 'Array' โดยมีดัชนีที่นับได้ ฉันไม่คิดว่าการแฮชการทำแผนที่เป็นไปได้ แต่คุณสามารถผ่าน Object-literal ซึ่งเป็นแนวปฏิบัติที่ดีถ้าคุณมี params มากกว่า 4 ตัวอยู่แล้ว
โคดี้

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

1

คำตอบสำหรับสิ่งนี้ต้องการ 3 ขั้นตอน:

  1. เพื่อรับค่าของพารามิเตอร์จริงที่ส่งผ่านไปยังฟังก์ชัน (ลองเรียกมันว่าargValues) ตรงไปตรงมาเพราะจะสามารถใช้งานได้เหมือนargumentsในฟังก์ชั่น
  2. เพื่อรับชื่อพารามิเตอร์จากฟังก์ชั่นลายเซ็น (ลองเรียกมันว่าargNames) สิ่งนี้ไม่ง่ายและต้องใช้การแยกวิเคราะห์ฟังก์ชั่น แทนที่จะทำ regex ที่ซับซ้อนด้วยตัวเองและกังวลเกี่ยวกับกรณีขอบ (พารามิเตอร์เริ่มต้น, ความคิดเห็น, ... ), คุณสามารถใช้ไลบรารีเช่น babylon ที่จะแยกฟังก์ชันเป็นแผนผังไวยากรณ์นามธรรมซึ่งคุณสามารถรับชื่อของพารามิเตอร์
  3. ขั้นตอนสุดท้ายคือการรวม 2 อาร์เรย์เข้าด้วยกันเป็น 1 อาร์เรย์ที่มีชื่อและค่าของพารามิเตอร์ทั้งหมด

รหัสจะเป็นเช่นนี้

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)

และวัตถุที่บันทึกจะเป็น

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

และนี่คือตัวอย่างการทำงานhttps://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a


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

1

ว้าวคำตอบมากมายแล้ว .. ฉันค่อนข้างแน่ใจว่ามันถูกฝังอยู่ ดังนั้นฉันคิดว่านี่อาจเป็นประโยชน์สำหรับบางคน

ฉันไม่พอใจกับคำตอบที่เลือกเช่นเดียวกับใน ES6 มันทำงานได้ไม่ดีกับค่าเริ่มต้น และมันยังไม่ได้ให้ข้อมูลค่าเริ่มต้น ฉันยังต้องการฟังก์ชั่นน้ำหนักเบาที่ไม่ได้ขึ้นอยู่กับ lib ภายนอก

ฟังก์ชั่นนี้มีประโยชน์มากสำหรับวัตถุประสงค์ในการดีบั๊กตัวอย่างเช่น: การบันทึกที่เรียกว่าฟังก์ชั่นที่มีพารามิเตอร์ค่าเริ่มต้นของพารามิเตอร์และอาร์กิวเมนต์

เมื่อวานนี้ฉันใช้เวลาไปกับการถอด RegExp ที่ถูกต้องเพื่อแก้ไขปัญหานี้และนี่คือสิ่งที่ฉันคิดขึ้นมา มันใช้งานได้ดีมากและฉันยินดีมากกับผลลัพธ์ที่ได้:

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

ในขณะที่คุณสามารถบอกชื่อพารามิเตอร์บางส่วนหายไปเนื่องจาก Babel transpiler ลบออกจากฟังก์ชั่น หากคุณจะเรียกใช้สิ่งนี้ใน NodeJS ล่าสุดจะทำงานได้ตามที่คาดไว้ (ผลลัพธ์ที่ได้รับความเห็นนั้นมาจาก NodeJS)

หมายเหตุอื่นตามที่ระบุในความคิดเห็นคือไม่สามารถใช้งานฟังก์ชันลูกศรแบบอินไลน์เป็นค่าเริ่มต้น สิ่งนี้ทำให้ซับซ้อนเพื่อแยกค่าโดยใช้ RegExp

โปรดแจ้งให้เราทราบหากสิ่งนี้มีประโยชน์สำหรับคุณ! ชอบที่จะได้ยินข้อเสนอแนะบางอย่าง!


1

ฉันจะให้ตัวอย่างสั้น ๆ ด้านล่าง:

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

คำตอบนี้มีประโยชน์ แต่ไม่ตอบคำถาม ฉันโหวตเพราะมันแก้ปัญหาของฉันได้ แต่ฉันคิดว่ามันน่าจะเหมาะสมกว่า บางทีค้นหาคำถามที่เกี่ยวข้อง?
chharvey

1

แพคเกจนี้ใช้การสร้างใหม่เพื่อสร้าง AST จากนั้นชื่อพารามิเตอร์จะถูกรวบรวมจากสิ่งเหล่านี้ทำให้สามารถรองรับการจับคู่รูปแบบอาร์กิวเมนต์เริ่มต้นฟังก์ชันลูกศรและคุณสมบัติ ES6 อื่น ๆ

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


1

ฉันแก้ไขเวอร์ชันที่นำมาจากAngularJSซึ่งใช้กลไกการฉีดแบบพึ่งพาเพื่อทำงานโดยไม่ต้องใช้ Angular ฉันได้อัปเดตSTRIP_COMMENTSregex เพื่อทำงานด้วยECMA6ดังนั้นจึงรองรับสิ่งต่าง ๆ เช่นค่าเริ่มต้นในลายเซ็น

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


0

คุณสามารถเข้าถึงค่าอาร์กิวเมนต์ที่ส่งผ่านไปยังฟังก์ชันโดยใช้คุณสมบัติ "อาร์กิวเมนต์"

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

ข้อโต้แย้งไม่ได้รับการคัดค้านหรือไม่ ดูคำแนะนำของ Ionut G. Stan ข้างต้น
vikasde

vikasde ถูกต้อง การเข้าถึงargumentsคุณสมบัติของอินสแตนซ์ของฟังก์ชันนั้นเลิกใช้แล้ว ดูdeveloper.mozilla.org/en/Core_JavaScript_1.5_Reference/
......

0

มันค่อนข้างง่าย

ในตอนแรกมีการคัดค้านarguments.callee- การอ้างอิงถึงฟังก์ชั่นที่เรียกว่า ในวินาทีถ้าคุณมีการอ้างอิงถึงฟังก์ชั่นของคุณคุณสามารถได้รับการแสดงข้อความของพวกเขา ที่สามถ้าคุณเรียกใช้ฟังก์ชันของคุณเป็นตัวสร้างคุณสามารถมีลิงค์ผ่าน yourObject.constructor หมายเหตุ: โซลูชั่นแรกเลิกใช้แล้วดังนั้นหากคุณไม่สามารถใช้งานได้คุณจะต้องคิดถึงสถาปัตยกรรมแอพของคุณด้วย หากคุณไม่ต้องการชื่อตัวแปรที่แน่นอนเพียงใช้ภายในตัวแปรภายในฟังก์ชั่นที่argumentsไม่มีเวทมนต์

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

พวกเขาทั้งหมดจะโทรไปที่สตริงและแทนที่ด้วยใหม่เพื่อให้เราสามารถสร้างผู้ช่วย:

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

ตัวอย่างบางส่วน:

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

สนุกกับ JS!

UPD: แจ็คอัลลันได้รับการแก้ปัญหาที่ดีขึ้นเล็กน้อย แจ็ค GJ!


สิ่งนี้อาจตรงไปตรงมามากขึ้นถ้าคุณใช้SomeFuncNameแทนarguments.callee(ทั้งสองชี้ไปที่วัตถุฟังก์ชันเอง)
Raphael Schweikert

0

ไม่ว่าจะแก้ปัญหาอย่างไรมันจะต้องไม่ทำลายฟังก์ชั่นที่toString()แปลกประหลาด

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

สกรีนช็อตจากคอนโซล

นอกจากนี้เหตุใดจึงต้องใช้นิพจน์ทั่วไปที่ซับซ้อน สามารถทำได้เช่น:

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

สิ่งนี้ใช้ได้ทุกที่กับทุกฟังก์ชั่นและ regex เพียงอันเดียวคือการลบช่องว่างที่ไม่ได้ประมวลผลสตริงทั้งหมดเนื่องจาก.splitเคล็ดลับ


0

เอาล่ะคำถามเก่า ๆ พร้อมคำตอบที่เพียงพอ นี่คือข้อเสนอของฉันที่ไม่ได้ใช้ regex ยกเว้นงานที่ต้องใช้ความพยายามในการลอกช่องว่าง (ฉันควรทราบว่าฟังก์ชั่น "strips_comments" จะเว้นวรรคออกจริง ๆ แทนที่จะเอาออกทางกายภาพนั่นเป็นเพราะฉันใช้ที่อื่นและด้วยเหตุผลต่าง ๆ จำเป็นต้องใช้ตำแหน่งของโทเค็นที่ไม่ใช่ความคิดเห็นดั้งเดิมเพื่อคงสภาพเดิมไว้)

มันเป็นบล็อกที่มีความยาวพอสมควรเนื่องจากการวางนี้รวมถึงกรอบการทดสอบขนาดเล็ก

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

0

นี่คือวิธีหนึ่ง:

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

หมายเหตุ: ใช้งานได้กับเบราว์เซอร์ที่รองรับarguments.calleeเท่านั้น


2
วงลูปส่งผลให้วงวนไม่สิ้นสุดพร้อมรหัสที่ให้ไว้ (ณ 20171121at1047EDT)
George 2.0 หวังว่า

@ George2.0Hope ขอบคุณที่ชี้ให้เห็น ฉันจะอัปเดตคำตอบ
Ates Goral

args.toSource ไม่ใช่ฟังก์ชั่น (บรรทัดที่ 20) ... แต่แม้ว่าคุณจะเปลี่ยนเป็น: console.log (args.toString ()); ... คุณได้รับ ... [วัตถุวัตถุ] ... ดีกว่าถ้าคุณทำ: console.log (JSON.stringify (args));
George 2.0 หวังว่า

ทุกอย่างเปลี่ยนไปเล็กน้อยตั้งแต่ปี 2009!
Ates Goral

1
ในกรณีที่ทุกคนปรากฏตัวที่นี่เพื่อทำปฏิกิริยาฟังก์ชันนี้ซึ่งยอดเยี่ยมจะไม่ทำงานในโหมดเข้มงวด
รายบุคคล

0

หมายเหตุ: หากคุณต้องการใช้การทำลายโครงสร้างพารามิเตอร์ ES6 ด้วยโซลูชันระดับสูงให้เพิ่มบรรทัดต่อไปนี้

if (result[0] === '{' && result[result.length - 1 === '}']) result = result.slice(1, -1)

-1

พารามิเตอร์ของฟังก์ชันภาพค่าสตริงแบบไดนามิกจาก JSON เนื่องจาก item.product_image2 เป็นสตริง URL คุณต้องใส่ไว้ในเครื่องหมายคำพูดเมื่อคุณเรียกใช้ changeImage Inside พารามิเตอร์

ฟังก์ชั่นของฉัน Onclick

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

ฟังก์ชั่นของฉัน

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.