Làm thế nào để có được tên / giá trị tham số hàm một cách linh hoạt?


301

Có cách nào để có được các tên tham số chức năng của một chức năng động không?

Hãy nói rằng chức năng của tôi trông như thế này:

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

Bây giờ, làm thế nào tôi có thể đưa một danh sách các tên tham số và giá trị của chúng vào một mảng từ bên trong hàm?


Cảm ơn tất cả mọi người. Sau khi tìm kiếm xung quanh, tôi tìm thấy giải pháp trên SO: stackoverflow.com/questions/914968/NH Nó sử dụng biểu thức chính quy để lấy tên param. Nó có lẽ không phải là giải pháp tốt nhất, tuy nhiên nó hiệu quả với tôi.
vikasde

8
hãy tiếp tục và đánh dấu điều này như đã trả lời bạn thân. không có câu trả lời mới đang đến
Matthew Graves

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

Câu trả lời:


323

Hàm sau sẽ trả về một mảng các tên tham số của bất kỳ hàm nào được truyền vào.

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

Ví dụ sử dụng:

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

Chỉnh sửa :

Với sự phát minh ra ES6, chức năng này có thể được tăng gấp ba bởi các tham số mặc định. Đây là một bản hack nhanh nên hoạt động trong hầu hết các trường hợp:

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

Tôi nói hầu hết các trường hợp bởi vì có một số điều sẽ làm nó tăng lên

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

Chỉnh sửa : Tôi cũng lưu ý vikasde muốn các giá trị tham số trong một mảng cũng. Điều này đã được cung cấp trong một biến cục bộ có tên là đối số.

trích từ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Fiances_and_feft_scope/argument :

Đối tượng đối số không phải là một mảng. Nó tương tự như một Array, nhưng không có bất kỳ thuộc tính Array nào ngoại trừ chiều dài. Ví dụ, nó không có phương thức pop. Tuy nhiên, nó có thể được chuyển đổi thành một mảng thực:

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

Nếu tướng generic có sẵn, người ta có thể sử dụng như sau:

var args = Array.slice(arguments);

12
Lưu ý rằng giải pháp này có thể thất bại vì nhận xét và khoảng trắng - ví dụ: var fn = function(a /* fooled you)*/,b){};sẽ dẫn đến ["a", "/*", "fooled", "you"]
bubersson

1
Tôi đã sửa đổi hàm để trả về một mảng trống (thay vì null) khi không có bất kỳ đối số nào
BT

2
Có một chi phí để biên dịch một regex, do đó bạn muốn tránh biên dịch các regex phức tạp hơn một lần. Đây là lý do tại sao nó được thực hiện ngoài chức năng
Jack Allan

2
ĐÚNG: Sẽ sửa đổi regex bằng công cụ sửa đổi / s mà perl cho phép như vậy '.' cũng có thể phù hợp với dòng mới. Điều này là cần thiết cho các bình luận nhiều dòng bên trong / * * /. Hóa ra regex Javascript không cho phép sửa đổi / s. Regex ban đầu sử dụng [/ s / S] không khớp với các ký tự dòng mới. SOOO, xin vui lòng bỏ qua các bình luận trước đó.
tgoneil

1
@andes Lưu ý rằng bạn đang bao gồm phần tổng hợp regex trong các bài kiểm tra của mình. Những điều này chỉ nên được thực hiện một lần. Kết quả của bạn có thể khác nếu bạn di chuyển phần biên dịch regex sang bước thiết lập thay vì kiểm tra
Jack Allan

123

Dưới đây là mã được lấy từ AngularJS sử dụng kỹ thuật cho cơ chế tiêm phụ thuộc của nó.

Và đây là một lời giải thích về nó được lấy từ http://docs.angularjs.org/tutorial/step_05

Bộ tiêm phụ thuộc của Angular cung cấp dịch vụ cho bộ điều khiển của bạn khi bộ điều khiển đang được xây dựng. Người tiêm phụ thuộc cũng đảm nhiệm việc tạo ra bất kỳ sự phụ thuộc quá độ nào mà dịch vụ có thể có (các dịch vụ thường phụ thuộc vào các dịch vụ khác).

