Có cách nào để cung cấp các tham số được đặt tên trong lệnh gọi hàm trong JavaScript không?


207

Tôi thấy tính năng tham số được đặt tên trong C # khá hữu ích trong một số trường hợp.

calculateBMI(70, height: 175);

Tôi có thể sử dụng cái gì nếu tôi muốn cái này trong JavaScript?


Điều tôi không muốn là đây:

myFunction({ param1: 70, param2: 175 });

function myFunction(params){
  // Check if params is an object
  // Check if the parameters I need are non-null
  // Blah blah
}

Cách tiếp cận đó tôi đã sử dụng. Có cách nào khác không?

Tôi ổn khi sử dụng bất kỳ thư viện để làm điều này.


Tôi không nghĩ rằng điều này là có thể, nhưng bạn có thể thử đặt một số không xác định ở những nơi trống rỗng. Đó là cách xấu. Sử dụng các đối tượng, tốt của nó.
Vladislav Qulin

14
Không, JavaScript / EcmaScript không hỗ trợ các tham số được đặt tên. Lấy làm tiếc.
smilly92

1
Tôi đã biết điều đó. Cảm ơn. Tôi đã tìm kiếm một số cách liên quan đến việc điều chỉnh những gì hiện có Functiontrong javascript có thể làm.
Robin Maben

1
Hiện tại Functiontrong Javascript không thể thay đổi cú pháp cốt lõi của Javascript
Gareth

2
Tôi không nghĩ Javascript hỗ trợ tính năng này. Tôi nghĩ rằng gần nhất bạn có thể đến với các tham số được đặt tên là (1) thêm nhận xét calculateBMI(70, /*height:*/ 175);, (2) cung cấp một đối tượng calculateBMI(70, {height: 175})hoặc (3) sử dụng hằng số const height = 175; calculateBMI(70, height);.
tfmontague

Câu trả lời:


211

ES2015 trở lên

Trong ES2015, phá hủy tham số có thể được sử dụng để mô phỏng các tham số được đặt tên. Nó sẽ yêu cầu người gọi vượt qua một đối tượng, nhưng bạn có thể tránh tất cả các kiểm tra bên trong hàm nếu bạn cũng sử dụng các tham số mặc định:

myFunction({ param1 : 70, param2 : 175});

function myFunction({param1, param2}={}){
  // ...function body...
}

// Or with defaults, 
function myFunc({
  name = 'Default user',
  age = 'N/A'
}={}) {
  // ...function body...
}

ES5

Có một cách để đến gần với những gì bạn muốn, nhưng nó dựa trên đầu ra của Function.prototype.toString [ES5] , đó là việc triển khai phụ thuộc ở một mức độ nào đó, vì vậy nó có thể không tương thích với nhiều trình duyệt.

Ý tưởng là phân tích các tên tham số từ biểu diễn chuỗi của hàm để bạn có thể liên kết các thuộc tính của một đối tượng với tham số tương ứng.

Một cuộc gọi chức năng sau đó có thể trông giống như

func(a, b, {someArg: ..., someOtherArg: ...});

trong đó ablà các đối số vị trí và đối số cuối cùng là một đối tượng với các đối số được đặt tên.

Ví dụ:

var parameterfy = (function() {
    var pattern = /function[^(]*\(([^)]*)\)/;

    return function(func) {
        // fails horribly for parameterless functions ;)
        var args = func.toString().match(pattern)[1].split(/,\s*/);

        return function() {
            var named_params = arguments[arguments.length - 1];
            if (typeof named_params === 'object') {
                var params = [].slice.call(arguments, 0, -1);
                if (params.length < args.length) {
                    for (var i = params.length, l = args.length; i < l; i++) {
                        params.push(named_params[args[i]]);
                    }
                    return func.apply(this, params);
                }
            }
            return func.apply(null, arguments);
        };
    };
}());

Mà bạn sẽ sử dụng như:

var foo = parameterfy(function(a, b, c) {
    console.log('a is ' + a, ' | b is ' + b, ' | c is ' + c);     
});

