Nhiều đối số so với đối tượng tùy chọn


157

Khi tạo một hàm JavaScript có nhiều đối số, tôi luôn phải đối mặt với lựa chọn này: truyền danh sách các đối số so với vượt qua một đối tượng tùy chọn.

Ví dụ: tôi đang viết một hàm để ánh xạ một nodeList thành một mảng:

function map(nodeList, callback, thisObject, fromIndex, toIndex){
    ...
}

Thay vào đó tôi có thể sử dụng cái này:

function map(options){
    ...
}

trong đó các tùy chọn là một đối tượng:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

Cái nào là cách được đề nghị? Có hướng dẫn khi nào nên sử dụng cái này so với cái kia không?

[Cập nhật] Dường như có sự đồng thuận ủng hộ đối tượng tùy chọn, vì vậy tôi muốn thêm một nhận xét: một lý do khiến tôi bị cám dỗ sử dụng danh sách các đối số trong trường hợp của mình là có hành vi phù hợp với JavaScript được xây dựng trong phương thức Array.map.


2
Tùy chọn thứ hai cung cấp cho bạn các đối số được đặt tên, đó là một điều tốt đẹp theo ý kiến ​​của tôi.
Werner Kvalem Vesterås

Họ là đối số tùy chọn hoặc yêu cầu?
Tôi ghét Lười

@ user1689607 trong ví dụ của tôi, ba cái cuối cùng là tùy chọn.
Barshe

Bởi vì hai đối số cuối cùng của bạn rất giống nhau, nếu người dùng chỉ thông qua cái này hoặc cái kia, bạn sẽ không bao giờ thực sự có thể biết cái nào được dự định. Do đó, bạn gần như cần các đối số được đặt tên. Nhưng tôi có thể đánh giá cao rằng bạn muốn duy trì một API tương tự như API gốc.
Tôi ghét Lười

1
Mô hình hóa sau API gốc không phải là một điều xấu, nếu chức năng của bạn làm điều gì đó tương tự. Tất cả bắt nguồn từ "điều gì làm cho mã dễ đọc nhất". Array.prototype.mapcó một API đơn giản không nên để bất kỳ lập trình viên bán kinh nghiệm nào bối rối.
Jeremy J Starcher

Câu trả lời:


160

Giống như nhiều người khác, tôi thường thích truyền options objectmột hàm thay vì truyền một danh sách dài các tham số, nhưng nó thực sự phụ thuộc vào ngữ cảnh chính xác.

Tôi sử dụng khả năng đọc mã như kiểm tra litmus.

Ví dụ, nếu tôi có chức năng này hãy gọi:

checkStringLength(inputStr, 10);

Tôi nghĩ rằng mã này khá dễ đọc theo cách của nó và truyền các tham số riêng lẻ là tốt.

Mặt khác, có các chức năng với các cuộc gọi như thế này:

initiateTransferProtocol("http", false, 150, 90, null, true, 18);

Hoàn toàn không thể đọc được trừ khi bạn làm một số nghiên cứu. Mặt khác, mã này đọc tốt:

initiateTransferProtocol({
  "protocol": "http",
  "sync":      false,
  "delayBetweenRetries": 150,
  "randomVarianceBetweenRetries": 90,
  "retryCallback": null,
  "log": true,
  "maxRetries": 18
 });

Đó là một nghệ thuật hơn là một khoa học, nhưng nếu tôi phải đặt tên cho quy tắc của ngón tay cái:

Sử dụng tham số tùy chọn nếu:

  • Bạn có nhiều hơn bốn tham số
  • Bất kỳ tham số nào là tùy chọn
  • Bạn đã bao giờ phải tìm kiếm chức năng để tìm ra những tham số nào
  • Nếu ai đó cố gắng bóp cổ bạn trong khi hét lên "ARRRRR!"

10
Câu trả lời chính xác. Nó phụ thuộc. Coi chừng bẫy boolean ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Trevor Dixon

2
Ồ vâng ... Tôi đã quên mất liên kết đó. Nó thực sự khiến tôi suy nghĩ lại về cách các API hoạt động và tôi thậm chí viết lại một vài đoạn mã sau khi biết tôi đã làm mọi thứ ngớ ngẩn. Cảm ơn!
Jeremy J Starcher

