diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md index f27f7d1d2..01cd14cb6 100644 --- a/1-js/04-object-basics/07-optional-chaining/article.md +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -1,42 +1,37 @@ - -# Optional chaining '?.' +# Optional Chaining '?.' [recent browser="new"] -The optional chaining `?.` is a safe way to access nested object properties, even if an intermediate property doesn't exist. - -## The "non-existing property" problem +Optional chaining หรือ `?.` เป็นวิธีที่ปลอดภัยในการเข้าถึงคุณสมบัติแบบซ้อนของออบเจ็กต์ แม้ว่าคุณสมบัติระหว่างทางจะไม่มีอยู่ก็ตาม -If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common. +## ปัญหาเมื่อเข้าถึงคุณสมบัติที่ไม่มีอยู่ -As an example, let's say we have `user` objects that hold the information about our users. +ยกตัวอย่างเช่น สมมติเรามีออบเจ็กต์ `user` ที่เก็บข้อมูลผู้ใช้ โดยผู้ใช้ส่วนใหญ่มีที่อยู่อยู่ใน `user.address` ซึ่งภายในมี `user.address.street` เก็บชื่อถนน แต่ผู้ใช้บางคนอาจไม่ได้ระบุที่อยู่ไว้ -Most of our users have addresses in `user.address` property, with the street `user.address.street`, but some did not provide them. - -In such case, when we attempt to get `user.address.street`, and the user happens to be without an address, we get an error: +เมื่อเราพยายามเข้าถึง `user.address.street` โดยที่ผู้ใช้ไม่มีที่อยู่ จะเกิดข้อผิดพลาดแบบนี้: ```js run -let user = {}; // a user without "address" property +let user = {}; // ผู้ใช้ที่ไม่มีคุณสมบัติ "address" -alert(user.address.street); // Error! +alert(user.address.street); // เกิด Error! ``` -That's the expected result. JavaScript works like this. As `user.address` is `undefined`, an attempt to get `user.address.street` fails with an error. +นั่นคือผลลัพธ์ปกติของ JavaScript เพราะ `user.address` เป็น `undefined` การเข้าถึง `user.address.street` จึงล้มเหลว -In many practical cases we'd prefer to get `undefined` instead of an error here (meaning "no street"). +ในความเป็นจริง หลายครั้งเราอาจต้องการให้คืนค่า `undefined` กลับมาแทนข้อผิดพลาด (เช่น หมายถึง "ไม่มีถนน") -...and another example. In Web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element. +อีกตัวอย่างคือเมื่อเรียกใช้ `document.querySelector('.elem')` เพื่อหาองค์ประกอบใน HTML ถ้าไม่เจอจะคืนค่า `null` กลับมา แล้วเมื่อเราพยายามเข้าถึง property ของมัน อย่าง `.innerHTML` ก็จะเกิด error เช่นกัน: ```js run -// document.querySelector('.elem') is null if there's no element -let html = document.querySelector('.elem').innerHTML; // error if it's null +// document.querySelector('.elem') จะเป็น null ถ้าไม่เจอ element นั้น +let html = document.querySelector('.elem').innerHTML; // เกิด Error ถ้าเป็น null ``` -Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result. +อีกครั้ง ในหลายกรณีที่ไม่เจอ element ก็ถือเป็นเรื่องปกติ เราอาจต้องการให้คืนค่า `html` เป็น `null` แทนที่จะเกิด error -How can we do this? +## วิธีแก้ไขเดิมก่อนมี Optional Chaining -The obvious solution would be to check the value using `if` or the conditional operator `?`, before accessing its property, like this: +ก่อนที่จะมี optional chaining `?.` เราต้องเช็คค่าด้วย `if` หรือ `&&` ก่อนที่จะเข้าถึง property ต่อๆ ไป เช่น: ```js let user = {}; @@ -44,55 +39,59 @@ let user = {}; alert(user.address ? user.address.street : undefined); ``` -It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. For more deeply nested properties, that becomes a problem as more repetitions are required. +หรือในกรณี `document.querySelector`: -E.g. let's try getting `user.address.street.name`. +```js run +let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null; +``` -We need to check both `user.address` and `user.address.street`: +ใช้ได้ แต่ดูยุ่งเหยิงและต้องเช็คซ้ำๆ โดยเฉพาะกับ property ที่ซ้อนกันหลายชั้น เช่น `user.address.street.name` ```js -let user = {}; // user has no address +let user = {}; // ผู้ใช้ไม่มีที่อยู่ alert(user.address ? user.address.street ? user.address.street.name : null : null); ``` -That's just awful, one may even have problems understanding such code. - -Don't even care to, as there's a better way to write it, using the `&&` operator: +อีกวิธีที่ดูดีกว่าเล็กน้อยคือใช้ `&&`: ```js run -let user = {}; // user has no address +let user = {}; // ผู้ใช้ไม่มีที่อยู่ -alert( user.address && user.address.street && user.address.street.name ); // undefined (no error) +alert( user.address && user.address.street && user.address.street.name ); // undefined (ไม่เกิด error) ``` -AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn't ideal. +แต่ก็ยังต้องเช็ค property ซ้ำๆ อยู่ดี -As you can see, property names are still duplicated in the code. E.g. in the code above, `user.address` appears three times. +นี่จึงเป็นที่มาของ optional chaining `?.` เพื่อแก้ปัญหานี้ครั้งเดียวจบ! -That's why the optional chaining `?.` was added to the language. To solve this problem once and for all! +## Optional Chaining กับ Property: `?.` -## Optional chaining +Optional chaining `?.` จะหยุดการทำงานทันที ถ้าค่าทางซ้ายของ `?.` เป็น `undefined` หรือ `null` และจะคืนค่า `undefined` กลับมา -The optional chaining `?.` stops the evaluation if the value before `?.` is `undefined` or `null` and returns `undefined`. +**ในบทความนี้ เพื่อความกระชับ เราจะใช้คำว่า "มีอยู่" หมายถึงไม่เป็น `null` หรือ `undefined`** -**Further in this article, for brevity, we'll be saying that something "exists" if it's not `null` and not `undefined`.** +กล่าวคือ `value?.prop`: +- จะทำงานเหมือน `value.prop` ถ้า `value` มีอยู่ +- ถ้า `value` เป็น `undefined/null` จะคืนค่า `undefined` -In other words, `value?.prop`: -- works as `value.prop`, if `value` exists, -- otherwise (when `value` is `undefined/null`) it returns `undefined`. - -Here's the safe way to access `user.address.street` using `?.`: +ตัวอย่างการใช้ `?.` เพื่อเข้าถึง `user.address.street`: ```js run -let user = {}; // user has no address +let user = {}; // ผู้ใช้ไม่มีที่อยู่ -alert( user?.address?.street ); // undefined (no error) +alert( user?.address?.street ); // undefined (ไม่เกิด error) ``` -The code is short and clean, there's no duplication at all. +โค้ดสั้นลง และไม่มีการเช็คซ้ำๆ + +กับตัวอย่าง `document.querySelector`: -Reading the address with `user?.address` works even if `user` object doesn't exist: +```js run +let html = document.querySelector('.elem')?.innerHTML; // จะเป็น undefined ถ้าไม่เจอ element +``` + +แม้แต่ในกรณีที่ไม่มีตัวแปร `user` เลย ก็ยังใช้ `?.` ได้อย่างปลอดภัย: ```js run let user = null; @@ -101,76 +100,70 @@ alert( user?.address ); // undefined alert( user?.address.street ); // undefined ``` -Please note: the `?.` syntax makes optional the value before it, but not any further. +โปรดสังเกตว่า `?.` ทำให้ส่วนทางซ้ายมันเป็น optional ได้ แต่ไม่ใช่ส่วนถัดไป -E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/undefined` (and returns `undefined` in that case), but that's only for `user`. Further properties are accessed in a regular way. If we want some of them to be optional, then we'll need to replace more `.` with `?.`. +ใน `user?.address.street.name` ตัว `?.` อนุญาตให้ `user` เป็น `null/undefined` แต่ส่วนที่เหลือ (`address`, `street`, `name`) จะถูกเข้าถึงแบบปกติ ถ้าเราอยากให้ส่วนอื่นเป็น optional ด้วย ต้องแทน `.` ด้วย `?.` เพิ่มเติม -```warn header="Don't overuse the optional chaining" -We should use `?.` only where it's ok that something doesn't exist. +```warn header="ใช้ ?. อย่างพอดี" +เราควรใช้ `?.` เฉพาะในกรณีที่ยอมรับได้หากบางอย่างไม่มีอยู่ -For example, if according to our coding logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`. +เช่น ถ้าตามตรรกะของโปรแกรมแล้ว `user` ต้องมีอยู่ แต่ `address` เป็น optional ได้ เราควรเขียน `user.address?.street` แต่ไม่ใช่ `user?.address?.street` -So, if `user` happens to be undefined due to a mistake, we'll see a programming error about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug. +มิฉะนั้น ถ้าใช้ `?.` มากเกินไป เวลามีข้อผิดพลาดขึ้นมาจริงๆ มันอาจถูกซ่อนเงียบไว้ ทำให้ยากต่อการดีบั๊กในภายหลัง ``` -````warn header="The variable before `?.` must be declared" -If there's no variable `user` at all, then `user?.anything` triggers an error: +````warn header="ตัวแปรทางซ้ายของ ?. ต้องถูกประกาศก่อน" +ถ้าไม่มีตัวแปรนั้นเลย เช่น `user?.anything` จะเกิด error: ```js run // ReferenceError: user is not defined user?.address; ``` -The variable must be declared (e.g. `let/const/var user` or as a function parameter). The optional chaining works only for declared variables. +ตัวแปรจะต้องถูกประกาศก่อน จากนั้น optional chaining จึงจะใช้งานได้ ```` -## Short-circuiting - -As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist. +## Short-Circuiting -So, if there are any further function calls or side effects, they don't occur. - -For instance: +`?.` จะหยุดการทำงานทันทีถ้าส่วนซ้ายไม่มีอยู่ (เป็น `null/undefined`) ดังนั้นถ้ามีการเรียก function หรือคำสั่งอื่นๆ ทางขวาของ `?.` จะไม่ถูกรันเลย ```js run let user = null; let x = 0; -user?.sayHi(x++); // no "sayHi", so the execution doesn't reach x++ +user?.sayHi(x++); // ไม่มี "user" จึงไม่ถึงการเรียก sayHi และ x++ -alert(x); // 0, value not incremented +alert(x); // 0, ค่า x ไม่ถูกเพิ่ม ``` -## Other variants: ?.(), ?.[] - -The optional chaining `?.` is not an operator, but a special syntax construct, that also works with functions and square brackets. +## Optional Chaining กับ Function: `?.()` -For example, `?.()` is used to call a function that may not exist. +`?.()` ใช้เพื่อเรียก function ที่อาจจะไม่มีอยู่ -In the code below, some of our users have `admin` method, and some don't: +ตัวอย่างเช่นในโค้ดด้านล่าง ผู้ใช้บางคนมี method `admin` บางคนไม่มี: ```js run let userAdmin = { admin() { - alert("I am admin"); + alert("ฉันคือ admin"); } }; let userGuest = {}; *!* -userAdmin.admin?.(); // I am admin +userAdmin.admin?.(); // ฉันคือ admin */!* -*!* -userGuest.admin?.(); // nothing (no such method) +*!* +userGuest.admin?.(); // ไม่มีอะไรเกิดขึ้น (ไม่มี method admin) */!* ``` -Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the user object exists, so it's safe read from it. +สังเกตว่าเราใช้ `.` เพื่อเข้าถึง `admin` ก่อน เพราะเรามั่นใจว่าตัวแปร `user` มีอยู่แน่ๆ จากนั้นจึงตามด้วย `?.()` -Then `?.()` checks the left part: if the admin function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors. +## Optional Chaining กับ [] : `?.[]` -The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist. +ถ้าเราต้องการใช้ `[]` เพื่อเข้าถึง property แทนที่จะใช้ `.` ก็ใช้ `?.[]` ได้ ```js run let key = "firstName"; @@ -179,42 +172,38 @@ let user1 = { firstName: "John" }; -let user2 = null; +let user2 = null; alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined ``` -Also we can use `?.` with `delete`: +และสามารถใช้กับ `delete` ได้ด้วย: ```js run -delete user?.name; // delete user.name if user exists +delete user?.name; // ลบ user.name ถ้า user มีอยู่ ``` -````warn header="We can use `?.` for safe reading and deleting, but not writing" -The optional chaining `?.` has no use at the left side of an assignment. +````warn header="ใช้ ?. ได้แค่อ่านและลบ ไม่ใช่การเขียน" +Optional chaining `?.` ไม่ใช้ในด้านซ้ายของการกำหนดค่า -For example: ```js run let user = null; -user?.name = "John"; // Error, doesn't work -// because it evaluates to undefined = "John" +user?.name = "John"; // Error เพราะจะประเมินเป็น undefined = "John" ``` - -It's just not that smart. ```` -## Summary +## สรุป -The optional chaining `?.` syntax has three forms: +ไวยากรณ์ Optional chaining `?.` มี 3 รูปแบบ: -1. `obj?.prop` -- returns `obj.prop` if `obj` exists, otherwise `undefined`. -2. `obj?.[prop]` -- returns `obj[prop]` if `obj` exists, otherwise `undefined`. -3. `obj.method?.()` -- calls `obj.method()` if `obj.method` exists, otherwise returns `undefined`. +1. `obj?.prop` -- คืนค่า `obj.prop` ถ้า `obj` มีอยู่ ไม่งั้นคืน `undefined` +2. `obj?.[prop]` -- คืนค่า `obj[prop]` ถ้า `obj` มีอยู่ ไม่งั้นคืน `undefined` +3. `obj.method?.()` -- เรียก `obj.method()` ถ้า `obj.method` มีอยู่ ไม่งั้นคืน `undefined` -As we can see, all of them are straightforward and simple to use. The `?.` checks the left part for `null/undefined` and allows the evaluation to proceed if it's not so. +`?.` จะเช็ค `null/undefined` ทางด้านซ้าย และอนุญาตให้เข้าถึงต่อไปได้ถ้าไม่ใช่ -A chain of `?.` allows to safely access nested properties. +การเรียงต่อกันหลายตัวของ `?.` ทำให้เข้าถึง property ที่ซ้อนกันได้อย่างปลอดภัย -Still, we should apply `?.` carefully, only where it's acceptable that the left part doesn't exist. So that it won't hide programming errors from us, if they occur. +อย่างไรก็ตาม ควรใช้ `?.` อย่างระมัดระวัง เฉพาะในกรณีที่ยอมรับได้หากบางอย่างไม่มีอยู่จริง เพื่อไม่ให้ข้อผิดพลาดที่ควรเกิดถูกซ่อนเงียบไป \ No newline at end of file