foo(1, 2, 3); // a is 1  | b is 2  | c is 3
foo(1, {b:2, c:3}); // a is 1  | b is 2  | c is 3
foo(1, {c:3}); // a is 1  | b is undefined  | c is 3
foo({a: 1, c:3}); // a is 1  | b is undefined  | c is 3 

BẢN GIỚI THIỆU

Có một số nhược điểm đối với phương pháp này (bạn đã được cảnh báo!):

  • Nếu đối số cuối cùng là một đối tượng, nó được coi là "đối tượng đối số được đặt tên"
  • Bạn sẽ luôn nhận được nhiều đối số như bạn đã xác định trong hàm, nhưng một số trong số chúng có thể có giá trị undefined(khác với không có giá trị nào cả). Điều đó có nghĩa là bạn không thể sử dụng arguments.lengthđể kiểm tra có bao nhiêu đối số đã được thông qua.

Thay vì có một hàm tạo trình bao bọc, bạn cũng có thể có một hàm chấp nhận một hàm và các giá trị khác nhau làm đối số, chẳng hạn như

call(func, a, b, {posArg: ... });

hoặc thậm chí mở rộng Function.prototypeđể bạn có thể làm:

foo.execute(a, b, {posArg: ...});

Vâng ... đây là một ví dụ cho điều đó: jsfiddle.net/9U328/1 (mặc dù bạn nên sử dụng Object.definePropertyvà đặt enumerablethành false). Người ta phải luôn luôn cẩn thận khi mở rộng các đối tượng bản địa. Toàn bộ cách tiếp cận cảm thấy một chút hack, vì vậy tôi sẽ không mong đợi nó hoạt động ngay bây giờ và mãi mãi;)
Felix Kling

Lưu ý Tôi sẽ nhận được để đưa này để sử dụng. Đánh dấu là câu trả lời! ... Bây giờ;)
Robin Maben

1
Rất nhỏ: Tôi không nghĩ cách tiếp cận này sẽ bắt được Hàm mũi tên EcmaScript 6 . Hiện tại không phải là một mối quan tâm lớn, nhưng có thể đáng được đề cập trong phần hãy cẩn thận trong câu trả lời của bạn.
NobodyMan

3
@NobodyMan: Đúng. Tôi đã viết câu trả lời này trước khi các chức năng mũi tên là một điều. Trong ES6, tôi thực sự sẽ dùng đến việc hủy tham số.
Felix Kling

1
Về vấn đề undefinedvs "không có giá trị", có thể nói thêm rằng đây chính xác là cách các giá trị mặc định của các hàm JS đang hoạt động - coi undefinednhư một giá trị còn thiếu.
Dmitri Zaitsev

71

Không - cách tiếp cận đối tượng là câu trả lời của JavaScript cho vấn đề này. Không có vấn đề gì với điều này với điều kiện là chức năng của bạn mong đợi một đối tượng hơn là các thông số riêng biệt.


34
@RobertMaben - Câu trả lời cho câu hỏi cụ thể được hỏi là không có cách riêng để thu thập các vars hoặc hàm được khai báo mà không biết chúng sống trên một không gian tên cụ thể. Chỉ vì câu trả lời ngắn gọn, nó không phủ nhận sự phù hợp của nó như một câu trả lời - bạn có đồng ý không? Có rất nhiều câu trả lời ngắn hơn, dọc theo dòng "không, không thể". Chúng có thể ngắn, nhưng chúng cũng là câu trả lời cho câu hỏi.
Mitya

3

1
Đây chắc chắn là một câu trả lời công bằng - 'tham số được đặt tên' là một tính năng ngôn ngữ. Cách tiếp cận đối tượng là điều tốt nhất tiếp theo trong trường hợp không có tính năng như vậy.
fatuhoku

32

