วิธีการใช้อาร์กิวเมนต์และพารามิเตอร์ ES6

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ นักพัฒนาใช้คุณลักษณะ ECMAScript 6 มากขึ้นเรื่อยๆ และในไม่ช้าคุณลักษณะเหล่านี้จะหลีกเลี่ยงไม่ได้ ในบทช่วยสอนนี้ คุณจะได้เรียนรู้วิธีที่ ECMAScript 6 อัปเกรดการจัดการพารามิเตอร์ใน JavaScript และอื่นๆ

ECMAScript 6 (หรือ ECMAScript 2015) เป็นเวอร์ชันล่าสุดของมาตรฐาน ECMAScript และมีการปรับปรุงการจัดการพารามิเตอร์ใน JavaScript อย่างน่าทึ่ง ตอนนี้เราสามารถใช้พารามิเตอร์การพัก ค่าดีฟอลต์ และการทำลายโครงสร้าง ท่ามกลางคุณสมบัติใหม่อื่นๆ

ในบทช่วยสอนนี้ เราจะสำรวจอาร์กิวเมนต์และพารามิเตอร์โดยละเอียดและดูว่า ECMAScript 6 อัปเกรดได้อย่างไร

อาร์กิวเมนต์กับพารามิเตอร์

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

 function foo(param1, param2) { // do something } foo(10, 20);

ในฟังก์ชันนี้ param1 และ param2 เป็นพารามิเตอร์ของฟังก์ชัน และค่าที่ส่งไปยังฟังก์ชัน ( 10 และ 20 ) เป็นอาร์กิวเมนต์

ตัวดำเนินการสเปรด (…)

ใน ECMAScript 5 เมธอด apply() เป็นเครื่องมือที่สะดวกสำหรับการส่งอาร์เรย์เป็นอาร์กิวเมนต์ไปยังฟังก์ชัน ตัวอย่างเช่น โดยทั่วไปจะใช้กับ Math.max() เพื่อค้นหาค่าสูงสุดในอาร์เรย์ พิจารณาส่วนย่อยของรหัสนี้:

 var myArray = [5, 10, 50]; Math.max(myArray); // Error: NaN Math.max.apply(Math, myArray); // 50

Math.max() ไม่รองรับอาร์เรย์ รับเฉพาะตัวเลขเท่านั้น เมื่ออาร์เรย์ถูกส่งไปยัง Math.max() อาร์เรย์จะเกิดข้อผิดพลาด แต่เมื่อใช้วิธี apply() อาร์เรย์จะถูกส่งเป็นตัวเลขเดี่ยว ดังนั้น Math.max() จึงสามารถจัดการได้

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

โชคดีที่ด้วยการแนะนำตัวดำเนินการสเปรดใน ECMAScript 6 เราไม่จำเป็นต้องใช้เมธอด apply() อีกต่อไป ด้วยตัวดำเนินการกระจาย เราสามารถขยายนิพจน์ออกเป็นหลายอาร์กิวเมนต์ได้อย่างง่ายดาย:

 var myArray = [5, 10, 50]; Math.max(...myArray); // 50

ที่นี่ ตัวดำเนินการสเปรดขยาย myArray เพื่อสร้างค่าแต่ละค่าสำหรับฟังก์ชัน ในขณะที่จำลองตัวดำเนินการสเปรดโดยใช้ apply() ใน ECMAScript 5 นั้นเป็นไปได้ ไวยากรณ์นั้นสับสนและขาดความยืดหยุ่นของตัวดำเนินการสเปรด ตัวดำเนินการสเปรดไม่เพียงแต่จะใช้งานง่ายขึ้นเท่านั้น แต่ยังมีฟีเจอร์อีกมากมาย ตัวอย่างเช่น สามารถใช้หลายครั้งและสามารถผสมกับอาร์กิวเมนต์อื่นในการเรียก function :

 function myFunction() { for(var i in arguments){ console.log(arguments[i]); } } var params = [10, 15]; myFunction(5, ...params, 20, ...[25]); // 5 10 15 20 25

ข้อดีอีกประการของตัวดำเนินการสเปรดคือสามารถใช้กับคอนสตรัคเตอร์ได้อย่างง่ายดาย:

 new Date(...[2016, 5, 6]); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)

