Bagaimana cara mendapatkan nama / nilai parameter fungsi secara dinamis?

301

Apakah ada cara untuk mendapatkan nama parameter fungsi dari suatu fungsi secara dinamis?

Katakanlah fungsi saya terlihat seperti ini:

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

Sekarang, bagaimana cara saya mendapatkan daftar nama parameter dan nilainya ke dalam array dari dalam fungsi?

vikasde
sumber
Terima kasih untuk semuanya. Setelah mencari-cari, saya menemukan solusi pada SO: stackoverflow.com/questions/914968/… Menggunakan regex untuk mendapatkan nama param. Ini mungkin bukan solusi terbaik, namun ini bekerja untuk saya.
vikasde
8
mari kita lanjutkan dan tandai ini sebagai teman yang dijawab. tidak ada jawaban baru yang datang.
Matthew Graves
function doSomething(...args) { /*use args*/}
caub

Jawaban:

323

Fungsi berikut akan mengembalikan array nama parameter dari setiap fungsi yang diteruskan.

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

Contoh penggunaan:

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

Edit :

Dengan ditemukannya ES6 fungsi ini dapat tersandung oleh parameter default. Ini adalah peretasan cepat yang seharusnya bisa digunakan dalam kebanyakan kasus:

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

Saya mengatakan sebagian besar kasus karena ada beberapa hal yang akan menjebaknya

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

Sunting : Saya juga mencatat vikasde menginginkan nilai parameter dalam array juga. Ini sudah disediakan dalam variabel lokal bernama argumen.

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

Objek argumen bukan Array. Ini mirip dengan Array, tetapi tidak memiliki properti Array kecuali panjang. Misalnya, tidak memiliki metode pop. Namun itu dapat dikonversi menjadi Array nyata:

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

Jika Array generics tersedia, seseorang dapat menggunakan yang berikut ini sebagai gantinya:

var args = Array.slice(arguments);
Jack Allan
sumber
12
Perhatikan bahwa solusi ini mungkin gagal karena komentar dan spasi - misalnya: var fn = function(a /* fooled you)*/,b){};akan menghasilkan ["a", "/*", "fooled", "you"]
bubersson
1
Saya memodifikasi fungsi untuk mengembalikan array kosong (bukan nol) ketika tidak ada argumen
BT
2
Ada biaya untuk menyusun regex, oleh karena itu Anda ingin menghindari kompilasi regex kompleks lebih dari sekali. Inilah sebabnya mengapa hal itu dilakukan di luar fungsi
Jack Allan
2
KOREKSI: Akan memodifikasi regex dengan pengubah a / s yang memungkinkan perl '.' juga dapat mencocokkan baris baru. Ini diperlukan untuk komentar multi-baris di dalam / * * /. Ternyata regex Javascript tidak mengizinkan pengubah / s. Regex asli menggunakan [/ s / S] tidak cocok dengan karakter baris baru. SOOO, harap abaikan komentar sebelumnya.
tgoneil
1
@ dan Perhatikan bahwa Anda menyertakan kompilasi regex dalam pengujian Anda. Ini hanya boleh dilakukan sekali. Hasil Anda mungkin berbeda jika Anda memindahkan kompilasi regex ke langkah pengaturan alih-alih tes
Jack Allan
123

Di bawah ini adalah kode yang diambil dari AngularJS yang menggunakan teknik untuk mekanisme injeksi ketergantungannya.

Dan berikut ini penjelasannya diambil dari http://docs.angularjs.org/tutorial/step_05

Injector dependensi sudut menyediakan layanan untuk controller Anda ketika controller sedang dibangun. Injektor dependensi juga menangani pembuatan dependensi transitif apa pun yang mungkin dimiliki layanan (layanan sering bergantung pada layanan lain).

Perhatikan bahwa nama-nama argumen itu penting, karena injektor menggunakannya untuk mencari dependensi.

