แนะนำ
ในบทนี้เราจะอธิบายกลยุทธ์ของการส่งพารามิเตอร์ไปยังฟังก์ชันฟังก์ชันใน eCmascript
ในวิทยาศาสตร์คอมพิวเตอร์กลยุทธ์นี้เรียกว่า "กลยุทธ์การประเมินผล" (หมายเหตุของลุง: บางคนบอกว่ามันถูกแปลเป็นกลยุทธ์การประเมินผลในขณะที่คนอื่นแปลเป็นกลยุทธ์การมอบหมายดูเนื้อหาต่อไปนี้ฉันคิดว่ามันเหมาะสมกว่าที่จะเรียกมันว่ากลยุทธ์การมอบหมาย ตัวอย่างเช่นในภาษาการเขียนโปรแกรมการตั้งค่ากฎสำหรับการประเมินผลหรือนิพจน์การคำนวณ กลยุทธ์สำหรับการส่งพารามิเตอร์ไปยังฟังก์ชั่นเป็นกรณีพิเศษ
http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/
เหตุผลในการเขียนบทความนี้คือมีคนในฟอรัมขอให้อธิบายกลยุทธ์บางอย่างสำหรับการผ่านพารามิเตอร์อย่างถูกต้อง เราได้ให้คำจำกัดความที่เกี่ยวข้องที่นี่โดยหวังว่ามันจะเป็นประโยชน์กับทุกคน
โปรแกรมเมอร์จำนวนมากเชื่อว่าใน JavaScript (แม้ในภาษาอื่น ๆ ) วัตถุจะถูกส่งผ่านโดยการอ้างอิงในขณะที่ประเภทค่าดั้งเดิมจะถูกส่งผ่านด้วยค่า นอกจากนี้บทความจำนวนมากพูดคุยเกี่ยวกับ "ความจริง" นี้ แต่หลายคนเข้าใจคำนี้จริง ๆ และมีกี่คนที่ถูกต้อง? เราจะอธิบายทีละคนในบทความนี้
ทฤษฎีทั่วไป
ควรสังเกตว่าในทฤษฎีการมอบหมายโดยทั่วไปมีสองกลยุทธ์การมอบหมาย: เข้มงวด - หมายความว่าพารามิเตอร์จะถูกคำนวณก่อนเข้าโปรแกรม ไม่ จำกัด - หมายความว่าการคำนวณพารามิเตอร์จะถูกคำนวณตามข้อกำหนดการคำนวณ (นั่นคือเทียบเท่ากับการคำนวณล่าช้า)
จากนั้นที่นี่เราพิจารณากลยุทธ์การถ่ายโอนพารามิเตอร์ฟังก์ชั่นพื้นฐานซึ่งมีความสำคัญมากจากจุดเริ่มต้นของ ECMASCript สิ่งแรกที่ควรทราบคือใช้กลยุทธ์การส่งผ่านพารามิเตอร์ที่เข้มงวดใน eCmascript (แม้ในภาษาอื่น ๆ เช่น C, Java, Python และ Ruby)
นอกจากนี้ลำดับการคำนวณของพารามิเตอร์ที่ส่งผ่านก็มีความสำคัญมากเช่นกัน - ใน eCmascript มันจะถูกจากซ้ายไปขวาและลำดับเบื้องต้น (จากขวาไปขวา) ที่ใช้ในภาษาอื่น ๆ สามารถใช้ได้
กลยุทธ์การส่งข้อมูลที่เข้มงวดนั้นแบ่งออกเป็นกลยุทธ์หลายอย่างและเราจะหารือเกี่ยวกับกลยุทธ์ที่สำคัญที่สุดในรายละเอียดในบทนี้
ไม่ใช่กลยุทธ์ทั้งหมดที่กล่าวถึงด้านล่างนี้ใช้ใน ECMASCript ดังนั้นเมื่อพูดถึงพฤติกรรมเฉพาะของกลยุทธ์เหล่านี้เราใช้รหัสหลอกเพื่อแสดง
ผ่านมูลค่า
โดยผ่านมูลค่านักพัฒนาหลายคนตระหนักดีว่าค่าของพารามิเตอร์เป็นสำเนาของค่าวัตถุที่ส่งโดยผู้โทร การเปลี่ยนค่าของพารามิเตอร์ภายในฟังก์ชั่นจะไม่ส่งผลกระทบต่อวัตถุภายนอก (ค่าของพารามิเตอร์ภายนอก) โดยทั่วไปแล้วหน่วยความจำใหม่จะถูกจัดสรรใหม่ (เราไม่ใส่ใจกับวิธีการจัดสรรหน่วยความจำ - มันยังเป็นสแต็กหรือการจัดสรรหน่วยความจำแบบไดนามิก) ค่าของบล็อกหน่วยความจำใหม่เป็นสำเนาของวัตถุภายนอกและค่าของมันถูกใช้ภายในฟังก์ชั่น
การคัดลอกรหัสมีดังนี้:
บาร์ = 10
ขั้นตอน foo (bararg):
Bararg = 20;
จบ
foo (บาร์)
// เปลี่ยนค่าภายใน foo จะไม่ส่งผลกระทบต่อมูลค่าของแถบภายใน
พิมพ์ (บาร์) // 10
อย่างไรก็ตามหากพารามิเตอร์ของฟังก์ชั่นไม่ใช่ค่าดั้งเดิม แต่เป็นวัตถุโครงสร้างที่ซับซ้อนมันจะนำปัญหาประสิทธิภาพที่ยอดเยี่ยม C ++ มีปัญหานี้ เมื่อผ่านโครงสร้างเป็นค่าลงในฟังก์ชั่นมันเป็นสำเนาที่สมบูรณ์
ลองยกตัวอย่างทั่วไปใช้กลยุทธ์การมอบหมายต่อไปนี้เพื่อทดสอบ คิดเกี่ยวกับฟังก์ชั่นที่ยอมรับพารามิเตอร์ 2 ตัว พารามิเตอร์แรกคือค่าของวัตถุและที่สองเป็นเครื่องหมายบูลีนเพื่อทำเครื่องหมายว่าวัตถุได้รับการแก้ไขอย่างสมบูรณ์ (กำหนดวัตถุใหม่ให้กับวัตถุ) หรือมีเพียงคุณสมบัติบางอย่างของวัตถุเท่านั้นที่ได้รับการแก้ไข
การคัดลอกรหัสมีดังนี้:
// หมายเหตุ: ต่อไปนี้เป็นรหัสหลอกทั้งหมดไม่ใช่การใช้งาน JS
bar = {
x: 10,
Y: 20
-
ขั้นตอน FOO (Bararg, Isfullchange):
ถ้า isfullchange:
Bararg = {z: 1, Q: 2}
การออก
จบ
Bararg.x = 100
Bararg.y = 200
จบ
foo (บาร์)
// ผ่านตามค่าวัตถุภายนอกจะไม่เปลี่ยนแปลง
พิมพ์ (บาร์) // {x: 10, y: 20}
// เปลี่ยนวัตถุอย่างสมบูรณ์ (กำหนดค่าใหม่)
foo (บาร์จริง)
// ไม่มีการเปลี่ยนแปลงอย่างใดอย่างหนึ่ง
พิมพ์ (bar) // {x: 10, y: 20} แทน {z: 1, q: 2}
ผ่านการอ้างอิง
การอ้างอิงผ่านการอ้างอิงที่รู้จักกันดีอีกครั้งไม่ใช่การคัดลอกค่า แต่เป็นการอ้างอิงโดยนัยกับวัตถุเช่นที่อยู่อ้างอิงโดยตรงของวัตถุภายนอก การเปลี่ยนแปลงใด ๆ ของพารามิเตอร์ภายในฟังก์ชันมีผลต่อค่าของวัตถุที่อยู่นอกฟังก์ชั่นเนื่องจากทั้งสองอ้างอิงวัตถุเดียวกันนั่นคือพารามิเตอร์เทียบเท่ากับนามแฝงของวัตถุภายนอก
Pseudocode:
การคัดลอกรหัสมีดังนี้:
ขั้นตอน FOO (Bararg, Isfullchange):
ถ้า isfullchange:
Bararg = {z: 1, Q: 2}
การออก
จบ
Bararg.x = 100
Bararg.y = 200
จบ
// ใช้วัตถุเดียวกันกับด้านบน
bar = {
x: 10,
Y: 20
-
// ผลลัพธ์ของการโทรโดยอ้างอิงมีดังนี้:
foo (บาร์)
// ค่าคุณสมบัติของวัตถุมีการเปลี่ยนแปลง
พิมพ์ (บาร์) // {x: 100, y: 200}
// การกำหนดค่าใหม่ยังส่งผลกระทบต่อวัตถุ
foo (บาร์จริง)
// วัตถุนี้เป็นวัตถุใหม่อยู่แล้ว
พิมพ์ (บาร์) // {z: 1, Q: 2}
กลยุทธ์นี้สามารถส่งผ่านวัตถุที่ซับซ้อนได้อย่างมีประสิทธิภาพมากขึ้นเช่นวัตถุโครงสร้างขนาดใหญ่ที่มีแอตทริบิวต์ขนาดใหญ่
โทรโดยการแบ่งปัน
ทุกคนรู้สองกลยุทธ์ข้างต้น แต่คุณอาจไม่รู้กลยุทธ์ที่คุณต้องการพูดคุยเกี่ยวกับที่นี่ (จริง ๆ แล้วมันเป็นกลยุทธ์ทางวิชาการ) อย่างไรก็ตามในไม่ช้าเราจะเห็นว่านี่เป็นกลยุทธ์ที่มีบทบาทสำคัญในกลยุทธ์การส่งพารามิเตอร์ใน ECMAScript
กลยุทธ์นี้ยังมีคำพ้องความหมายบางอย่าง: "ผ่านโดยวัตถุ" หรือ "ผ่านโดยการแบ่งปันวัตถุ"
กลยุทธ์นี้ถูกเสนอในปี 1974 โดย Barbara Liskov สำหรับภาษาการเขียนโปรแกรม CLU
จุดสำคัญของกลยุทธ์นี้คือฟังก์ชั่นได้รับสำเนา (คัดลอก) ของวัตถุและสำเนาอ้างอิงเชื่อมโยงกับพารามิเตอร์ที่เป็นทางการและค่าของมัน
เราไม่สามารถเรียกการอ้างอิงที่ปรากฏที่นี่ "ผ่านการอ้างอิง" เนื่องจากพารามิเตอร์ที่ได้รับจากฟังก์ชั่นไม่ใช่นามแฝงวัตถุโดยตรง แต่เป็นสำเนาของที่อยู่อ้างอิง
ความแตกต่างที่สำคัญที่สุดคือฟังก์ชั่นกำหนดค่าใหม่ให้กับพารามิเตอร์ภายในจะไม่ส่งผลกระทบต่อวัตถุภายนอก (เช่นกรณีที่ส่งผ่านโดยการอ้างอิงในตัวอย่างข้างต้น) แต่เนื่องจากพารามิเตอร์เป็นสำเนาที่อยู่วัตถุเดียวกันที่เข้าถึงภายนอกและภายใน (ตัวอย่างเช่นวัตถุภายนอกไม่ได้สำเนาทั้งหมดตามที่คุณต้องการผ่าน การเปลี่ยนค่าแอตทริบิวต์ของวัตถุพารามิเตอร์จะส่งผลกระทบต่อวัตถุภายนอก
การคัดลอกรหัสมีดังนี้:
ขั้นตอน FOO (Bararg, Isfullchange):
ถ้า isfullchange:
Bararg = {z: 1, Q: 2}
การออก
จบ
Bararg.x = 100
Bararg.y = 200
จบ
// ยังคงใช้โครงสร้างวัตถุนี้
bar = {
x: 10,
Y: 20
-
// การผ่านโดยการบริจาคจะส่งผลกระทบต่อวัตถุ
foo (บาร์)
// คุณสมบัติของวัตถุได้รับการแก้ไขแล้ว
พิมพ์ (บาร์) // {x: 100, y: 200}
// การมอบหมายใหม่ไม่ทำงาน
foo (บาร์จริง)
// ยังคงเป็นค่าข้างต้น
พิมพ์ (บาร์) // {x: 100, y: 200}
สมมติฐานของการประมวลผลนี้คือวัตถุที่ใช้ในภาษาส่วนใหญ่ไม่ใช่ค่าดั้งเดิม
ผ่านการแบ่งปันเป็นกรณีพิเศษของการผ่านโดยมูลค่า
กลยุทธ์ในการส่งมอบโดยการแบ่งปันใช้ในหลายภาษา: Java, Ecmascript, Python, Ruby, Visual Basic ฯลฯ นอกจากนี้ชุมชน Python ได้ใช้คำนี้และสามารถใช้ในภาษาอื่น ๆ ได้เนื่องจากชื่ออื่น ๆ มักจะทำให้ผู้คนรู้สึกสับสน ในกรณีส่วนใหญ่เช่นใน Java, Ecmascript หรือ Visual Basic กลยุทธ์นี้เรียกว่าการส่งต่อโดยค่า - ความหมาย: ค่าพิเศษ - สำเนาอ้างอิง (สำเนา)
ในอีกด้านหนึ่งมันเป็นเช่นนี้ - พารามิเตอร์ที่ส่งผ่านไปยังฟังก์ชั่นเป็นเพียงชื่อของค่าที่ถูกผูกไว้ (ที่อยู่อ้างอิง) และจะไม่ส่งผลกระทบต่อวัตถุภายนอก
ในทางกลับกันคำศัพท์เหล่านี้ได้รับการพิจารณาว่ากินผิดโดยไม่ต้องเจาะลึกลงไปในฟอรัมจำนวนมากกำลังพูดถึงวิธีส่งผ่านวัตถุไปยังฟังก์ชั่น JavaScript)
มีคำพูดในทฤษฎีทั่วไปที่ผ่านมูลค่า: แต่ในเวลานี้ค่านี้คือสิ่งที่เราเรียกว่าสำเนาที่อยู่ (คัดลอก) ดังนั้นจึงไม่ทำลายกฎ
ในทับทิมกลยุทธ์นี้เรียกว่าผ่านโดยอ้างอิง ให้ฉันพูดอีกครั้ง: มันไม่ได้ผ่านในแง่ของสำเนาของโครงสร้างขนาดใหญ่ (ตัวอย่างเช่นไม่ใช่ตามมูลค่า) และในทางกลับกันเราไม่ได้ดำเนินการอ้างอิงไปยังวัตถุดั้งเดิมและไม่สามารถแก้ไขได้ ดังนั้นแนวคิดของคำศัพท์ข้ามระยะนี้อาจทำให้เกิดความสับสนมากขึ้น
ไม่มีกรณีพิเศษที่ส่งผ่านโดยการอ้างอิงในทางทฤษฎีเช่นกรณีพิเศษที่ส่งผ่านด้วยค่า
แต่ก็ยังจำเป็นที่จะต้องเข้าใจกลยุทธ์เหล่านี้ในเทคโนโลยีที่กล่าวถึงข้างต้น (Java, Ecmascript, Python, Ruby, อื่น ๆ ) ในความเป็นจริงกลยุทธ์ที่พวกเขาใช้คือผ่านการแบ่งปัน
กดแชร์และตัวชี้
สำหรับс/с ++ กลยุทธ์นี้เหมือนกับการส่งผ่านตามค่าตัวชี้ แต่มีความแตกต่างที่สำคัญ - กลยุทธ์นี้สามารถใช้พอยน์เตอร์และเปลี่ยนวัตถุได้อย่างสมบูรณ์ อย่างไรก็ตามโดยทั่วไปการจัดสรรตัวชี้ค่า (ที่อยู่) ให้กับบล็อกหน่วยความจำใหม่ (นั่นคือบล็อกหน่วยความจำที่อ้างอิงก่อนหน้านี้ยังคงไม่เปลี่ยนแปลง); การเปลี่ยนคุณสมบัติของวัตถุผ่านตัวชี้จะส่งผลกระทบต่อวัตถุภายนอกของ Adong
ดังนั้นและหมวดหมู่ตัวชี้เราสามารถเห็นได้อย่างชัดเจนว่าสิ่งนี้ถูกส่งผ่านด้วยค่าที่อยู่ ในกรณีนี้การส่งผ่านโดยการแบ่งปันเป็นเพียง "น้ำตาลสังเคราะห์" เช่นพฤติกรรมการกำหนดตัวชี้ (แต่ไม่ใช่ dereference) หรือการปรับเปลี่ยนคุณสมบัติเช่นการอ้างอิง (ไม่จำเป็นต้องดำเนินการ dereference) บางครั้งอาจเป็นชื่อ "ตัวชี้ปลอดภัย"
อย่างไรก็ตามс/с + + ยังมีน้ำตาลวากยสัมพันธ์พิเศษเมื่ออ้างถึงคุณสมบัติของวัตถุโดยไม่ต้องใช้คำพอยน์เตอร์ที่เห็นได้ชัด:
การคัดลอกรหัสมีดังนี้:
obj-> x แทน (*obj) .x
อุดมการณ์ที่เกี่ยวข้องอย่างใกล้ชิดที่สุดกับ C ++ สามารถมองเห็นได้จากการใช้งาน "สมาร์ทพอยน์เตอร์" ตัวอย่างเช่นใน Boost :: Shared_ptr ตัวดำเนินการที่ได้รับมอบหมายและตัวสร้างการคัดลอกจะมากเกินไปและตัวนับอ้างอิงของวัตถุยังใช้เพื่อลบวัตถุผ่าน GC ประเภทข้อมูลนี้มีชื่อที่คล้ายกัน - share_ptr
การใช้งาน ecmascript
ตอนนี้เรารู้ว่านโยบายของการส่งวัตถุเป็นพารามิเตอร์ใน ECMAScript - ผ่านการแชร์: การแก้ไขคุณสมบัติของพารามิเตอร์จะส่งผลกระทบต่อภายนอกในขณะที่การกำหนดค่าใหม่จะไม่ส่งผลกระทบต่อวัตถุภายนอก อย่างไรก็ตามดังที่เราได้กล่าวไว้ข้างต้นนักพัฒนา ECMAScript โดยทั่วไปเรียกมันว่า: ผ่านตามค่ายกเว้นว่าค่าเป็นสำเนาของที่อยู่อ้างอิง
นักประดิษฐ์ JavaScript Brendan Ashe ยังเขียนว่า: สิ่งที่ผ่านไปคือสำเนาของการอ้างอิง (สำเนาของที่อยู่) ดังนั้นสิ่งที่ทุกคนพูดถึงในฟอรัมกล่าวว่าถูกต้องภายใต้คำอธิบายนี้
แม่นยำยิ่งขึ้นพฤติกรรมนี้สามารถเข้าใจได้ว่าเป็นการมอบหมายอย่างง่าย เราจะเห็นว่าภายในเป็นวัตถุที่แตกต่างกันอย่างสิ้นเชิง แต่มีการอ้างอิงค่าเดียวกันนั่นคือสำเนาที่อยู่
รหัส ecmascript:
การคัดลอกรหัสมีดังนี้:
var foo = {x: 10, y: 20};
var bar = foo;
การแจ้งเตือน (bar === foo); // จริง
bar.x = 100;
bar.y = 200;
การแจ้งเตือน ([foo.x, foo.y]); // [100, 200]
นั่นคือตัวระบุสองตัว (การผูกชื่อ) จะถูกผูกไว้กับวัตถุเดียวกันในหน่วยความจำและแชร์วัตถุนี้:
ค่า foo: addr (0xff) => {x: 100, y: 200} (ที่อยู่ 0xff) <= ค่าบาร์: addr (0xff)
สำหรับการมอบหมายใหม่การเชื่อมโยงเป็นตัวระบุวัตถุใหม่ (ที่อยู่ใหม่) โดยไม่ส่งผลกระทบต่อวัตถุที่ถูกผูกไว้ก่อนหน้านี้:
การคัดลอกรหัสมีดังนี้:
bar = {z: 1, q: 2};
การแจ้งเตือน ([foo.x, foo.y]); // [100, 200] ไม่มีการเปลี่ยนแปลง
การแจ้งเตือน ([bar.z, bar.q]); // [1, 2] แต่ตอนนี้การอ้างอิงเป็นวัตถุใหม่
นั่นคือ Foo และ Bar ตอนนี้มีค่าที่แตกต่างและที่อยู่ที่แตกต่างกัน:
การคัดลอกรหัสมีดังนี้:
ค่า foo: addr (0xff) => {x: 100, y: 200} (ที่อยู่ 0xff)
ค่าบาร์: addr (0xfa) => {z: 1, q: 2} (ที่อยู่ 0xfa)
ให้ฉันเน้นอีกครั้งว่าค่าของวัตถุที่กล่าวถึงที่นี่คือที่อยู่ไม่ใช่โครงสร้างวัตถุเองและการกำหนดตัวแปรให้กับตัวแปรอื่น - การอ้างอิงถึงค่าที่กำหนด ดังนั้นตัวแปรทั้งสองจึงอ้างถึงที่อยู่หน่วยความจำเดียวกัน การกำหนดถัดไปคือที่อยู่ใหม่ซึ่งคือการแก้ไขการเชื่อมโยงกับที่อยู่ของวัตถุเก่าแล้วผูกกับที่อยู่ของวัตถุใหม่ นี่คือความแตกต่างที่สำคัญที่สุดระหว่างการผ่านการอ้างอิง
นอกจากนี้หากเราพิจารณาเพียงระดับนามธรรมที่ได้รับจากมาตรฐาน ECMA-262 เราจะเห็นแนวคิดของ "ค่า" ในอัลกอริทึมซึ่งใช้ "ค่า" ที่ผ่านมา (อาจเป็นค่าดั้งเดิมหรือวัตถุ) แต่ตามคำจำกัดความข้างต้นของเรา
อย่างไรก็ตามเพื่อหลีกเลี่ยงความเข้าใจผิด (ทำไมคุณสมบัติของวัตถุภายนอกสามารถเปลี่ยนแปลงได้ภายในฟังก์ชั่น) รายละเอียดระดับการใช้งานยังคงต้องได้รับการพิจารณาที่นี่ - สิ่งที่เราเห็นจะถูกส่งผ่านโดยการแบ่งปันหรือกล่าวอีกนัยหนึ่ง - ผ่านตัวชี้ที่ปลอดภัย
เวอร์ชันคำศัพท์
มากำหนดคำศัพท์ของนโยบายนี้ใน ECMASCript
มันสามารถเรียกได้ว่า "ผ่านโดยค่า" - ค่าที่กล่าวถึงที่นี่เป็นกรณีพิเศษนั่นคือค่าคือการคัดลอกที่อยู่ จากระดับนี้เราสามารถพูดได้ว่า: วัตถุใน eCmascript ยกเว้นข้อยกเว้นจะถูกส่งผ่านตามค่าซึ่งเป็นระดับของ ecmascript abstraction
หรือในกรณีนี้มันถูกเรียกว่า "ผ่านโดยการแบ่งปัน" โดยเฉพาะ ผ่านสิ่งนี้คุณสามารถเห็นความแตกต่างระหว่างการส่งผ่านแบบดั้งเดิมโดยค่าและผ่านโดยการอ้างอิง สถานการณ์นี้สามารถแบ่งออกเป็นสองสถานการณ์: 1: ค่าดั้งเดิมถูกส่งผ่านตามมูลค่า; 2: วัตถุถูกส่งผ่านโดยการแบ่งปัน
วลี "ฟังก์ชั่นโดยการอ้างอิงประเภท" ไม่มีส่วนเกี่ยวข้องกับ ecmascript และมันผิด
สรุปแล้ว
ฉันหวังว่าโพสต์นี้จะช่วยเรียนรู้รายละเอียดเพิ่มเติมระดับมหภาคและการใช้งานใน ECMAScript เช่นเคยหากมีคำถามใด ๆ โปรดอย่าลังเลที่จะพูดคุย