Vấn đề này đã được một peeve vật nuôi của tôi trong một thời gian. Tôi là một lập trình viên dày dạn kinh nghiệm với nhiều ngôn ngữ. Một trong những ngôn ngữ yêu thích của tôi mà tôi rất vui khi sử dụng là Python. Python hỗ trợ các tham số được đặt tên mà không có bất kỳ mánh khóe nào .... Kể từ khi tôi bắt đầu sử dụng Python (một thời gian trước đây), mọi thứ trở nên dễ dàng hơn. Tôi tin rằng mọi ngôn ngữ nên hỗ trợ các tham số có tên, nhưng thực tế không phải vậy.

Nhiều người nói chỉ cần sử dụng thủ thuật "Truyền đối tượng" để bạn có tên tham số.

/**
 * My Function
 *
 * @param {Object} arg1 Named arguments
 */
function myFunc(arg1) { }

myFunc({ param1 : 70, param2 : 175});

Và nó hoạt động rất tốt, ngoại trừ ..... khi nói đến hầu hết các IDE ngoài kia, rất nhiều nhà phát triển của chúng tôi dựa vào gợi ý loại / đối số trong IDE của chúng tôi. Cá nhân tôi sử dụng PHP Storm (Cùng với các IDE JetBrains khác như PyCharm cho python và AppCode cho Objective C)

Và vấn đề lớn nhất khi sử dụng thủ thuật "Truyền đối tượng" là khi bạn gọi hàm, IDE cung cấp cho bạn một gợi ý loại duy nhất và đó là ... Làm thế nào chúng ta phải biết các tham số và loại nào sẽ đi vào đối tượng arg1?

Tôi không biết những tham số nào nên đi trong arg1

Vì vậy, ... mẹo "Truyền đối tượng" không hiệu quả với tôi ... Nó thực sự gây ra nhiều đau đầu hơn khi phải xem tài liệu của từng hàm trước khi tôi biết tham số nào mà hàm mong đợi .... Chắc chắn, nó rất tuyệt cho khi bạn đang duy trì mã hiện có, nhưng thật kinh khủng khi viết mã mới.

Chà, đây là kỹ thuật tôi sử dụng .... Bây giờ, có thể có một số vấn đề với nó, và một số nhà phát triển có thể nói với tôi rằng tôi đang làm sai, và tôi có một suy nghĩ cởi mở khi nói về những điều này ... Tôi luôn sẵn sàng xem xét các cách tốt hơn để hoàn thành một nhiệm vụ ... Vì vậy, nếu có vấn đề với kỹ thuật này, thì các bình luận đều được chào đón.

/**
 * My Function
 *
 * @param {string} arg1 Argument 1
 * @param {string} arg2 Argument 2
 */
function myFunc(arg1, arg2) { }

var arg1, arg2;
myFunc(arg1='Param1', arg2='Param2');

Bằng cách này, tôi có cả hai thế giới tốt nhất ... mã mới rất dễ viết vì IDE của tôi cung cấp cho tôi tất cả các gợi ý đối số phù hợp ... Và, trong khi duy trì mã sau này, tôi có thể thấy trong nháy mắt, không chỉ giá trị được truyền cho hàm, nhưng cũng là tên của đối số. Chi phí duy nhất tôi thấy là khai báo tên đối số của bạn dưới dạng các biến cục bộ để tránh gây ô nhiễm không gian tên toàn cầu. Chắc chắn, đó là một chút gõ thêm, nhưng tầm thường so với thời gian cần thiết để tìm kiếm các tài liệu trong khi viết mã mới hoặc duy trì mã hiện có.

Bây giờ, tôi có tất cả các tham số và loại khi tạo mã mới


2
Điều duy nhất với kỹ thuật này là thực tế là bạn không thể thay đổi thứ tự của các tham số ... Mặc dù vậy, cá nhân tôi vẫn ổn với điều đó.
Ray Perea

13
Có vẻ như đây chỉ là vấn đề rắc rối khi một số nhà bảo trì trong tương lai xuất hiện và nghĩ rằng họ có thể thay đổi thứ tự đối số (nhưng rõ ràng là không thể).
không ai