/**
 * @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
sumber
40
@apaidnerd dengan darah setan dan bibit setan, rupanya. Regex ?! Akan keren jika ada cara yang dibangun di JS, bukan.
Aditya MP
6
@apaidnerd, sangat benar! Hanya dipikirkan - bagaimana mungkin itu diterapkan? Sebenarnya saya berpikir tentang menggunakan functionName.toString () tapi saya berharap untuk sesuatu yang lebih elegan (dan mungkin lebih cepat)
sasha.sochka
2
@ sasha.sochka, datang ke sini bertanya-tanya hal yang persis sama, setelah menyadari tidak ada cara untuk mendapatkan nama parameter dengan javascript
Hart Simha
14
untuk menghemat waktu orang, Anda bisa mendapatkan fungsi ini dari sudut via annotate = angular.injector.$$annotate
Nick
3
Saya benar-benar pergi mencari di internet untuk topik ini karena saya ingin tahu bagaimana Angular melakukannya ... sekarang saya tahu dan saya tahu terlalu banyak!
Steven Hunt
40

Berikut ini adalah solusi terbaru yang berupaya menangani semua kasus tepi yang disebutkan di atas dengan cara yang ringkas:

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

Output tes singkat (contoh uji lengkap terlampir di bawah):

'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
sumber
Ini terputus ketika ada komentar satu baris. Coba ini: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham
4
Anda mungkin harus mengganti func + ''dengan Function.toString.call(func)untuk mempertahankan kasus ketika fungsi memiliki implementasi .toString () kustom.
Paul Go
1
panah gemuk =>.split(/\)[\{=]/, 1)[0]
Matt
1
ini membagi objek yang dirusak (seperti ({ a, b, c })) ke semua parameter di dalam destrukturisasi. untuk menjaga benda yang rusak tetap utuh, ubah yang terakhir .splitke: .split(/,(?![^{]*})/g)
Michael Auderer
1
Ini juga tidak akan berfungsi ketika ada nilai string default yang berisi "//" atau "/ *"
skerit
23

Solusi yang lebih sedikit rawan kesalahan terhadap spasi dan komentar adalah:

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

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

["hi", "you"]
bubersson
sumber
1
@AlexMills Satu hal yang saya perhatikan adalah bahwa Spec for Arrow Functions mengatakan bahwa mereka seharusnya tidak diperlakukan sebagai 'fungsi.' Berarti tidak cocok untuk ini untuk mencocokkan fungsi array. 'Ini' tidak diatur dengan cara yang sama, dan mereka seharusnya tidak dipanggil sebagai fungsi juga. Itu adalah sesuatu yang saya pelajari dengan cara yang sulit. ($ myService) => $ myService.doSomething () terlihat keren, tetapi ini merupakan penyalahgunaan dari Array Functions.
Andrew T Finnell
20

Banyak jawaban di sini menggunakan regex, ini bagus tapi tidak terlalu baik untuk penambahan bahasa (seperti fungsi panah dan kelas). Yang juga perlu diperhatikan adalah bahwa jika Anda menggunakan salah satu dari fungsi ini pada kode yang diperkecil maka ia akan pergi 🔥. Itu akan menggunakan apa pun nama yang diperkecil itu. Sudut menyiasatinya dengan memungkinkan Anda untuk mengirimkan serangkaian string yang cocok dengan urutan argumen saat mendaftarkannya dengan wadah DI. Demikian seterusnya dengan solusinya:

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

Ini menangani masalah parse asli dan beberapa tipe fungsi lainnya (mis. Fungsi panah). Inilah gagasan tentang apa yang bisa dan tidak bisa ditangani sebagaimana adanya:

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

Bergantung pada apa yang ingin Anda gunakan untuk ES6 Proxy dan perusakan mungkin merupakan pilihan terbaik Anda. Misalnya jika Anda ingin menggunakannya untuk injeksi dependensi (menggunakan nama params) maka Anda dapat melakukannya sebagai berikut:

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

Ini bukan resolver paling canggih di luar sana tetapi memberikan gambaran tentang bagaimana Anda dapat menggunakan Proxy untuk menanganinya jika Anda ingin menggunakan args parser untuk DI sederhana. Namun ada satu peringatan kecil dalam pendekatan ini. Kita perlu menggunakan penugasan yang merusak alih-alih params yang normal. Ketika kami melewati proxy injector, destrukturisasi sama dengan memanggil pengambil pada objek.

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

Ini menghasilkan sebagai berikut:

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

Ini menghubungkan seluruh aplikasi. Bit terbaik adalah bahwa aplikasi ini mudah untuk diuji (Anda bisa langsung membuat instance setiap kelas dan lulus mengejek / bertopik / dll). Juga jika Anda perlu menukar implementasi, Anda dapat melakukannya dari satu tempat. Semua ini dimungkinkan karena objek JS Proxy.

Catatan: Ada banyak pekerjaan yang perlu dilakukan untuk ini sebelum akan siap untuk penggunaan produksi tetapi itu memberikan gambaran seperti apa bentuknya.

Ini agak terlambat dalam jawabannya tetapi mungkin membantu orang lain yang memikirkan hal yang sama. 👍

James Drew
sumber
13

Saya tahu ini adalah pertanyaan lama, tetapi pemula telah meniru ini seolah-olah ini adalah praktik yang baik dalam kode apa pun. Sebagian besar waktu, harus mengurai representasi string fungsi untuk menggunakan nama parameternya hanya menyembunyikan kesalahan dalam logika kode.

Parameter fungsi sebenarnya disimpan dalam objek mirip array yang disebut arguments, di mana argumen pertama adalah arguments[0], yang kedua adalah arguments[1]dan seterusnya. Menulis nama parameter dalam tanda kurung dapat dilihat sebagai sintaks steno. Ini:

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

...sama dengan:

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

    console.log("does something");
}

Variabel itu sendiri disimpan dalam ruang lingkup fungsi, bukan sebagai properti dalam objek. Tidak ada cara untuk mengambil nama parameter melalui kode karena itu hanya simbol yang mewakili variabel dalam bahasa manusia.

Saya selalu menganggap representasi string dari fungsi sebagai alat untuk keperluan debugging, terutama karena argumentsobjek seperti array ini . Anda tidak diharuskan untuk memberikan nama pada argumen di tempat pertama. Jika Anda mencoba mem-parsing fungsi yang dirangkai, itu tidak benar-benar memberi tahu Anda tentang parameter tambahan tanpa nama yang mungkin diperlukan.

Inilah situasi yang bahkan lebih buruk dan lebih umum. Jika suatu fungsi memiliki lebih dari 3 atau 4 argumen, mungkin logis untuk meneruskannya sebagai objek, yang lebih mudah untuk dikerjakan.

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

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

Dalam hal ini, fungsi itu sendiri akan dapat membaca objek yang diterimanya dan mencari propertinya serta mendapatkan nama dan nilainya, tetapi mencoba mengurai representasi string dari fungsi tersebut hanya akan memberi Anda "keberatan" untuk parameter, yang tidak berguna sama sekali.

Kartu domino
sumber
1
Saya pikir kasus untuk hal seperti ini biasanya: debugging / logging, semacam dekorator yang melakukan hal-hal yang funky (istilah teknis 😁), atau membangun kerangka kerja injeksi ketergantungan untuk aplikasi Anda untuk menyuntikkan secara otomatis berdasarkan nama argumen (ini adalah seberapa sudutnya bekerja). Case use lain yang sangat menarik adalah pada promisify-node (ini adalah library yang mengambil fungsi yang biasanya mengambil callback dan kemudian mengubahnya menjadi Promise). Mereka menggunakan ini untuk menemukan nama umum untuk panggilan balik (seperti cb / panggilan balik / dll) dan kemudian mereka dapat memeriksa apakah fungsinya async atau sinkronisasi sebelum membungkusnya.
James Drew
Lihat file ini untuk parsernya . Agak naif tetapi menangani sebagian besar kasus.
James Drew
Menarik, saya terkejut perpustakaan seperti itu mendapat perhatian. Nah, ada beberapa masalah terbuka tentang situasi bermasalah seperti yang saya jelaskan. Seperti yang saya katakan, jika itu untuk keperluan debugging baik-baik saja, tetapi bergantung pada konversi string fungsi di lingkungan produksi terlalu berisiko.
Domino
Pada sidenote "lucu", Anda dapat membuat semua fungsi bertindak seolah-olah mereka adalah built-in anonim dengan menjalankan ini:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino
1
Setuju, agak berantakan untuk menanganinya seperti ini. Penggunaan terbaik dan paling mudah adalah injeksi ketergantungan. Dalam hal ini Anda memiliki kode dan dapat menangani penamaan dan area kode lainnya. Saya pikir ini adalah tempat yang paling banyak digunakan. Saya saat ini menggunakan esprima (bukan regex) dan ES6 Proxy( perangkap konstruktor dan menerapkan perangkap ) dan Reflectionuntuk menangani DI untuk beberapa modul saya. Itu cukup solid.
James Drew
10

Karena JavaScript adalah bahasa scripting, saya merasa bahwa introspeksinya harus mendukung mendapatkan nama parameter fungsi. Punting pada fungsionalitas itu merupakan pelanggaran prinsip pertama, jadi saya memutuskan untuk mengeksplorasi masalah lebih lanjut.

Itu mengarahkan saya ke pertanyaan ini tetapi tidak ada solusi bawaan. Yang mengarahkan saya ke jawaban ini yang menjelaskan bahwa argumentshanya usang di luar fungsi, sehingga kami tidak dapat lagi menggunakan myFunction.argumentsatau mendapatkan:

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

Saatnya menyingsingkan lengan baju kami dan mulai bekerja:

⭐ Mengambil parameter fungsi memerlukan parser karena ekspresi kompleks seperti 4*(5/3)dapat digunakan sebagai nilai default. Jadi jawaban Gaafar atau jawaban James Drew sejauh ini merupakan pendekatan terbaik.

Saya mencoba parser babylon dan esprima tetapi sayangnya mereka tidak dapat mengurai fungsi anonim yang berdiri sendiri, seperti yang ditunjukkan dalam jawaban Mateusz Charytoniuk . Saya menemukan solusi lain dengan mengelilingi kode di dalam tanda kurung, agar tidak mengubah logika:

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

Baris baru mencegah masalah dengan //(komentar satu baris).

⭐ Jika parser tidak tersedia, opsi terbaik berikutnya adalah menggunakan teknik coba-dan-benar seperti Angular.js's dependency injector regular expressions. Saya menggabungkan versi fungsional jawaban Lambder dengan jawaban humbletim dan menambahkan ARROWboolean opsional untuk mengendalikan apakah fungsi panah lemak ES6 diizinkan oleh ekspresi reguler.


Berikut adalah dua solusi yang saya kumpulkan. Perhatikan bahwa ini tidak memiliki logika untuk mendeteksi apakah suatu fungsi memiliki sintaks yang valid, mereka hanya mengekstrak argumen. Ini biasanya ok karena kita biasanya melewatkan fungsi yang diuraikan getArguments()sehingga sintaksnya sudah valid.

Saya akan mencoba mengumpulkan solusi ini sebaik mungkin, tetapi tanpa upaya dari pengelola JavaScript, ini akan tetap menjadi masalah terbuka.

Versi Node.js (tidak dapat dijalankan hingga StackOverflow mendukung 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() {}');

Contoh kerja penuh:

https://repl.it/repls/SandybrownPhonyAngles

Versi browser (perhatikan bahwa ia berhenti pada nilai default kompleks pertama):

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

Contoh kerja penuh:

https://repl.it/repls/StupendousShowyOffices

Zack Morris
sumber
jika saya tidak salah, dalam versi browser Anda, persyaratan pertama di FUNC_ARGS akan berfungsi untuk fungsi panah dan tradisional, jadi Anda tidak memerlukan bagian kedua dan agar Anda bisa menghilangkan ketergantungan pada ARROW.
pwilcox
Ini bagus! Saya sedang mencari solusi seperti ini yang menggunakan parser untuk menutupi sintaks ES6. Saya berencana untuk menggunakan ini untuk membuat pencocokan "mengimplementasikan antarmuka" lelucon karena hanya menggunakan function.length memiliki batasan dengan parameter default, dan saya ingin dapat menegaskan parameter sisanya.
cue8chalk
Perlu ditunjukkan bahwa case uji kelima yang berisi tanda kurung dalam nilai default saat ini gagal. Saya berharap regex-fu saya cukup kuat untuk diperbaiki, maaf!
Dale Anderson
8

Saya telah membaca sebagian besar jawabannya di sini, dan saya ingin menambahkan satu kalimat.

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

atau

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

atau untuk fungsi satu-liner di ECMA6

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

__

Katakanlah Anda memiliki fungsi

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

Kode di bawah ini akan kembali "abc,def,ghi,jkl"

Kode itu juga akan berfungsi dengan pengaturan fungsi yang diberikan Camilo Martin :

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

Juga dengan komentar Bubersson tentang jawaban Jack Allan :

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

__

Penjelasan

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

Ini menciptakan Ekspresi Reguler dengan new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). Saya harus menggunakan new RegExpkarena saya menyuntikkan variabel ( Function.name, nama fungsi yang ditargetkan) ke dalam RegExp.

Contoh Jika nama fungsi adalah "foo" ( function foo()), RegExp akan menjadi /foo\s*\((.*?)\)/.

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

Kemudian itu mengubah seluruh fungsi menjadi string, dan menghapus semua baris baru. Menghapus baris baru membantu pengaturan fungsi yang diberikan Camilo Martin .

.exec(...)[1]

Ini RegExp.prototype.execfungsinya. Ini pada dasarnya cocok dengan Eksponen Reguler ( new RegExp()) ke dalam String ( Function.toString()). Kemudian [1]akan mengembalikan Kelompok Pengambilan pertama yang ditemukan dalam Eksponen Reguler ( (.*?)).

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

Ini akan menghapus setiap komentar di dalam /*dan */, dan menghapus semua spasi.