Lưu ý rằng tên của các đối số là đáng kể, bởi vì người tiêm sử dụng chúng để tra cứu các phụ thuộc.

/**
 * @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 với dòng máu của quỷ và sinh ra của satan, rõ ràng. Regex?! Sẽ rất tuyệt nếu có một cách xây dựng trong JS, phải không.
MP Aditya

6
@apaidnerd, rất đúng! Chỉ cần nghĩ - làm thế nào trong địa ngục được thực hiện? Trên thực tế tôi đã nghĩ về việc sử dụng functionName.toString () nhưng tôi hy vọng điều gì đó thanh lịch hơn (và có lẽ nhanh hơn)
sasha.sochka

2
@ sasha.sochka, đến đây tự hỏi chính xác điều tương tự, sau khi nhận ra không có cách nào được xây dựng để có được tên tham số với javascript
Hart Simha

14
để tiết kiệm thời gian của mọi người, bạn có thể có được chức năng này từ góc cạnh annotate = angular.injector.$$annotate
Nick

3
Tôi thực sự đã tìm kiếm trên Internet cho chủ đề này bởi vì tôi tò mò làm thế nào Angular đã làm nó ... bây giờ tôi biết và tôi cũng biết quá nhiều!
Steven Hunt

40

Đây là một giải pháp cập nhật cố gắng giải quyết tất cả các trường hợp cạnh được đề cập ở trên một cách gọn nhẹ:

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

Đầu ra thử nghiệm viết tắt (các trường hợp thử nghiệm đầy đủ được đính kèm bên dưới):

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


Điều này phá vỡ khi bình luận một dòng có mặt. Hãy thử điều này: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham

4
Bạn có thể nên thay thế func + ''bằng Function.toString.call(func)để bảo vệ chống lại trường hợp khi hàm có triển khai .toString () tùy chỉnh.
Paul Go

1
mũi tên béo =>.split(/\)[\{=]/, 1)[0]
Matt

1
cái này chia các đối tượng bị phá hủy (như ({ a, b, c })) thành tất cả các tham số bên trong việc hủy. để giữ nguyên các vật thể bị phá hủy, hãy thay đổi cái cuối cùng .splitthành: .split(/,(?![^{]*})/g)
Michael Auderer

1
Điều này cũng sẽ không hoạt động khi có các giá trị chuỗi mặc định có chứa "//" hoặc "/ *"
skerit

23

Giải pháp ít bị lỗi về không gian và nhận xét sẽ là:

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

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

["hi", "you"]

1
@AlexMills Một điều tôi nhận thấy là Spec for Arrow Function nói rằng chúng không nên được coi là 'hàm'. Có nghĩa là nó sẽ không phù hợp với điều này để phù hợp với các hàm mảng. 'Cái này' không được đặt theo cùng một cách và chúng cũng không nên được gọi dưới dạng hàm. Đó là điều tôi học được một cách khó khăn. ($ myService) => $ myService.doSthing () trông rất tuyệt, nhưng đó là sự lạm dụng của Hàm Array.
Andrew T Finnell

20

Rất nhiều câu trả lời ở đây sử dụng regexes, điều này tốt nhưng nó không xử lý các bổ sung mới cho ngôn ngữ quá tốt (như các hàm và lớp mũi tên). Cũng cần lưu ý là nếu bạn sử dụng bất kỳ chức năng nào trong số các mã được rút gọn này thì nó sẽ hoạt động. Nó sẽ sử dụng bất cứ tên rút gọn nào. Angular khắc phục điều này bằng cách cho phép bạn chuyển vào một chuỗi các chuỗi được sắp xếp khớp với thứ tự của các đối số khi đăng ký chúng với vùng chứa DI. Vì vậy, với giải pháp:

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ều này xử lý vấn đề phân tích cú pháp gốc và một vài loại hàm khác (ví dụ: hàm mũi tên). Đây là một ý tưởng về những gì nó có thể và không thể xử lý như sau:

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

Tùy thuộc vào những gì bạn muốn sử dụng nó cho ES6 Proxy và phá hủy có thể là lựa chọn tốt nhất của bạn. Ví dụ: nếu bạn muốn sử dụng nó để tiêm phụ thuộc (sử dụng tên của params) thì bạn có thể làm như sau:

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

Nó không phải là trình phân giải tiên tiến nhất hiện có nhưng nó đưa ra ý tưởng về cách bạn có thể sử dụng Proxy để xử lý nó nếu bạn muốn sử dụng trình phân tích cú pháp args cho DI đơn giản. Tuy nhiên, có một cảnh báo nhỏ trong phương pháp này. Chúng ta cần sử dụng các bài tập hủy hoại thay vì các thông số thông thường. Khi chúng ta truyền vào proxy của trình tiêm, việc hủy bỏ cũng giống như gọi hàm getter trên đối tượng.

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

Điều này xuất ra như sau:

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

Nó nối dây toàn bộ ứng dụng. Điểm tốt nhất là ứng dụng này rất dễ kiểm tra (bạn chỉ có thể khởi tạo từng lớp và chuyển qua các bản mô phỏng / sơ khai / vv). Ngoài ra nếu bạn cần trao đổi thực hiện, bạn có thể làm điều đó từ một nơi duy nhất. Tất cả điều này là có thể vì các đối tượng Proxy Proxy.

Lưu ý: Có rất nhiều công việc cần phải được thực hiện để làm điều này trước khi nó sẵn sàng để sử dụng sản xuất nhưng nó đưa ra ý tưởng về việc nó sẽ trông như thế nào.

Câu trả lời hơi muộn nhưng nó có thể giúp những người khác đang nghĩ về điều tương tự. 👍


13

Tôi biết đây là một câu hỏi cũ, nhưng những người mới bắt đầu đã sao chép nó xung quanh như thể đây là cách thực hành tốt trong bất kỳ mã nào. Hầu hết thời gian, phải phân tích biểu diễn chuỗi của hàm để sử dụng tên tham số của nó chỉ che giấu một lỗ hổng trong logic của mã.

Các tham số của hàm thực sự được lưu trữ trong một đối tượng giống như mảng được gọi arguments, trong đó đối số thứ nhất là arguments[0], đối số thứ hai arguments[1], v.v. Viết tên tham số trong ngoặc đơn có thể được xem như một cú pháp tốc ký. Điều này:

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

... giống như:

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

    console.log("does something");
}

Các biến được lưu trữ trong phạm vi của hàm, không phải là các thuộc tính trong một đối tượng. Không có cách nào để lấy tên tham số thông qua mã vì nó chỉ là một biểu tượng đại diện cho biến trong ngôn ngữ của con người.

Tôi luôn coi biểu diễn chuỗi của một hàm là một công cụ cho mục đích gỡ lỗi, đặc biệt là vì argumentsđối tượng giống như mảng này . Bạn không bắt buộc phải đặt tên cho các đối số ở vị trí đầu tiên. Nếu bạn thử phân tích một hàm được xâu chuỗi, nó thực sự không cho bạn biết về các tham số không tên khác mà nó có thể mất.

Đây là một tình huống thậm chí còn tồi tệ hơn và phổ biến hơn. Nếu một hàm có nhiều hơn 3 hoặc 4 đối số, thì có thể logic để truyền cho nó một đối tượng, điều này dễ làm việc hơn.

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

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

Trong trường hợp này, chính hàm sẽ có thể đọc qua đối tượng mà nó nhận và tìm các thuộc tính của nó và nhận cả tên và giá trị của chúng, nhưng cố gắng phân tích biểu diễn chuỗi của hàm sẽ chỉ cung cấp cho bạn "obj" cho các tham số, không hữu ích chút nào


1
Tôi nghĩ trường hợp của một cái gì đó như thế này thường là: gỡ lỗi / ghi nhật ký, một số loại trang trí thực hiện công cụ thú vị (thuật ngữ kỹ thuật 😁) hoặc xây dựng khung tiêm phụ thuộc cho các ứng dụng của bạn để tự động tiêm dựa trên tên đối số (đây là cách góc cạnh làm). Một trường hợp sử dụng thực sự thú vị khác là trong nút promisify (đó là một thư viện có chức năng thường lấy lại cuộc gọi và sau đó chuyển đổi nó thành Promise). Họ sử dụng điều này để tìm tên phổ biến cho các cuộc gọi lại (như cb / gọi lại / vv) và sau đó họ có thể kiểm tra xem chức năng này có đồng bộ hoặc đồng bộ hóa trước khi gói nó không.
James đã vẽ

Xem tập tin này cho trình phân tích cú pháp của họ . Nó hơi ngây thơ nhưng nó xử lý phần lớn các trường hợp.
James đã vẽ

Thật thú vị, tôi ngạc nhiên khi một thư viện như vậy có bất kỳ sự chú ý. Vâng, có nhiều vấn đề mở về các tình huống có vấn đề như những gì tôi đã mô tả. Như tôi đã nói, nếu dùng cho mục đích gỡ lỗi thì không sao, nhưng dựa vào chuyển đổi chuỗi chức năng trong môi trường sản xuất thì quá rủi ro.
Domino

Trên một sidenote "hài hước", bạn có thể làm cho tất cả các chức năng hoạt động như thể chúng được tích hợp ẩn danh bằng cách chạy này:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino

1
Đồng ý, sẽ hơi lộn xộn khi xử lý nó như thế này. Việc sử dụng tốt nhất và đơn giản nhất là tiêm phụ thuộc. Trong trường hợp đó, bạn sở hữu mã và có thể xử lý việc đặt tên và các khu vực khác của mã. Tôi nghĩ rằng đây là nơi nó sẽ được sử dụng nhiều nhất. Tôi hiện đang sử dụng esprima (thay vì regex) và ES6 Proxy( bẫy xây dựngáp dụng bẫy ) và Reflectionđể xử lý DI cho một số mô-đun của tôi. Nó khá chắc chắn.
James Drew

10

Vì JavaScript là ngôn ngữ kịch bản, tôi cảm thấy rằng tính hướng nội của nó sẽ hỗ trợ nhận các tên tham số hàm. Dựa vào chức năng đó là vi phạm các nguyên tắc đầu tiên, vì vậy tôi quyết định tìm hiểu thêm về vấn đề này.

Điều đó dẫn tôi đến câu hỏi này nhưng không có giải pháp tích hợp. Điều này dẫn tôi đến câu trả lời này giải thích rằng argumentschỉ bị phản đối ngoài chức năng, vì vậy chúng tôi không thể sử dụng nữa myFunction.argumentshoặc chúng tôi nhận được:

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

Thời gian để xắn tay áo của chúng tôi và đi làm:

Truy xuất tham số hàm yêu cầu trình phân tích cú pháp vì các biểu thức phức tạp như 4*(5/3)có thể được sử dụng làm giá trị mặc định. Vì vậy, câu trả lời của Gaafar hoặc câu trả lời của James Drew cho đến nay là cách tiếp cận tốt nhất.

Tôi đã thử các trình phân tích cú pháp babylonesprima nhưng không may là chúng không thể phân tích các hàm ẩn danh độc lập, như được chỉ ra trong câu trả lời của Mateusz Charytoniuk . Tôi đã tìm ra một cách giải quyết khác mặc dù bằng cách bao quanh mã trong ngoặc đơn, để không thay đổi logic:

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

Các dòng mới ngăn chặn các vấn đề với //(bình luận một dòng).

Nếu không có trình phân tích cú pháp, tùy chọn tốt nhất tiếp theo là sử dụng một kỹ thuật đã thử và đúng như các biểu thức chính quy của trình tiêm phụ thuộc của Angular.js. Tôi kết hợp một phiên bản chức năng của câu trả lời Lambder của với câu trả lời humbletim của và thêm một tùy chọn ARROWboolean để kiểm soát cho dù chức năng ES6 mỡ mũi tên được cho phép bởi các biểu thức thông thường.


Đây là hai giải pháp tôi đặt cùng nhau. Lưu ý rằng những điều này không có logic để phát hiện xem một hàm có cú pháp hợp lệ hay không, chúng chỉ trích xuất các đối số. Điều này thường ổn vì chúng ta thường chuyển các hàm được phân tích cú pháp để getArguments()cú pháp của chúng đã hợp lệ.

Tôi sẽ cố gắng quản lý các giải pháp này tốt nhất có thể, nhưng không cần nỗ lực từ những người duy trì JavaScript, đây sẽ vẫn là một vấn đề mở.

Phiên bản Node.js (không thể chạy được cho đến khi StackOverflow hỗ trợ 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() {}');

Ví dụ làm việc đầy đủ:

https://repl.it/repls/SandybrownPhonyAngles

Phiên bản trình duyệt (lưu ý rằng nó dừng ở giá trị mặc định phức tạp đầu tiên):

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

Ví dụ làm việc đầy đủ:

https://repl.it/repls/StupendousShowy Offerices


Nếu tôi không nhầm, trong phiên bản trình duyệt của bạn, điều kiện đầu tiên trong FUNC_ARGS sẽ hoạt động cho cả hai mũi tên và các chức năng truyền thống, vì vậy bạn không cần phần thứ hai và vì vậy bạn có thể loại bỏ sự phụ thuộc vào M ARI TÊN.
pwilcox

Điều đó thật tuyệt! Tôi đang tìm kiếm một giải pháp như thế này sử dụng trình phân tích cú pháp để trình bày cú pháp ES6. Tôi đang lên kế hoạch sử dụng điều này để tạo ra một trò chơi phù hợp với Jest vì nó chỉ đơn giản là sử dụng function.length có những hạn chế với các tham số mặc định và tôi muốn có thể xác nhận các tham số còn lại.
cue8chalk

Cần chỉ ra rằng trường hợp thử nghiệm thứ năm có chứa dấu ngoặc đơn trong giá trị mặc định hiện không thành công. Tôi ước regex-fu của tôi đủ mạnh để sửa chữa, xin lỗi!
Dale Anderson

8

Tôi đã đọc hầu hết các câu trả lời ở đây, và tôi muốn thêm một lớp lót của tôi.

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

hoặc là

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

hoặc cho chức năng một lớp lót trong ECMA6

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

__

Giả sử bạn có một chức năng

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

Mã dưới đây sẽ trở lại "abc,def,ghi,jkl"

Mã đó cũng sẽ hoạt động với việc thiết lập chức năng mà Camilo Martin đã cung cấp:

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

Cũng với nhận xét của Bubersson về câu trả lời của Jack Allan :

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

__

Giải trình

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

Điều này tạo ra một biểu thức chính quy với new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). Tôi phải sử dụng new RegExpvì tôi đang tiêm một biến ( Function.name, tên của hàm đang được nhắm mục tiêu) vào RegExp.

Ví dụ Nếu tên hàm là "foo" ( function foo()), RegExp sẽ là /foo\s*\((.*?)\)/.

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

Sau đó, nó chuyển đổi toàn bộ chức năng thành một chuỗi và loại bỏ tất cả các dòng mới. Loại bỏ các dòng mới giúp thiết lập chức năng Camilo Martin đã đưa ra.

.exec(...)[1]

Đây là RegExp.prototype.execchức năng. Về cơ bản, nó khớp với Số mũ thông thường ( new RegExp()) vào Chuỗi ( Function.toString()). Sau đó, [1]sẽ trả về Nhóm Capture đầu tiên được tìm thấy trong Số mũ thường xuyên ( (.*?)).

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

Điều này sẽ xóa mọi bình luận bên trong /**/, và xóa tất cả các khoảng trắng.