แน่นอน เราสามารถเขียนโค้ดก่อนหน้าใน ECMAScript 5 ได้ แต่เราจำเป็นต้องใช้รูปแบบที่ซับซ้อนเพื่อหลีกเลี่ยงข้อผิดพลาดประเภท:

 new Date.apply(null, [2016, 4, 24]); // TypeError: Date.apply is not a constructor new (Function.prototype.bind.apply(Date, [null].concat([2016, 5, 6]))); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)

การสนับสนุนเบราว์เซอร์ตัวดำเนินการกระจายในการเรียกใช้ฟังก์ชัน

เบราว์เซอร์เดสก์ท็อป:

โครเมียม Firefox Internet Explorer Microsoft Edge โอเปร่า ซาฟารี
46 27 ได้รับการสนับสนุน 7.1

เบราว์เซอร์มือถือ:

Chrome สำหรับ Android Firefox Mobile Safari Mobile Opera Mobile IE มือถือ
46 27 8

พารามิเตอร์ส่วนที่เหลือ

พารามิเตอร์ rest มีไวยากรณ์เดียวกันกับตัวดำเนินการ spread แต่แทนที่จะขยายอาร์เรย์เป็นพารามิเตอร์ พารามิเตอร์จะรวบรวมพารามิเตอร์และเปลี่ยนเป็นอาร์เรย์

 function myFunction(...options) { return options; } myFunction('a', 'b', 'c'); // ["a", "b", "c"]

หากไม่มีอาร์กิวเมนต์ พารามิเตอร์ rest จะถูกตั้งค่าเป็นอาร์เรย์ว่าง:

 function myFunction(...options) { return options; } myFunction(); // []

พารามิเตอร์ rest มีประโยชน์อย่างยิ่งเมื่อสร้างฟังก์ชัน Variadic (ฟังก์ชันที่ยอมรับจำนวนตัวแปรของอาร์กิวเมนต์) ประโยชน์ของการเป็นอาร์เรย์ พารามิเตอร์ส่วนที่เหลือสามารถแทนที่วัตถุ arguments ได้อย่างง่ายดาย (ซึ่งเราจะอธิบายในบทช่วยสอนนี้ในภายหลัง) พิจารณาฟังก์ชันนี้ ซึ่งเขียนด้วย ECMAScript 5:

 function checkSubstrings(string) { for (var i = 1; i < arguments.length; i++) { if (string.indexOf(arguments[i]) === -1) { return false; } } return true; } checkSubstrings('this is a string', 'is', 'this'); // true

ฟังก์ชันนี้จะตรวจสอบว่าสตริงมีสตริงย่อยจำนวนหนึ่งหรือไม่ ปัญหาแรกของฟังก์ชันนี้คือ เราต้องมองเข้าไปในเนื้อความของ function เพื่อดูว่าต้องใช้หลายอาร์กิวเมนต์ ปัญหาที่สองคือการวนซ้ำต้องเริ่มจาก 1 แทนที่จะเป็น 0 เพราะ arguments[0] ชี้ไปที่อาร์กิวเมนต์แรก หากภายหลังเราตัดสินใจเพิ่มพารามิเตอร์อื่นก่อนหรือหลังสตริง เราอาจลืมอัปเดตลูป ด้วยพารามิเตอร์ที่เหลือ เราหลีกเลี่ยงปัญหาเหล่านี้ได้อย่างง่ายดาย:

 function checkSubstrings(string, ...keys) { for (var key of keys) { if (string.indexOf(key) === -1) { return false; } } return true; } checkSubstrings('this is a string', 'is', 'this'); // true

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

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

 function logArguments(a, ...params, b) { console.log(a, params, b); } logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter

ข้อจำกัดอีกประการหนึ่งคืออนุญาตให้ใช้พารามิเตอร์ส่วนที่เหลือเพียงตัวเดียวในการประกาศ function :

 function logArguments(...param1, ...param2) { } logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter

รองรับเบราว์เซอร์พารามิเตอร์ส่วนที่เหลือ

เบราว์เซอร์เดสก์ท็อป:

โครเมียม Firefox Internet Explorer Microsoft Edge โอเปร่า ซาฟารี
47 15 ได้รับการสนับสนุน 34

เบราว์เซอร์มือถือ:

Chrome สำหรับ Android Firefox Mobile Safari Mobile Opera Mobile IE มือถือ
47 15

พารามิเตอร์เริ่มต้น

พารามิเตอร์เริ่มต้นใน ECMAScript 5

