ดังที่เราทราบแล้วว่าฟังก์ชันใน JavaScript นั้นเป็นค่า
ทุกค่าใน JavaScript มีประเภท ฟังก์ชันเป็นประเภทใด?
ใน JavaScript ฟังก์ชันคือวัตถุ
วิธีที่ดีในการจินตนาการถึงฟังก์ชันต่างๆ ก็คือ "วัตถุการกระทำ" ที่สามารถเรียกได้ เราไม่เพียงแต่เรียกพวกมันเท่านั้น แต่ยังถือว่าพวกมันเป็นวัตถุด้วย เช่น เพิ่ม/ลบคุณสมบัติ ส่งผ่านการอ้างอิง เป็นต้น
วัตถุฟังก์ชันมีคุณสมบัติที่ใช้งานได้บางอย่าง
ตัวอย่างเช่น ชื่อของฟังก์ชันสามารถเข้าถึงได้เป็นคุณสมบัติ “name”:
ฟังก์ชั่น sayHi() { alert("สวัสดี"); - การแจ้งเตือน (sayHi.name); //ทักทาย.
อะไรที่ตลกดี ตรรกะในการตั้งชื่อนั้นฉลาด นอกจากนี้ยังกำหนดชื่อที่ถูกต้องให้กับฟังก์ชันแม้ว่าจะไม่ได้สร้างขึ้นมาเลยก็ตาม จากนั้นจึงกำหนดชื่อให้ทันที:
ให้ sayHi = function() { alert("สวัสดี"); - การแจ้งเตือน (sayHi.name); // sayHi (มีชื่อนะ!)
นอกจากนี้ยังใช้งานได้หากการมอบหมายเสร็จสิ้นผ่านค่าเริ่มต้น:
ฟังก์ชั่น f(sayHi = ฟังก์ชั่น() {}) { การแจ้งเตือน (sayHi.name); // sayHi (ได้ผล!) - ฉ();
ในข้อกำหนดคุณลักษณะนี้เรียกว่า "ชื่อตามบริบท" หากฟังก์ชันไม่มีมาให้ ก็จะคิดออกจากบริบทในการมอบหมายงาน
วิธีการของวัตถุก็มีชื่อเช่นกัน:
ให้ผู้ใช้ = { พูดสวัสดี() { - - บอกลา: ฟังก์ชั่น () { - - - การแจ้งเตือน (user.sayHi.name); //ทักทาย. การแจ้งเตือน (user.sayBye.name); //บอกลา
แม้ว่าจะไม่มีเวทย์มนตร์ก็ตาม มีหลายกรณีที่ไม่มีทางรู้ชื่อที่ถูกต้องได้ ในกรณีนั้น คุณสมบัติชื่อจะว่างเปล่า เช่นที่นี่:
// ฟังก์ชั่นที่สร้างขึ้นภายในอาร์เรย์ ให้ arr = [function() {}]; การแจ้งเตือน ( arr[0].ชื่อ ); // <สตริงว่าง> // เครื่องยนต์ไม่มีทางตั้งชื่อให้ถูกต้องจึงไม่มี
อย่างไรก็ตาม ในทางปฏิบัติ ฟังก์ชันส่วนใหญ่จะมีชื่อ
มีคุณสมบัติ "ความยาว" ในตัวอีกตัวที่ส่งกลับจำนวนพารามิเตอร์ของฟังก์ชัน เช่น:
ฟังก์ชัน f1(ก) {} ฟังก์ชัน f2(ก, ข) {} ฟังก์ชั่นมากมาย(a, b, ...เพิ่มเติม) {} การแจ้งเตือน (f1.length); // 1 การแจ้งเตือน (f2.length); // 2 การแจ้งเตือน (หลายความยาว); // 2
ตรงนี้เราจะเห็นว่าพารามิเตอร์ส่วนที่เหลือจะไม่ถูกนับ
บางครั้งคุณสมบัติ length
ถูกใช้สำหรับการวิปัสสนาในฟังก์ชันที่ทำงานกับฟังก์ชันอื่น
ตัวอย่างเช่น ในโค้ดด้านล่าง ฟังก์ชัน ask
จะยอมรับ question
ที่จะถามและฟังก์ชัน handler
จำนวนเท่าใดก็ได้ที่จะโทร
เมื่อผู้ใช้ให้คำตอบแล้ว ฟังก์ชันจะเรียกตัวจัดการ เราสามารถส่งผ่านตัวจัดการได้สองประเภท:
ฟังก์ชันอาร์กิวเมนต์เป็นศูนย์ ซึ่งจะถูกเรียกเมื่อผู้ใช้ให้คำตอบที่เป็นบวกเท่านั้น
ฟังก์ชันที่มีอาร์กิวเมนต์ ซึ่งถูกเรียกใช้ในกรณีใดกรณีหนึ่งและส่งกลับคำตอบ
ในการเรียก handler
อย่างถูกต้อง เราจะตรวจสอบคุณสมบัติ handler.length
แนวคิดก็คือเรามีไวยากรณ์ตัวจัดการที่เรียบง่ายและไม่มีการโต้แย้งสำหรับกรณีเชิงบวก (ตัวแปรที่พบบ่อยที่สุด) แต่สามารถรองรับตัวจัดการสากลได้เช่นกัน:
ฟังก์ชั่นถาม(คำถาม, ...ตัวจัดการ) { ให้ isYes = ยืนยัน (คำถาม); สำหรับ (ให้ตัวจัดการของตัวจัดการ) { ถ้า (ตัวจัดการความยาว == 0) { ถ้า (ใช่) ตัวจัดการ (); } อื่น { ตัวจัดการ (คือใช่); - - - // สำหรับคำตอบเชิงบวก จะมีการเรียกตัวจัดการทั้งสองตัว // สำหรับคำตอบเชิงลบ เฉพาะคำตอบที่สองเท่านั้น Ask("คำถาม?", () => alert('คุณบอกว่าใช่'), result => alert(result));
นี่เป็นกรณีเฉพาะของสิ่งที่เรียกว่าความหลากหลาย - การปฏิบัติต่อข้อโต้แย้งแตกต่างกันขึ้นอยู่กับประเภทของมัน หรือในกรณีของเราขึ้นอยู่กับ length
แนวคิดนี้มีประโยชน์ในไลบรารี JavaScript
เรายังสามารถเพิ่มคุณสมบัติของเราเองได้
ที่นี่เราเพิ่มคุณสมบัติ counter
เพื่อติดตามจำนวนการโทรทั้งหมด:
ฟังก์ชั่น sayHi() { alert("สวัสดี"); //ลองนับดูว่าเราวิ่งไปกี่ครั้งแล้ว sayHi.เคาน์เตอร์++; - sayHi.เคาน์เตอร์ = 0; //ค่าเริ่มต้น พูดว่าสวัสดี(); // สวัสดี พูดว่าสวัสดี(); // สวัสดี alert( `เรียกว่า ${sayHi.counter} ครั้ง` ); //โทรมา2ครั้ง
คุณสมบัติไม่ใช่ตัวแปร
คุณสมบัติที่กำหนดให้กับฟังก์ชันเช่น sayHi.counter = 0
ไม่ได้ กำหนด counter
นับตัวแปรภายในเครื่อง กล่าวอีกนัยหนึ่ง counter
คุณสมบัติและตัวแปร let counter
เป็นสองสิ่งที่ไม่เกี่ยวข้องกัน
เราสามารถปฏิบัติต่อฟังก์ชันเสมือนเป็นวัตถุ เก็บคุณสมบัติไว้ในนั้นได้ แต่นั่นไม่มีผลกระทบต่อการดำเนินการของมัน ตัวแปรไม่ใช่คุณสมบัติของฟังก์ชันและในทางกลับกัน สิ่งเหล่านี้เป็นเพียงโลกคู่ขนาน
คุณสมบัติของฟังก์ชันสามารถแทนที่การปิดได้ในบางครั้ง ตัวอย่างเช่น เราสามารถเขียนตัวอย่างฟังก์ชันตัวนับใหม่ได้จากบท Variable scope, closure เพื่อใช้คุณสมบัติของฟังก์ชัน:
ฟังก์ชัน makeCounter() { // แทน: // ให้นับ = 0 ตัวนับฟังก์ชัน () { กลับเคาน์เตอร์นับ++; - เคาน์เตอร์นับ = 0; เคาน์เตอร์ส่งคืน; - ให้เคาน์เตอร์ = makeCounter(); การแจ้งเตือน( ตัวนับ() ); // 0 การแจ้งเตือน( ตัวนับ() ); // 1
ขณะนี้ count
ถูกจัดเก็บไว้ในฟังก์ชันโดยตรง ไม่ใช่ในสภาพแวดล้อมของคำศัพท์ภายนอก
ดีกว่าหรือแย่กว่าการใช้การปิด?
ข้อแตกต่างที่สำคัญคือ หากค่าของ count
อยู่ในตัวแปรภายนอก โค้ดภายนอกจะไม่สามารถเข้าถึงได้ เฉพาะฟังก์ชันที่ซ้อนกันเท่านั้นที่สามารถแก้ไขได้ และถ้ามันเชื่อมโยงกับฟังก์ชัน สิ่งนั้นก็เป็นไปได้:
ฟังก์ชัน makeCounter() { ตัวนับฟังก์ชัน () { กลับเคาน์เตอร์นับ++; - เคาน์เตอร์นับ = 0; เคาน์เตอร์ส่งคืน; - ให้เคาน์เตอร์ = makeCounter(); เคาน์เตอร์นับ = 10; การแจ้งเตือน( ตัวนับ() ); // 10
ดังนั้นการเลือกนำไปปฏิบัติจึงขึ้นอยู่กับเป้าหมายของเรา
Named Function Expression หรือ NFE เป็นคำสำหรับ Function Expressions ที่มีชื่อ
ตัวอย่างเช่น ลองใช้ Function Expression ธรรมดา:
ให้ sayHi = ฟังก์ชั่น (ใคร) { alert(`สวัสดี ${who}`); -
และเพิ่มชื่อเข้าไปว่า:
ให้ sayHi = ฟังก์ชั่น func(ใคร) { alert(`สวัสดี ${who}`); -
เราประสบความสำเร็จอะไรที่นี่หรือไม่? จุดประสงค์ของชื่อ "func"
เพิ่มเติมนั้นคืออะไร
ก่อนอื่น โปรดทราบว่าเรายังมี Function Expression อยู่ การเพิ่มชื่อ "func"
หลัง function
ไม่ได้ทำให้เป็นการประกาศฟังก์ชัน เนื่องจากยังคงสร้างให้เป็นส่วนหนึ่งของนิพจน์การกำหนด
การเพิ่มชื่อดังกล่าวก็ไม่ได้ทำลายสิ่งใดเลย
ฟังก์ชั่นยังคงมีอยู่ในชื่อ sayHi()
:
ให้ sayHi = ฟังก์ชั่น func(ใคร) { alert(`สวัสดี ${who}`); - sayHi("จอห์น"); //สวัสดีจอห์น
มีสองสิ่งพิเศษเกี่ยวกับชื่อ func
นั่นคือเหตุผล:
อนุญาตให้ฟังก์ชันอ้างอิงตัวเองภายใน
ไม่สามารถมองเห็นได้นอกฟังก์ชัน
ตัวอย่างเช่น ฟังก์ชัน sayHi
ด้านล่างจะเรียกตัวเองอีกครั้งว่า "Guest"
หากไม่มี who
ให้มา:
ให้ sayHi = ฟังก์ชั่น func(ใคร) { ถ้า (ใคร) { alert(`สวัสดี ${who}`); } อื่น { func("แขก"); // ใช้ func เพื่อเรียกตัวเองอีกครั้ง - - พูดว่าสวัสดี(); //สวัสดีแขก // แต่นี่จะไม่ทำงาน: ฟังก์ชั่น(); // เกิดข้อผิดพลาด ไม่ได้กำหนด func (ไม่สามารถมองเห็นได้นอกฟังก์ชัน)
ทำไมเราถึงใช้ func
? อาจจะแค่ใช้ sayHi
สำหรับการโทรแบบซ้อนใช่ไหม
ที่จริงแล้ว ในกรณีส่วนใหญ่เราสามารถ:
ให้ sayHi = ฟังก์ชั่น (ใคร) { ถ้า (ใคร) { alert(`สวัสดี ${who}`); } อื่น { sayHi("แขก"); - -
ปัญหาของโค้ดนั้นคือ sayHi
อาจมีการเปลี่ยนแปลงในโค้ดภายนอก หากฟังก์ชันถูกกำหนดให้กับตัวแปรอื่นแทน โค้ดจะเริ่มแสดงข้อผิดพลาด:
ให้ sayHi = ฟังก์ชั่น (ใคร) { ถ้า (ใคร) { alert(`สวัสดี ${who}`); } อื่น { sayHi("แขก"); // ข้อผิดพลาด: sayHi ไม่ใช่ฟังก์ชัน - - ยินดีต้อนรับ = ทักทาย; พูดสวัสดี = null; ยินดีต้อนรับ(); // เกิดข้อผิดพลาด การโทร SayHi แบบซ้อนไม่ทำงานอีกต่อไป!
นั่นเกิดขึ้นเพราะฟังก์ชันรับคำว่า sayHi
จากสภาพแวดล้อมคำศัพท์ภายนอก ไม่มี sayHi
ในเครื่อง ดังนั้นจึงใช้ตัวแปรภายนอก และในขณะที่มีการโทรที่ sayHi
ภายนอกนั้นเป็น null
ชื่อทางเลือกที่เราสามารถใส่ลงใน Function Expression มีไว้เพื่อแก้ไขปัญหาประเภทนี้
ลองใช้มันเพื่อแก้ไขโค้ดของเรา:
ให้ sayHi = ฟังก์ชั่น func(ใคร) { ถ้า (ใคร) { alert(`สวัสดี ${who}`); } อื่น { func("แขก"); // ตอนนี้ทุกอย่างเรียบร้อยดี - - ยินดีต้อนรับ = ทักทาย; พูดสวัสดี = null; ยินดีต้อนรับ(); // สวัสดีแขก (การโทรแบบซ้อนใช้งานได้)
ตอนนี้มันใช้งานได้แล้ว เพราะชื่อ "func"
นั้นเป็น function-local ไม่ได้นำมาจากภายนอก (และไม่สามารถมองเห็นได้จากที่นั่น) ข้อมูลจำเพาะรับประกันว่าจะอ้างอิงฟังก์ชันปัจจุบันเสมอ
โค้ดภายนอกยังคงมีตัวแปร sayHi
หรือ welcome
และ func
ก็คือ "ชื่อฟังก์ชันภายใน" ซึ่งเป็นวิธีที่ฟังก์ชันสามารถเรียกตัวเองได้อย่างน่าเชื่อถือ
ไม่มีสิ่งใดสำหรับการประกาศฟังก์ชัน
คุณลักษณะ “ชื่อภายใน” ที่อธิบายไว้ที่นี่ใช้ได้กับนิพจน์ฟังก์ชันเท่านั้น ไม่ใช่สำหรับการประกาศฟังก์ชัน สำหรับการประกาศฟังก์ชัน ไม่มีไวยากรณ์สำหรับการเพิ่มชื่อ "ภายใน"
บางครั้ง เมื่อเราต้องการชื่อภายในที่เชื่อถือได้ ก็เป็นเหตุผลที่ต้องเขียนแบบฟอร์ม Function Declaration ใหม่เป็น Named Function Expression
ฟังก์ชั่นเป็นวัตถุ
ที่นี่เราครอบคลุมคุณสมบัติของพวกเขา:
name
– ชื่อฟังก์ชัน โดยปกติจะนำมาจากคำจำกัดความของฟังก์ชัน แต่ถ้าไม่มี JavaScript จะพยายามเดาจากบริบท (เช่น การมอบหมายงาน)
length
คือจำนวนอาร์กิวเมนต์ในคำจำกัดความของฟังก์ชัน พารามิเตอร์ที่เหลือจะไม่ถูกนับ
ถ้าฟังก์ชันถูกประกาศเป็น Function Expression (ไม่ใช่ในโฟลว์โค้ดหลัก) และมีชื่ออยู่ ก็จะเรียกว่า Named Function Expression ชื่อนี้สามารถใช้เพื่ออ้างอิงถึงตัวเอง สำหรับการโทรแบบเรียกซ้ำหรืออื่นๆ
นอกจากนี้ ฟังก์ชันอาจมีคุณสมบัติเพิ่มเติม ไลบรารี JavaScript ที่มีชื่อเสียงหลายแห่งใช้ประโยชน์จากคุณลักษณะนี้ได้อย่างดีเยี่ยม
พวกเขาสร้างฟังก์ชัน "หลัก" และแนบฟังก์ชัน "ตัวช่วย" อื่น ๆ อีกมากมายเข้าไปด้วย ตัวอย่างเช่น ไลบรารี jQuery จะสร้างฟังก์ชันชื่อ $
ไลบรารี lodash สร้างฟังก์ชัน _
แล้วเพิ่ม _.clone
, _.keyBy
และคุณสมบัติอื่น ๆ ลงไป (ดูเอกสารเมื่อคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับฟังก์ชันเหล่านี้) จริงๆ แล้ว พวกเขาทำเพื่อลดมลพิษในพื้นที่โลก ดังนั้นห้องสมุดเดียวจึงมีตัวแปรระดับโลกเพียงตัวแปรเดียวเท่านั้น ซึ่งจะช่วยลดความเป็นไปได้ของความขัดแย้งในการตั้งชื่อ
ดังนั้นฟังก์ชันสามารถทำงานที่มีประโยชน์ได้ด้วยตัวเองและยังมีฟังก์ชันอื่นๆ มากมายในคุณสมบัติอีกด้วย
ความสำคัญ: 5
แก้ไขโค้ดของ makeCounter()
เพื่อให้ตัวนับสามารถลดขนาดและตั้งค่าตัวเลขได้:
counter()
ควรส่งคืนหมายเลขถัดไป (เช่นเดิม)
counter.set(value)
ควรตั้งค่าตัวนับเป็น value
counter.decrease()
ควรลดตัวนับลง 1
ดูโค้ดแซนด์บ็อกซ์สำหรับตัวอย่างการใช้งานที่สมบูรณ์
ป.ล. คุณสามารถใช้การปิดหรือคุณสมบัติฟังก์ชันเพื่อรักษาจำนวนปัจจุบันได้ หรือเขียนทั้งสองรูปแบบ
เปิดแซนด์บ็อกซ์พร้อมการทดสอบ
โซลูชันใช้ count
ในตัวแปรภายในเครื่อง แต่วิธีการบวกจะถูกเขียนลงใน counter
โดยตรง พวกเขาใช้สภาพแวดล้อมคำศัพท์ภายนอกเหมือนกัน และยังสามารถเข้าถึง count
ปัจจุบันได้
ฟังก์ชั่น makeCounter() { ให้นับ = 0; ตัวนับฟังก์ชัน () { จำนวนการส่งคืน++; - counter.set = มูลค่า => นับ = มูลค่า; counter.decrease = () => นับ--; เคาน์เตอร์ส่งคืน; -
เปิดโซลูชันพร้อมการทดสอบในแซนด์บ็อกซ์
ความสำคัญ: 2
เขียน sum
ฟังก์ชันที่จะมีลักษณะดังนี้:
ผลรวม(1)(2) == 3; // 1 + 2 ผลรวม(1)(2)(3) == 6; // 1 + 2 + 3 ผลรวม(5)(-1)(2) == 6 ผลรวม(6)(-1)(-2)(-3) == 0 ผลรวม(0)(1)(2)(3)(4)(5) == 15
PS คำแนะนำ: คุณอาจต้องตั้งค่าออบเจ็กต์ที่กำหนดเองเป็นการแปลงดั้งเดิมสำหรับฟังก์ชันของคุณ
เปิดแซนด์บ็อกซ์พร้อมการทดสอบ
เพื่อให้ทุกอย่างทำงาน ได้ ผลลัพธ์ของผล sum
ต้องเป็นฟังก์ชัน
ฟังก์ชั่นนั้นจะต้องเก็บค่าปัจจุบันระหว่างการโทรไว้ในหน่วยความจำ
ตามภารกิจนั้น ฟังก์ชันจะต้องกลายเป็นตัวเลขเมื่อใช้ใน ==
ฟังก์ชันเป็นวัตถุ ดังนั้นการแปลงจึงเกิดขึ้นตามที่อธิบายไว้ในบทที่ Object เป็นการแปลงแบบดั้งเดิม และเราสามารถจัดเตรียมวิธีการของเราเองที่ส่งคืนตัวเลขได้
ตอนนี้รหัส:
ผลรวมฟังก์ชัน (a) { ให้ currentSum = a; ฟังก์ชัน ฉ(ข) { ผลรวมปัจจุบัน += b; กลับฉ; - f.toString = ฟังก์ชั่น() { กลับผลรวมปัจจุบัน; - กลับฉ; - การแจ้งเตือน( ผลรวม(1)(2) ); // 3 การแจ้งเตือน( ผลรวม(5)(-1)(2) ); // 6 การแจ้งเตือน( ผลรวม(6)(-1)(-2)(-3) ); // 0 การแจ้งเตือน( ผลรวม(0)(1)(2)(3)(4)(5) ); // 15
โปรดทราบว่าฟังก์ชัน sum
ใช้งานได้จริงเพียงครั้งเดียวเท่านั้น มันจะส่งคืนฟังก์ชัน f
จากนั้น ในการเรียกครั้งต่อไปแต่ละครั้ง f
จะเพิ่มพารามิเตอร์ให้กับผลรวม currentSum
และส่งคืนตัวเอง
ไม่มีการเรียกซ้ำในบรรทัดสุดท้ายของ f
นี่คือลักษณะของการเรียกซ้ำ:
ฟังก์ชัน ฉ(ข) { ผลรวมปัจจุบัน += b; กลับฉ(); // <-- โทรซ้ำ -
และในกรณีของเรา เราแค่คืนฟังก์ชันโดยไม่ต้องเรียกมัน:
ฟังก์ชัน ฉ(ข) { ผลรวมปัจจุบัน += b; กลับฉ; // <-- ไม่เรียกตัวเองแต่คืนตัวมันเอง -
f
นี้จะถูกใช้ในการโทรครั้งต่อไป และคืนตัวเองอีกครั้ง กี่ครั้งก็ได้ตามต้องการ จากนั้น เมื่อใช้เป็นตัวเลขหรือสตริง toString
จะส่งกลับค่า currentSum
นอกจากนี้เรายังสามารถใช้ Symbol.toPrimitive
หรือ valueOf
ที่นี่สำหรับการแปลง
เปิดโซลูชันพร้อมการทดสอบในแซนด์บ็อกซ์