Điều này hiện cũng hỗ trợ đọc và hiểu các hàm mũi tên ( =>), chẳng hạn như f = (a, b) => void 0;, trong đó Function.toString()sẽ trả về (a, b) => void 0thay vì các hàm thông thường function f(a, b) { return void 0; }. Biểu thức chính quy ban đầu sẽ có một lỗi trong sự nhầm lẫn của nó, nhưng bây giờ được tính đến.

Sự thay đổi là từ new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) thành new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


Nếu bạn muốn biến tất cả các tham số thành một mảng thay vì một chuỗi được phân tách bằng dấu phẩy, cuối cùng chỉ cần thêm .split(',').


khá đẹp. một dòng, xử lý các chức năng mũi tên, không phụ thuộc bên ngoài và bao gồm quá nhiều trường hợp cạnh, ít nhất là cho mục đích sử dụng của tôi. nếu bạn có thể đưa ra một số giả định hợp lý về chữ ký hàm bạn sẽ xử lý thì đây là tất cả những gì bạn cần. cảm ơn!
charlie ầm ầm

Không hoạt động với chức năng mũi tên đơn giản này : f = (a, b) => void 0; vào ngày getParameters(f)tôi nhận đượcTypeError: Cannot read property '1' of null
AT

@AT Tôi vừa cập nhật câu trả lời để khắc phục hỗ trợ cho vấn đề của bạn
Jaketr00

