วิธีที่ดีที่สุดในการพิจารณาว่าอาร์กิวเมนต์ไม่ได้ถูกส่งไปยังฟังก์ชัน JavaScript


236

ตอนนี้ฉันได้เห็น 2 วิธีในการพิจารณาว่ามีการส่งผ่านอาร์กิวเมนต์ไปยังฟังก์ชัน JavaScript หรือไม่ ฉันสงสัยว่าวิธีหนึ่งดีกว่าวิธีอื่นหรือไม่หรือวิธีใดวิธีหนึ่งที่ใช้ไม่ดี

 function Test(argument1, argument2) {
      if (Test.arguments.length == 1) argument2 = 'blah';

      alert(argument2);
 }

 Test('test');

หรือ

 function Test(argument1, argument2) {
      argument2 = argument2 || 'blah';

      alert(argument2);
 }

 Test('test');

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

ตัวเลือกอื่นตามที่Tomกล่าวถึง:

function Test(argument1, argument2) {
    if(argument2 === null) {
        argument2 = 'blah';
    }

    alert(argument2);
}

ตามความคิดเห็นของ Juan มันจะเป็นการดีกว่าถ้าจะเปลี่ยนคำแนะนำของ Tom เป็น:

function Test(argument1, argument2) {
    if(argument2 === undefined) {
        argument2 = 'blah';
    }

    alert(argument2);
}

มันเหมือนกันจริงๆ หากคุณมีจำนวนอาร์กิวเมนต์คงที่ให้ไปกับวิธีที่สองมิฉะนั้นอาจเป็นการง่ายกว่าที่จะทำซ้ำโดยใช้อาร์เรย์อาร์กิวเมนต์
Luca Matteis

7
อาร์กิวเมนต์ที่ไม่ได้ผ่านเข้ามาเป็นไม่ได้กำหนด การทดสอบด้วยความเท่าเทียมอย่างเข้มงวดกับค่า null จะล้มเหลว คุณควรใช้ความเท่าเทียมอย่างเข้มงวดกับไม่ได้กำหนด
Juan Mendes

19
โปรดจำไว้ว่า: argument2 || 'blah';จะส่งผลให้ 'blah' ถ้าargument2เป็นfalse(!) ไม่ใช่เพียงแค่ถ้ามันไม่ได้กำหนด ถ้าargument2เป็นแบบบูลและฟังก์ชั่นจะถูกส่งfalseให้มันสายที่จะกลับมา 'blah' แม้จะargument2ถูกกำหนดไว้อย่างถูกต้อง
Sandy Gifford

9
@SandyGifford: ปัญหาเดียวกันหากargument2เป็น0, หรือ'' null
rvighne

@rvighne จริงมาก การตีความวัตถุและการคัดเลือกที่ไม่ซ้ำกันของ Javascript นั้นทั้งหมดเป็นส่วนที่ดีที่สุดและแย่ที่สุด
Sandy Gifford

คำตอบ:


274

มีหลายวิธีในการตรวจสอบว่ามีการส่งผ่านอาร์กิวเมนต์ไปยังฟังก์ชันหรือไม่ นอกเหนือจากคำถามทั้งสองที่คุณกล่าวถึงในคำถาม (ต้นฉบับ) ของคุณ - การตรวจสอบarguments.lengthหรือการใช้||โอเปอเรเตอร์เพื่อให้ค่าเริ่มต้น - หนึ่งสามารถตรวจสอบอาร์กิวเมนต์สำหรับundefinedผ่านargument2 === undefinedหรือtypeof argument2 === 'undefined'ถ้าเป็นหวาดระแวง (ดูความคิดเห็น)

ใช้||ประกอบการได้กลายเป็นมาตรฐานการปฏิบัติ - ทั้งหมดเด็กเย็นทำมัน - แต่ต้องระวัง: ค่าเริ่มต้นจะเริ่มทำงานถ้าประเมินอาร์กิวเมนต์falseซึ่งหมายความว่ามันจริงอาจจะundefined, null, false, 0, ''(หรือสิ่งอื่นใดที่Boolean(...)ให้ผลตอบแทนfalse)

