ข้อโต้แย้งหลายประการกับวัตถุตัวเลือก


157

เมื่อสร้างฟังก์ชั่น JavaScript ที่มีอาร์กิวเมนต์หลายตัวฉันต้องเผชิญหน้ากับตัวเลือกนี้เสมอ: ส่งรายการอาร์กิวเมนต์และส่งผ่านตัวเลือกวัตถุ

ตัวอย่างเช่นฉันกำลังเขียนฟังก์ชั่นเพื่อแมป nodeList ไปยังอาร์เรย์:

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

ฉันสามารถใช้สิ่งนี้แทน:

function map(options){
    ...
}

โดยที่ options เป็นวัตถุ:

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

วิธีใดที่แนะนำ มีแนวทางสำหรับการใช้หนึ่งกับอื่น ๆ ?

[อัปเดต] ดูเหมือนจะมีฉันทามติเห็นด้วยกับวัตถุอ็อปชั่นดังนั้นฉันจึงอยากจะเพิ่มความคิดเห็น: เหตุผลหนึ่งที่ฉันถูกล่อลวงให้ใช้รายการอาร์กิวเมนต์ในกรณีของฉันคือพฤติกรรมที่สอดคล้องกับ JavaScript สร้างขึ้นในวิธีการ array.map


2
ตัวเลือกที่สองให้คุณตั้งชื่ออาร์กิวเมนต์ซึ่งเป็นสิ่งที่ดีในความคิดของฉัน
Werner Kvalem Vesterås

พวกเขาเป็นอาร์กิวเมนต์ตัวเลือกหรือจำเป็น?
ฉันเกลียด Lazy

@ user1689607 ในตัวอย่างของฉันสามตัวสุดท้ายเป็นตัวเลือก
Christophe

เนื่องจากข้อโต้แย้งสองข้อสุดท้ายของคุณคล้ายกันมากหากผู้ใช้ส่งผ่านข้อใดข้อหนึ่งเท่านั้นคุณจะไม่สามารถรู้ได้ว่าข้อใดตั้งใจ ด้วยเหตุนี้คุณต้องมีอาร์กิวเมนต์ที่มีชื่อ แต่ฉันขอขอบคุณที่คุณต้องการรักษา API ที่คล้ายคลึงกับ Native API
ฉันเกลียด Lazy

1
การสร้างแบบจำลองหลังจาก API ดั้งเดิมไม่ใช่สิ่งที่เลวร้ายหากฟังก์ชันของคุณทำสิ่งที่คล้ายกัน ทุกอย่างลงมาที่ "สิ่งที่ทำให้โค้ดอ่านง่ายที่สุด" Array.prototype.mapมี API อย่างง่ายที่ไม่ควรปล่อยให้ coder ที่มีประสบการณ์กึ่งสับสน
Jeremy J Starcher

คำตอบ:


160

เช่นเดียวกับคนอื่น ๆ ฉันมักชอบส่งผ่านoptions objectไปยังฟังก์ชันแทนที่จะส่งรายการพารามิเตอร์ที่ยาว แต่จริง ๆ แล้วมันขึ้นอยู่กับบริบทที่แน่นอน

ฉันใช้การอ่านรหัสเป็นการทดสอบสารสีน้ำเงิน

ตัวอย่างเช่นถ้าฉันมีการเรียกใช้ฟังก์ชันนี้:

checkStringLength(inputStr, 10);

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

ในทางตรงกันข้ามมีฟังก์ชั่นที่มีการโทรเช่นนี้:

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

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

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

มันเป็นศิลปะมากกว่าวิทยาศาสตร์ แต่ถ้าฉันต้องตั้งชื่อกฎง่ายๆ:

ใช้พารามิเตอร์ตัวเลือกหาก:

  • คุณมีพารามิเตอร์มากกว่าสี่ตัว
  • พารามิเตอร์ใด ๆ เป็นทางเลือก
  • คุณต้องค้นหาฟังก์ชันเพื่อหาพารามิเตอร์ที่ต้องใช้
  • หากมีคนพยายามบีบคอคุณในขณะที่กรีดร้อง "ARRRRRRG"

10
คำตอบที่ดี มันขึ้นอยู่กับ. ระวังกับดักบูลีนariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
Trevor Dixon

2
โอ้ใช่ ... ฉันลืมลิงค์นี้ไปแล้ว มันทำให้ฉันคิดใหม่เกี่ยวกับวิธีการทำงานของ API และฉันยังได้เขียนโค้ดอีกหลายชิ้นหลังจากเรียนรู้ว่าฉันทำสิ่งต่าง ๆ เป็นใบ้ ขอบคุณ!
Jeremy J Starcher

1
'คุณเคยมีที่จะมองขึ้นฟังก์ชั่นที่จะคิดออกว่าจะใช้เวลาพารามิเตอร์' - การใช้ตัวเลือกวัตถุแทนจะไม่ให้คุณเสมอต้องมองไปถึงวิธีการที่จะคิดออกคีย์ที่มีความจำเป็นและสิ่งที่พวกเขาเรียกว่า Intellisense ใน IDE ไม่แสดงข้อมูลนี้ในขณะที่พารามิเตอร์ทำ ใน IDEs ส่วนใหญ่คุณสามารถเลื่อนเม้าส์เหนือเมธอดและมันจะแสดงให้คุณเห็นว่าพารามิเตอร์คืออะไร
simbolo