Cảm ơn rất nhiều nhưng hãy nhớ rằng dấu ngoặc đơn không còn cần thiết nữa, vì vậy bạn có thể làm những việc như thế getParameters(a => b => c => d => a*b*c*d), với mã của bạn vẫn mang lại điều đó TypeError: Cannot read property '1' of nulltrong khi điều này hoạt động stackoverflow.com/a/29123804
AT

Không hoạt động khi hàm có các giá trị mặc định (vai trò, tên = "bob") Tham số được trích xuất là name = "bob" thay vì "tên" dự kiến
Dmitri

7
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

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


7

Bạn cũng có thể sử dụng trình phân tích cú pháp "esprima" để tránh nhiều vấn đề với các bình luận, khoảng trắng và những thứ khác trong danh sách tham số.

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

Nó hoạt động ngay cả với mã như thế này:

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

6

Tôi đã thử làm điều này trước đây, nhưng chưa bao giờ tìm thấy một cách thức để hoàn thành nó. Tôi cuối cùng đã chuyển qua một đối tượng và sau đó lặp qua nó.

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

Tôi có nhiều hàm đã được xác định trước đang được gọi với các tham số tiêu chuẩn thay vì một đối tượng. Thay đổi mọi thứ sẽ mất nhiều thời gian.
vikasde