ดังนั้นคำถามคือเมื่อใดควรใช้การตรวจสอบที่พวกเขาทั้งหมดให้ผลลัพธ์ที่แตกต่างกันเล็กน้อย

การตรวจสอบการarguments.lengthจัดแสดงแสดงพฤติกรรมที่ 'ถูกต้องที่สุด' แต่อาจไม่เป็นไปได้ถ้ามีอาร์กิวเมนต์ที่เป็นทางเลือกมากกว่าหนึ่งรายการ

การทดสอบสำหรับundefined'ที่ดีที่สุด' ถัดไป - เพียง 'ล้มเหลว' ถ้าฟังก์ชั่นนั้นถูกเรียกอย่างชัดเจนว่ามีundefinedค่าซึ่งในทุกโอกาสควรได้รับการปฏิบัติเช่นเดียวกับการข้ามอาร์กิวเมนต์

การใช้||โอเปอเรเตอร์อาจก่อให้เกิดการใช้ค่าเริ่มต้นแม้ว่าจะมีการระบุอาร์กิวเมนต์ที่ถูกต้อง ในทางกลับกันพฤติกรรมของมันอาจจะต้องการจริง

เพื่อสรุป: ใช้เฉพาะเมื่อคุณรู้ว่าคุณกำลังทำอะไร!

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

อีกวิธีที่ดีในการจัดทำค่าเริ่มต้นโดยใช้arguments.lengthเป็นไปได้โดยการผ่านเลเบลของคำสั่ง switch:

function test(requiredArg, optionalArg1, optionalArg2, optionalArg3) {
    switch(arguments.length) {
        case 1: optionalArg1 = 'default1';
        case 2: optionalArg2 = 'default2';
        case 3: optionalArg3 = 'default3';
        case 4: break;
        default: throw new Error('illegal argument count')
    }
    // do stuff
}

นี่เป็นข้อเสียที่ความตั้งใจของโปรแกรมเมอร์ไม่ชัดเจน (มองเห็น) และใช้ 'หมายเลขมายากล'; ดังนั้นจึงอาจเกิดข้อผิดพลาดได้ง่าย


42
คุณควรตรวจสอบ typeof argument2 === "undefined" ในกรณีที่มีคนกำหนด "undefined"
เจดับบลิว

133
ฉันจะเพิ่มการแจ้งเตือน - แต่ไอ้คนป่วยที่ทำอะไรแบบนั้น?
Christoph

12
ไม่ได้กำหนดเป็นตัวแปรในพื้นที่ทั่วโลก การค้นหาตัวแปรนั้นในขอบเขตส่วนกลางช้ากว่าการค้นหาตัวแปรในขอบเขตภายใน แต่ที่เร็วที่สุดคือการใช้ typeof x === "ไม่ได้กำหนด"
บาง

5
น่าสนใจ ในทางตรงกันข้ามการเปรียบเทียบ === ไม่ได้กำหนดอาจเร็วกว่าการเปรียบเทียบสตริง การทดสอบของฉันดูเหมือนจะบ่งบอกว่าคุณพูดถูก แต่: x === ยังไม่ได้กำหนดจำเป็น ~ 1.5x เวลาประเภท x === 'ไม่ได้กำหนด'
Christoph

4
@ Christoph: หลังจากอ่านความคิดเห็นของคุณฉันถามไปรอบ ๆ ฉันสามารถพิสูจน์ได้ว่าการเปรียบเทียบสตริงไม่ใช่เพียงการเปรียบเทียบตัวชี้เท่านั้นเนื่องจากการเปรียบเทียบสตริงขนาดยักษ์นั้นใช้เวลานานกว่าสตริงขนาดเล็ก อย่างไรก็ตามการเปรียบเทียบสองสตริงขนาดมหึมานั้นช้ามาก ๆ ถ้ามันถูกสร้างขึ้นมาพร้อมกับการต่อข้อมูล แต่จะเร็วมากถ้ามันถูกสร้างขึ้นมาจากการมอบหมายครั้งแรก ดังนั้นจึงอาจจะทำงานโดยการทดสอบตัวชี้ตามด้วยตัวอักษรตามตัวอักษรทดสอบดังนั้นใช่ฉันก็เต็มไปด้วยอึ :) ขอบคุณสำหรับ enlightening ฉัน ...
ฆ Mendes