Ini juga sekarang mendukung =>fungsi membaca dan memahami tanda panah ( ), seperti f = (a, b) => void 0;, yang Function.toString()akan mengembalikan (a, b) => void 0fungsi yang normal function f(a, b) { return void 0; }. Ekspresi reguler asli akan membuat kesalahan dalam kebingungannya, tetapi sekarang diperhitungkan.

Perubahan adalah dari new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) ke new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


Jika Anda ingin menjadikan semua parameter menjadi Array alih-alih String yang dipisahkan oleh koma, pada akhirnya tambahkan saja .split(',').

Jaketr00
sumber
cukup bagus satu baris, menangani fungsi panah, tidak ada dependensi eksternal, dan mencakup lebih dari cukup kasus tepi, setidaknya untuk penggunaan yang saya maksudkan. jika Anda dapat membuat beberapa asumsi yang masuk akal tentang tanda tangan fungsi Anda akan berurusan dengan ini semua yang Anda butuhkan. Terima kasih!
charlie roberts
Tidak bekerja dengan fungsi panah sederhana ini: f = (a, b) => void 0; pada getParameters(f)saya dapatkanTypeError: Cannot read property '1' of null
AT
@ ATA Saya baru saja memperbarui jawaban untuk memperbaiki dukungan untuk masalah Anda
Jaketr00
Terima kasih ... tetapi perlu diingat bahwa tanda kurung tidak lagi diperlukan, sehingga Anda dapat melakukan hal-hal seperti getParameters(a => b => c => d => a*b*c*d), yang dengan kode Anda masih memberikan itu TypeError: Cannot read property '1' of null... sedangkan yang ini berfungsi stackoverflow.com/a/29123804
AT
Tidak berfungsi ketika fungsi memiliki nilai default (peran, nama = "bob") Parameter yang diekstraksi adalah name = "bob" alih-alih "nama" yang diharapkan
Dmitri
7
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

