สวัสดีปีใหม่ และยินดีต้อนรับกลับสู่ Ruby Magic! ในตอนฤดูหนาวนี้ เราจะเจาะลึกถึงขอบเขตและขอบเขต ดังนั้นสวมสกีของคุณและตามเราไปลึกเข้าไปในป่า
ครั้งที่แล้ว เราดูการปิดใน Ruby โดยเปรียบเทียบบล็อค โปรค และแลมบ์ดา นอกเหนือจากความแตกต่างระหว่างทั้งสามประเภทแล้ว เราได้กล่าวถึงสิ่งที่กำหนด การปิด .
การปิดเป็นฟังก์ชันชั้นหนึ่งที่มีสภาพแวดล้อม สภาพแวดล้อมคือการแมปกับตัวแปรที่มีอยู่เมื่อมีการสร้างการปิด การปิดจะยังคงเข้าถึงตัวแปรเหล่านี้ แม้ว่าจะถูกกำหนดไว้ในขอบเขตอื่นก็ตาม
เราได้สำรวจฟังก์ชันระดับเฟิร์สคลาสของ Ruby แล้ว แต่เราข้าม สภาพแวดล้อม ไปอย่างสะดวก . ในตอนนี้ เราจะมาดูว่าสภาพแวดล้อมนั้นทำงานอย่างไรสำหรับการปิด คลาส และอินสแตนซ์ของคลาส โดยพิจารณาว่า Ruby จัดการกับ ขอบเขตคำศัพท์ อย่างไร ผ่านการผูกมัด .
ขอบเขตคำศัพท์
ในการเขียนโปรแกรม ขอบเขตหมายถึง การโยง มีอยู่ในส่วนเฉพาะของรหัส การผูกมัด หรือ การผูกชื่อ ผูกชื่อกับการอ้างอิงหน่วยความจำ เช่น ชื่อของตัวแปรกับค่าของมัน ขอบเขตกำหนดสิ่งที่ self
หมายถึง วิธีการที่สามารถเรียกได้ และตัวแปรที่มีอยู่
Ruby ก็เหมือนกับภาษาโปรแกรมสมัยใหม่ส่วนใหญ่ที่ใช้ static scope ซึ่งมักเรียกว่า lexical scope (ตรงข้ามกับขอบเขตไดนามิก) ขอบเขตปัจจุบันขึ้นอยู่กับโครงสร้างของรหัสและกำหนดตัวแปรที่มีอยู่ในส่วนเฉพาะของรหัส ซึ่งหมายความว่าขอบเขตจะเปลี่ยนไปเมื่อโค้ดข้ามไปมาระหว่างเมธอด บล็อก และคลาส เนื่องจากอาจมีตัวแปรในเครื่องต่างกันได้ เป็นต้น
def bar
foo = 1
foo
end
bar # => 1
ในวิธีนี้ เราสร้าง ตัวแปรภายในเครื่อง ภายในวิธีการและพิมพ์ไปยังคอนโซล ตัวแปร อยู่ในขอบเขต ภายในวิธีการตามที่ถูกสร้างขึ้นที่นั่น
foo = 1
def bar
foo
end
bar # => NameError (undefined local variable or method `foo' for main:Object)
ในตัวอย่างนี้ เราสร้างตัวแปรนอกเมธอด เมื่อเราเรียกใช้ตัวแปรภายในเมธอด เราจะได้รับข้อผิดพลาด เนื่องจากตัวแปร อยู่นอกขอบเขต . ตัวแปรในเครื่องมีการกำหนดขอบเขตอย่างเข้มงวด หมายความว่าเมธอดไม่สามารถเข้าถึงตัวแปรภายนอกตัวมันเองได้ เว้นแต่จะส่งผ่านเป็นอาร์กิวเมนต์
@foo = 1
def bar
@foo
end
bar # => 1
ในขณะที่ตัวแปรโลคัลพร้อมใช้งานในเครื่อง ตัวแปรอินสแตนซ์ ใช้ได้กับทุกวิธีของอินสแตนซ์คลาส
ขอบเขตที่สืบทอดมาและการรวม Proc
ดังที่เราได้เห็นในตัวอย่างก่อนหน้านี้ ขอบเขตจะขึ้นอยู่กับตำแหน่งในโค้ด ตัวแปรโลคัลที่กำหนดนอกเมธอดไม่อยู่ในขอบเขตภายในเมธอด แต่สามารถทำให้ใช้ได้โดยเปลี่ยนเป็นตัวแปรอินสแตนซ์ เมธอดไม่สามารถเข้าถึงตัวแปรโลคัลที่กำหนดไว้ภายนอกได้ เนื่องจากเมธอดมีขอบเขตของตัวเอง โดยมีการเชื่อมโยงของตัวเอง
Procs (รวมถึงบล็อกและแลมบ์ดาโดยการขยาย) ต่างกัน เมื่อใดก็ตามที่ proc ถูกสร้างอินสแตนซ์ การเชื่อมโยงจะถูกสร้างขึ้นซึ่งรับช่วงการอ้างอิงไปยังตัวแปรในเครื่องในบริบทที่บล็อกถูกสร้างขึ้น
foo = 1
Proc.new { foo }.call # => 1
ในตัวอย่างนี้ เราตั้งค่าตัวแปรชื่อ foo
ถึง 1
. ภายใน ออบเจ็กต์ Proc ที่สร้างในบรรทัดที่สองจะสร้างการเชื่อมโยงใหม่ เมื่อเรียกใช้ proc เราสามารถขอค่าของตัวแปรได้
เนื่องจากการเชื่อมโยงจะถูกสร้างขึ้นเมื่อมีการเริ่มต้น proc เราจึงไม่สามารถสร้าง proc ก่อนกำหนดตัวแปรได้ แม้ว่าบล็อกจะไม่ถูกเรียกจนกว่าจะมีการกำหนดตัวแปรแล้ว
proc = Proc.new { foo }
foo = 1
proc.call # => NameError (undefined local variable or method `foo' for main:Object)
การเรียก proc จะสร้าง NameError
เนื่องจากตัวแปรไม่ได้ถูกกำหนดในการผูกของ proc ดังนั้น ควรกำหนดตัวแปรใดๆ ที่เข้าถึงใน proc ก่อนที่ proc จะถูกสร้างขึ้นหรือส่งผ่านเป็นอาร์กิวเมนต์
foo = 1
proc = Proc.new { foo }
foo = 2
proc.call # => 2
อย่างไรก็ตาม เราสามารถเปลี่ยนแปลงตัวแปรได้หลังจากที่ถูกกำหนดในบริบทหลักแล้ว เนื่องจากการเชื่อมโยงของ proc มีการอ้างอิงถึงตัวแปรนั้นแทนที่จะคัดลอก
foo = 1
Proc.new { foo = 2 }.call
foo #=> 2
ในตัวอย่างนี้ เราจะเห็นว่า foo
ตัวแปรชี้ไปที่วัตถุเดียวกันเมื่ออยู่ใน proc กับภายนอก เราสามารถอัปเดตภายใน proc เพื่อให้ตัวแปรภายนอกอัปเดตได้เช่นกัน
การผูกมัด
เพื่อติดตามขอบเขตปัจจุบัน Ruby ใช้ bindings ซึ่งสรุปบริบทการดำเนินการในแต่ละตำแหน่งในโค้ด binding
เมธอดส่งคืน binding
วัตถุที่อธิบายการผูกที่ตำแหน่งปัจจุบัน
foo = 1
binding.local_variables # => [:foo]
วัตถุโยงมีเมธอดชื่อ #local_variables
ซึ่งส่งคืนชื่อของตัวแปรท้องถิ่นทั้งหมดที่มีอยู่ในขอบเขตปัจจุบัน
foo = 1
binding.eval("foo") # => 1
สามารถประเมินรหัสในการผูกโดยใช้ #eval
กระบวนการ. ตัวอย่างข้างต้นไม่มีประโยชน์มากนัก เพียงเรียก foo
ก็จะได้ผลเช่นเดียวกัน อย่างไรก็ตาม เนื่องจากการเชื่อมเป็นวัตถุที่ส่งต่อได้ จึงสามารถนำมาใช้กับสิ่งที่น่าสนใจกว่าได้ มาดูตัวอย่างกัน
ตัวอย่างชีวิตจริง
ตอนนี้เราได้เรียนรู้เกี่ยวกับการผูกมัดเพื่อความปลอดภัยในโรงรถของเราแล้ว เช่น นำมันออกไปที่เนินลาดแล้วเล่นหิมะ นอกเหนือจากการใช้การโยงภายในของ Ruby ทั่วทั้งภาษาแล้ว ยังมีบางสถานการณ์ที่มีการใช้ออบเจกต์การโยงอย่างชัดเจน ตัวอย่างที่ดีคือ ERB—ระบบเทมเพลตของ Ruby
require 'erb'
x = 1
def y
2
end
template = ERB.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`")
template.result(binding) # => "x is 1, y() returns 2, self is `main`"
ในตัวอย่างนี้ เราสร้างตัวแปรชื่อ x
, วิธีการที่เรียกว่า y
และเทมเพลต ERB ที่อ้างอิงทั้งสองอย่าง จากนั้นเราก็ส่งการเชื่อมโยงปัจจุบันไปยัง ERB#result
ซึ่งประเมินแท็ก ERB ในเทมเพลตและส่งคืนสตริงพร้อมตัวแปรที่กรอก
ภายใต้ประทุน ERB ใช้ Binding#eval
เพื่อประเมินเนื้อหาของแท็ก ERB แต่ละรายการในขอบเขตของการผูกที่ผ่าน การนำไปใช้อย่างง่ายซึ่งใช้ได้กับตัวอย่างข้างต้นอาจมีลักษณะดังนี้:
class DiyErb
def initialize(template)
@template = template
end
def result(binding)
@template.gsub(/<%=(.+?)%>/) do
binding.eval($1)
end
end
end
x = 1
def y
2
end
template = DiyErb.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`")
template.result(binding) # => "x is 1, y() returns 2, self is `main`"
DiyErb
คลาสใช้สตริงเทมเพลตในการเริ่มต้น มัน #result
method ค้นหาแท็ก ERB ทั้งหมดและแทนที่ด้วยผลลัพธ์ของการประเมินเนื้อหา ในการทำเช่นนั้น มันเรียก Binding#eval
ในการเชื่อมโยงที่ผ่าน โดยมีเนื้อหาของแท็ก ERB
โดยผ่านการผูกปัจจุบันเมื่อเรียก #result
วิธีการ eval
การเรียกสามารถเข้าถึงตัวแปรที่กำหนดนอกเมธอด และแม้กระทั่งภายนอกคลาส โดยไม่ต้องส่งผ่านอย่างชัดเจน
เราเสียเธอไปในป่าหรือเปล่า
เราหวังว่าคุณจะสนุกกับการเล่นสกีในป่าของเรา เราเจาะลึกเข้าไปในขอบเขตและการปิด หลังจากกลบเกลื่อนไปแล้ว เราหวังว่าเราจะไม่สูญเสียคุณในป่า โปรดแจ้งให้เราทราบหากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการผูก หรือมีหัวข้อ Ruby อื่น ๆ ที่คุณต้องการเจาะลึก
ขอขอบคุณที่ติดตามเราและโปรดปิดการผูกมัดของคุณก่อนที่จะปล่อยให้นักพัฒนาซอฟต์แวร์คนต่อไป หากคุณชอบทริปมหัศจรรย์เหล่านี้ คุณอาจต้องการสมัครรับ Ruby Magic เพื่อรับอีเมลเมื่อเราเผยแพร่บทความใหม่เดือนละครั้ง