1
'Bạn đã bao giờ phải bất lực nhìn lên các chức năng để tìm ra những gì các tham số phải mất' - Sử dụng một tùy chọn đối tượng thay vào đó, sẽ không bạn luôn phải tìm kiếm phương pháp để tìm ra chìa khóa đó là cần thiết và những gì họ đang gọi. Intellisense trong IDE không hiển thị thông tin này, trong khi params thì có. Trong hầu hết các IDE, bạn chỉ cần di chuột qua phương thức và nó sẽ cho bạn thấy các thông số là gì.
simbolo

1
Nếu bạn quan tâm đến hậu quả hiệu suất của bit 'thực hành tốt nhất về mã hóa' này, thì đây là một thử nghiệm jsPerf theo cả hai cách: jsperf.com/feft-boolean-argument-vs-options-object/10 Lưu ý đến sự thay đổi nhỏ trong thay thế thứ ba, trong đó tôi sử dụng một đối tượng tùy chọn 'cài sẵn' (không đổi), có thể được thực hiện khi bạn có nhiều cuộc gọi (trong suốt thời gian chạy, ví dụ: trang web của bạn) với cùng cài đặt, được biết đến trong thời gian phát triển (nói ngắn gọn là : khi các giá trị tùy chọn của bạn được mã hóa cứng trong mã nguồn của bạn).
Ger Hobbelt

2
@Sean Thành thật mà nói, tôi không còn sử dụng kiểu mã hóa này nữa. Tôi đã chuyển sang TypeScript và sử dụng các tham số được đặt tên.
Jeremy J Starcher

28

Nhiều đối số chủ yếu là cho các tham số bắt buộc. Không có gì sai với họ.

Nếu bạn có các tham số tùy chọn, nó sẽ phức tạp. Nếu một trong số họ dựa vào những cái khác, để họ có một thứ tự nhất định (ví dụ: cái thứ tư cần cái thứ ba), bạn vẫn nên sử dụng nhiều đối số. Gần như tất cả các phương thức EcmaScript và DOM gốc đều hoạt động như thế này. Một ví dụ điển hình là openphương pháp XMLHTTPrequests , trong đó 3 đối số cuối cùng là tùy chọn - quy tắc giống như "không có mật khẩu mà không có người dùng" (xem thêm tài liệu MDN ).

Đối tượng tùy chọn có ích trong hai trường hợp:

  • Bạn đã có quá nhiều thông số khiến nó trở nên khó hiểu: Việc "đặt tên" sẽ giúp bạn, bạn không phải lo lắng về thứ tự của chúng (đặc biệt là nếu chúng có thể thay đổi)
  • Bạn đã có các tham số tùy chọn. Các đối tượng rất linh hoạt, và không có bất kỳ trật tự nào, bạn chỉ cần vượt qua những thứ bạn cần và không có gì khác (hoặc undefineds).

Trong trường hợp của bạn, tôi khuyên bạn nên map(nodeList, callback, options). nodelistcallbackđược yêu cầu, ba đối số khác chỉ thỉnh thoảng đến và có mặc định hợp lý.

Một ví dụ khác là JSON.stringify. Bạn có thể muốn sử dụng spacetham số mà không thông qua replacerhàm - sau đó bạn phải gọi …, null, 4). Một đối tượng đối số có thể tốt hơn, mặc dù nó không thực sự hợp lý chỉ với 2 tham số.


+1 câu hỏi tương tự như @ trevor-dixon: bạn đã thấy hỗn hợp này được sử dụng trong thực tế chưa, ví dụ như trong các thư viện js?
Barshe

Một ví dụ có thể là các phương thức ajax của jQuery . Họ chấp nhận URL [bắt buộc] làm đối số đầu tiên và đối số tùy chọn lớn là đối số thứ hai.
Bergi

thật ki cục! Tôi chưa bao giờ nhận thấy điều này trước đây. Tôi đã luôn thấy nó được sử dụng với url như một thuộc tính tùy chọn ...
Barshe

Có, jQuery thực hiện điều kỳ lạ với các tham số tùy chọn của nó trong khi vẫn tương thích ngược :-)
Bergi