=> ["a", "b", "c"]

Akan
sumber
7

Anda juga dapat menggunakan parser "esprima" untuk menghindari banyak masalah dengan komentar, spasi putih, dan hal-hal lain di dalam daftar parameter.

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

Ia bekerja bahkan dengan kode seperti ini:

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

Saya sudah mencoba melakukan ini sebelumnya, tetapi tidak pernah menemukan cara praktis untuk menyelesaikannya. Saya akhirnya melewati sebuah objek dan kemudian mengulanginya.

//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
sumber
Saya harus banyak fungsi yang sudah ditentukan sebelumnya yang dipanggil dengan parameter standar, bukan satu objek. Mengubah segalanya akan membutuhkan banyak waktu.
vikasde
2
Tanggapan ini bukan jawaban untuk pertanyaan awal yang didefinisikan dengan tepat. Ini menunjukkan solusi untuk masalah yang sama sekali berbeda. Pertanyaan aslinya mengacu pada teknik yang digunakan AngularJS untuk injeksi ketergantungannya. Nama argumen bermakna karena sesuai dengan dependensi modul yang secara otomatis disediakan oleh DI.
Lambder
4

Saya tidak tahu apakah solusi ini sesuai dengan masalah Anda, tetapi ini memungkinkan Anda mendefinisikan kembali fungsi apa pun yang Anda inginkan, tanpa harus mengubah kode yang menggunakannya. Panggilan yang ada akan menggunakan params yang diposisikan, sedangkan implementasi fungsi dapat menggunakan "params bernama" (parameter hash tunggal).