17

หากคุณกำลังใช้ jQuery หนึ่งตัวเลือกที่ดี (โดยเฉพาะอย่างยิ่งสำหรับสถานการณ์ที่ซับซ้อน) คือการใช้วิธีการขยายของ jQuery

function foo(options) {

    default_options = {
        timeout : 1000,
        callback : function(){},
        some_number : 50,
        some_text : "hello world"
    };

    options = $.extend({}, default_options, options);
}

หากคุณเรียกใช้ฟังก์ชันเช่นนี้:

foo({timeout : 500});

ตัวแปรตัวเลือกจะเป็น:

{
    timeout : 500,
    callback : function(){},
    some_number : 50,
    some_text : "hello world"
};

15

นี่เป็นหนึ่งในไม่กี่กรณีที่ฉันพบการทดสอบ:

if(! argument2) {  

}

ทำงานค่อนข้างดีและมีความหมายที่ถูกต้อง syntactically

(ด้วยข้อ จำกัด ที่เกิดขึ้นพร้อมกันซึ่งฉันจะไม่อนุญาตให้มีค่า Null ที่ถูกต้องargument2ซึ่งมีความหมายอื่นอยู่บ้าง แต่นั่นจะทำให้สับสนจริงๆ )

แก้ไข:

นี่เป็นตัวอย่างที่ดีมากของความแตกต่างของโวหารระหว่างภาษาที่พิมพ์อย่างหลวม ๆ กับภาษาที่พิมพ์อย่างรุนแรง และตัวเลือกโวหารที่จาวาสคริปต์กำบังในโพดำ

การตั้งค่าส่วนตัวของฉัน (โดยไม่มีการวิจารณ์สำหรับการตั้งค่าอื่น ๆ ) คือความเรียบง่าย ยิ่งรหัสน้อยต้องบอกว่าตราบใดที่ฉันสอดคล้องและรัดกุมยิ่งคนน้อยต้องเข้าใจความหมายของฉันให้ถูกต้อง

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

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

(ฉันคิดว่าข้อเสียที่ร้ายแรงในวิธีที่คนเหล่านี้ใช้ภาษาแบบไดนามิกบ่อยครั้งที่ผู้คนไม่ต้องการเลิกการทดสอบแบบคงที่และจบลงด้วยการแกล้งทำ)

ฉันได้เห็นสิ่งนี้อย่างโจ่งแจ้งที่สุดในการเปรียบเทียบโค้ด ActionScript 3 ที่ครอบคลุมกับโค้ดจาวาสคริปต์ที่หรูหรา AS3 นั้นอาจเป็น 3 หรือ 4 เท่าของ js จำนวนมากและความน่าเชื่อถือที่ฉันสงสัยว่าอย่างน้อยก็ไม่ดีขึ้นเพียงเพราะจำนวน (3-4X) ของการตัดสินใจเข้ารหัสที่ทำ

อย่างที่คุณพูด Shog9, YMMV : D


1
if (! argument2) argument2 = 'default' เทียบเท่ากับ argument2 = argument2 || เริ่มต้น '- ฉันคิดว่ารุ่นที่สองสายตาอื่น ๆ ที่ชื่นชอบ ...
คริสโต

5
และฉันก็พบว่ามัน verbose และเสียสมาธิมากขึ้น แต่มันเป็นความชอบส่วนตัวฉันแน่ใจ
dkretz

4
@le dorfier: มันยังห้ามการใช้สตริงว่าง, 0, และบูลีนเท็จ
Shog9

@le dorfier: เหนือกว่าสุนทรียภาพมีหนึ่งความแตกต่างที่สำคัญ: หลังได้อย่างมีประสิทธิภาพสร้างเส้นทางที่สองของการดำเนินการซึ่งอาจดึงดูดผู้ดูแลประมาทเพื่อเพิ่มพฤติกรรมนอกเหนือจากการกำหนดค่าเริ่มต้นที่เรียบง่าย แน่นอน YMMV
Shog9

3
เกิดอะไรขึ้นถ้า parameter2 เป็นบูลีน === false; หรือฟังก์ชั่น {return false;}?
fresko