JavaScript ไม่สนับสนุนพารามิเตอร์เริ่มต้นใน ECMAScript 5 แต่มีวิธีแก้ปัญหาที่ง่าย การใช้ตัวดำเนินการ OR แบบลอจิคัล ( || ) ภายในฟังก์ชัน เราสามารถจำลองพารามิเตอร์เริ่มต้นใน ECMAScript 5 ได้อย่างง่ายดาย พิจารณาฟังก์ชันนี้:

 function foo(param1, param2) { param1 = param1 || 10; param2 = param2 || 10; console.log(param1, param2); } foo(5, 5); // 5 5 foo(5); // 5 10 foo(); // 10 10

ฟังก์ชันนี้คาดหวังสองอาร์กิวเมนต์ แต่เมื่อถูกเรียกโดยไม่มีอาร์กิวเมนต์ จะใช้ค่าเริ่มต้น ภายในฟังก์ชัน อาร์กิวเมนต์ที่หายไปจะถูกตั้งค่าโดยอัตโนมัติเป็นไม่ได้กำหนด ดังนั้นเราจึงสามารถตรวจจับอาร์กิวเมนต์เหล่านี้และประกาศค่าเริ่มต้นสำหรับอาร์กิวเมนต์เหล่านี้ได้ ในการตรวจจับอาร์กิวเมนต์ที่ขาดหายไปและตั้งค่าเริ่มต้น เราใช้ตัวดำเนินการ OR แบบลอจิคัล ( || ) โอเปอเรเตอร์นี้ตรวจสอบอาร์กิวเมนต์แรก: ถ้าเป็นจริง โอเปอเรเตอร์จะส่งกลับ ถ้าไม่ใช่ ตัวดำเนินการจะส่งกลับอาร์กิวเมนต์ที่สอง

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

 function foo(param1, param2) { if(param1 === undefined){ param1 = 10; } if(param2 === undefined){ param2 = 10; } console.log(param1, param2); } foo(0, null); // 0, null foo(); // 10, 10

ภายในฟังก์ชันนี้ ประเภทของอาร์กิวเมนต์ที่ส่งผ่านจะถูกตรวจสอบเพื่อให้แน่ใจว่าไม่ได้กำหนดไว้ก่อนที่จะกำหนดค่าเริ่มต้น วิธีนี้ต้องใช้โค้ดเพิ่มอีกเล็กน้อย แต่เป็นทางเลือกที่ปลอดภัยกว่า และทำให้เราสามารถส่งค่า 0 และ null ไปยังฟังก์ชันได้

พารามิเตอร์เริ่มต้นใน ECMAScript 6

ด้วย ECMAScript 6 เราไม่จำเป็นต้องตรวจสอบค่าที่ไม่ได้กำหนดเพื่อจำลองพารามิเตอร์เริ่มต้นอีกต่อไป ตอนนี้เราสามารถใส่ค่าดีฟอลต์ลงในการประกาศ function ได้:

 function foo(a = 10, b = 10) { console.log(a, b); } foo(5); // 5 10 foo(0, null); // 0 null

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

 function getParam() { alert("getParam was called"); return 3; } function multiply(param1, param2 = getParam()) { return param1 * param2; } multiply(2, 5); // 10 multiply(2); // 6 (also displays an alert dialog)

โปรดทราบว่าฟังก์ชัน getParam จะถูกเรียกใช้ก็ต่อเมื่อละเว้นอาร์กิวเมนต์ที่สอง ดังนั้น เมื่อเราเรียกใช้ฟังก์ชัน multiply() ด้วยสองพารามิเตอร์ การแจ้งเตือนจะไม่ปรากฏ

คุณสมบัติที่น่าสนใจอีกประการของพารามิเตอร์เริ่มต้นคือเราสามารถอ้างถึงพารามิเตอร์และตัวแปรอื่น ๆ ในการประกาศ function :

 function myFunction(a=10, b=a) { console.log('a = ' + a + '; b = ' + b); } myFunction(); // a=10; b=10 myFunction(22); // a=22; b=22 myFunction(2, 4); // a=2; b=4

คุณยังสามารถดำเนินการในการประกาศ function :

 function myFunction(a, b = ++a, c = a*b) { console.log(c); } myFunction(5); // 36

โปรดทราบว่า JavaScript ประเมินพารามิเตอร์เริ่มต้น ณ เวลาโทร ไม่เหมือนกับภาษาอื่นๆ บางภาษา:

 function add(value, array = []) { array.push(value); return array; } add(5); // [5] add(6); // [6], not [5, 6]

