โดยปกติแล้วออบเจ็กต์จะถูกสร้างขึ้นเพื่อแสดงถึงเอนทิตีในโลกแห่งความเป็นจริง เช่น ผู้ใช้ คำสั่งซื้อ และอื่นๆ:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" อายุ: 30 -
และในโลกแห่งความเป็นจริง ผู้ใช้สามารถ ดำเนินการได้ : เลือกบางอย่างจากตะกร้าสินค้า เข้าสู่ระบบ ออกจากระบบ ฯลฯ
การดำเนินการจะแสดงใน JavaScript ตามฟังก์ชันในคุณสมบัติ
ในการเริ่มต้น เรามาสอน user
ให้ทักทายกัน:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" อายุ: 30 - user.sayHi = ฟังก์ชั่น () { alert("สวัสดี!"); - user.sayHi(); // สวัสดี!
ที่นี่เราเพิ่งใช้ Function Expression เพื่อสร้างฟังก์ชันและกำหนดให้กับคุณสมบัติ user.sayHi
ของอ็อบเจ็กต์
จากนั้นเราก็สามารถเรียกมันว่า user.sayHi()
ได้ ผู้ใช้สามารถพูดได้แล้ว!
ฟังก์ชั่นที่เป็นคุณสมบัติของวัตถุเรียกว่า วิธีการ ของมัน
ตรงนี้เรามีเมธอด sayHi
ของ object user
แน่นอนว่าเราสามารถใช้ฟังก์ชันที่ประกาศไว้ล่วงหน้าเป็นวิธีการได้ เช่นนี้
ให้ผู้ใช้ = { - - // ก่อนอื่นประกาศ ฟังก์ชั่น sayHi() { alert("สวัสดี!"); - // จากนั้นเพิ่มเป็นวิธีการ user.sayHi = พูดสวัสดี; user.sayHi(); // สวัสดี!
การเขียนโปรแกรมเชิงวัตถุ
เมื่อเราเขียนโค้ดของเราโดยใช้อ็อบเจ็กต์เพื่อเป็นตัวแทนของเอนทิตี นั่นเรียกว่าการเขียนโปรแกรมเชิงวัตถุ หรือเรียกสั้นๆ ว่า “OOP”
OOP เป็นเรื่องใหญ่ เป็นศาสตร์ที่น่าสนใจในตัวมันเอง วิธีการเลือกหน่วยงานที่เหมาะสม? จะจัดระเบียบปฏิสัมพันธ์ระหว่างพวกเขาได้อย่างไร? นั่นคือสถาปัตยกรรม และมีหนังสือดีๆ มากมายเกี่ยวกับหัวข้อนั้น เช่น “รูปแบบการออกแบบ: องค์ประกอบของซอฟต์แวร์เชิงวัตถุที่ใช้ซ้ำได้” โดย E. Gamma, R. Helm, R. Johnson, J. Vissides หรือ “การวิเคราะห์เชิงวัตถุและการออกแบบด้วย Applications” โดย G. Booch และอีกมากมาย
มีไวยากรณ์ที่สั้นกว่าสำหรับวิธีการในวัตถุตามตัวอักษร:
// วัตถุเหล่านี้ทำเช่นเดียวกัน ผู้ใช้ = { พูดสวัสดี: ฟังก์ชั่น () { alert("สวัสดี"); - - // วิธีการจดชวเลขดูดีกว่าใช่ไหม? ผู้ใช้ = { sayHi() { // เหมือนกับ "sayHi: function(){...}" alert("สวัสดี"); - -
ตามที่แสดงไปแล้ว เราสามารถละเว้น "function"
และเขียนว่า sayHi()
ได้
ถ้าจะพูดความจริง สัญกรณ์ก็ไม่เหมือนกันหมด มีความแตกต่างเล็กน้อยที่เกี่ยวข้องกับการสืบทอดวัตถุ (จะกล่าวถึงในภายหลัง) แต่ตอนนี้ไม่สำคัญแล้ว ในเกือบทุกกรณี ควรใช้ไวยากรณ์ที่สั้นกว่า
เป็นเรื่องปกติที่วิธี object จำเป็นต้องเข้าถึงข้อมูลที่จัดเก็บไว้ใน object เพื่อทำงานของมัน
ตัวอย่างเช่น โค้ดภายใน user.sayHi()
อาจต้องมีชื่อ user
ในการเข้าถึงวัตถุ วิธีการสามารถใช้คำสำคัญ this
ได้
ค่า this
คือวัตถุ “ก่อนจุด” ซึ่งเป็นวัตถุที่ใช้ในการเรียกเมธอด
ตัวอย่างเช่น:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" อายุ: 30, พูดสวัสดี() { // "นี่" คือ "วัตถุปัจจุบัน" การแจ้งเตือน (this.name); - - user.sayHi(); // จอห์น
ในระหว่างการดำเนินการ user.sayHi()
ค่าของ this
จะเป็น user
ในทางเทคนิคแล้ว ยังสามารถเข้าถึงอ็อบเจ็กต์ได้โดยไม่ต้อง this
โดยการอ้างอิงผ่านตัวแปรภายนอก:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" อายุ: 30, พูดสวัสดี() { การแจ้งเตือน (ชื่อผู้ใช้); // "ผู้ใช้" แทนที่จะเป็น "สิ่งนี้" - -
…แต่โค้ดดังกล่าวไม่น่าเชื่อถือ หากเราตัดสินใจคัดลอก user
ไปยังตัวแปรอื่น เช่น admin = user
และเขียนทับ user
ด้วยอย่างอื่น มันก็จะเข้าถึงวัตถุผิด
ที่แสดงให้เห็นด้านล่าง:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" อายุ: 30, พูดสวัสดี() { การแจ้งเตือน (ชื่อผู้ใช้.ชื่อ); // นำไปสู่ข้อผิดพลาด - - ให้ผู้ดูแลระบบ = ผู้ใช้; ผู้ใช้ = โมฆะ; // เขียนทับเพื่อทำให้สิ่งต่าง ๆ ชัดเจน admin.sayHi(); // TypeError: ไม่สามารถอ่านคุณสมบัติ 'ชื่อ' ของ null ได้
หากเราใช้ this.name
แทน user.name
ภายใน alert
โค้ดก็จะใช้งานได้
ใน JavaScript คีย์เวิร์ด this
มีพฤติกรรมไม่เหมือนกับภาษาโปรแกรมอื่นๆ ส่วนใหญ่ สามารถใช้ในฟังก์ชันใดก็ได้ แม้ว่าจะไม่ใช่วิธีการของวัตถุก็ตาม
ไม่มีข้อผิดพลาดทางไวยากรณ์ในตัวอย่างต่อไปนี้:
ฟังก์ชั่น sayHi() { การแจ้งเตือน( this.name ); -
ค่าของ this
ได้รับการประเมินระหว่างรันไทม์ ขึ้นอยู่กับบริบท
ตัวอย่างเช่น ฟังก์ชันเดียวกันนี้ถูกกำหนดให้กับวัตถุสองชิ้นที่แตกต่างกันและมี "สิ่งนี้" ที่แตกต่างกันในการเรียก:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" }; ให้ผู้ดูแลระบบ = { ชื่อ: "ผู้ดูแลระบบ" }; ฟังก์ชั่น sayHi() { การแจ้งเตือน( this.name ); - // ใช้ฟังก์ชันเดียวกันในวัตถุสองชิ้น user.f = พูดสวัสดี; admin.f = พูดสวัสดี; // การโทรเหล่านี้มีความแตกต่างกัน // "นี่" ภายในฟังก์ชันคือวัตถุ "ก่อนจุด" user.f(); // จอห์น (ผู้ใช้ == นี้) admin.f(); // ผู้ดูแลระบบ (นี่ == ผู้ดูแลระบบ) ผู้ดูแลระบบ['f'](); // ผู้ดูแลระบบ (ใช้เครื่องหมายจุดหรือวงเล็บเหลี่ยมในการเข้าถึงเมธอด – ไม่สำคัญ)
กฎนั้นง่ายมาก: ถ้า obj.f()
ถูกเรียก สิ่ง this
จะถือเป็น obj
ระหว่างการเรียก f
อาจเป็น user
หรือ admin
ในตัวอย่างด้านบน
การโทรโดยไม่มีวัตถุ: this == undefined
เรายังสามารถเรียกใช้ฟังก์ชันโดยไม่มีวัตถุได้เลย:
ฟังก์ชั่น sayHi() { การแจ้งเตือน (สิ่งนี้); - พูดว่าสวัสดี(); // ไม่ได้กำหนด
ในกรณี this
จะ undefined
ในโหมดเข้มงวด หากเราพยายามเข้าถึง this.name
จะมีข้อผิดพลาดเกิดขึ้น
ในโหมดที่ไม่เข้มงวด ค่าของ this
ในกรณีนี้จะเป็น วัตถุโกลบอล ( window
ในเบราว์เซอร์ เราจะพูดถึงมันในภายหลังในบท วัตถุโกลบอล ) นี่คือลักษณะการทำงานในอดีตที่ "use strict"
โดยปกติแล้วการเรียกดังกล่าวจะเป็นข้อผิดพลาดในการเขียนโปรแกรม หากมี this
อยู่ภายในฟังก์ชัน ก็คาดว่าจะถูกเรียกในบริบทของวัตถุ
ผลที่ตามมาจากการหลุดพ้น this
หากคุณมาจากภาษาโปรแกรมอื่น คุณอาจคุ้นเคยกับแนวคิดเรื่อง "bound this
" โดยที่วิธีการที่กำหนดไว้ในอ็อบเจ็กต์จะมี this
อ้างอิงถึงอ็อบเจ็กต์นั้นเสมอ
ใน JavaScript this
คือ "ฟรี" ค่าของมันถูกประเมิน ณ เวลาโทร และไม่ขึ้นอยู่กับตำแหน่งที่ประกาศวิธีการ แต่ขึ้นอยู่กับว่าวัตถุใดอยู่ "ก่อนจุด"
แนวคิดของรันไทม์ที่ประเมิน this
มีทั้งข้อดีและข้อเสีย ในด้านหนึ่ง ฟังก์ชันสามารถนำมาใช้ซ้ำกับวัตถุต่างๆ ได้ ในทางกลับกัน ความยืดหยุ่นที่มากขึ้นจะทำให้เกิดข้อผิดพลาดได้มากขึ้น
จุดยืนของเราในที่นี้ไม่ใช่การตัดสินว่าการตัดสินใจออกแบบภาษานี้ดีหรือไม่ดี เราจะเข้าใจวิธีการทำงาน วิธีรับผลประโยชน์ และหลีกเลี่ยงปัญหา
ฟังก์ชั่นลูกศรมีความพิเศษ: พวกเขาไม่มี this
"เป็นของตัวเอง" หากเราอ้างอิง this
จากฟังก์ชันดังกล่าว มันจะนำมาจากฟังก์ชัน "ปกติ" ภายนอก
ตัวอย่างเช่น ที่นี่ arrow()
ใช้ this
จากเมธอด user.sayHi()
ภายนอก:
ให้ผู้ใช้ = { ชื่อแรก: "อิลยา" พูดสวัสดี() { ให้ลูกศร = () => การแจ้งเตือน (this.firstName); ลูกศร(); - - user.sayHi(); //อิลยา
นั่นเป็นคุณลักษณะพิเศษของฟังก์ชันลูกศร ซึ่งจะมีประโยชน์เมื่อเราไม่ต้องการแยก this
ออกจากกัน แต่ต้องการแยกออกจากบริบทภายนอก ต่อมาในบทที่ฟังก์ชันลูกศรกลับมาอีกครั้ง เราจะเจาะลึกลงไปในฟังก์ชันลูกศรมากขึ้น
ฟังก์ชั่นที่เก็บไว้ในคุณสมบัติของวัตถุเรียกว่า “วิธีการ”
วิธีการอนุญาตให้วัตถุ "ทำหน้าที่" เหมือน object.doSomething()
วิธีการสามารถอ้างอิงวัตถุเช่น this
ค่าของ this
ถูกกำหนดไว้ที่รันไทม์
เมื่อมีการประกาศฟังก์ชัน ฟังก์ชันอาจใช้ this
แต่ฟังก์ชัน this
ไม่มีค่าจนกว่าจะเรียกใช้ฟังก์ชัน
สามารถคัดลอกฟังก์ชันระหว่างวัตถุได้
เมื่อฟังก์ชันถูกเรียกใช้ในรูปแบบ "method": object.method()
ค่าของ this
ระหว่างการโทรจะเป็น object
โปรดทราบว่าฟังก์ชั่นลูกศรนั้นพิเศษ: ไม่มี this
เมื่อเข้าถึง this
ภายในฟังก์ชันลูกศร ก็จะถูกนำมาจากภายนอก
ความสำคัญ: 5
ที่นี่ฟังก์ชัน makeUser
ส่งคืนวัตถุ
ผลของการเข้าถึง ref
คืออะไร? ทำไม
ฟังก์ชั่น makeUser() { กลับ { ชื่อ: "จอห์น" อ้างอิง: นี้ - - ให้ผู้ใช้ = makeUser(); การแจ้งเตือน ( user.ref.name ); //ผลเป็นยังไงบ้าง?
คำตอบ: ข้อผิดพลาด
ลองมัน:
ฟังก์ชั่น makeUser() { กลับ { ชื่อ: "จอห์น" อ้างอิง: นี้ - - ให้ผู้ใช้ = makeUser(); การแจ้งเตือน ( user.ref.name ); // ข้อผิดพลาด: ไม่สามารถอ่านคุณสมบัติ 'ชื่อ' ของไม่ได้กำหนด
นั่นเป็นเพราะกฎที่ตั้งค่า this
ไม่ได้ดูที่คำจำกัดความของวัตถุ เฉพาะช่วงเวลาการโทรเท่านั้นที่สำคัญ
ที่นี่ค่าของภายใน makeUser()
this
undefined
เนื่องจากมันถูกเรียกว่าเป็นฟังก์ชัน ไม่ใช่เป็นวิธีการที่มีไวยากรณ์ "จุด"
ค่า this
เป็นค่าเดียวสำหรับฟังก์ชันทั้งหมด บล็อกโค้ด และตัวอักษรของวัตถุจะไม่ส่งผลต่อค่านี้
ดังนั้น ref: this
จริง ๆ แล้วใช้กระแสของฟังก์ชัน this
เราสามารถเขียนฟังก์ชันใหม่และส่งกลับ this
เดิมด้วยค่า undefined
:
ฟังก์ชัน makeUser(){ คืนสิ่งนี้; // คราวนี้ไม่มีวัตถุตามตัวอักษร - การแจ้งเตือน( makeUser().ชื่อ ); // ข้อผิดพลาด: ไม่สามารถอ่านคุณสมบัติ 'ชื่อ' ของไม่ได้กำหนด
ดังที่คุณเห็นผลลัพธ์ของ alert( makeUser().name )
เหมือนกับผลลัพธ์ของ alert( user.ref.name )
จากตัวอย่างก่อนหน้านี้
นี่เป็นกรณีตรงกันข้าม:
ฟังก์ชั่น makeUser() { กลับ { ชื่อ: "จอห์น" อ้างอิง() { คืนสิ่งนี้; - - - ให้ผู้ใช้ = makeUser(); การแจ้งเตือน( user.ref().ชื่อ ); // จอห์น
ตอนนี้มันใช้งานได้แล้ว เพราะ user.ref()
เป็นวิธีการ และค่าของ this
จะถูกตั้งค่าเป็นวัตถุก่อน .
-
ความสำคัญ: 5
สร้าง calculator
วัตถุด้วยสามวิธี:
read()
พร้อมท์ให้ใส่ค่าสองค่าและบันทึกเป็นคุณสมบัติของวัตถุที่มีชื่อ a
และ b
ตามลำดับ
sum()
ส่งคืนผลรวมของค่าที่บันทึกไว้
mul()
คูณค่าที่บันทึกไว้และส่งกลับผลลัพธ์
ให้เครื่องคิดเลข = { // ... รหัสของคุณ ... - เครื่องคิดเลขอ่าน(); การแจ้งเตือน( เครื่องคิดเลข.ผลรวม() ); การแจ้งเตือน( Calculator.mul() );
เรียกใช้การสาธิต
เปิดแซนด์บ็อกซ์พร้อมการทดสอบ
ให้เครื่องคิดเลข = { ผลรวม() { กลับ this.a + this.b; - มัล() { กลับ this.a * this.b; - อ่าน() { this.a = +prompt('a?', 0); this.b = +พร้อมท์('b?', 0); - - เครื่องคิดเลขอ่าน(); การแจ้งเตือน( เครื่องคิดเลข.ผลรวม() ); การแจ้งเตือน( Calculator.mul() );
เปิดโซลูชันพร้อมการทดสอบในแซนด์บ็อกซ์
ความสำคัญ: 2
มีวัตถุ ladder
ที่ให้คุณขึ้นและลงได้:
ให้บันได = { ขั้นตอน: 0, ขึ้น() { นี้.ขั้นตอน++; - ลง() { นี้.ขั้นตอน--; - showStep: function() { // แสดงขั้นตอนปัจจุบัน การแจ้งเตือน( this.step ); - -
ตอนนี้หากเราต้องโทรหลายครั้งตามลำดับ เราสามารถทำได้ดังนี้:
บันไดขึ้น(); บันไดขึ้น(); บันได.ลง(); บันได.showStep(); // 1 บันได.ลง(); บันได.showStep(); // 0
แก้ไขโค้ดของ up
, down
และ showStep
เพื่อให้การโทรสามารถเชื่อมต่อได้ดังนี้:
ladder.up().up().down().showStep().down().showStep(); // แสดง 1 แล้ว 0
วิธีการดังกล่าวใช้กันอย่างแพร่หลายในไลบรารี JavaScript
เปิดแซนด์บ็อกซ์พร้อมการทดสอบ
วิธีแก้ไขคือการส่งคืนออบเจ็กต์เองจากการโทรทุกครั้ง
ให้บันได = { ขั้นตอน: 0, ขึ้น() { นี้.ขั้นตอน++; คืนสิ่งนี้; - ลง() { นี้.ขั้นตอน--; คืนสิ่งนี้; - showStep() { การแจ้งเตือน( this.step ); คืนสิ่งนี้; - - ladder.up().up().down().showStep().down().showStep(); // แสดง 1 แล้ว 0
นอกจากนี้เรายังสามารถเขียนสายเดียวต่อบรรทัด สำหรับสายโซ่ยาวจะอ่านง่ายกว่า:
บันไดปีน .ขึ้น() .ขึ้น() .ลง() .showStep() // 1 .ลง() .showStep(); // 0
เปิดโซลูชันพร้อมการทดสอบในแซนด์บ็อกซ์