1
หากคุณสนใจผลการทำงานของบิตของ 'การเขียนโค้ดวิธีปฏิบัติที่ดีที่สุด' นี่คือการทดสอบ jsPerf ทั้งสองวิธี: jsperf.com/function-boolean-arguments-vs-options-object/10 หมายเหตุการบิดเล็กน้อยใน ทางเลือกที่สามที่ฉันใช้วัตถุตัวเลือก 'ที่ตั้งไว้ล่วงหน้า' (ค่าคงที่) ซึ่งสามารถทำได้เมื่อคุณโทรหลายครั้ง (ในช่วงอายุการใช้งานของรันไทม์เช่นเว็บเพจของคุณ) ด้วยการตั้งค่าเดียวกันหรือที่รู้จักกันในเวลาพัฒนา : เมื่อค่าตัวเลือกของคุณจะถูก hardcoded ในซอร์สโค้ดของคุณ)
Ger Hobbelt

2
@Sean สุจริตฉันไม่ได้ใช้การเข้ารหัสแบบนี้เลย ฉันเปลี่ยนไปใช้ TypeScript และใช้พารามิเตอร์ที่มีชื่อ
Jeremy J Starcher

28

อาร์กิวเมนต์หลายตัวส่วนใหญ่เป็นพารามิเตอร์บังคับ ไม่มีอะไรผิดปกติกับพวกเขา

หากคุณมีพารามิเตอร์ทางเลือกมันจะซับซ้อน หากหนึ่งในนั้นอาศัยที่อื่น ๆ เพื่อให้พวกเขามีคำสั่งที่แน่นอน (เช่นที่สี่ต้องการหนึ่งในสาม) คุณยังคงควรใช้อาร์กิวเมนต์หลายรายการ EcmaScript และ native-methods เกือบทั้งหมดใช้วิธีนี้ได้ ตัวอย่างที่ดีคือopenวิธีการของ XMLHTTPrequestsโดยที่อาร์กิวเมนต์ 3 ตัวสุดท้ายเป็นตัวเลือกกฎจะเหมือนกับ "ไม่มีรหัสผ่านโดยไม่มีผู้ใช้" (ดูMDN เอกสาร )

วัตถุตัวเลือกมีประโยชน์ในสองกรณี:

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

map(nodeList, callback, options)ในกรณีของคุณผมอยากแนะนำให้ nodelistและcallbackจำเป็นต้องมีอีกสามข้อโต้แย้งที่มาเป็นครั้งคราวเท่านั้นและมีค่าเริ่มต้นที่เหมาะสม

JSON.stringifyอีกตัวอย่างหนึ่งคือ คุณอาจต้องการที่จะใช้spaceพารามิเตอร์โดยไม่ต้องผ่านreplacerฟังก์ชั่น - …, null, 4)แล้วคุณจะต้องโทร วัตถุอากิวเมนต์อาจดีกว่าแม้ว่ามันจะไม่สมเหตุสมผลสำหรับพารามิเตอร์ 2 ตัวเท่านั้น


+1 คำถามเดียวกันกับ @ trevor-dixon: คุณเคยเห็นมิกซ์นี้ที่ใช้ในทางปฏิบัติตัวอย่างเช่นในไลบรารี js หรือไม่
Christophe

ตัวอย่างเช่นอาจจะเป็นของ jQuery วิธีอาแจ็กซ์ พวกเขายอมรับ URL [จำเป็น] เป็นอาร์กิวเมนต์แรกและอาร์กิวเมนต์ตัวเลือกขนาดใหญ่เป็นอาร์กิวเมนต์ที่สอง
Bergi

แปลกมาก! ฉันไม่เคยสังเกตเรื่องนี้มาก่อน ผมเคยเห็นเสมอว่ามันใช้กับ URL ที่เป็นสถานที่ให้บริการตัวเลือก ...
Christophe

ใช่ jQuery ไม่สิ่งแปลกกับพารามิเตอร์ที่ไม่จำเป็นในขณะที่การเข้าพัก :-) รองรับหลัง
Bergi

1
ในความคิดของฉันนี่เป็นคำตอบเดียวที่นี่
Benjamin Gruenbaum

11

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

การสร้างออบเจ็กต์ยังหมายถึงตัวเลือกที่สามารถใช้งานได้ง่ายในหลายฟังก์ชั่น:

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

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

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

สมมติฐาน (สมเหตุสมผล) ที่นี่คือวัตถุจะมีคู่คีย์เดียวกันเสมอ หากคุณกำลังทำงานกับ API อึ / ไม่สอดคล้องกันแสดงว่าคุณมีปัญหาที่แตกต่างกันในมือของคุณ
backdesk

9

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

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

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