Saya pikir Anda akan tetap memodifikasi definisi fungsi yang ada jadi, mengapa tidak memiliki fungsi pabrik yang membuat apa yang Anda inginkan:

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

Semoga ini bisa membantu.

Ionuț G. Stan
sumber
4

Cara yang tepat untuk melakukan ini adalah dengan menggunakan parser JS. Berikut ini adalah contoh menggunakan biji .

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

Kode di sini menemukan nama-nama dari tiga (formal) parameter fungsi f. Ia melakukannya dengan memberi makan fke dalam acorn.parse().

Itay Maman
sumber
bagaimana dengan nilai?
eran otzap
2

Saya tidak tahu cara mendapatkan daftar parameter tetapi Anda dapat melakukan ini untuk mendapatkan berapa banyak yang diharapkan.

alert(doSomething.length);
Ólafur Waage
sumber
function gotcha (a, b = false, c) {}; alert(gotcha.length)
balupton
2

Mengambil jawaban dari @ jack-allan saya memodifikasi fungsi sedikit untuk memungkinkan properti default ES6 seperti:

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

masih kembali [ '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;
    }
}
padang rumput
sumber
1
Terima kasih, milik Anda adalah satu-satunya di utas ini yang bekerja untuk saya.
PADA
1

Bagaimana saya biasanya melakukannya:

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