1
Theo tôi, đây là câu trả lời lành mạnh duy nhất ở đây.
Benjamin Gruenbaum

11

Sử dụng các tùy chọn 'làm đối tượng' sẽ là tốt nhất. Bạn không phải lo lắng về thứ tự của các thuộc tính và có sự linh hoạt hơn trong việc dữ liệu nào được thông qua (ví dụ: tham số tùy chọn)

Tạo một đối tượng cũng có nghĩa là các tùy chọn có thể dễ dàng được sử dụng trên nhiều chức năng:

options={
    nodeList:...,
    callback:...,
    thisObject:...,
    fromIndex:...,
    toIndex:...
}

function1(options){
    alert(options.nodeList);
}

function2(options){
    alert(options.fromIndex);
}

Giả định (hợp lý) ở đây là đối tượng sẽ luôn có các cặp khóa giống nhau. Nếu bạn đang làm việc với API tào lao / không nhất quán thì bạn sẽ gặp vấn đề khác.
backdesk

9

Tôi nghĩ rằng nếu bạn đang khởi tạo một cái gì đó hoặc gọi một phương thức của một đối tượng, bạn muốn sử dụng một đối tượng tùy chọn. Nếu đó là một hàm hoạt động chỉ với một hoặc hai tham số và trả về một giá trị, thì nên sử dụng danh sách đối số.

Trong một số trường hợp, thật tốt khi sử dụng cả hai. Nếu hàm của bạn có một hoặc hai tham số bắt buộc và một loạt các tham số tùy chọn, hãy tạo hai tham số đầu tiên bắt buộc và thứ ba là tùy chọn băm tùy chọn.

Trong ví dụ của bạn, tôi sẽ làm map(nodeList, callback, options). Nodelist và gọi lại là bắt buộc, khá dễ dàng để biết những gì đang xảy ra chỉ bằng cách đọc một cuộc gọi đến nó, và nó giống như các chức năng bản đồ hiện có. Bất kỳ tùy chọn khác có thể được thông qua như là một tham số thứ ba tùy chọn.


+1 thú vị. Bạn đã thấy nó được sử dụng trong thực tế, ví dụ trong các thư viện js?
Barshe

7

Nhận xét của bạn về câu hỏi:

trong ví dụ của tôi ba cái cuối cùng là tùy chọn.

Vậy tại sao không làm điều này? (Lưu ý: Đây là Javascript khá thô. Thông thường tôi sẽ sử dụng defaulthàm băm và cập nhật nó với các tùy chọn được truyền bằng cách sử dụng Object.extend hoặc JQuery.extend hoặc tương tự ..)

function map(nodeList, callback, options) {
   options = options || {};
   var thisObject = options.thisObject || {};
   var fromIndex = options.fromIndex || 0;
   var toIndex = options.toIndex || 0;
}

Vì vậy, bây giờ vì bây giờ rõ ràng hơn nhiều tùy chọn và những gì không, tất cả những điều này là sử dụng hợp lệ của chức năng:

map(nodeList, callback);
map(nodeList, callback, {});
map(nodeList, callback, null);
map(nodeList, callback, {
   thisObject: {some: 'object'},
});
map(nodeList, callback, {
   toIndex: 100,
});
map(nodeList, callback, {
   thisObject: {some: 'object'},
   fromIndex: 0,
   toIndex: 100,
});

Điều này tương tự như câu trả lời của @ trevor-dixon.
Barshe

5

Tôi có thể đến bữa tiệc muộn một chút với phản hồi này, nhưng tôi đã tìm kiếm ý kiến ​​của các nhà phát triển khác về chính chủ đề này và tình cờ thấy chủ đề này.

Tôi rất không đồng ý với hầu hết những người trả lời, và bên cạnh cách tiếp cận 'nhiều đối số'. Đối số chính của tôi là nó không khuyến khích các kiểu chống khác như "biến đổi và trả lại đối tượng param" hoặc "chuyển cùng một đối tượng param sang các hàm khác". Tôi đã làm việc trong các cơ sở mã đã lạm dụng rộng rãi mô hình chống này và việc gỡ lỗi mã này nhanh chóng trở thành không thể. Tôi nghĩ rằng đây là một quy tắc rất cụ thể của Javascript, vì Javascript không được gõ mạnh và cho phép các đối tượng có cấu trúc tùy ý như vậy.