7

มีความแตกต่างอย่างมีนัยสำคัญ มาตั้งค่ากรณีทดสอบกันบ้าง:

var unused; // value will be undefined
Test("test1", "some value");
Test("test2");
Test("test3", unused);
Test("test4", null);
Test("test5", 0);
Test("test6", "");

ด้วยวิธีแรกที่คุณอธิบายการทดสอบครั้งที่สองเท่านั้นที่จะใช้ค่าเริ่มต้น วิธีที่สองจะเริ่มต้น แต่แรก (ตาม JS จะแปลงundefined, null, 0และ""ลงในแบบบูลfalse. และถ้าคุณมีการใช้วิธีการของทอมเพียงการทดสอบที่สี่จะใช้ค่าเริ่มต้น!

วิธีการที่คุณเลือกนั้นขึ้นอยู่กับพฤติกรรมที่คุณต้องการ หากค่าอื่นนอกเหนือจากundefinedที่อนุญาตargument2คุณอาจต้องการรูปแบบบางอย่างในครั้งแรก; หากต้องการค่าที่ไม่ใช่ศูนย์, ไม่เป็นโมฆะ, ไม่ว่างเปล่าดังนั้นวิธีที่สองจึงเป็นอุดมคติ - แน่นอนมันมักจะถูกใช้เพื่อกำจัดค่าที่หลากหลายดังกล่าวออกจากการพิจารณาอย่างรวดเร็ว


7
url = url === undefined ? location.href : url;

คำตอบของกระดูกเปลือย คำอธิบายบางอย่างจะไม่เจ็บ
Paul Rooney

เป็นตัวดำเนินการที่ประกอบไปด้วยสามส่วนที่รัดกุมพูดว่า: หาก url ไม่ได้กำหนด (ขาดหายไป) ให้ตั้งค่าตัวแปร url ให้เป็น location.href (หน้าเว็บปัจจุบัน) มิฉะนั้นให้ตั้งค่าตัวแปร url เป็น url ที่กำหนดไว้
rmooney

5

ใน ES6 (ES2015) คุณสามารถใช้พารามิเตอร์เริ่มต้น

function Test(arg1 = 'Hello', arg2 = 'World!'){
  alert(arg1 + ' ' +arg2);
}

Test('Hello', 'World!'); // Hello World!
Test('Hello'); // Hello World!
Test(); // Hello World!


คำตอบนี้น่าสนใจจริงๆและอาจมีประโยชน์ในการเลือก แม้ว่ามันจะไม่ได้ตอบคำถามจริงๆ
Ulysse BN

อย่างที่ฉันเห็น - เขาต้องการนิยามหาเรื่องหากไม่ได้กำหนดดังนั้นฉันโพสต์ข้อมูลที่เป็นประโยชน์
Andriy2

จุดของฉันคือคุณไม่ได้ตอบวิธีที่ดีที่สุดเพื่อตรวจสอบว่าข้อโต้แย้งไม่ได้ส่งไปยังฟังก์ชัน JavaScript แต่คำถามที่อาจจะได้รับการตอบข้อโต้แย้งโดยใช้ค่าเริ่มต้น: ยกตัวอย่างเช่นการตั้งชื่อคุณกับข้อโต้แย้งและการตรวจสอบถ้าค่าแน่นอน"default value" "default value"
Ulysse BN

4

ฉันขอโทษฉันยังไม่สามารถแสดงความคิดเห็นดังนั้นเพื่อตอบคำตอบของทอม ... ใน javascript (undefined! = null) == false ในความเป็นจริงฟังก์ชั่นจะไม่ทำงานกับ "null" คุณควรใช้ "undefined"


1
และทอมมีสอง upvotes สำหรับคำตอบที่ผิด - มันเสมอดีที่จะรู้ว่าวิธีการที่ดีในการทำงานเหล่านี้ระบบชุมชน;)
คริสโต

3

ทำไมไม่ใช้!!โอเปอเรเตอร์? ผู้ประกอบการนี้วางไว้ก่อนตัวแปรเปิดการบูลีน (ถ้าฉันเข้าใจดี) ดังนั้น!!undefinedและ!!null(และแม้!!NaNซึ่งอาจจะเป็นที่น่าสนใจมาก) falseจะกลับมา

