คุณสมบัติ "prototype"
ถูกใช้อย่างกว้างขวางโดยแกนหลักของ JavaScript เอง ฟังก์ชันคอนสตรัคเตอร์ในตัวทั้งหมดใช้งาน
ขั้นแรกเราจะดูรายละเอียด และวิธีการใช้เพื่อเพิ่มความสามารถใหม่ให้กับออบเจ็กต์ในตัว
สมมติว่าเราส่งออกวัตถุว่าง:
ให้ obj = {}; การแจ้งเตือน (obj); // "[วัตถุวัตถุ]" ?
รหัสที่สร้างสตริง "[object Object]"
อยู่ที่ไหน นั่นเป็นวิธี toString
ในตัว แต่มันอยู่ที่ไหน obj
ว่างเปล่า!
…แต่สัญกรณ์แบบสั้น obj = {}
นั้นเหมือนกับ obj = new Object()
โดยที่ Object
เป็นฟังก์ชันตัวสร้างวัตถุในตัว โดยมี prototype
ของตัวเองอ้างอิงถึงวัตถุขนาดใหญ่ด้วย toString
และวิธีการอื่น ๆ
นี่คือสิ่งที่เกิดขึ้น:
เมื่อมีการเรียกใช้ new Object()
(หรือวัตถุตามตัวอักษร {...}
ถูกสร้างขึ้น) [[Prototype]]
ของมันถูกตั้งค่าเป็น Object.prototype
ตามกฎที่เรากล่าวถึงในบทที่แล้ว:
ดังนั้นเมื่อมีการเรียก obj.toString()
วิธีการนี้ก็นำมาจาก Object.prototype
เราสามารถตรวจสอบได้ดังนี้:
ให้ obj = {}; การแจ้งเตือน (obj.__proto__ === Object.prototype); // จริง การแจ้งเตือน (obj.toString === obj.__proto__.toString); //จริง การแจ้งเตือน (obj.toString === Object.prototype.toString); //จริง
โปรดทราบว่าไม่มี [[Prototype]]
อีกต่อไปในสายโซ่ด้านบน Object.prototype
:
การแจ้งเตือน (Object.prototype.__proto__); // โมฆะ
ออบเจ็กต์บิวท์อินอื่นๆ เช่น Array
, Date
, Function
และอื่นๆ ยังเก็บวิธีการไว้ในต้นแบบอีกด้วย
ตัวอย่างเช่น เมื่อเราสร้างอาร์เรย์ [1, 2, 3]
ตัวสร้าง new Array()
เป็นค่าเริ่มต้นจะถูกใช้เป็นการภายใน ดังนั้น Array.prototype
จึงกลายเป็นต้นแบบและจัดเตรียมวิธีการต่างๆ นั่นเป็นหน่วยความจำที่มีประสิทธิภาพมาก
ตามข้อกำหนดแล้ว ต้นแบบในตัวทั้งหมดจะมี Object.prototype
อยู่ด้านบน นั่นเป็นสาเหตุที่บางคนบอกว่า “ทุกสิ่งสืบทอดมาจากวัตถุ”
นี่คือภาพรวม (สำหรับ 3 บิวด์อินที่พอดี):
มาตรวจสอบต้นแบบด้วยตนเอง:
ให้ arr = [1, 2, 3]; // มันสืบทอดมาจาก Array.prototype? การแจ้งเตือน ( arr.__proto__ === Array.prototype ); // จริง // จากนั้นมาจาก Object.prototype? การแจ้งเตือน ( arr.__proto__.__proto__ === Object.prototype ); // จริง // และมีค่าว่างอยู่ด้านบน การแจ้งเตือน ( arr.__โปรโต__.__โปรโต__.__โปรโต__ ); // โมฆะ
วิธีการบางอย่างในต้นแบบอาจทับซ้อนกัน เช่น Array.prototype
มี toString
ของตัวเองซึ่งแสดงรายการองค์ประกอบที่คั่นด้วยเครื่องหมายจุลภาค:
ให้ arr = [1, 2, 3] การแจ้งเตือน(arr); // 1,2,3 <-- ผลลัพธ์ของ Array.prototype.toString
อย่างที่เราเคยเห็นมาก่อน Object.prototype
ต้องมี toString
เช่นกัน แต่ Array.prototype
นั้นอยู่ใกล้กว่าในห่วงโซ่ ดังนั้นจึงมีการใช้ตัวแปรอาร์เรย์
เครื่องมือในเบราว์เซอร์เช่นคอนโซลนักพัฒนาซอฟต์แวร์ Chrome ยังแสดงการสืบทอด ( console.dir
อาจจำเป็นต้องใช้สำหรับวัตถุในตัว):
วัตถุที่มาพร้อมเครื่องอื่นๆ ก็ทำงานในลักษณะเดียวกันเช่นกัน ฟังก์ชันคู่ - เป็นอ็อบเจ็กต์ของตัวสร้าง Function
ในตัว และวิธีการ ( call
/ apply
และอื่น ๆ ) ถูกนำมาจาก Function.prototype
ฟังก์ชั่นก็มี toString
ของตัวเองเช่นกัน
ฟังก์ชัน ฉ() {} การแจ้งเตือน (f.__proto__ == Function.prototype); // จริง การแจ้งเตือน (f.__proto__.__proto__ == Object.prototype); // จริง, สืบทอดจากวัตถุ
สิ่งที่ซับซ้อนที่สุดเกิดขึ้นกับสตริง ตัวเลข และบูลีน
อย่างที่เราจำได้พวกมันไม่ใช่วัตถุ แต่ถ้าเราพยายามเข้าถึงคุณสมบัติของพวกเขา ออบเจ็กต์ wrapper ชั่วคราวจะถูกสร้างขึ้นโดยใช้ตัวสร้างในตัว String
, Number
และ Boolean
พวกเขาให้วิธีการและหายไป
วัตถุเหล่านี้ถูกสร้างขึ้นโดยมองไม่เห็นสำหรับเราและเครื่องยนต์ส่วนใหญ่จะเพิ่มประสิทธิภาพให้กับวัตถุเหล่านี้ แต่ข้อกำหนดจะอธิบายไว้ในลักษณะนี้ทุกประการ วิธีการของอ็อบเจ็กต์เหล่านี้ยังอยู่ในต้นแบบด้วย ซึ่งมีให้ใช้เป็น String.prototype
, Number.prototype
และ Boolean.prototype
ค่า null
และ undefined
ไม่มีตัวหุ้มอ็อบเจ็กต์
ค่าพิเศษ null
และ undefined
จะแยกออกจากกัน ไม่มีตัวห่ออ็อบเจ็กต์ ดังนั้นจึงไม่มีเมธอดและคุณสมบัติสำหรับพวกมัน และไม่มีต้นแบบที่สอดคล้องกันเช่นกัน
ต้นแบบดั้งเดิมสามารถแก้ไขได้ ตัวอย่างเช่น ถ้าเราเพิ่มเมธอดให้กับ String.prototype
มันจะใช้ได้กับสตริงทั้งหมด:
String.prototype.show = ฟังก์ชั่น () { การแจ้งเตือน (สิ่งนี้); - "บูม!".show(); // บูม!
ในระหว่างกระบวนการพัฒนา เราอาจมีแนวคิดสำหรับวิธีการในตัวใหม่ๆ ที่เราต้องการ และเราอาจถูกล่อลวงให้เพิ่มแนวคิดเหล่านี้ลงในต้นแบบดั้งเดิม แต่นั่นเป็นความคิดที่ไม่ดีโดยทั่วไป
สำคัญ:
ต้นแบบมีอยู่ทั่วโลก ดังนั้นจึงเกิดข้อขัดแย้งได้ง่าย หากสองไลบรารีเพิ่มเมธอด String.prototype.show
ไลบรารีใดไลบรารีหนึ่งจะเขียนทับวิธีการของอีกไลบรารีหนึ่ง
ดังนั้น โดยทั่วไปแล้ว การปรับเปลี่ยนต้นแบบดั้งเดิมถือเป็นความคิดที่ไม่ดี
ในการเขียนโปรแกรมสมัยใหม่ มีเพียงกรณีเดียวเท่านั้นที่ได้รับการอนุมัติการแก้ไขต้นแบบดั้งเดิม นั่นคือการโพลีฟิลลิ่ง
Polyfilling เป็นคำที่ใช้แทนวิธีการที่มีอยู่ในข้อกำหนดเฉพาะของ JavaScript แต่กลไก JavaScript เฉพาะยังไม่รองรับ
จากนั้นเราอาจนำไปใช้ด้วยตนเองและเติมต้นแบบในตัวด้วย
ตัวอย่างเช่น:
if (!String.prototype.repeat) { // หากไม่มีวิธีการดังกล่าว // เพิ่มลงในต้นแบบ String.prototype.repeat = ฟังก์ชั่น (n) { // ทำซ้ำสตริง n ครั้ง // อันที่จริงโค้ดควรซับซ้อนกว่านี้เล็กน้อย // (อัลกอริทึมทั้งหมดอยู่ในข้อกำหนด) // แต่แม้กระทั่งโพลีฟิลที่ไม่สมบูรณ์ก็มักจะถือว่าดีเพียงพอ กลับอาร์เรย์ใหม่(n + 1).เข้าร่วม(นี้); - - alert( "ลา".ทำซ้ำ(3) ); //ลาล่า
ในบท Decorators and forwarding, call/apply เราได้พูดถึงวิธีการยืมแล้ว
นั่นคือเมื่อเรานำเมธอดจากวัตถุหนึ่งแล้วคัดลอกไปยังอีกวัตถุหนึ่ง
วิธีการบางอย่างของต้นแบบดั้งเดิมมักถูกยืมมา
ตัวอย่างเช่น หากเรากำลังสร้างออบเจ็กต์ที่มีลักษณะคล้ายอาร์เรย์ เราอาจต้องการคัดลอกเมธอด Array
บางส่วนลงไป
เช่น
ให้ obj = { 0: "สวัสดี" 1: "โลก!", ความยาว: 2, - obj.join = อาร์เรย์.prototype.join; การแจ้งเตือน( obj.join(',') ); // สวัสดีชาวโลก!
ใช้งานได้เนื่องจากอัลกอริทึมภายในของวิธี join
ในตัวสนใจเฉพาะดัชนีที่ถูกต้องและคุณสมบัติ length
เท่านั้น มันไม่ได้ตรวจสอบว่าวัตถุนั้นเป็นอาร์เรย์จริงหรือไม่ วิธีการในตัวหลายวิธีเป็นเช่นนั้น
ความเป็นไปได้อีกอย่างหนึ่งคือการสืบทอดโดยการตั้งค่า obj.__proto__
เป็น Array.prototype
ดังนั้นเมธอด Array
ทั้งหมดจะพร้อมใช้งานใน obj
โดยอัตโนมัติ
แต่นั่นเป็นไปไม่ได้ถ้า obj
สืบทอดมาจากวัตถุอื่นอยู่แล้ว โปรดจำไว้ว่าเราสามารถสืบทอดจากวัตถุได้ครั้งละหนึ่งวัตถุเท่านั้น
วิธีการยืมมีความยืดหยุ่น ช่วยให้สามารถผสมผสานฟังก์ชันการทำงานจากวัตถุต่างๆ ได้หากจำเป็น
ออบเจ็กต์บิวท์อินทั้งหมดมีรูปแบบเดียวกัน:
วิธีการจะถูกเก็บไว้ในต้นแบบ ( Array.prototype
, Object.prototype
, Date.prototype
ฯลฯ )
ออบเจ็กต์จะเก็บเฉพาะข้อมูลเท่านั้น (รายการอาร์เรย์ คุณสมบัติของออบเจ็กต์ วันที่)
ดั้งเดิมยังจัดเก็บวิธีการไว้ในต้นแบบของออบเจ็กต์ wrapper: Number.prototype
, String.prototype
และ Boolean.prototype
เฉพาะ undefined
และ null
เท่านั้นที่ไม่มีวัตถุตัวตัดคำ
ต้นแบบในตัวสามารถแก้ไขหรือเติมด้วยวิธีใหม่ได้ แต่ไม่แนะนำให้เปลี่ยน กรณีที่อนุญาตเพียงอย่างเดียวอาจเป็นเมื่อเราเพิ่มมาตรฐานใหม่ แต่เอ็นจิ้น JavaScript ยังไม่รองรับ
ความสำคัญ: 5
เพิ่มไปยังต้นแบบของฟังก์ชันทั้งหมดด้วยเมธอด defer(ms)
ที่รันฟังก์ชันหลังจาก ms
มิลลิวินาที
หลังจากที่คุณทำแล้ว โค้ดดังกล่าวควรใช้งานได้:
ฟังก์ชัน ฉ() { alert("สวัสดี!"); - f.เลื่อน(1,000); // แสดงคำว่า "สวัสดี!" หลังจากผ่านไป 1 วินาที
Function.prototype.defer = ฟังก์ชั่น (ms) { setTimeout (นี่, ms); - ฟังก์ชัน ฉ() { alert("สวัสดี!"); - f.เลื่อน(1,000); // แสดงคำว่า "สวัสดี!" หลังจาก 1 วินาที
ความสำคัญ: 4
เพิ่มเมธอด defer(ms)
ลงในต้นแบบของฟังก์ชันทั้งหมดที่ส่งคืน wrapper ซึ่งทำให้การโทรล่าช้าไป ms
มิลลิวินาที
นี่คือตัวอย่างวิธีการทำงาน:
ฟังก์ชัน ฉ(ก, ข) { การแจ้งเตือน (a + b); - f.เลื่อน(1,000)(1, 2); // แสดง 3 หลังจาก 1 วินาที
โปรดทราบว่าข้อโต้แย้งควรถูกส่งผ่านไปยังฟังก์ชันดั้งเดิม
Function.prototype.defer = ฟังก์ชั่น (ms) { ให้ f = นี่; ฟังก์ชันส่งคืน (...args) { setTimeout(() => f.apply (นี่, args), ms); - - //ตรวจสอบมัน ฟังก์ชัน ฉ(ก, ข) { การแจ้งเตือน (a + b); - f.เลื่อน(1,000)(1, 2); // แสดง 3 หลังจาก 1 วินาที
โปรดทราบ: เราใช้ this
ใน f.apply
เพื่อให้การตกแต่งของเราใช้งานได้สำหรับวิธีวัตถุ
ดังนั้นหากฟังก์ชัน wrapper ถูกเรียกเป็นวิธีการแบบอ็อบเจ็กต์ this
จะถูกส่งผ่านไปยังวิธีดั้งเดิม f
Function.prototype.defer = ฟังก์ชั่น (ms) { ให้ f = นี่; ฟังก์ชันส่งคืน (...args) { setTimeout(() => f.apply (นี่, args), ms); - - ให้ผู้ใช้ = { ชื่อ: "จอห์น" พูดสวัสดี() { การแจ้งเตือน (this.name); - - user.sayHi = user.sayHi.defer(1,000); user.sayHi();