การสนับสนุนเบราว์เซอร์พารามิเตอร์เริ่มต้น

เบราว์เซอร์เดสก์ท็อป:

คุณสมบัติ โครเมียม Firefox Internet Explorer Microsoft Edge โอเปร่า ซาฟารี
การสนับสนุนขั้นพื้นฐาน 49 15 14
พารามิเตอร์ที่ไม่มีค่าเริ่มต้นหลังจากพารามิเตอร์เริ่มต้น 49 26 14

เบราว์เซอร์มือถือ:

คุณสมบัติ Chrome สำหรับ Android Firefox Mobile Safari Mobile Opera Mobile IE มือถือ
การสนับสนุนขั้นพื้นฐาน 49 15
พารามิเตอร์ที่ไม่มีค่าเริ่มต้นหลังจากพารามิเตอร์เริ่มต้น 46 26

การทำลายล้าง

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

ใน ECMAScript 5 มักใช้อ็อบเจ็กต์การกำหนดค่าเพื่อจัดการกับพารามิเตอร์ทางเลือกจำนวนมาก โดยเฉพาะอย่างยิ่งเมื่อลำดับของคุณสมบัติไม่สำคัญ พิจารณาฟังก์ชั่นนี้:

 function initiateTransfer(options) { var protocol = options.protocol, port = options.port, delay = options.delay, retries = options.retries, timeout = options.timeout, log = options.log; // code to initiate transfer } options = { protocol: 'http', port: 800, delay: 150, retries: 10, timeout: 500, log: true }; initiateTransfer(options);

รูปแบบนี้มักใช้โดยนักพัฒนา JavaScript และทำงานได้ดี แต่เราต้องดูภายในเนื้อหาของ function เพื่อดูว่าคาดหวังพารามิเตอร์ใดบ้าง ด้วยพารามิเตอร์ที่ทำลายล้าง เราสามารถระบุพารามิเตอร์ในการประกาศ function ได้อย่างชัดเจน:

 function initiateTransfer({protocol, port, delay, retries, timeout, log}) { // code to initiate transfer }; var options = { protocol: 'http', port: 800, delay: 150, retries: 10, timeout: 500, log: true } initiateTransfer(options);

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

นอกจากนี้เรายังสามารถรวมพารามิเตอร์ที่ทำลายล้างกับพารามิเตอร์ปกติได้:

 function initiateTransfer(param1, {protocol, port, delay, retries, timeout, log}) { // code to initiate transfer } initiateTransfer('some value', options);

โปรดทราบว่าข้อผิดพลาดประเภทจะถูกส่งออกไปถ้าพารามิเตอร์ถูกละเว้นในการเรียก function :

 function initiateTransfer({protocol, port, delay, retries, timeout, log}) { // code to initiate transfer } initiateTransfer(); // TypeError: Cannot match against 'undefined' or 'null'

นี่เป็นพฤติกรรมที่ต้องการเมื่อเราต้องการพารามิเตอร์ แต่ถ้าเราต้องการให้พารามิเตอร์เหล่านี้เป็นทางเลือกล่ะ เพื่อป้องกันข้อผิดพลาดนี้เมื่อพารามิเตอร์หายไป เราจำเป็นต้องกำหนดค่าเริ่มต้นให้กับพารามิเตอร์ที่ทำลาย:

 function initiateTransfer({protocol, port, delay, retries, timeout, log} = {}) { // code to initiate transfer } initiateTransfer(); // no error

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

นอกจากนี้เรายังสามารถกำหนดค่าเริ่มต้นให้กับแต่ละพารามิเตอร์ที่ทำลาย:

 function initiateTransfer({ protocol = 'http', port = 800, delay = 150, retries = 10, timeout = 500, log = true }) { // code to initiate transfer }

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

การทำลายล้างการสนับสนุนเบราว์เซอร์

เบราว์เซอร์เดสก์ท็อป:

คุณสมบัติ โครเมียม Firefox Internet Explorer Microsoft Edge โอเปร่า ซาฟารี
การสนับสนุนขั้นพื้นฐาน 49 2.0 14 7.1
พารามิเตอร์ที่ถูกทำลายด้วยการกำหนดค่าเริ่มต้น 49 47 14

เบราว์เซอร์มือถือ:

คุณสมบัติ Chrome สำหรับ Android Firefox Mobile Safari Mobile Opera Mobile IE มือถือ
การสนับสนุนขั้นพื้นฐาน 49 1 8
พารามิเตอร์ที่ไม่มีค่าเริ่มต้นหลังจากพารามิเตอร์เริ่มต้น 49 47

ผ่านข้อโต้แย้ง

มีสองวิธีในการส่งผ่านอาร์กิวเมนต์ไปยังฟังก์ชัน: โดยการอ้างอิงหรือตามค่า การแก้ไขอาร์กิวเมนต์ที่ส่งผ่านโดยการอ้างอิงจะมีผลทั่วโลก แต่การแก้ไขอาร์กิวเมนต์ที่ส่งผ่านโดยค่าจะสะท้อนให้เห็นในฟังก์ชันเท่านั้น

ในบางภาษา เช่น Visual Basic และ PowerShell เรามีตัวเลือกในการระบุว่าจะส่งอาร์กิวเมนต์โดยการอ้างอิงหรือตามค่า แต่นั่นไม่ใช่กรณีของ JavaScript

ผ่านอาร์กิวเมนต์ตามค่า

ในทางเทคนิค JavaScript สามารถส่งผ่านค่าเท่านั้น เมื่อเราส่งอาร์กิวเมนต์ไปยังฟังก์ชันตามค่า สำเนาของค่านั้นจะถูกสร้างขึ้นภายในขอบเขตของ function ดังนั้น การเปลี่ยนแปลงใดๆ ของค่าจะสะท้อนให้เห็นภายใน function เท่านั้น พิจารณาตัวอย่างนี้:

 var a = 5; function increment(a) { a = ++a; console.log(a); } increment(a); // 6 console.log(a); // 5

ในที่นี้ การปรับเปลี่ยนอาร์กิวเมนต์ภายในฟังก์ชันจะไม่มีผลกับค่าดั้งเดิม ดังนั้น เมื่อตัวแปรถูกบันทึกจากภายนอกฟังก์ชัน ค่าที่พิมพ์ยังคงเป็น 5

ผ่านอาร์กิวเมนต์โดยการอ้างอิง

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

พิจารณาฟังก์ชั่นนี้:

 function foo(param){ param.bar = 'new value'; } obj = { bar : 'value' } console.log(obj.bar); // value foo(obj); console.log(obj.bar); // new value

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

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

การตรวจสอบประเภทและพารามิเตอร์ที่ขาดหายไปหรือเพิ่มเติม

ในภาษาที่มีการพิมพ์ที่ชัดเจน เราต้องระบุประเภทของพารามิเตอร์ในการประกาศ function แต่ JavaScript ขาดคุณสมบัตินี้ ใน JavaScript ไม่สำคัญว่าข้อมูลประเภทใดหรือกี่อาร์กิวเมนต์ที่เราส่งผ่านไปยังฟังก์ชัน

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

จำนวนอาร์กิวเมนต์และพารามิเตอร์อาจแตกต่างกันในสองวิธี:

  • อาร์กิวเมนต์น้อยกว่าพารามิเตอร์
    พารามิเตอร์ที่ขาดหายไปจะเท่ากับ undefined
  • อาร์กิวเมนต์มากกว่าพารามิเตอร์
    พารามิเตอร์พิเศษจะถูกละเว้น แต่สามารถดึงข้อมูลได้ผ่านอาร์กิวเมนต์ตัวแปรพิเศษคล้ายอาร์เรย์ (จะกล่าวถึงต่อไป)

อาร์กิวเมนต์บังคับ

หากไม่มีอาร์กิวเมนต์ในการเรียก function อาร์กิวเมนต์จะถูกตั้งค่าเป็น undefined เราสามารถใช้ประโยชน์จากพฤติกรรมนี้และแสดงข้อผิดพลาดหากละเว้นอาร์กิวเมนต์:

 function foo(mandatory, optional) { if (mandatory === undefined) { throw new Error('Missing parameter: mandatory'); } }

ใน ECMAScript 6 เราสามารถดำเนินการเพิ่มเติมและใช้พารามิเตอร์เริ่มต้นเพื่อตั้งค่าอาร์กิวเมนต์บังคับ:

 function throwError() { throw new Error('Missing parameter'); } function foo(param1 = throwError(), param2 = throwError()) { // do something } foo(10, 20); // ok foo(10); // Error: missing parameter

วัตถุอาร์กิวเมนต์

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

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

 function checkParams(param1) { console.log(param1); // 2 console.log(arguments[0], arguments[1]); // 2 3 console.log(param1 + arguments[0]); // 2 + 2 } checkParams(2, 3);

ฟังก์ชันนี้คาดว่าจะได้รับอาร์กิวเมนต์เดียวเท่านั้น เมื่อเราเรียกมันด้วยสองอาร์กิวเมนต์ อาร์กิวเมนต์แรกสามารถเข้าถึงได้ในฟังก์ชันโดยชื่อพารามิเตอร์ param1 หรืออาร์กิวเมนต์ object arguments[0] แต่อาร์กิวเมนต์ที่สองสามารถเข้าถึงได้เป็น arguments[1] เท่านั้น นอกจากนี้ โปรดทราบว่าวัตถุ arguments สามารถใช้ร่วมกับอาร์กิวเมนต์ที่มีชื่อได้

ออบเจ็กต์ arguments มีรายการสำหรับอาร์กิวเมนต์แต่ละรายการที่ส่งไปยังฟังก์ชัน และดัชนีของรายการแรกเริ่มต้นที่ 0 หากเราต้องการเข้าถึงอาร์กิวเมนต์เพิ่มเติมในตัวอย่างข้างต้น เราจะเขียน arguments[2] arguments[3] และอื่นๆ

เราสามารถข้ามการตั้งค่าที่มีชื่อพารามิเตอร์ไปพร้อมกันและใช้วัตถุ arguments :

 function checkParams() { console.log(arguments[1], arguments[0], arguments[2]); } checkParams(2, 4, 6); // 4 2 6

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

 function checkParams(...params) { console.log(params[1], params[0], params[2]); // 4 2 6 console.log(arguments[1], arguments[0], arguments[2]); // 4 2 6 } checkParams(2, 4, 6);

วัตถุ arguments เป็นวัตถุคล้ายอาร์เรย์ แต่ไม่มีวิธีการอาร์เรย์เช่น slice() และ foreach() เพื่อที่จะใช้วิธีการอาร์เรย์บนวัตถุ arguments จะต้องแปลงวัตถุเป็นอาร์เรย์จริงก่อน:

 function sort() { var a = Array.prototype.slice.call(arguments); return a.sort(); } sort(40, 20, 50, 30); // [20, 30, 40, 50]

ในฟังก์ชันนี้ Array.prototype.slice.call() ถูกใช้เป็นวิธีที่รวดเร็วในการแปลงอ็อบเจ็กต์ arguments เป็นอาร์เรย์ ถัดไป sort() วิธีการเรียงลำดับรายการของอาร์เรย์และส่งกลับ

ECMAScript 6 มีวิธีการที่ตรงไปตรงมายิ่งขึ้น Array.from() การเพิ่มใหม่ใน ECMAScript 6 สร้างอาร์เรย์ใหม่จากอ็อบเจ็กต์ที่เหมือนอาร์เรย์:

 function sort() { var a = Array.from(arguments); return a.sort(); } sort(40, 20, 50, 30); // [20, 30, 40, 50]

คุณสมบัติความยาว

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

 function countArguments() { console.log(arguments.length); } countArguments(); // 0 countArguments(10, null, "string"); // 3

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

 function foo(param1, param2) { if (arguments.length < 2) { throw new Error("This function expects at least two arguments"); } else if (arguments.length === 2) { // do something } }

พารามิเตอร์ส่วนที่เหลือเป็นอาร์เรย์ ดังนั้นจึงมีคุณสมบัติด้าน length ใน ECMAScript 6 โค้ดก่อนหน้าสามารถเขียนใหม่ด้วยพารามิเตอร์ส่วนที่เหลือ:

 function foo(...params) { if (params.length < 2) { throw new Error("This function expects at least two arguments"); } else if (params.length === 2) { // do something } }

คุณสมบัติของผู้โทรและผู้โทร

คุณสมบัติ callee หมายถึงฟังก์ชันที่กำลังทำงานอยู่ และ caller หมายถึงฟังก์ชันที่เรียกใช้ฟังก์ชันที่กำลังดำเนินการอยู่ ในโหมดเข้มงวด ECMAScript 5 คุณสมบัติเหล่านี้เลิกใช้แล้ว และการพยายามเข้าถึงจะทำให้เกิด TypeError

คุณสมบัติ arguments.callee มีประโยชน์ในฟังก์ชันแบบเรียกซ้ำ (ฟังก์ชันแบบเรียกซ้ำเป็นฟังก์ชันปกติที่อ้างอิงถึงตัวเองโดยใช้ชื่อ) โดยเฉพาะอย่างยิ่งเมื่อไม่มีชื่อฟังก์ชัน (ฟังก์ชันที่ไม่ระบุตัวตน) เนื่องจากฟังก์ชันที่ไม่ระบุชื่อไม่มีชื่อ วิธีเดียวที่จะอ้างอิงถึงได้คือโดย arguments.callee

 var result = (function(n) { if (n <= 1) { return 1; } else { return n * arguments.callee(n - 1); } })(4); // 24

อาร์กิวเมนต์วัตถุในโหมดเข้มงวดและไม่เข้มงวด

ในโหมดไม่เคร่งครัดของ ECMAScript 5 อ็อบเจ็กต์ arguments มีคุณลักษณะที่ผิดปกติ: ทำให้ค่าซิงค์กับค่าของพารามิเตอร์ที่มีชื่อที่สอดคล้องกัน

พิจารณาส่วนย่อยของรหัสต่อไปนี้:

 function foo(param) { console.log(param === arguments[0]); // true arguments[0] = 500; console.log(param === arguments[0]); // true return param } foo(200); // 500

ภายในฟังก์ชันนี้ ค่าใหม่ถูกกำหนดให้กับ arguments[0] เนื่องจากค่าของ arguments จะซิงค์กับค่าของพารามิเตอร์ที่มีชื่อเสมอ การเปลี่ยนแปลงเป็น arguments[0] จะเปลี่ยนค่าของ param ด้วย อันที่จริง มันเหมือนกับชื่อที่แตกต่างกันสองชื่อสำหรับตัวแปรเดียวกัน ในโหมดเข้มงวด ECMAScript 5 พฤติกรรมที่สับสนของวัตถุ arguments นี้ถูกลบออก:

 "use strict"; function foo(param) { console.log(param === arguments[0]); // true arguments[0] = 500; console.log(param === arguments[0]); // false return param } foo(200); // 200

คราวนี้ การเปลี่ยน arguments[0] จะไม่มีผลกับ param และผลลัพธ์เป็นไปตามที่คาดไว้ ผลลัพธ์ของฟังก์ชันนี้ใน ECMAScript 6 จะเหมือนกับในโหมดเข้มงวดของ ECMAScript 5 แต่โปรดทราบว่าเมื่อใช้ค่าเริ่มต้นในการประกาศ function วัตถุ arguments จะไม่ได้รับผลกระทบ:

 function foo(param1, param2 = 10, param3 = 20) { console.log(param1 === arguments[0]); // true console.log(param2 === arguments[1]); // true console.log(param3 === arguments[2]); // false console.log(arguments[2]); // undefined console.log(param3); // 20 } foo('string1', 'string2');

ในฟังก์ชันนี้ แม้ว่า param3 จะมีค่าเริ่มต้น แต่ก็ไม่เท่ากับ arguments[2] เนื่องจากมีเพียงสองอาร์กิวเมนต์เท่านั้นที่ส่งผ่านไปยังฟังก์ชัน กล่าวอีกนัยหนึ่ง การตั้งค่าเริ่มต้นไม่มีผลกับวัตถุ arguments

บทสรุป

ECMAScript 6 ได้นำการปรับปรุงขนาดเล็กและใหญ่หลายร้อยรายการมาสู่ JavaScript นักพัฒนาใช้ฟีเจอร์ ECMAScript 6 มากขึ้นเรื่อยๆ และในไม่ช้าฟีเจอร์เหล่านี้จะหลีกเลี่ยงไม่ได้ ในบทช่วยสอนนี้ เราได้เรียนรู้ว่า ECMAScript 6 ได้อัปเกรดการจัดการพารามิเตอร์ใน JavaScript ได้อย่างไร แต่เราเพิ่งจะขีดข่วนพื้นผิวของ ECMAScript 6 ฟีเจอร์ใหม่และน่าสนใจอื่นๆ ของภาษานั้นคุ้มค่าที่จะลองดู

ลิงค์

  • ตารางความเข้ากันได้ของ ECMAScript 6, Juriy Zaytsev
  • “ข้อกำหนดภาษา ECMAScript 2015” ECMA International