ปรับปรุงความรู้ JavaScript ของคุณโดยการอ่านซอร์สโค้ด
เผยแพร่แล้ว: 2022-03-10คุณจำครั้งแรกที่คุณเจาะลึกลงไปในซอร์สโค้ดของไลบรารีหรือเฟรมเวิร์กที่คุณใช้บ่อยได้หรือไม่? สำหรับฉัน ช่วงเวลานั้นมาในช่วงงานแรกของฉันในฐานะนักพัฒนาส่วนหน้าเมื่อสามปีที่แล้ว
เราเพิ่งเสร็จสิ้นการเขียนกรอบงานเดิมภายในที่เราเคยใช้ในการสร้างหลักสูตรอีเลิร์นนิงใหม่ ในตอนเริ่มต้นของการเขียนใหม่ เราได้ใช้เวลาตรวจสอบโซลูชันต่างๆ มากมาย รวมถึง Mithril, Inferno, Angular, React, Aurelia, Vue และ Polymer เนื่องจากฉันเป็นมือใหม่อย่างมาก (ฉันเพิ่งเปลี่ยนจากการทำข่าวเป็นการพัฒนาเว็บ) ฉันจำได้ว่ารู้สึกกังวลใจกับความซับซ้อนของแต่ละเฟรมเวิร์กและไม่เข้าใจวิธีการทำงานของแต่ละเฟรมเวิร์ก
ความเข้าใจของฉันเพิ่มขึ้นเมื่อฉันเริ่มตรวจสอบกรอบการทำงานที่เราเลือกอย่าง Mithril อย่างละเอียดยิ่งขึ้น ตั้งแต่นั้นมา ความรู้ของฉันเกี่ยวกับ JavaScript และการเขียนโปรแกรมโดยทั่วไป - ได้รับความช่วยเหลืออย่างมากจากชั่วโมงที่ฉันได้ใช้เวลาในการขุดลึกลงไปในความกล้าของห้องสมุดที่ฉันใช้ทุกวันทั้งในที่ทำงานหรือในโครงการของฉันเอง ในโพสต์นี้ ฉันจะแบ่งปันวิธีการบางอย่างที่คุณสามารถนำไลบรารี่หรือกรอบงานที่คุณโปรดปรานไปใช้เป็นเครื่องมือทางการศึกษา
ประโยชน์ของการอ่านซอร์สโค้ด
ประโยชน์หลักๆ ประการหนึ่งของการอ่านซอร์สโค้ดคือจำนวนสิ่งที่คุณเรียนรู้ได้ เมื่อฉันดู codebase ของ Mithril ครั้งแรก ฉันมีความคิดที่คลุมเครือว่า DOM เสมือนคืออะไร เมื่อฉันทำเสร็จแล้ว ฉันก็พบว่า DOM เสมือนเป็นเทคนิคที่เกี่ยวข้องกับการสร้างทรีออบเจ็กต์ที่อธิบายว่าอินเทอร์เฟซผู้ใช้ของคุณควรมีลักษณะอย่างไร ต้นไม้นั้นจะถูกเปลี่ยนเป็นองค์ประกอบ DOM โดยใช้ DOM API เช่น document.createElement
การอัปเดตทำได้โดยการสร้างทรีใหม่ที่อธิบายสถานะในอนาคตของอินเทอร์เฟซผู้ใช้ จากนั้นเปรียบเทียบกับออบเจกต์จากทรีเก่า
ฉันได้อ่านเกี่ยวกับเรื่องนี้ทั้งหมดในบทความและบทช่วยสอนต่างๆ และถึงแม้จะเป็นประโยชน์ แต่การได้สังเกตการทำงานในบริบทของแอปพลิเคชันที่เราจัดส่งให้นั้นก็เป็นเรื่องที่ให้ความกระจ่างมากสำหรับฉัน นอกจากนี้ยังสอนฉันว่าควรถามคำถามใดเมื่อเปรียบเทียบกรอบงานต่างๆ ตัวอย่างเช่น แทนที่จะดูดาว GitHub ตอนนี้ฉันรู้แล้วว่าต้องถามคำถามเช่น "วิธีที่แต่ละเฟรมเวิร์กดำเนินการอัปเดตส่งผลต่อประสิทธิภาพและประสบการณ์ของผู้ใช้อย่างไร"
ประโยชน์อีกประการหนึ่งคือการเพิ่มความซาบซึ้งและความเข้าใจในสถาปัตยกรรมแอปพลิเคชันที่ดีของคุณ แม้ว่าโครงการโอเพนซอร์สส่วนใหญ่มักใช้โครงสร้างเดียวกันกับที่เก็บข้อมูล แต่แต่ละโครงการก็มีความแตกต่าง โครงสร้างของ Mithril ค่อนข้างแบน และหากคุณคุ้นเคยกับ API ของมัน คุณสามารถคาดเดาโค้ดในโฟลเดอร์ต่างๆ เช่น render
, router
และ request
ได้ ในทางกลับกัน โครงสร้างของ React สะท้อนถึงสถาปัตยกรรมใหม่ ผู้ดูแลได้แยกโมดูลที่รับผิดชอบสำหรับการอัปเดต UI ( react-reconciler
) ออกจากโมดูลที่รับผิดชอบสำหรับการแสดงผลองค์ประกอบ DOM ( react-dom
)
ข้อดีอย่างหนึ่งของสิ่งนี้คือตอนนี้นักพัฒนาสามารถเขียนตัวแสดงภาพที่กำหนดเองได้ง่ายขึ้นโดยเชื่อมต่อกับแพ็คเกจ react-reconciler
Parcel ซึ่งเป็นโมดูลบันเดิลที่ฉันเพิ่งศึกษามาไม่นานนี้ ยังมีโฟลเดอร์ packages
อย่าง React ด้วย โมดูลคีย์มีชื่อว่า parcel-bundler
และมีโค้ดที่รับผิดชอบในการสร้างบันเดิล การปั่นเซิร์ฟเวอร์โมดูลยอดนิยม และเครื่องมือบรรทัดคำสั่ง
ข้อดีอีกประการหนึ่ง ซึ่งทำให้ฉันประหลาดใจก็คือ คุณจะรู้สึกสบายใจมากขึ้นในการอ่านข้อกำหนดจาวาสคริปต์อย่างเป็นทางการซึ่งกำหนดวิธีการทำงานของภาษา ครั้งแรกที่ฉันอ่านข้อมูลจำเพาะคือตอนที่ฉันกำลังตรวจสอบความแตกต่างระหว่างการ throw Error
และข้อ throw new Error
(การแจ้งเตือนสปอยเลอร์ - ไม่มี) ฉันตรวจสอบสิ่งนี้เพราะฉันสังเกตเห็นว่า Mithril ใช้ throw Error
ในการใช้งานฟังก์ชั่น m
ของมัน และฉันสงสัยว่าจะมีประโยชน์มากกว่า throw new Error
หรือไม่ ตั้งแต่นั้นมา ฉันได้เรียนรู้ว่าตัวดำเนินการเชิงตรรกะ &&
และ ||
ไม่จำเป็นต้องส่งคืนบูลีน พบกฎที่ควบคุมวิธีที่ตัวดำเนินการความเท่าเทียมกัน ==
บังคับค่าและเหตุผล Object.prototype.toString.call({})
ส่งคืน '[object Object]'
เทคนิคการอ่านซอร์สโค้ด
มีหลายวิธีในการเข้าถึงซอร์สโค้ด ฉันพบวิธีที่ง่ายที่สุดในการเริ่มต้นคือการเลือกวิธีการจากไลบรารีที่คุณเลือกและบันทึกสิ่งที่เกิดขึ้นเมื่อคุณเรียกใช้ อย่าจัดทำเอกสารทุกขั้นตอนแต่พยายามระบุขั้นตอนและโครงสร้างโดยรวม
ฉันเพิ่งทำสิ่งนี้ด้วย ReactDOM.render
และเรียนรู้มากมายเกี่ยวกับ React Fiber และเหตุผลบางประการเบื้องหลังการใช้งาน โชคดีที่ React เป็นเฟรมเวิร์กที่ได้รับความนิยม ฉันพบบทความมากมายที่เขียนโดยนักพัฒนารายอื่นในประเด็นเดียวกัน ซึ่งทำให้กระบวนการนี้เร็วขึ้น
การดำน้ำลึกนี้ยังแนะนำให้ฉันรู้จักกับแนวคิดของการตั้งเวลาแบบร่วมมือ วิธี window.requestIdleCallback
และตัวอย่างในโลกแห่งความเป็นจริงของรายการที่เชื่อมโยง (React จัดการการอัปเดตโดยใส่ไว้ในคิวซึ่งเป็นรายการลิงก์ของการอัปเดตตามลำดับความสำคัญ) เมื่อทำเช่นนี้ ขอแนะนำให้สร้างแอปพลิเคชันพื้นฐานโดยใช้ไลบรารี สิ่งนี้ทำให้การดีบักง่ายขึ้นเพราะคุณไม่ต้องจัดการกับการติดตามสแต็กที่เกิดจากไลบรารีอื่น
หากฉันไม่ได้ทำการตรวจสอบเชิงลึก ฉันจะเปิดโฟลเดอร์ /node_modules
ในโครงการที่ฉันกำลังทำงานอยู่ หรือฉันจะไปที่ที่เก็บ GitHub สิ่งนี้มักจะเกิดขึ้นเมื่อฉันพบจุดบกพร่องหรือคุณสมบัติที่น่าสนใจ เมื่ออ่านโค้ดบน GitHub ตรวจสอบให้แน่ใจว่าคุณกำลังอ่านจากเวอร์ชันล่าสุด คุณสามารถดูโค้ดจากการคอมมิตด้วยแท็กเวอร์ชันล่าสุดได้โดยคลิกปุ่มที่ใช้เปลี่ยนสาขาและเลือก "แท็ก" ไลบรารีและเฟรมเวิร์กกำลังอยู่ระหว่างการเปลี่ยนแปลงตลอดไป ดังนั้นคุณจึงไม่ต้องการเรียนรู้เกี่ยวกับบางสิ่งที่อาจจะหายไปในเวอร์ชันถัดไป
อีกวิธีหนึ่งที่เกี่ยวข้องน้อยกว่าในการอ่านซอร์สโค้ดคือสิ่งที่ฉันชอบเรียกว่าวิธี 'การมองคร่าวๆ' เมื่อฉันเริ่มอ่านโค้ดตั้งแต่เนิ่นๆ ฉันติดตั้ง express.js เปิดโฟลเดอร์ /node_modules
และดำเนินการอ้างอิง หาก README
ไม่ได้ให้คำอธิบายที่น่าพอใจแก่ฉัน ฉันจะอ่านที่มา การทำเช่นนี้ทำให้ฉันค้นพบสิ่งที่น่าสนใจเหล่านี้:
- Express ขึ้นอยู่กับสองโมดูลซึ่งทั้งคู่รวมอ็อบเจ็กต์ แต่ทำในลักษณะที่แตกต่างกันมาก
merge-descriptors
จะเพิ่มคุณสมบัติที่พบโดยตรงบนวัตถุต้นทางเท่านั้น และมันยังรวมคุณสมบัติที่ไม่สามารถระบุได้ ในขณะที่utils-merge
จะวนซ้ำเฉพาะคุณสมบัติที่นับได้ของวัตถุเช่นเดียวกับที่พบในห่วงโซ่ต้นแบบmerge-descriptors
ใช้Object.getOwnPropertyNames()
และObject.getOwnPropertyDescriptor()
ในขณะที่utils-merge
ใช้สำหรับfor..in
- โมดูล
setprototypeof
มีวิธีการตั้งค่าแบบข้ามแพลตฟอร์มสำหรับการสร้างต้นแบบของอ็อบเจกต์ที่สร้างอินสแตนซ์ -
escape-html
เป็นโมดูล 78 บรรทัดสำหรับการหลีกเลี่ยงสตริงของเนื้อหา เพื่อให้สามารถสอดแทรกในเนื้อหา HTML ได้
แม้ว่าการค้นพบนี้จะไม่มีประโยชน์ในทันที แต่การมีความเข้าใจทั่วไปเกี่ยวกับการอ้างอิงที่ใช้โดยไลบรารีหรือกรอบงานของคุณนั้นมีประโยชน์
เมื่อพูดถึงการดีบักโค้ดส่วนหน้า เครื่องมือดีบั๊กของเบราว์เซอร์คือเพื่อนที่ดีที่สุดของคุณ เหนือสิ่งอื่นใด สิ่งเหล่านี้อนุญาตให้คุณหยุดโปรแกรมเมื่อใดก็ได้ และตรวจสอบสถานะของโปรแกรม ข้ามการดำเนินการของฟังก์ชัน หรือก้าวเข้าหรือออกจากโปรแกรม บางครั้งสิ่งนี้จะไม่สามารถทำได้ในทันทีเนื่องจากโค้ดถูกย่อให้เล็กสุด ฉันมักจะ unminify และคัดลอกโค้ด unminified ลงในไฟล์ที่เกี่ยวข้องในโฟลเดอร์ /node_modules
กรณีศึกษา: ฟังก์ชันการเชื่อมต่อของ Redux
React-Redux เป็นไลบรารี่ที่ใช้จัดการสถานะของแอปพลิเคชัน React เมื่อต้องจัดการกับไลบรารียอดนิยมเช่นนี้ ฉันเริ่มต้นด้วยการค้นหาบทความที่เขียนเกี่ยวกับการนำไปใช้ ในการทำเช่นนั้นสำหรับกรณีศึกษานี้ ฉันมาเจอบทความนี้ นี่เป็นข้อดีอีกอย่างของการอ่านซอร์สโค้ด ขั้นตอนการวิจัยมักจะนำคุณไปสู่บทความที่ให้ข้อมูลเช่นนี้ ซึ่งจะช่วยปรับปรุงความคิดและความเข้าใจของคุณเองเท่านั้น
connect
เป็นฟังก์ชัน React-Redux ซึ่งเชื่อมต่อส่วนประกอบ React กับที่เก็บ Redux ของแอปพลิเคชัน ยังไง? ตามเอกสาร มันทำสิ่งต่อไปนี้:
“...ส่งคืนคลาสส่วนประกอบใหม่ที่เชื่อมต่อซึ่งรวมส่วนประกอบที่คุณส่งผ่านเข้ามา”
หลังจากอ่านแล้ว ฉันจะถามคำถามต่อไปนี้:
- ฉันรู้รูปแบบหรือแนวความคิดใด ๆ ที่ฟังก์ชันรับอินพุตแล้วส่งคืนอินพุตเดียวกันนั้นพร้อมฟังก์ชันเพิ่มเติมหรือไม่
- ถ้าฉันรู้รูปแบบดังกล่าว ฉันจะใช้สิ่งนี้ตามคำอธิบายที่ให้ไว้ในเอกสารได้อย่างไร
โดยปกติ ขั้นตอนต่อไปคือการสร้างแอปตัวอย่างพื้นฐานที่ใช้การ connect
อย่างไรก็ตาม ในโอกาสนี้ ฉันเลือกใช้แอป React ใหม่ที่เรากำลังสร้างที่ Limejump เพราะฉันต้องการเข้าใจการ connect
ภายในบริบทของแอปพลิเคชัน ซึ่งในที่สุดจะเข้าสู่สภาพแวดล้อมการผลิต
องค์ประกอบที่ฉันมุ่งเน้นมีลักษณะดังนี้:
class MarketContainer extends Component { // code omitted for brevity } const mapDispatchToProps = dispatch => { return { updateSummary: (summary, start, today) => dispatch(updateSummary(summary, start, today)) } } export default connect(null, mapDispatchToProps)(MarketContainer);
เป็นส่วนประกอบคอนเทนเนอร์ที่ห่อหุ้มส่วนประกอบเชื่อมต่อที่มีขนาดเล็กลงสี่ชิ้น สิ่งแรกที่คุณเจอในไฟล์ที่เอ็กซ์พอร์ตวิธีการ connect
คือความคิดเห็นนี้: connect เป็น Facade เหนือ connectAdvanced โดยไม่ต้องไปไกล เรามีช่วงเวลาการเรียนรู้ครั้งแรกของเรา: โอกาสในการสังเกตรูปแบบการออกแบบซุ้มในการดำเนินการ ในตอนท้ายของไฟล์เราจะเห็นว่าการ connect
ส่งออกการเรียกใช้ฟังก์ชันที่เรียกว่า createConnect
พารามิเตอร์ของมันคือค่าดีฟอลต์จำนวนหนึ่งซึ่งถูกทำลายล้างดังนี้:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {})
อีกครั้ง เราเจอช่วงเวลาแห่งการเรียนรู้อื่น: การ ส่งออกฟังก์ชันที่เรียกใช้ และ การทำลายโครงสร้างอาร์กิวเมนต์ของฟังก์ชันเริ่มต้น ส่วนการทำลายล้างเป็นช่วงเวลาแห่งการเรียนรู้เพราะมีการเขียนโค้ดดังนี้:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory })
มันจะส่งผลให้เกิดข้อผิดพลาด Uncaught TypeError: Cannot destructure property 'connectHOC' of 'undefined' or 'null'.
เนื่องจากฟังก์ชันไม่มีอาร์กิวเมนต์เริ่มต้นที่จะถอยกลับ
หมายเหตุ : สำหรับข้อมูลเพิ่มเติม คุณสามารถอ่านบทความของ David Walsh ช่วงเวลาแห่งการเรียนรู้บางช่วงอาจดูไร้สาระ ทั้งนี้ขึ้นอยู่กับความรู้ภาษาของคุณ ดังนั้นคุณควรจดจ่ออยู่กับสิ่งที่คุณไม่เคยเห็นมาก่อนหรือจำเป็นต้องเรียนรู้เพิ่มเติม
createConnect
เองไม่ได้ทำอะไรในเนื้อหาฟังก์ชัน มันส่งคืนฟังก์ชั่นที่เรียกว่า connect
อันที่ฉันใช้ที่นี่:
export default connect(null, mapDispatchToProps)(MarketContainer)
ต้องใช้อาร์กิวเมนต์สี่ตัว เป็นทางเลือกทั้งหมด และอาร์กิวเมนต์สามตัวแรกต้องผ่านฟังก์ชัน match
ซึ่งช่วยกำหนดพฤติกรรมตามอาร์กิวเมนต์ที่มีอยู่และประเภทค่าของอาร์กิวเมนต์ ตอนนี้ เนื่องจากอาร์กิวเมนต์ที่สองที่มีให้เพื่อ match
เป็นหนึ่งในสามฟังก์ชันที่นำเข้ามา connect
ฉันต้องตัดสินใจว่าจะติดตามเธรดใด
มีช่วงเวลาแห่งการเรียนรู้ด้วยฟังก์ชันพร็อกซีที่ใช้ในการห่ออาร์กิวเมนต์แรกเพื่อ connect
หากอาร์กิวเมนต์เหล่านี้เป็นฟังก์ชัน ยูทิลิตี isPlainObject
ที่ใช้ตรวจสอบวัตถุธรรมดาหรือโมดูล warning
ซึ่งแสดงวิธีตั้งค่าดีบักเกอร์ให้ทำลายข้อยกเว้นทั้งหมด หลังจากฟังก์ชันจับคู่ เรามาที่ connectHOC
ซึ่งเป็นฟังก์ชันที่ใช้ส่วนประกอบ React ของเราและเชื่อมต่อกับ Redux เป็นการเรียกใช้ฟังก์ชันอื่นที่ส่งคืน wrapWithConnect
ซึ่งเป็นฟังก์ชันที่จัดการการเชื่อมต่อส่วนประกอบกับร้านค้า
เมื่อดูการใช้งานของ connectHOC
ฉันสามารถชื่นชมได้ว่าทำไมต้อง connect
เพื่อซ่อนรายละเอียดการใช้งาน เป็นหัวใจสำคัญของ React-Redux และมีตรรกะที่ไม่จำเป็นต้องเปิดเผยผ่านการ connect
แม้ว่าฉันจะจบการดำน้ำลึกที่นี่ หากฉันทำต่อ นี่ก็เป็นเวลาที่เหมาะสมที่จะปรึกษากับเอกสารอ้างอิงที่ฉันพบก่อนหน้านี้ เนื่องจากมีคำอธิบายโดยละเอียดอย่างเหลือเชื่อของฐานรหัส
สรุป
การอ่านซอร์สโค้ดนั้นยากในตอนแรก แต่ทุกอย่างจะง่ายขึ้นเมื่อเวลาผ่านไป เป้าหมายไม่ใช่เพื่อเข้าใจทุกอย่าง แต่เพื่อออกไปด้วยมุมมองที่แตกต่างและความรู้ใหม่ กุญแจสำคัญคือการไตร่ตรองเกี่ยวกับกระบวนการทั้งหมดและอยากรู้อยากเห็นทุกอย่างอย่างเข้มข้น
ตัวอย่างเช่น ฉันพบว่าฟังก์ชัน isPlainObject
น่าสนใจเพราะใช้ฟังก์ชันนี้ if (typeof obj !== 'object' || obj === null) return false
เพื่อให้แน่ใจว่าอาร์กิวเมนต์ที่กำหนดเป็นอ็อบเจกต์ธรรมดา เมื่อฉันอ่านการใช้งานครั้งแรก ฉันสงสัยว่าเหตุใดจึงไม่ใช้ Object.prototype.toString.call(opts) !== '[object Object]'
ซึ่งใช้โค้ดน้อยกว่าและแยกความแตกต่างระหว่างอ็อบเจ็กต์และประเภทย่อยของอ็อบเจ็กต์ เช่น Date วัตถุ. อย่างไรก็ตาม การอ่านบรรทัดถัดไปเผยให้เห็นว่าในเหตุการณ์ที่ไม่น่าเป็นไปได้อย่างยิ่งที่นักพัฒนาที่ใช้การ connect
ส่งคืนอ็อบเจ็กต์ Date ตัวอย่างเช่น Object.getPrototypeOf(obj) === null
check จะจัดการสิ่งนี้
ความน่าสนใจอีกเล็กน้อยใน isPlainObject
คือรหัสนี้:
while (Object.getPrototypeOf(baseProto) !== null) { baseProto = Object.getPrototypeOf(baseProto) }
การค้นหาโดย Google บางอย่างทำให้ฉันมาที่เธรด StackOverflow และปัญหา Redux ที่อธิบายว่าโค้ดนั้นจัดการกับกรณีต่างๆ อย่างไร เช่น การตรวจสอบกับวัตถุที่มาจาก iFrame
ลิงค์ที่เป็นประโยชน์ในการอ่านซอร์สโค้ด
- “วิธีการย้อนกลับกรอบงานวิศวกร” Max Koretskyi, Medium
- “วิธีการอ่านโค้ด” Aria Stewart, GitHub