2
Câu trả lời này không phải là câu trả lời cho câu hỏi ban đầu được xác định chính xác. Nó cho thấy một giải pháp cho vấn đề hoàn toàn khác nhau. Câu hỏi ban đầu đề cập đến kỹ thuật AngularJS sử dụng phương pháp tiêm phụ thuộc của nó. Tên đối số có ý nghĩa vì chúng tương ứng với các phụ thuộc của mô-đun mà DI tự động cung cấp.
Lambder

4

Tôi không biết giải pháp này có phù hợp với vấn đề của bạn không, nhưng nó cho phép bạn xác định lại bất kỳ chức năng nào bạn muốn mà không phải thay đổi mã sử dụng nó. Các cuộc gọi hiện tại sẽ sử dụng các tham số định vị, trong khi việc thực hiện chức năng có thể sử dụng "các tham số có tên" (một tham số băm duy nhất).

Tôi nghĩ rằng dù sao bạn cũng sẽ sửa đổi các định nghĩa hàm hiện có, vậy tại sao không có hàm xuất xưởng tạo ra thứ bạn muốn:

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

Hy vọng nó giúp.


4

Cách thích hợp để làm điều này là sử dụng trình phân tích cú pháp JS. Dưới đây là một ví dụ sử dụng acorn .

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