Anda bahkan dapat ref argumen dengan nama fungsi seperti:

name.arguments;

Semoga ini membantu!

Cody
sumber
4
Dan di mana nama-nama parameter fungsi?
Andrej
Ah ... Maksudmu kamu menginginkannya dalam bentuk hash? Seolah-olah: var args = name.arguments; console.log('I WANNa SEE', args);menampilkan sesuatu seperti "{arg1: {...}, arg2: 'string'}"? Ini mungkin jelas hal-hal: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Argumen Fungsi adalah objek seperti 'Array', memiliki indeks Enumerable. Saya tidak berpikir pemetaan hash adalah mungkin, tetapi Anda bisa melewati Object-literal yang merupakan praktik yang baik jika Anda memiliki lebih dari 4 params.
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
sumber
1

Jawaban untuk ini membutuhkan 3 langkah:

  1. Untuk mendapatkan nilai parameter aktual yang diteruskan ke fungsi (sebut saja argValues). Ini lurus ke depan karena akan tersedia argumentsdi dalam fungsi.
  2. Untuk mendapatkan nama parameter dari tanda tangan fungsi (sebut saja itu argNames ). Ini tidak semudah dan membutuhkan parsing fungsi. Alih-alih melakukan regex kompleks sendiri dan khawatir tentang kasus tepi (parameter default, komentar, ...), Anda dapat menggunakan perpustakaan seperti babylon yang akan menguraikan fungsi menjadi pohon sintaksis abstrak dari mana Anda dapat memperoleh nama-nama parameter.
  3. Langkah terakhir adalah menggabungkan 2 array menjadi 1 array yang memiliki nama dan nilai semua parameter.

