เข้าใจ JavaScript this keyword
ถ้าคุณอยากเป็น programmer ไม่ว่าจะเลือกสาขาไหน คุณเกือบแน่นอนต้องเผชิญกับ JavaScript มันพัฒนาจากต้นกำเนิดปกติสู่ true powerhouse ครอบคลุม front end, backend, mobile, games, desktop apps, และแม้แต่ machine learning!
ตอนนี้ ก่อนจะกลายเป็น JavaScript master และก้าวต่อไป คุณต้องเรียน fundamentals this keyword ควรเป็นหมายเลขหนึ่งในรายการเรียนรู้
this จริงๆ คืออะไร?
พูดง่ายๆ this คือ pointer วิธีอ้างอิงว่า object ไหนเรียก function หรือ method ใน JavaScript code ในภาษาพูด this คือสิ่งที่อยู่ทางซ้ายของ dot
This มีสองการใช้งานหลัก มาสำรวจด้วยตัวอย่าง
สร้าง objects ด้วย this
function Book(title, author, genre) { this.title = title; this.author = author; this.genre = genre; } const book1 = new Book("Lord Of The Rings", "J.R.R. Tolkien", "Fantasy"); const book2 = new Book("Tools Of Titans", "Tim Ferriss", "Self-help"); console.log(book1); console.log(book2);
โค้ดข้างต้นผลลัพธ์:
ที่นี่ เรากำหนด constructor function สร้าง book objects ที่รับ title, author, genre เป็น parameters
Constructor function คือ object "blueprint" function ที่เราสร้าง objects ของชื่อที่กำหนด (Book ในกรณีนี้)
เราสร้าง object ชื่อ myBook ส่ง parameters ที่ต้องการ (called arguments ตอน call หรือ invoke function ต่างจาก parameters ตอน define)
ท้ายสุด log myBook ได้ object พร้อม properties ที่ตรงกัน ด้วยวิธีนี้ ทุก book object ใหม่ที่สร้าง จะได้ค่าที่ส่งผ่านมา assigned ให้
เข้าถึง properties ด้วย this:
const girl = { name: "Sarah", age: 26, introduce() { console.log( "Hi! " + "I'm " + this.name + " and I'm " + this.age + " years old." ); }, };
ที่นี่เรากำหนด variable ชื่อ girl เก็บ object ที่มี name และ age properties และ introduce function
Function log ประโยคแนะนำตัวใช้ properties ของ object เข้าถึงด้วย this.wanted_property
This และ scope ใน JavaScript
โดย default ตอนใช้ใน global scope หมายถึงไม่ได้อยู่ใน defined function หรือ object this จะ refer ไปที่ global object ใน browser window คือ global object
ตอนคุณประกาศ primitive data (strings, numbers, booleans…), object, หรือ function ใน JavaScript พวกมัน attach ไปที่ global (window) object ทั้งหมด
อะไรที่เขียน "in the open" คุณอ่านได้เหมือนมี window. เขียนหน้ามัน มาดู code snippet นี้:
function returnThis() { return this; }
ก่อน เรากำหนด function returnThis ใน global scope คืนค่า this แล้วเรียกด้วย returnThis()
เพราะ globally scoped เรามองได้ว่า window.returnThis() ซึ่งคืนค่าอะไรอยู่ทางซ้ายของ dot
Object functions a.k.a. methods
ถ้าเรากำหนด object ใหม่ที่มี function เดียวกันอยู่ในมัน (called object method) และเรียกอีกครั้ง เราจะได้ object นั้น (แทนด้วย curly braces) คืนมา
const object = { returnThis() { return this; }, }; object.returnThis();
The gotcha ของ this และสามวิธีแก้
ถ้า function ไม่ได้ tied directly กับ object this ภายในฟังก์ชันจะ refer ไป global object
const weirdBehaviorObject = { directlyTiedFunction() { return this; }, directlyTiedOuterFunction() { return function indirectNestedFunction() { return this; }; }, };
ลองรันใน browser ดูสิได้อะไร:
เรากำหนด object มี function หนึ่งคืนค่า this อีกอันคืนค่า inner function ที่คืนค่า this
ตอนจะเรียก indirectNestedFunction เราเขียน () หนึ่งครั้ง เรียก outer function ที่คืนค่า inner
จากนั้น เรียก inner ด้วย () อีกครั้ง คืนค่า window
นี่คือพฤติกรรมลึกลับของ JavaScript หนึ่ง ทำให้เกิด surprises ที่ไม่คาดคิดและไม่พึงประสงค์
โชคดีที่มีวิธีแก้ สามวิธี: แรก ทำ closure (วิธี pre-ES6) สอง ใช้ arrow functions สาม ใช้ bind method ที่เกี่ยวข้องกับ call และ apply methods
#1 แก้ this ด้วย closures
Closure คือ persistent memory store หมายถึง variable ไม่หายตอนฟังก์ชันจบ execution มันถูกสร้างโดย JavaScript engine ตอน nested function อ้างอิง variable ที่ defined ใน outer function เพื่อเข้าถึงได้แม้ outer function จบการทำงาน นี่คือรูปแบบกับ this example:
const objectWithClosure = { closureFunction() { const self = this; return function enclosedFunction() { return self; }; }, };
เรากำหนด object มี closureFunction ที่ประกาศ variable self เก็บ this value เพราะ closureFunction tied directly กับ containing object ค่า this refer ไปที่มัน ตอนใน enclosedFunction this ปกติ refer ไป window object เพราะเราใช้ closure เข้าถึง this จาก containing function เรา preserve calling object เป็นค่าของมันได้
#2 แก้ this ด้วย arrow functions
วิธีที่สอง ใช้ arguably หนึ่งใน best features ที่มาพร้อม ES6 คือ arrow functions
Arrow functions เป็น syntax สั้นกระชับสำหรับเขียนฟังก์ชัน นอกจากนี้ พวกมันจะ look one level up ใน scope หา this, หมายถึงในฟังก์ชันที่ contain มัน
ถ้าเรากำหนด object แบบนี้:
const arrowFunctionObject = { outerFunc() { return () => this; }, };
เห็นว่า arrow function คืนค่า actual object ไม่ใช่ window
เพราะมัน look up ไป outerFunction ที่เรียกมัน ซึ่ง this refer ไป arrowFunctionObject ดังนั้น arrow functions มี built-in closure แบบของตัวเอง
Nested arrow functions
แน่นอน นี่คือ JavaScript นี่คือ gotcha อีกอัน น่าจะไม่เจอนอก technical interview แต่เพียงพอที่จะรู้
เกิดอะไรถ้า nest arrow function ภายใน regular function และอีกอันภายในฟังก์ชันอีกตัว? ดูแบบนี้:
const arrowNestedInRegularFunctionObject = { outerFunction() { return function regularFunction() { return () => this; }; }, };
Object ของเรามี outerFunction ที่ตอนเรียก คืนค่า inner regularFunction
เราเรียก regularFunction คืนค่า innermost arrow function
ตอนเรียก มันคืนค่า window object
Arrow function จะ look up only 1 level up ใน scope เพราะ regularFunction ที่ contain มันไม่ tied directly กับ object window จึงถูกคืนค่าแทน
แต่ถ้าเรา nest arrow function ภายใน arrow function ภายใน regular function เราจะได้ expected result คือ actual containing object
const nestedArrowFunctionObject = { outerFunction() { return () => { return () => this; }; }, };
ในกรณีนี้ inner arrow function look up ไป outer outer ก็เป็น arrow function จึง look up อีกระดับ regular outerFunction tied directly กับ calling object และผลคือ object ถูกคืนค่าเป็น this.
#3 แก้ this ด้วย bind method
สุดท้าย วิธีที่สาม ใช้ bind method Bind method ใช้ explicitly declare ว่า this ควร refer ไปไหน มันรับ object ส่งเป็น argument และ bind ไปที่ function's this keyword
const arrowNestedInRegularFunctionObject = { outerFunction() { return function regularFunction() { return () => this; }.bind(arrowNestedInRegularFunctionObject); }, };
ในตัวอย่าง เรา chain bind ไปที่ฟังก์ชันที่ปกติคืนค่า window object เป็น this. เราส่ง object ที่เราอยากให้ referenced เป็น this เป็น argument ปัญหาแก้แล้ว
This ไมายากตอนคุณเข้าใจ JavaScript
เฮ่! นั่นคือการดำน้ำลึกพอสมควร ตอนนี้คุณอาจถาม ทำไมต้องยุ่งยากและสับสนกับ this?
เหตุผลคือ ต่างจากทุกอย่างใน JavaScript this เป็น dynamically scoped หมายความว่า JavaScript engine กำหนด this represent อะไร at runtime ตอนโค้ด execute โดยดูที่ from where และ who is calling it
ในทางกลับกัน ทุกอย่างใน JavaScript เป็น lexically scoped หมายถึงค่าถูกกำหนดที่ variable ถูก define ตอนโค้ดถูกเขียน
ขอบคุณ this เราสร้าง object "blueprint" ครั้งเดียว และ dynamically สร้าง objects ด้วย property values ต่างๆ ได้ เช่นเราเห็นใน book example
เรายังทำ methods ที่ expose และ manipulate object's properties ด้วย this.wanted_property ได้
เราทำงานรอบ JavaScript's quirks และ command behavior มันได้ confidently ผลลัพธ์ที่คาดหวัง
ตอนนี้คุณเข้าใจ this ใน JavaScript รวมถึง fundamental concepts อื่นๆ ไปทดลอง ฝึกฝน และก้าวสู่ JavaScript mastery ได้มั่นใจมากขึ้น
You got this!