Mã ở đây tìm tên của ba tham số (chính thức) của hàm f. Nó làm như vậy bằng cách cho ăn fvào acorn.parse().


Còn các giá trị thì sao?
eran otzap

2

Tôi không biết làm thế nào để có được một danh sách các tham số nhưng bạn có thể làm điều này để có được bao nhiêu nó mong đợi.

alert(doSomething.length);

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

2

Lấy câu trả lời từ @ jack-allan Tôi đã sửa đổi chức năng một chút để cho phép các thuộc tính mặc định ES6 như:

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

vẫn quay về [ '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
Cảm ơn, bạn là người duy nhất trong chủ đề này làm việc cho tôi.
TẠI

1

Làm thế nào tôi thường làm điều đó:

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

Bạn thậm chí có thể giới thiệu các đối số bằng tên hàm như:

name.arguments;

Hi vọng điêu nay co ich!


4
Và đâu là tên của các tham số chức năng?
Andrej

À ... Ý bạn là bạn muốn nó ở dạng băm? Như thể: var args = name.arguments; console.log('I WANNa SEE', args);xuất ra một cái gì đó như "{arg1: {...}, arg2: 'chuỗi'}"? Điều này có thể làm sáng tỏ mọi thứ : (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Đối số chức năng là một đối tượng giống như 'Mảng', có vô số chỉ số. Tôi không nghĩ rằng ánh xạ băm là có thể, nhưng bạn chỉ có thể vượt qua một đối tượng theo nghĩa đen trong đó là cách thực hành tốt nếu bạn có nhiều hơn 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

1

Câu trả lời cho điều này đòi hỏi 3 bước:

  1. Để có được các giá trị của các tham số thực tế được truyền cho hàm (hãy gọi nó argValues). Điều này là thẳng về phía trước vì nó sẽ có sẵn như argumentsbên trong chức năng.
  2. Để lấy tên tham số từ chữ ký hàm (hãy gọi nó argNames). Điều này không dễ dàng và đòi hỏi phải phân tích chức năng. Thay vì tự thực hiện regex phức tạp và lo lắng về các trường hợp cạnh (tham số mặc định, nhận xét, ...), bạn có thể sử dụng thư viện như babylon sẽ phân tích hàm thành một cây cú pháp trừu tượng để bạn có thể lấy tên của các tham số.
  3. Bước cuối cùng là nối 2 mảng lại với nhau thành 1 mảng có tên và giá trị của tất cả các tham số.

Mã sẽ như thế này

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)

và đối tượng đăng nhập sẽ là

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

Và đây là một ví dụ hoạt động 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

Wow rất nhiều câu trả lời đã .. Tôi khá chắc chắn rằng điều này sẽ bị chôn vùi. Mặc dù vậy tôi nghĩ rằng điều này có thể hữu ích cho một số người.

Tôi không hoàn toàn hài lòng với các câu trả lời đã chọn vì trong ES6, nó không hoạt động tốt với các giá trị mặc định. Và nó cũng không cung cấp thông tin giá trị mặc định. Tôi cũng muốn một chức năng nhẹ không phụ thuộc vào lib bên ngoài.

Hàm này rất hữu ích cho mục đích gỡ lỗi, ví dụ: ghi nhật ký hàm với các tham số, giá trị param và đối số mặc định của nó.

Tôi đã dành một chút thời gian cho việc này ngày hôm qua, bẻ khóa RegExp đúng để giải quyết vấn đề này và đây là những gì tôi nghĩ ra. Nó hoạt động rất tốt và tôi rất hài lòng với kết quả:

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

Như bạn có thể nói một số tên tham số biến mất vì bộ chuyển mã Babel loại bỏ chúng khỏi hàm. Nếu bạn chạy cái này trong NodeJS mới nhất thì nó hoạt động như mong đợi (Kết quả nhận xét là từ NodeJS).

Một lưu ý khác, như đã nêu trong bình luận là nó không hoạt động với các hàm mũi tên được gạch chéo làm giá trị mặc định. Điều này chỉ đơn giản là làm cho nó phức tạp để trích xuất các giá trị bằng RegExp.

Xin vui lòng cho tôi biết nếu điều này là hữu ích cho bạn! Rất thích nghe một số thông tin phản hồi!


1

Tôi sẽ cho bạn một ví dụ ngắn dưới đây:

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

Ví dụ đó chỉ là để lấy tên của các tham số cho bạn.
myzhou

Câu trả lời này rất hữu ích, nhưng nó không trả lời câu hỏi. Tôi đã bỏ phiếu vì nó giải quyết vấn đề của tôi, nhưng tôi nghĩ nó nên được chuyển đi đâu đó thích hợp hơn. Có lẽ tìm kiếm các câu hỏi liên quan?
chharvey


1

Tôi đã sửa đổi phiên bản lấy từ AngularJS thực hiện cơ chế tiêm phụ thuộc để hoạt động mà không có Angular. Tôi cũng đã cập nhật STRIP_COMMENTSregex để làm việc ECMA6, vì vậy nó hỗ trợ những thứ như giá trị mặc định trong chữ ký.

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

Bạn có thể truy cập các giá trị đối số được truyền cho một hàm bằng cách sử dụng thuộc tính "đối số".

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

Không phải là tranh luận không được tán thành? Xem gợi ý của Iovy G. Stan ở trên.
vikasde

vikasde là đúng. Truy cập thuộc argumentstính của một thể hiện chức năng không được chấp nhận. Xem developer.mozilla.org/en/Core_JavaScript_1.5_Reference/,
Ionuț G. Stan

0

Nó khá dễ.

Đầu tiên, có một sự phản đối arguments.callee- một tham chiếu đến hàm được gọi. Ở lần thứ hai nếu bạn có một tham chiếu đến chức năng của mình, bạn có thể dễ dàng có được biểu diễn văn bản của họ. Ở lần thứ ba nếu bạn gọi hàm của mình là hàm tạo, bạn cũng có thể có một liên kết thông qua yourObject.constructor. Lưu ý: Giải pháp đầu tiên không dùng nữa vì vậy nếu bạn không thể sử dụng nó, bạn cũng phải suy nghĩ về kiến ​​trúc ứng dụng của mình. Nếu bạn không cần tên biến chính xác, chỉ cần sử dụng bên trong một biến nội bộ của hàm argumentsmà không cần bất kỳ phép thuật nào.

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Fiances_and_feft_scope/argument/callee

Tất cả trong số họ sẽ gọi toString và thay thế bằng re để chúng tôi có thể tạo một trình trợ giúp:

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

Vài ví dụ:

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

Thưởng thức với JS!

CẬP NHẬT: Jack Allan đã được cung cấp một chút giải pháp tốt hơn thực sự. GJ Jack!


Điều này có thể còn đơn giản hơn nữa nếu bạn sử dụng SomeFuncNamethay vì arguments.callee(cả hai đều trỏ đến chính đối tượng hàm).
Raphael Schweikert

0

Dù là giải pháp nào, nó cũng không được phá vỡ các chức năng wierd, mà toString()trông giống như wierd:

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

ảnh chụp màn hình từ bảng điều khiển

Ngoài ra, tại sao sử dụng các biểu thức chính quy phức tạp? Điều này có thể được thực hiện như:

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

Điều này hoạt động ở mọi nơi với mọi chức năng và regex duy nhất là loại bỏ khoảng trắng thậm chí không xử lý toàn bộ chuỗi do .splitthủ thuật.


0

Ok vì vậy một câu hỏi cũ với nhiều câu trả lời đầy đủ. đây là lời đề nghị của tôi không sử dụng regex, ngoại trừ nhiệm vụ cấp độ là tước khoảng trắng. (Tôi nên lưu ý rằng chức năng "dải_comments" thực sự loại bỏ chúng, thay vì loại bỏ chúng một cách vật lý. Đó là vì tôi sử dụng nó ở nơi khác và vì nhiều lý do cần vị trí của các mã thông báo không nhận xét ban đầu để giữ nguyên)

Đây là một khối mã khá dài vì việc dán này bao gồm một khung kiểm tra nhỏ.

    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

Đây là một cách:

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

Lưu ý: Điều này chỉ hoạt động trên các trình duyệt hỗ trợ arguments.callee.


2
Vòng lặp while dẫn đến một vòng lặp vô hạn với mã được cung cấp (kể từ 20171121at1047EDT)
George 2.0 Hope

@ George2.0ITH Cảm ơn bạn đã chỉ ra điều đó. Tôi sẽ cập nhật câu trả lời.
Ates Goral

args.toSource không phải là một hàm (dòng 20) ... nhưng ngay cả khi bạn thay đổi nó thành: console.log (args.toString ()); ... bạn nhận được ... [đối tượng đối tượng] ... tốt hơn nếu bạn làm: console.log (JSON.opesify (args));
George 2.0 Hy vọng

Mọi thứ đã thay đổi khá nhiều kể từ năm 2009!
Ates Goral

1
trong trường hợp bất cứ ai xuất hiện ở đây cho React, chức năng này rất tuyệt vời sẽ không hoạt động ở chế độ nghiêm ngặt.

0

Lưu ý: nếu bạn muốn sử dụng hủy tham số ES6 với giải pháp hàng đầu, hãy thêm dòng sau.

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

-1

hàm tham số chuỗi giá trị hình ảnh động từ JSON . Vì item.product_image2 là một chuỗi URL, nên bạn cần đặt nó trong dấu ngoặc kép khi bạn gọi tham số changeImage bên trong tham số.

Chức năng của tôi Onclick

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

Chức năng của tôi

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.