Kode akan seperti ini

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)

dan objek yang dicatat adalah

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

Dan inilah contoh yang berfungsi https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a

Gafi
sumber
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;
}
pengguna3018868
sumber
1

Wow sudah banyak jawaban .. Saya cukup yakin ini terkubur. Meski begitu saya pikir ini mungkin berguna untuk beberapa orang.

Saya tidak sepenuhnya puas dengan jawaban yang dipilih seperti pada ES6 tidak bekerja dengan baik dengan nilai default. Dan itu juga tidak memberikan informasi nilai default. Saya juga menginginkan fungsi yang ringan yang tidak bergantung pada lib eksternal.

Fungsi ini sangat berguna untuk keperluan debugging, misalnya: mencatat fungsi yang dipanggil dengan params, nilai-nilai param default dan argumen.

Saya menghabiskan beberapa waktu untuk ini kemarin, memecahkan RegExp yang tepat untuk menyelesaikan masalah ini dan inilah yang saya temukan. Ini bekerja dengan sangat baik dan saya sangat senang dengan hasilnya:

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

Seperti yang Anda tahu beberapa nama parameter menghilang karena Babel transpiler menghapusnya dari fungsi. Jika Anda akan menjalankan ini di NodeJS terbaru itu berfungsi seperti yang diharapkan (Hasil komentar dari NodeJS).

Catatan lain, seperti yang dinyatakan dalam komentar adalah bahwa tidak berfungsi dengan fungsi panah sebaris sebagai nilai default. Ini hanya membuatnya menjadi jauh lebih kompleks untuk mengekstraksi nilai menggunakan RegExp.

Tolong beri tahu saya jika ini bermanfaat bagi Anda! Senang mendengar umpan balik!

SnailCrusher
sumber
1

Saya akan memberi Anda contoh singkat di bawah ini:

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
sumber
Contoh itu hanya untuk mendapatkan nama parameter untuk Anda.
myzhou
Jawaban ini bermanfaat, tetapi tidak menjawab pertanyaan. Saya memilih karena memecahkan masalah saya, tetapi saya pikir itu harus dipindahkan ke tempat yang lebih tepat. Mungkin mencari pertanyaan terkait?
chharvey
1

Paket ini menggunakan penyusunan ulang untuk membuat AST dan kemudian nama parameter dikumpulkan dari mereka, ini memungkinkannya untuk mendukung pencocokan pola, argumen default, fungsi panah dan fitur ES6 lainnya.

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

pengguna3436035
sumber
1

Saya telah memodifikasi versi yang diambil dari AngularJS yang mengimplementasikan mekanisme injeksi dependensi untuk bekerja tanpa Angular. Saya juga telah memperbarui STRIP_COMMENTSregex untuk bekerja ECMA6, sehingga mendukung hal-hal seperti nilai default pada tanda tangan.

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
sumber
0

Anda dapat mengakses nilai argumen yang diteruskan ke fungsi menggunakan properti "argumen".

    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