นี่คือตัวอย่าง:

function foo(bar){
    console.log(!!bar);
}

foo("hey") //=> will log true

foo() //=> will log false

ไม่ทำงานกับบูลีน true, ศูนย์และสตริงว่าง ตัวอย่างเช่น foo (0); จะเข้าสู่ระบบเป็นเท็จ แต่ foo (1) จะเข้าสู่ระบบจริง
rosell.dk

2

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

function foo(options) {
    var config = { // defaults
        list: 'string value',
        of: [a, b, c],
        optional: {x: y},
        objects: function(param){
           // do stuff here
        }
    }; 
    if(options !== undefined){
        for (i in config) {
            if (config.hasOwnProperty(i)){
                if (options[i] !== undefined) { config[i] = options[i]; }
            }
        }
    }
}

2

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

this.setCurrent = function(value) {
  this.current = value || 0;
};

นี่หมายความว่าจำเป็นถ้าค่าของvalueไม่มีอยู่ / ผ่าน - ตั้งเป็น 0

ค่อนข้างเย็นฮะ!


1
นี่หมายถึง "ถ้าvalueเท่ากับให้falseตั้งเป็น 0" นี่คือความแตกต่างที่ลึกซึ้ง แต่สำคัญมาก
ชาร์ลส์วู้ด

@CharlesWood มูลค่าไม่ผ่าน / วิธีปัจจุบันเป็นเท็จเท่านั้น
Mohammed Zameer

1
แน่นอนว่าถ้าตรงกับความต้องการสำหรับฟังก์ชั่นของคุณ แต่ถ้าตัวอย่างเช่นพารามิเตอร์ของคุณคือบูลีนแล้วtrueและfalseเป็นค่าที่ถูกต้องและคุณอาจต้องการที่จะมีพฤติกรรมที่สามสำหรับเมื่อพารามิเตอร์จะไม่ผ่านเลย (โดยเฉพาะถ้าฟังก์ชั่นมีมากกว่าหนึ่งพารามิเตอร์)
Charles Wood

1
และฉันควรยอมรับว่านี่เป็นข้อโต้แย้งที่ใหญ่หลวงในวิทยาการคอมพิวเตอร์และอาจจบลงด้วยการเป็นเรื่องของความเห็น: D
Charles Wood

@CharlesWood ขอโทษที่มาสายไปงานเลี้ยง ฉันขอแนะนำให้คุณเพิ่มคำตอบเหล่านี้พร้อมตัวเลือกการแก้ไข
Mohammed Zameer

1

บางครั้งคุณอาจต้องการตรวจสอบชนิดโดยเฉพาะอย่างยิ่งถ้าคุณใช้ฟังก์ชั่นเป็น getter และ setter รหัสต่อไปนี้คือ ES6 (จะไม่ทำงานใน EcmaScript 5 หรือเก่ากว่า):

class PrivateTest {
    constructor(aNumber) {
        let _aNumber = aNumber;

        //Privileged setter/getter with access to private _number:
        this.aNumber = function(value) {
            if (value !== undefined && (typeof value === typeof _aNumber)) {
                _aNumber = value;
            }
            else {
                return _aNumber;
            }
        }
    }
}

0
function example(arg) {
  var argumentID = '0'; //1,2,3,4...whatever
  if (argumentID in arguments === false) {
    console.log(`the argument with id ${argumentID} was not passed to the function`);
  }
}

Object.prototypeเพราะอาร์เรย์สืบทอดมาจาก พิจารณา⇑เพื่อทำให้โลกดีขึ้น


-1

fnCalledFunction (Param1, Param2, window.YourOptionalParameter)

ถ้าฟังก์ชั่นด้านบนถูกเรียกใช้จากหลายสถานที่และคุณแน่ใจว่าพารามิเตอร์ 2 ตัวแรกถูกส่งผ่านจากทุกที่ แต่ไม่แน่ใจเกี่ยวกับพารามิเตอร์ที่ 3 จากนั้นคุณสามารถใช้หน้าต่าง

window.param3 จะจัดการหากไม่ได้กำหนดจากวิธีการโทร

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