1
@AndrewMedico Tôi đồng ý ... có vẻ như bạn chỉ có thể thay đổi thứ tự đối số như trong Python. Điều duy nhất tôi có thể nói về điều đó là họ sẽ tìm ra thực sự nhanh chóng khi thay đổi thứ tự đối số phá vỡ chương trình.
Ray Perea

7
Tôi cho rằng điều đó myFunc(/*arg1*/ 'Param1', /*arg2*/ 'Param2');tốt hơn myFunc(arg1='Param1', arg2='Param2');, bởi vì điều đó không có cơ hội lừa người đọc hơn bất kỳ cuộc tranh luận có tên thực tế nào đang diễn ra
Eric

2
Mẫu được đề xuất không liên quan gì đến các đối số được đặt tên, thứ tự vẫn là vấn đề, các tên không cần phải đồng bộ với các tham số thực tế, không gian tên bị ô nhiễm với các biến không cần thiết và các mối quan hệ được ngụ ý không có ở đó.
Dmitri Zaitsev

24

Nếu bạn muốn làm rõ từng tham số là gì, thay vì chỉ gọi

someFunction(70, 115);

tại sao không làm như sau

var width = 70, height = 115;
someFunction(width, height);

chắc chắn, đó là một dòng mã bổ sung, nhưng nó chiến thắng về khả năng đọc.