sumber
Bukankah argumen sudah usang? Lihat saran dari Ionut G. Stan di atas.
vikasde
Vikasde benar. Mengakses argumentsproperti instance fungsi sudah tidak digunakan lagi. Lihat developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
Ionuț G. Stan
0

Cukup mudah.

Pada awalnya ada yang ditinggalkan arguments.callee- referensi ke fungsi yang disebut. Yang kedua jika Anda memiliki referensi ke fungsi Anda, Anda dapat dengan mudah mendapatkan representasi tekstual mereka. Pada yang ketiga jika Anda memanggil fungsi Anda sebagai konstruktor Anda juga dapat memiliki tautan melalui yourObject.constructor. NB: Solusi pertama sudah usang jadi jika Anda tidak bisa tidak menggunakannya, Anda juga harus memikirkan arsitektur aplikasi Anda. Jika Anda tidak memerlukan nama variabel yang tepat, gunakan saja di dalam fungsi variabel internal argumentstanpa sihir apa pun.

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

Semuanya akan memanggil toString dan ganti dengan re agar kita dapat membuat helper:

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

Beberapa contoh:

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

Nikmati dengan JS!

UPD: Jack Allan diberikan solusi yang sedikit lebih baik sebenarnya. GJ Jack!

Alex Yaroshevich
sumber
Ini bisa lebih mudah jika Anda menggunakan SomeFuncNamealih-alih arguments.callee(keduanya menunjuk ke objek fungsi itu sendiri).
Raphael Schweikert
0

Apa pun solusinya, itu tidak boleh rusak pada fungsi aneh, yang toString()tampak sama anehnya:

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

tangkapan layar dari konsol

Juga, mengapa menggunakan ekspresi reguler yang kompleks? Ini bisa dilakukan seperti:

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

Ini bekerja di mana-mana dengan setiap fungsi, dan satu-satunya regex adalah penghapusan spasi putih yang bahkan tidak memproses seluruh string karena .splittriknya.

Camilo Martin
sumber
0

Ok jadi pertanyaan lama dengan banyak jawaban yang memadai. di sini adalah penawaran saya yang tidak menggunakan regex, kecuali untuk tugas kasar stripping spasi putih. (Saya harus mencatat bahwa fungsi "strips_comments" sebenarnya menghapusnya, daripada menghapusnya secara fisik. Itu karena saya menggunakannya di tempat lain dan karena berbagai alasan memerlukan lokasi token non komentar asli untuk tetap utuh)

Ini adalah blok kode yang cukup panjang karena pasta ini termasuk kerangka uji mini.

    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);
tidak disinkronkan
sumber
0

Ini salah satu caranya:

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

Catatan: Ini hanya berfungsi pada browser yang mendukung arguments.callee.

Makan Goral
sumber
2
Loop sementara menghasilkan loop tak terbatas dengan kode yang disediakan (pada 20171121at1047EDT)
George 2.0 Hope
@ George2.0 Semoga Terima kasih telah menunjukkan itu. Saya akan memperbarui jawabannya.
Ates Goral
args.toSource bukan fungsi (baris 20) ... tetapi bahkan jika Anda mengubahnya ke: console.log (args.toString ()); ... Anda dapatkan ... [objek objek] ... lebih baik jika Anda melakukannya: console.log (JSON.stringify (args));
George 2.0 Hope
Banyak hal telah berubah sejak 2009!
Ates Goral
1
kalau-kalau ada yang muncul di sini untuk Bereaksi, fungsi ini, yang luar biasa, tidak akan bekerja dalam mode ketat.
Perorangan11
0

Catatan: jika Anda ingin menggunakan destruksi parameter ES6 dengan solusi teratas tambahkan baris berikut.

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

parameter fungsi nilai string gambar secara dinamis dari JSON . Karena item.product_image2 adalah string URL, Anda harus memasukkannya ke dalam tanda kutip ketika Anda memanggil changeImage di dalam parameter.

My Function Onclick

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

Fungsi saya

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
KAUSHAL J. SATHWARA
sumber