Ý kiến ​​cá nhân của tôi là các nhà phát triển nên rõ ràng khi gọi các hàm, tránh truyền dữ liệu dư thừa và tránh sửa đổi theo tham chiếu. Không phải là mẫu này ngăn cản việc viết mã ngắn gọn, chính xác. Tôi chỉ cảm thấy nó làm cho dự án của bạn dễ dàng rơi vào thực tiễn phát triển xấu.

Hãy xem xét các mã khủng khiếp sau:

function main() {
    const x = foo({
        param1: "something",
        param2: "something else",
        param3: "more variables"
    });

    return x;
}

function foo(params) {
    params.param1 = "Something new";
    bar(params);
    return params;
}


function bar(params) {
    params.param2 = "Something else entirely";
    const y = baz(params);
    return params.param2;
}

function baz(params) {
    params.params3 = "Changed my mind";
    return params;
}

Loại này không chỉ yêu cầu tài liệu rõ ràng hơn để xác định ý định, mà còn để lại chỗ cho các lỗi mơ hồ. Điều gì xảy ra nếu một nhà phát triển sửa đổi param1trongbar() ? Bạn nghĩ sẽ mất bao lâu để xem qua một cơ sở mã có kích thước chính xác để nắm bắt điều này? Phải thừa nhận rằng, đây là ví dụ hơi bất cẩn vì cho rằng các nhà phát triển đã cam kết một số chống mẫu vào thời điểm này. Nhưng nó cho thấy việc vượt qua các đối tượng có chứa các tham số cho phép có nhiều lỗi và sự mơ hồ hơn, đòi hỏi mức độ nhận thức và quan sát chính xác cao hơn.

Chỉ cần hai xu của tôi về vấn đề này!


3

Nó phụ thuộc.

Dựa trên quan sát của tôi về các thiết kế thư viện phổ biến đó, đây là các kịch bản chúng ta nên sử dụng đối tượng tùy chọn:

  • Danh sách tham số dài (> 4).
  • Một số hoặc tất cả các tham số là tùy chọn và chúng không dựa trên một thứ tự nhất định.
  • Danh sách tham số có thể phát triển trong bản cập nhật API trong tương lai.
  • API sẽ được gọi từ mã khác và tên API không đủ rõ ràng để nói ý nghĩa của các tham số. Vì vậy, nó có thể cần tên tham số mạnh mẽ để dễ đọc.

Và các kịch bản để sử dụng danh sách tham số:

  • Danh sách tham số ngắn (<= 4).
  • Hầu hết hoặc tất cả các tham số được yêu cầu.
  • Các tham số tùy chọn theo một thứ tự nhất định. (tức là: $ .get)
  • Dễ dàng cho biết các tham số có nghĩa là tên API.

2

Đối tượng được ưu tiên hơn, bởi vì nếu bạn vượt qua một đối tượng, việc mở rộng số lượng thuộc tính trong các đối tượng đó sẽ dễ dàng hơn và bạn không phải theo dõi thứ tự mà các đối số của bạn đã được thông qua.


1

Đối với một hàm thường sử dụng một số đối số được xác định trước, bạn nên sử dụng đối tượng tùy chọn tốt hơn. Ví dụ ngược lại sẽ là một cái gì đó giống như một hàm nhận được vô số đối số như: setCSS ({height: 100}, {width: 200}, {background: "# 000"}).


0

Tôi sẽ xem xét các dự án javascript lớn.

Những thứ như google map bạn sẽ thường thấy rằng các đối tượng được khởi tạo yêu cầu một đối tượng nhưng các hàm yêu cầu tham số. Tôi nghĩ điều này có liên quan đến các đối số TÙY CHỌN.

Nếu bạn cần các đối số mặc định hoặc đối số tùy chọn, một đối tượng có thể sẽ tốt hơn vì nó linh hoạt hơn. Nhưng nếu bạn không đối số chức năng bình thường thì rõ ràng hơn.

Javascript cũng có một argumentsđối tượng. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Fiances_and_feft_scope/argument

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.