4
+1 để tuân theo nguyên tắc KISS, cộng với nó cũng giúp gỡ lỗi. Tuy nhiên, tôi nghĩ mỗi var nên nằm trên một dòng riêng, mặc dù có một hiệu suất nhẹ ( http://stackoverflow.com/questions/9672635/javascript-var-statement-and-performance ).
clairestreb

21
Nó không chỉ là về dòng mã bổ sung, mà còn về thứ tự của các đối số và làm cho chúng trở thành tùy chọn. Vì vậy, bạn có thể viết điều này với các tham số được đặt tên: someFunction(height: 115);nhưng nếu bạn viết someFunction(height);bạn thực sự đang thiết lập chiều rộng.
Francisco Presencia

Trong trường hợp đó, CoffeeScript hỗ trợ các đối số được đặt tên. Nó sẽ cho phép bạn viết chỉ someFunction(width = 70, height = 115);. Các biến được khai báo ở đầu phạm vi hiện tại trong mã JavaScript được tạo.
Audun Olsen

6

Một cách khác là sử dụng các thuộc tính của một đối tượng phù hợp, ví dụ như vậy:

function plus(a,b) { return a+b; };

Plus = { a: function(x) { return { b: function(y) { return plus(x,y) }}}, 
         b: function(y) { return { a: function(x) { return plus(x,y) }}}};

sum = Plus.a(3).b(5);

Tất nhiên đối với ví dụ này, nó có phần vô nghĩa. Nhưng trong trường hợp hàm trông như

do_something(some_connection_handle, some_context_parameter, some_value)

nó có thể hữu ích hơn Nó cũng có thể được kết hợp với ý tưởng "tham số" để tạo ra một đối tượng như vậy từ một hàm hiện có theo cách chung. Đó là với mỗi tham số, nó sẽ tạo ra một thành viên có thể đánh giá thành phiên bản được đánh giá một phần của hàm.

Ý tưởng này tất nhiên có liên quan đến Schönfinkeling aka Currying.


Đây là một ý tưởng gọn gàng và thậm chí còn trở nên tốt hơn nếu bạn kết hợp nó với các thủ thuật hướng nội đối số. Thật không may, nó không hoạt động với các đối số tùy chọn
Eric

2

Có một cách khác. Nếu bạn truyền đối tượng bằng tham chiếu, thuộc tính của đối tượng đó sẽ xuất hiện trong phạm vi cục bộ của hàm. Tôi biết điều này hoạt động với Safari (chưa kiểm tra các trình duyệt khác) và tôi không biết tính năng này có tên hay không, nhưng ví dụ dưới đây minh họa việc sử dụng nó.

Mặc dù trong thực tế tôi không nghĩ rằng điều này mang lại bất kỳ giá trị chức năng nào ngoài kỹ thuật bạn đang sử dụng, nó sẽ sạch hơn một chút về mặt ngữ nghĩa. Và nó vẫn yêu cầu chuyển một tham chiếu đối tượng hoặc một đối tượng theo nghĩa đen.

function sum({ a:a, b:b}) {
    console.log(a+'+'+b);
    if(a==undefined) a=0;
    if(b==undefined) b=0;
    return (a+b);
}

// will work (returns 9 and 3 respectively)
console.log(sum({a:4,b:5}));
console.log(sum({a:3}));

// will not work (returns 0)
console.log(sum(4,5));
console.log(sum(4));

2

Dùng thử Node-6.4.0 (process.versions.v8 = '5.0.71.60') và Node Chakracore-v7.0.0-pre8 và sau đó là Chrome-52 (V8 = 5.2.361,49), tôi nhận thấy rằng các tham số được đặt tên gần như thực hiện, nhưng trật tự đó vẫn được ưu tiên. Tôi không thể tìm thấy những gì tiêu chuẩn ECMA nói.

>function f(a=1, b=2){ console.log(`a=${a} + b=${b} = ${a+b}`) }

> f()
a=1 + b=2 = 3
> f(a=5)
a=5 + b=2 = 7
> f(a=7, b=10)
a=7 + b=10 = 17

Nhưng yêu cầu đặt hàng !! Đó có phải là hành vi tiêu chuẩn?

> f(b=10)
a=10 + b=2 = 12

10
Đó không phải là những gì bạn nghĩ. Kết quả của b=10biểu thức là 10và đó là những gì được truyền cho hàm. f(bla=10)cũng sẽ hoạt động (nó gán 10 cho biến blavà chuyển giá trị cho hàm sau đó).
Matthias Kestenholz

1
Điều này cũng tạo ra abcác biến trong phạm vi toàn cầu như là một tác dụng phụ.
Gershom

2

Hàm gọi fvới các tham số được đặt tên được truyền làm đối tượng

o = {height: 1, width: 5, ...}

về cơ bản là gọi thành phần của nó f(...g(o))trong đó tôi đang sử dụng cú pháp trải rộng và glà một bản đồ "ràng buộc" kết nối các giá trị đối tượng với các vị trí tham số của chúng.

Bản đồ liên kết chính xác là thành phần còn thiếu, có thể được biểu thị bằng mảng các khóa của nó:

// map 'height' to the first and 'width' to the second param
binding = ['height', 'width']

// take binding and arg object and return aray of args
withNamed = (bnd, o) => bnd.map(param => o[param])

// call f with named args via binding
f(...withNamed(binding, {hight: 1, width: 5}))

Lưu ý ba thành phần tách rời: hàm, đối tượng với các đối số được đặt tên và ràng buộc. Việc tách rời này cho phép rất linh hoạt để sử dụng cấu trúc này, trong đó ràng buộc có thể được tùy chỉnh tùy ý theo định nghĩa của hàm và được mở rộng tùy ý tại thời điểm gọi hàm.

Ví dụ, bạn có thể muốn viết tắt heightwidthnhư hwbên trong định nghĩa của chức năng của bạn, để làm cho nó ngắn hơn và sạch hơn, trong khi bạn vẫn muốn gọi nó với tên đầy đủ cho rõ ràng:

// use short params
f = (h, w) => ...

// modify f to be called with named args
ff = o => f(...withNamed(['height', 'width'], o))

// now call with real more descriptive names
ff({height: 1, width: 5})

Tính linh hoạt này cũng hữu ích hơn cho lập trình chức năng, trong đó các chức năng có thể được chuyển đổi tùy ý với tên param gốc của chúng bị mất.


0

Đến từ Python này đã làm phiền tôi. Tôi đã viết một trình bao bọc / Proxy đơn giản cho nút sẽ chấp nhận cả các đối tượng từ khóa và vị trí.

https://github.com/vinces1979/node-def/blob/master/README.md


Nếu tôi hiểu chính xác, giải pháp của bạn yêu cầu phân biệt các thông số vị trí và được đặt tên trong định nghĩa của hàm, điều đó có nghĩa là tôi không có quyền tự do đưa ra lựa chọn này tại thời điểm cuộc gọi.
Dmitri Zaitsev

@DmitriZaitsev: Trong cộng đồng Python (với các đối số từ khóa là một tính năng hàng ngày), chúng tôi coi đó là một ý tưởng rất tốt để có thể buộc người dùng chỉ định các đối số tùy chọn theo từ khóa; Điều này giúp tránh sai sót.
Tobias

@Tobias Buộc người dùng làm như vậy trong JS là một vấn đề được giải quyết : f(x, opt), đâu optlà một đối tượng. Cho dù nó giúp tránh hoặc tạo ra lỗi (chẳng hạn như gây ra lỗi chính tả và nỗi đau để nhớ tên từ khóa) vẫn là một câu hỏi.
Dmitri Zaitsev

@DmitriZaitsev: Trong Python, điều này hoàn toàn tránh được các lỗi, bởi vì (tất nhiên) các đối số từ khóa là một tính năng ngôn ngữ cốt lõi ở đó. Đối với các đối số chỉ từ khóa , Python 3 có cú pháp đặc biệt (trong khi trong Python 2, bạn sẽ bật các khóa từ kwargs dict từng cái một và cuối cùng nâng lên TypeErrornếu các khóa không xác định còn lại). Bạn f(x, opt)giải pháp cho phép các fchức năng để làm điều gì đó giống như trong Python 2, nhưng bạn sẽ cần phải xử lý tất cả các giá trị mặc định cho mình.
Tobias

@Tobias Đề xuất này có liên quan đến JS không? Nó dường như mô tả cách thức hoạt f(x, opt)động, trong khi tôi không thấy cách này giúp trả lời câu hỏi, ví dụ như bạn muốn làm cả hai request(url)request({url: url})điều đó sẽ không thể thực hiện được bằng cách tạo urltham số chỉ từ khóa.
Dmitri Zaitsev

-1

Trái với những gì thường được tin, các tham số được đặt tên có thể được triển khai trong JavaScript trường học cũ (chỉ dành cho tham số boolean) bằng cách sử dụng quy ước mã hóa đơn giản, gọn gàng, như được hiển thị bên dưới.

function f(p1=true, p2=false) {
    ...
}

f(!!"p1"==false, !!"p2"==true); // call f(p1=false, p2=true)

Hãy cẩn thận:

  • Thứ tự của các đối số phải được giữ nguyên - nhưng mẫu vẫn hữu ích, vì nó làm rõ ràng đối số thực tế có nghĩa là tham số chính thức nào mà không phải grep cho chữ ký hàm hoặc sử dụng IDE.

  • Điều này chỉ hoạt động cho booleans. Tuy nhiên, tôi chắc chắn một mô hình tương tự có thể được phát triển cho các loại khác bằng cách sử dụng ngữ nghĩa cưỡng chế kiểu duy nhất của JavaScript.


Bạn vẫn đang giới thiệu theo vị trí, không phải bằng tên ở đây.
Dmitri Zaitsev

@DmitriZaitsev vâng, tôi thậm chí đã nói như vậy ở trên. Tuy nhiên, mục đích của các đối số được đặt tên là để làm cho người đọc thấy rõ ý nghĩa của từng đối số; nó là một dạng tài liệu Giải pháp của tôi cho phép tài liệu được nhúng trong lệnh gọi hàm mà không cần dùng đến các bình luận, trông có vẻ không gọn gàng.
user234461

Điều này giải quyết một vấn đề khác nhau. Câu hỏi là về việc chuyển qua heighttên bất kể thứ tự param.
Dmitri Zaitsev

@DmitriZaitsev thực sự, câu hỏi không nói gì về thứ tự tham số, hoặc tại sao OP muốn sử dụng thông số có tên.
dùng234461

Nó thực hiện bằng cách tham khảo C # trong đó việc truyền tham số theo tên theo bất kỳ thứ tự nào là chìa khóa.
Dmitri Zaitsev
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.