+1 น่าสนใจ คุณเคยเห็นมันใช้ในการปฏิบัติตัวอย่างเช่นในห้องสมุด js?
Christophe

7

ความคิดเห็นของคุณเกี่ยวกับคำถาม:

ในตัวอย่างของฉันสามตัวสุดท้ายเป็นตัวเลือก

ดังนั้นทำไมไม่ทำเช่นนี้? (หมายเหตุ: นี่เป็น Javascript ที่ค่อนข้างปกติโดยปกติฉันจะใช้defaultแฮชและอัปเดตพร้อมกับตัวเลือกที่ส่งผ่านโดยใช้Object.extendหรือJQuery.extendหรือคล้ายกัน .. )

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

ดังนั้นตอนนี้เนื่องจากตอนนี้เห็นได้ชัดว่ามีอะไรเพิ่มเติมหรือไม่และสิ่งเหล่านี้ทั้งหมดคือการใช้ฟังก์ชันที่ถูกต้อง:

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

นี่คล้ายกับคำตอบของ @ trevor-dixon
Christophe

5

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

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

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

พิจารณารหัสที่น่ากลัวต่อไปนี้:

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

ประเภทนี้ไม่เพียง แต่ต้องการเอกสารที่ชัดเจนมากขึ้นเพื่อระบุเจตนา แต่ยังมีที่ว่างสำหรับข้อผิดพลาดที่คลุมเครือ เกิดอะไรขึ้นถ้าปรับเปลี่ยนนักพัฒนาparam1ในbar() ? คุณคิดว่าจะต้องใช้เวลานานแค่ไหนในการดูขนาดฐานรหัสที่พอเพียงเพื่อรับสิ่งนี้ เป็นที่ยอมรับตัวอย่างนี้มีลักษณะไม่ตรงไปตรงมาเล็กน้อยเนื่องจากถือว่านักพัฒนาซอฟต์แวร์ได้กำหนดรูปแบบการต่อต้านหลายจุดแล้ว แต่มันแสดงให้เห็นว่าการผ่านวัตถุที่มีพารามิเตอร์ช่วยให้มีพื้นที่มากขึ้นสำหรับความผิดพลาดและความกำกวมที่ต้องมีระดับของความมีสติและการปฏิบัติตามความถูกต้อง const มากขึ้น

เพียงแค่สองเซ็นต์ของฉันกับปัญหา!


3

มันขึ้นอยู่กับ.

จากการสังเกตของฉันเกี่ยวกับการออกแบบไลบรารี่ยอดนิยมนี่คือสถานการณ์ที่เราควรใช้อ็อบเจกต์ออปชั่น:

  • รายการพารามิเตอร์มีความยาว (> 4)
  • พารามิเตอร์บางส่วนหรือทั้งหมดเป็นตัวเลือกและไม่จำเป็นต้องใช้คำสั่งซื้อที่แน่นอน
  • รายการพารามิเตอร์อาจเพิ่มขึ้นในการอัปเดต API ในอนาคต
  • API จะถูกเรียกจากรหัสอื่นและชื่อ API นั้นไม่ชัดเจนพอที่จะบอกความหมายของพารามิเตอร์ได้ ดังนั้นจึงอาจต้องการชื่อพารามิเตอร์ที่แข็งแกร่งเพื่อให้สามารถอ่านได้

และสถานการณ์ที่จะใช้รายการพารามิเตอร์:

  • รายการพารามิเตอร์สั้น (<= 4)
  • จำเป็นต้องใช้พารามิเตอร์ส่วนใหญ่หรือทั้งหมด
  • พารามิเตอร์ตัวเลือกอยู่ในลำดับที่แน่นอน (เช่น: $ .get)
  • ง่ายต่อการบอกพารามิเตอร์ความหมายโดยใช้ชื่อ API

2

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


1

สำหรับฟังก์ชันที่มักจะใช้อาร์กิวเมนต์ที่กำหนดไว้ล่วงหน้าคุณควรใช้อ็อพเจ็กอ็อพชั่น ตัวอย่างตรงข้ามจะเป็นฟังก์ชันที่รับจำนวนอาร์กิวเมนต์ที่ไม่สิ้นสุดเช่น: setCSS ({height: 100}, {width: 200}, {background: "# 000"})


0

ฉันจะดูโครงการจาวาสคริปต์ขนาดใหญ่

สิ่งต่างๆเช่นแผนที่ของ Google คุณจะพบว่าวัตถุที่สร้างอินสแตนซ์ต้องการวัตถุ แต่ฟังก์ชั่นต้องการพารามิเตอร์ ฉันคิดว่าสิ่งนี้เกี่ยวข้องกับ OPTION argumemnts

หากคุณต้องการอาร์กิวเมนต์เริ่มต้นหรืออาร์กิวเมนต์ที่เป็นตัวเลือกวัตถุน่าจะดีกว่าเนื่องจากมีความยืดหยุ่นมากกว่า แต่ถ้าคุณไม่โต้แย้งการทำงานปกติจะชัดเจนมากขึ้น

Javascript ก็มีargumentsวัตถุเช่นกัน https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.