From 09484840cab9a459615c5fb88ebab16ece41aec1 Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:26:19 +0700 Subject: [PATCH 1/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 225 +++++++++--------- 1 file changed, 112 insertions(+), 113 deletions(-) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index 3e52a1d51..15a277ff0 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -1,115 +1,118 @@ +# การแปลงออบเจ็กต์เป็นค่าปฐมภูมิ -# Object to primitive conversion +เมื่อออบเจ็กต์เกี่ยวข้องกับการดำเนินการ เช่น การบวก (`obj1 + obj2`), การลบ (`obj1 - obj2`), หรือการแสดงผลด้วย `alert(obj)`, JavaScript จะแปลงออบเจ็กต์เป็นค่าปฐมภูมิ (primitive) โดยอัตโนมัติก่อนที่จะทำการดำเนินการ -What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`? +JavaScript ไม่อนุญาตให้กำหนดวิธีการทำงานของตัวดำเนินการบนออบเจ็กต์โดยตรง แตกต่างจากภาษาโปรแกรมอื่นๆ บางภาษา เช่น Ruby หรือ C++ เราไม่สามารถใช้เมท็อดพิเศษของออบเจ็กต์เพื่อจัดการการบวกหรือตัวดำเนินการอื่นๆ ได้ -JavaScript doesn't exactly allow to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle an addition (or other operators). +ในกรณีเหล่านี้ ออบเจ็กต์จะถูกแปลงเป็นค่าปฐมภูมิโดยอัตโนมัติ และจากนั้นการดำเนินการก็จะเกิดขึ้นบนค่าปฐมภูมิเหล่านี้ ส่งผลเป็นค่าปฐมภูมิ -In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value. +นี่เป็นข้อจำกัดสำคัญ: ผลลัพธ์ของ `obj1 + obj2` (หรือการดำเนินการทางคณิตศาสตร์อื่นๆ) ไม่สามารถเป็นออบเจ็กต์อีกอันได้! -That's an important limitation, as the result of `obj1 + obj2` can't be another object! +ตัวอย่างเช่น เราไม่สามารถสร้างออบเจ็กต์ที่แทนเวกเตอร์หรือเมทริกซ์ แล้วบวกพวกมันเข้าด้วยกัน และคาดหวังให้ได้ออบเจ็กต์ที่ "ผสมกัน" เป็นผลลัพธ์ สิ่งนี้ไม่สามารถทำได้ใน JavaScript -E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board". +เนื่องจากข้อจำกัดนี้ จึงไม่ค่อยมีการใช้งานการดำเนินการทางคณิตศาสตร์กับออบเจ็กต์ในโปรเจ็กต์จริงๆ เท่าไหร่ เมื่อมันเกิดขึ้น มักจะเป็นเพราะข้อผิดพลาดในการเขียนโค้ด -So, because we can't do much here, there's no maths with objects in real projects. When it happens, it's usually because of a coding mistake. +ในบทนี้ เราจะกล่าวถึงวิธีที่ออบเจ็กต์ถูกแปลงเป็นค่าปฐมภูมิ และวิธีกำหนดเองให้กับการแปลง -In this chapter we'll cover how an object converts to primitive and how to customize it. +เรามีจุดประสงค์สองอย่าง: -We have two purposes: +1. เข้าใจสิ่งที่เกิดขึ้นในกรณีที่เกิดข้อผิดพลาดในการเขียนโค้ด เมื่อการดำเนินการดังกล่าวเกิดขึ้นโดยไม่ได้ตั้งใจ +2. รู้จักข้อยกเว้นที่การดำเนินการดังกล่าวสามารถทำได้และดูดี เช่น การลบหรือเปรียบเทียบวันที่ (ออบเจ็กต์ `Date`) -1. It will allow us to understand what's going on in case of coding mistakes, when such an operation happened accidentally. -2. There are exceptions, where such operations are possible and look good. E.g. subtracting or comparing dates (`Date` objects). We'll come across them later. +## กฎการแปลง -## Conversion rules +ในบทเรียนเกี่ยวกับการแปลงชนิดข้อมูล เราได้เห็นกฎสำหรับการแปลงเป็นตัวเลข สตริง และบูลีนของค่าปฐมภูมิ อย่างไรก็ตาม เราทิ้งช่องว่างไว้สำหรับออบเจ็กต์ ตอนนี้เรารู้เกี่ยวกับเมท็อดและสัญลักษณ์แล้ว เราสามารถเติมช่องว่างนั้นได้ -In the chapter we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it. +1. ไม่มีการแปลงเป็นบูลีน ออบเจ็กต์ทั้งหมดเป็น `true` ในบริบทบูลีน เรียบง่ายแค่นั้น มีเพียงการแปลงเป็นตัวเลขและสตริงเท่านั้น +2. การแปลงเป็นตัวเลขเกิดขึ้นเมื่อเราลบออบเจ็กต์หรือใช้ฟังก์ชันทางคณิตศาสตร์ ตัวอย่างเช่น ออบเจ็กต์ `Date` สามารถนำมาลบกันได้ และผลลัพธ์ของ `date1 - date2` คือผลต่างของเวลาระหว่างสองวันที่ +3. การแปลงเป็นสตริงมักจะเกิดขึ้นเมื่อเราแสดงผลออบเจ็กต์ด้วย `alert(obj)` และในบริบทที่คล้ายกัน -1. All objects are `true` in a boolean context. There are only numeric and string conversions. -2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates. -3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts. +เราสามารถใช้เมท็อดพิเศษของออบเจ็กต์เพื่อดำเนินการแปลงเป็นสตริงและตัวเลขด้วยตัวเองได้ -We can fine-tune string and numeric conversion, using special object methods. +ตอนนี้ มาดำดิ่งลงไปในรายละเอียดทางเทคนิคเพื่อครอบคลุมหัวข้อนี้อย่างลึกซึ้งกัน -There are three variants of type conversion, that happen in various situations. +## คำใบ้ (Hints) -They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): +JavaScript ตัดสินใจอย่างไรว่าจะใช้การแปลงแบบใด? + +มีสามรูปแบบของการแปลงชนิดข้อมูลที่เกิดขึ้นในสถานการณ์ต่างๆ พวกมันถูกเรียกว่า "คำใบ้" (hints) ตามที่ระบุในข้อกำหนด: `"string"` -: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`: +: สำหรับการแปลงออบเจ็กต์เป็นสตริง เมื่อเรากำลังทำการดำเนินการกับออบเจ็กต์ที่คาดหวังสตริง เช่น `alert`: ```js - // output + // แสดงผล alert(obj); - // using object as a property key + // ใช้ออบเจ็กต์เป็นคีย์ของคุณสมบัติ anotherObj[obj] = 123; ``` `"number"` -: For an object-to-number conversion, like when we're doing maths: +: สำหรับการแปลงออบเจ็กต์เป็นตัวเลข เช่นเมื่อเรากำลังคำนวณทางคณิตศาสตร์: ```js - // explicit conversion + // แปลงโดยชัดแจ้ง let num = Number(obj); - // maths (except binary plus) - let n = +obj; // unary plus + // คณิตศาสตร์ (ยกเว้นบวกเลขฐานสอง) + let n = +obj; // บวกเอกภาคี let delta = date1 - date2; - // less/greater comparison + // การเปรียบเทียบน้อยกว่า/มากกว่า let greater = user1 > user2; ``` + ฟังก์ชันทางคณิตศาสตร์ในตัวส่วนใหญ่ก็รวมการแปลงดังกล่าวด้วย + `"default"` -: Occurs in rare cases when the operator is "not sure" what type to expect. +: เกิดขึ้นในกรณีที่พบน้อย เมื่อตัวดำเนินการ "ไม่แน่ใจ" ว่าคาดหวังข้อมูลชนิดใด - For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. + ตัวอย่างเช่น บวกเลขฐานสอง `+` สามารถทำงานได้ทั้งกับสตริง (เชื่อมต่อพวกมัน) และตัวเลข (บวกพวกมัน) ดังนั้นถ้าการบวกเลขฐานสองได้รับออบเจ็กต์เป็นอาร์กิวเมนต์ มันจะใช้คำใบ้ `"default"` เพื่อแปลงออบเจ็กต์นั้น - Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used. + นอกจากนี้ ถ้าออบเจ็กต์ถูกเปรียบเทียบด้วย `==` กับสตริง ตัวเลข หรือสัญลักษณ์ มันจะไม่ชัดเจนว่าควรทำการแปลงแบบใด ดังนั้นคำใบ้ `"default"` จะถูกใช้ ```js - // binary plus uses the "default" hint + // การบวกเลขฐานสองใช้คำใบ้ "default" let total = obj1 + obj2; - // obj == number uses the "default" hint + // obj == number ใช้คำใบ้ "default" if (user == 1) { ... }; ``` - The greater and less comparison operators, such as `<` `>`, can work with both strings and numbers too. Still, they use the `"number"` hint, not `"default"`. That's for historical reasons. + ตัวดำเนินการเปรียบเทียบน้อยกว่าและมากกว่า เช่น `<` `>` ก็สามารถทำงานได้ทั้งกับสตริงและตัวเลขเช่นกัน แต่พวกมันใช้คำใบ้ `"number"` ไม่ใช่ `"default"` ด้วยเหตุผลทางประวัติศาสตร์ - In practice though, we don't need to remember these peculiar details, because all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we can do the same. +ในทางปฏิบัติ สิ่งต่างๆ เรียบง่ายกว่านั้นเล็กน้อย -```smart header="No `\"boolean\"` hint" -Please note -- there are only three hints. It's that simple. +ออบเจ็กต์ในตัวทั้งหมด ยกเว้นหนึ่งกรณี (ออบเจ็กต์ `Date`) จะใช้การแปลง `"default"` แบบเดียวกับ `"number"` และเราก็ควรทำแบบเดียวกันนั้น -There is no "boolean" hint (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions. -``` +แต่ก็ยังสำคัญที่จะต้องรู้เกี่ยวกับคำใบ้ทั้งสามแบบและเหตุผลที่พวกมันมีอยู่ -**To do the conversion, JavaScript tries to find and call three object methods:** +**เพื่อดำเนินการแปลง JavaScript พยายามค้นหาและเรียกเมท็อดสามอย่างของออบเจ็กต์:** -1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists, -2. Otherwise if hint is `"string"` - - try `obj.toString()` and `obj.valueOf()`, whatever exists. -3. Otherwise if hint is `"number"` or `"default"` - - try `obj.valueOf()` and `obj.toString()`, whatever exists. +1. เรียก `obj[Symbol.toPrimitive](hint)` - เมท็อดที่มีคีย์สัญลักษณ์ `Symbol.toPrimitive` (สัญลักษณ์ของระบบ) ถ้ามีเมท็อดดังกล่าวอยู่ +2. มิฉะนั้น ถ้าคำใบ้เป็น `"string"`: + - ลองเรียก `obj.toString()` หรือ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ +3. มิฉะนั้น ถ้าคำใบ้เป็น `"number"` หรือ `"default"`: + - ลองเรียก `obj.valueOf()` หรือ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ ## Symbol.toPrimitive -Let's start from the first method. There's a built-in symbol named `Symbol.toPrimitive` that should be used to name the conversion method, like this: +มาเริ่มต้นด้วยเมท็อดแรกกัน มีสัญลักษณ์ในตัวที่เรียกว่า `Symbol.toPrimitive` ซึ่งควรใช้เป็นชื่อเมท็อดการแปลง แบบนี้: ```js obj[Symbol.toPrimitive] = function(hint) { - // here goes the code to convert this object to a primitive - // it must return a primitive value - // hint = one of "string", "number", "default" + // โค้ดเพื่อแปลงออบเจ็กต์นี้เป็นค่าปฐมภูมิ + // ต้องคืนค่าปฐมภูมิ + // hint = "string", "number", หรือ "default" อย่างใดอย่างหนึ่ง }; ``` -If the method `Symbol.toPrimitive` exists, it's used for all hints, and no more methods are needed. +ถ้าเมท็อด `Symbol.toPrimitive` มีอยู่ มันจะถูกใช้สำหรับทุกคำใบ้ และไม่จำเป็นต้องใช้เมท็อดอื่นๆ อีก -For instance, here `user` object implements it: +ตัวอย่างเช่น ที่นี่ออบเจ็กต์ `user` ใช้เมท็อดนี้: -```js run +```js let user = { name: "John", money: 1000, @@ -120,63 +123,61 @@ let user = { } }; -// conversions demo: +// ตัวอย่างการแปลง: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` -As we can see from the code, `user` becomes a self-descriptive string or a money amount depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases. - +อย่างที่เราเห็นจากโค้ด `user` จะกลายเป็นสตริงที่อธิบายตัวเองหรือจำนวนเงิน ขึ้นอยู่กับการแปลง เมท็อดเดียวคือ `user[Symbol.toPrimitive]` จัดการกับทุกกรณีการแปลง ## toString/valueOf -If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: +ถ้าไม่มี `Symbol.toPrimitive` JavaScript จะพยายามหาเมท็อด `toString` และ `valueOf`: -- For the "string" hint: `toString`, and if it doesn't exist, then `valueOf` (so `toString` has the priority for string conversions). -- For other hints: `valueOf`, and if it doesn't exist, then `toString` (so `valueOf` has the priority for maths). +- สำหรับคำใบ้ `"string"`: เรียก `toString` ก่อน และถ้ามันไม่มีหรือคืนค่าออบเจ็กต์แทนค่าปฐมภูมิ ก็จะเรียก `valueOf` (ดังนั้น `toString` จะมีความสำคัญมากกว่าสำหรับการแปลงเป็นสตริง) +- สำหรับคำใบ้อื่นๆ: เรียก `valueOf` ก่อน และถ้ามันไม่มีหรือคืนค่าออบเจ็กต์แทนค่าปฐมภูมิ ก็จะเรียก `toString` (ดังนั้น `valueOf` จะมีความสำคัญมากกว่าสำหรับการคำนวณทางคณิตศาสตร์) -Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. +เมท็อด `toString` และ `valueOf` มาจากสมัยโบราณ พวกมันไม่ใช่สัญลักษณ์ (สัญลักษณ์ยังไม่มีในอดีต) แต่เป็นเมท็อดชื่อสตริง "ปกติ" พวกมันให้วิธีการ "แบบเก่า" อีกแบบในการใช้การแปลง -These methods must return a primitive value. If `toString` or `valueOf` returns an object, then it's ignored (same as if there were no method). +เมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิเพื่อให้ทำงาน (ถ้ามีการกำหนด) -By default, a plain object has following `toString` and `valueOf` methods: +โดยค่าเริ่มต้น ออบเจ็กต์ธรรมดาจะมีเมท็อด `toString` และ `valueOf` ดังนี้: -- The `toString` method returns a string `"[object Object]"`. -- The `valueOf` method returns the object itself. +- `toString` คืนสตริง `"[object Object]"` +- `valueOf` คืนออบเจ็กต์ตัวมันเอง -Here's the demo: +นี่คือตัวอย่าง: -```js run +```js let user = {name: "John"}; alert(user); // [object Object] alert(user.valueOf() === user); // true ``` -So if we try to use an object as a string, like in an `alert` or so, then by default we see `[object Object]`. +ดังนั้นถ้าเราพยายามใช้ออบเจ็กต์เป็นสตริง เช่นใน `alert` หรืออะไรทำนองนั้น โดยค่าเริ่มต้นเราจะเห็น `[object Object]` -The default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. +`valueOf` เริ่มต้นถูกกล่าวถึงที่นี่เพื่อความครบถ้วนเท่านั้น เพื่อหลีกเลี่ยงความสับสน อย่างที่คุณเห็น มันคืนออบเจ็กต์ตัวมันเอง และดังนั้นจึงถูกเพิกเฉย อย่าถามฉันว่าทำไม -- นั่นเป็นเหตุผลทางประวัติศาสตร์ เราสามารถสมมติว่ามันไม่มีอยู่ก็ได้ -Let's implement these methods to customize the conversion. +มาเพิ่มเมท็อดเหล่านี้เพื่อกำหนดการแปลงเองกัน -For instance, here `user` does the same as above using a combination of `toString` and `valueOf` instead of `Symbol.toPrimitive`: +ตัวอย่างเช่น ที่นี่ `user` ทำแบบเดียวกับด้านบนโดยใช้ `toString` และ `valueOf` ร่วมกันแทน `Symbol.toPrimitive`: -```js run +```js let user = { name: "John", money: 1000, - // for hint="string" + // สำหรับคำใบ้ "string" toString() { return `{name: "${this.name}"}`; }, - // for hint="number" or "default" + // สำหรับคำใบ้ "number" หรือ "default" valueOf() { return this.money; } - }; alert(user); // toString -> {name: "John"} @@ -184,11 +185,11 @@ alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500 ``` -As we can see, the behavior is the same as the previous example with `Symbol.toPrimitive`. +อย่างที่เราเห็น พฤติกรรมจะเหมือนกับตัวอย่างก่อนหน้าที่ใช้ `Symbol.toPrimitive` -Often we want a single "catch-all" place to handle all primitive conversions. In this case, we can implement `toString` only, like this: +บ่อยครั้งที่เราต้องการจุดเดียวที่ "รวมทุกอย่าง" เพื่อจัดการกับการแปลงเป็นค่าปฐมภูมิทั้งหมด ในกรณีนี้ เราสามารถใช้แค่ `toString` อย่างเดียว แบบนี้: -```js run +```js let user = { name: "John", @@ -201,77 +202,75 @@ alert(user); // toString -> John alert(user + 500); // toString -> John500 ``` -In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions. +เมื่อไม่มี `Symbol.toPrimitive` และ `valueOf`, `toString` จะจัดการการแปลงเป็นค่าปฐมภูมิทั้งหมด -### A conversion can return any primitive type +### การแปลงสามารถคืนค่าปฐมภูมิชนิดใดก็ได้ -The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive. +สิ่งสำคัญที่ควรรู้เกี่ยวกับเมท็อดการแปลงเป็นค่าปฐมภูมิทั้งหมดคือ พวกมันไม่จำเป็นต้องคืนค่าปฐมภูมิที่ "ตรงตามคำใบ้" -There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint `"number"`. +ไม่มีการควบคุมว่า `toString` จะคืนสตริงหรือไม่ หรือว่าเมท็อด `Symbol.toPrimitive` จะคืนตัวเลขสำหรับคำใบ้ `"number"` หรือไม่ -The only mandatory thing: these methods must return a primitive, not an object. +สิ่งเดียวที่บังคับคือ เมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิ ไม่ใช่ออบเจ็กต์ -```smart header="Historical notes" -For historical reasons, if `toString` or `valueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript. +```smart header="หมายเหตุทางประวัติศาสตร์" +ด้วยเหตุผลทางประวัติศาสตร์ ถ้า `toString` หรือ `valueOf` คืนออบเจ็กต์ จะไม่เกิด error แต่ค่าดังกล่าวจะถูกเพิกเฉย (เหมือนกับว่าเมท็อดไม่มีอยู่) นั่นเป็นเพราะในสมัยโบราณ ไม่มีแนวคิด "error" ที่ดีใน JavaScript -In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there will be an error. +ในทางตรงกันข้าม `Symbol.toPrimitive` เข้มงวดกว่า มัน *ต้อง* คืนค่าปฐมภูมิ มิฉะนั้นจะเกิด error ``` -## Further conversions +## การแปลงเพิ่มเติม -As we know already, many operators and functions perform type conversions, e.g. multiplication `*` converts operands to numbers. +ตามที่เรารู้แล้ว ตัวดำเนินการและฟังก์ชันหลายตัวทำการแปลงชนิดข้อมูล เช่น คูณ `*` จะแปลงตัวถูกดำเนินการเป็นตัวเลข -If we pass an object as an argument, then there are two stages: -1. The object is converted to a primitive (using the rules described above). -2. If the resulting primitive isn't of the right type, it's converted. +ถ้าเราส่งออบเจ็กต์เป็นอาร์กิวเมนต์ จะมีสองขั้นตอนในการคำนวณ: +1. ออบเจ็กต์ถูกแปลงเป็นค่าปฐมภูมิ (โดยใช้กฎที่อธิบายไว้ด้านบน) +2. หากจำเป็นสำหรับการคำนวณต่อไป ค่าปฐมภูมิที่ได้จะถูกแปลงอีกด้วย -For instance: +ตัวอย่างเช่น: -```js run +```js let obj = { - // toString handles all conversions in the absence of other methods + // toString จัดการการแปลงทั้งหมดเมื่อไม่มีเมท็อดอื่น toString() { return "2"; } }; -alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number +alert(obj * 2); // 4, ออบเจ็กต์ถูกแปลงเป็นค่าปฐมภูมิ "2" จากนั้นการคูณทำให้มันเป็นตัวเลข ``` -1. The multiplication `obj * 2` first converts the object to primitive (that's a string `"2"`). -2. Then `"2" * 2` becomes `2 * 2` (the string is converted to number). +1. การคูณ `obj * 2` แปลงออบเจ็กต์เป็นค่าปฐมภูมิก่อน (ซึ่งเป็นสตริง `"2"`) +2. จากนั้น `"2" * 2` กลายเป็น `2 * 2` (สตริงถูกแปลงเป็นตัวเลข) -Binary plus will concatenate strings in the same situation, as it gladly accepts a string: +การบวกเลขฐานสองจะเชื่อมต่อสตริงในสถานการณ์เดียวกัน เพราะมันยอมรับสตริงด้วยความยินดี: -```js run +```js let obj = { toString() { return "2"; } }; -alert(obj + 2); // 22 ("2" + 2), conversion to primitive returned a string => concatenation +alert(obj + 2); // 22 ("2" + 2), การแปลงเป็นค่าปฐมภูมิคืนสตริง => เชื่อมต่อ ``` -## Summary - -The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value. +## สรุป -There are 3 types (hints) of it: -- `"string"` (for `alert` and other operations that need a string) -- `"number"` (for maths) -- `"default"` (few operators) +การแปลงออบเจ็กต์เป็นค่าปฐมภูมิจะถูกเรียกโดยอัตโนมัติโดยฟังก์ชันและตัวดำเนินการในตัวหลายตัวที่คาดหวังค่าปฐมภูมิ -The specification describes explicitly which operator uses which hint. There are very few operators that "don't know what to expect" and use the `"default"` hint. Usually for built-in objects `"default"` hint is handled the same way as `"number"`, so in practice the last two are often merged together. +มีสามชนิด (คำใบ้) ของการแปลง: +- `"string"` (สำหรับ `alert` และการดำเนินการอื่นๆ ที่ต้องการสตริง) +- `"number"` (สำหรับการคำนวณทางคณิตศาสตร์) +- `"default"` (สำหรับตัวดำเนินการไม่กี่ตัว ปกติออบเจ็กต์จะใช้มันแบบเดียวกับ `"number"`) -The conversion algorithm is: +ข้อกำหนดระบุอย่างชัดเจนว่าตัวดำเนินการใดใช้คำใบ้ใด -1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, -2. Otherwise if hint is `"string"` - - try `obj.toString()` and `obj.valueOf()`, whatever exists. -3. Otherwise if hint is `"number"` or `"default"` - - try `obj.valueOf()` and `obj.toString()`, whatever exists. +อัลกอริทึมการแปลงคือ: -In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes. +1. เรียก `obj[Symbol.toPrimitive](hint)` ถ้าเมท็อดมีอยู่ +2. มิฉะนั้น ถ้าคำใบ้เป็น `"string"`: + - ลองเรียก `obj.toString()` หรือ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ +3. มิฉะนั้น ถ้าคำใบ้เป็น `"number"` หรือ `"default"`: + - ลองเรียก `obj.valueOf()` หรือ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ -As for math operations, JavaScript doesn't provide a way to "override" them using methods, so real life projects rarely use them on objects. +ในทางปฏิบัติ มักจะเพียงพอที่จะใช้แค่ `obj.toString()` เป็นเมท็อด "รวมทุกอย่าง" สำหรับการแปลงเป็นสตริง ซึ่งควรคืนตัวแทนของออบเจ็กต์ที่ "อ่านได้โดยมนุษย์" เพื่อวัตถุประสงค์ในการบันทึกหรือการดีบั๊ก \ No newline at end of file From 6caa0e89d633cd3c9be91453f497d9852045a5b2 Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:34:01 +0700 Subject: [PATCH 2/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 229 +++--------------- 1 file changed, 40 insertions(+), 189 deletions(-) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index 15a277ff0..0a07778bb 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -1,116 +1,58 @@ # การแปลงออบเจ็กต์เป็นค่าปฐมภูมิ -เมื่อออบเจ็กต์เกี่ยวข้องกับการดำเนินการ เช่น การบวก (`obj1 + obj2`), การลบ (`obj1 - obj2`), หรือการแสดงผลด้วย `alert(obj)`, JavaScript จะแปลงออบเจ็กต์เป็นค่าปฐมภูมิ (primitive) โดยอัตโนมัติก่อนที่จะทำการดำเนินการ +เกิดอะไรขึ้นเมื่อนำออบเจ็กต์มาบวกกัน (`obj1 + obj2`), ลบกัน (`obj1 - obj2`) หรือแสดงผลด้วย `alert(obj)`? -JavaScript ไม่อนุญาตให้กำหนดวิธีการทำงานของตัวดำเนินการบนออบเจ็กต์โดยตรง แตกต่างจากภาษาโปรแกรมอื่นๆ บางภาษา เช่น Ruby หรือ C++ เราไม่สามารถใช้เมท็อดพิเศษของออบเจ็กต์เพื่อจัดการการบวกหรือตัวดำเนินการอื่นๆ ได้ +JavaScript ไม่อนุญาตให้กำหนดวิธีการทำงานของตัวดำเนินการบนออบเจ็กต์เอง แตกต่างจากบางภาษาโปรแกรม เช่น Ruby หรือ C++ ที่เราสามารถใช้เมท็อดพิเศษของออบเจ็กต์เพื่อจัดการการดำเนินการต่างๆ ได้ -ในกรณีเหล่านี้ ออบเจ็กต์จะถูกแปลงเป็นค่าปฐมภูมิโดยอัตโนมัติ และจากนั้นการดำเนินการก็จะเกิดขึ้นบนค่าปฐมภูมิเหล่านี้ ส่งผลเป็นค่าปฐมภูมิ +ในกรณีเหล่านี้ ออบเจ็กต์จะถูกแปลงเป็นค่าปฐมภูมิโดยอัตโนมัติก่อน จากนั้นการดำเนินการจะเกิดขึ้นบนค่าปฐมภูมิ และส่งผลเป็นค่าปฐมภูมิเช่นกัน -นี่เป็นข้อจำกัดสำคัญ: ผลลัพธ์ของ `obj1 + obj2` (หรือการดำเนินการทางคณิตศาสตร์อื่นๆ) ไม่สามารถเป็นออบเจ็กต์อีกอันได้! +นี่ถือเป็นข้อจำกัดสำคัญ: ผลลัพธ์ของ `obj1 + obj2` (หรือการดำเนินการทางคณิตศาสตร์อื่นๆ) ไม่สามารถเป็นออบเจ็กต์ได้! -ตัวอย่างเช่น เราไม่สามารถสร้างออบเจ็กต์ที่แทนเวกเตอร์หรือเมทริกซ์ แล้วบวกพวกมันเข้าด้วยกัน และคาดหวังให้ได้ออบเจ็กต์ที่ "ผสมกัน" เป็นผลลัพธ์ สิ่งนี้ไม่สามารถทำได้ใน JavaScript +เราจึงไม่สามารถสร้างออบเจ็กต์ที่แทนเวกเตอร์หรือเมทริกซ์ แล้วบวกมันเข้าด้วยกันโดยหวังให้ได้ผลลัพธ์เป็นออบเจ็กต์ที่ "รวม" กัน ข้อจำกัดนี้ทำให้ไม่อาจเขียนโค้ดในลักษณะนั้นได้เลย -เนื่องจากข้อจำกัดนี้ จึงไม่ค่อยมีการใช้งานการดำเนินการทางคณิตศาสตร์กับออบเจ็กต์ในโปรเจ็กต์จริงๆ เท่าไหร่ เมื่อมันเกิดขึ้น มักจะเป็นเพราะข้อผิดพลาดในการเขียนโค้ด +ด้วยเหตุนี้ในโปรเจ็กต์จริงจึงไม่ค่อยมีการใช้งานการคำนวณทางคณิตศาสตร์กับออบเจ็กต์ หากมันเกิดขึ้น ก็มักจะเป็นเพราะข้อผิดพลาดในการเขียนโค้ดเสียมากกว่า -ในบทนี้ เราจะกล่าวถึงวิธีที่ออบเจ็กต์ถูกแปลงเป็นค่าปฐมภูมิ และวิธีกำหนดเองให้กับการแปลง +ในบทความนี้ เราจะกล่าวถึงวิธีที่ออบเจ็กต์ถูกแปลงเป็นค่าปฐมภูมิ และวิธีกำหนดการแปลงด้วยตัวเอง โดยมีจุดประสงค์สองประการคือ: -เรามีจุดประสงค์สองอย่าง: - -1. เข้าใจสิ่งที่เกิดขึ้นในกรณีที่เกิดข้อผิดพลาดในการเขียนโค้ด เมื่อการดำเนินการดังกล่าวเกิดขึ้นโดยไม่ได้ตั้งใจ -2. รู้จักข้อยกเว้นที่การดำเนินการดังกล่าวสามารถทำได้และดูดี เช่น การลบหรือเปรียบเทียบวันที่ (ออบเจ็กต์ `Date`) +1. เพื่อให้เข้าใจสิ่งที่เกิดขึ้นเมื่อมีข้อผิดพลาดในการเขียนโค้ด ซึ่งทำให้เกิดการแปลงโดยไม่ได้ตั้งใจ +2. เพื่อรู้จักข้อยกเว้นที่การแปลงดังกล่าวสามารถทำได้และให้ผลลัพธ์ที่ถูกต้อง เช่น การลบหรือเปรียบเทียบวันที่ (Date) ## กฎการแปลง -ในบทเรียนเกี่ยวกับการแปลงชนิดข้อมูล เราได้เห็นกฎสำหรับการแปลงเป็นตัวเลข สตริง และบูลีนของค่าปฐมภูมิ อย่างไรก็ตาม เราทิ้งช่องว่างไว้สำหรับออบเจ็กต์ ตอนนี้เรารู้เกี่ยวกับเมท็อดและสัญลักษณ์แล้ว เราสามารถเติมช่องว่างนั้นได้ - -1. ไม่มีการแปลงเป็นบูลีน ออบเจ็กต์ทั้งหมดเป็น `true` ในบริบทบูลีน เรียบง่ายแค่นั้น มีเพียงการแปลงเป็นตัวเลขและสตริงเท่านั้น -2. การแปลงเป็นตัวเลขเกิดขึ้นเมื่อเราลบออบเจ็กต์หรือใช้ฟังก์ชันทางคณิตศาสตร์ ตัวอย่างเช่น ออบเจ็กต์ `Date` สามารถนำมาลบกันได้ และผลลัพธ์ของ `date1 - date2` คือผลต่างของเวลาระหว่างสองวันที่ -3. การแปลงเป็นสตริงมักจะเกิดขึ้นเมื่อเราแสดงผลออบเจ็กต์ด้วย `alert(obj)` และในบริบทที่คล้ายกัน - -เราสามารถใช้เมท็อดพิเศษของออบเจ็กต์เพื่อดำเนินการแปลงเป็นสตริงและตัวเลขด้วยตัวเองได้ - -ตอนนี้ มาดำดิ่งลงไปในรายละเอียดทางเทคนิคเพื่อครอบคลุมหัวข้อนี้อย่างลึกซึ้งกัน - -## คำใบ้ (Hints) - -JavaScript ตัดสินใจอย่างไรว่าจะใช้การแปลงแบบใด? - -มีสามรูปแบบของการแปลงชนิดข้อมูลที่เกิดขึ้นในสถานการณ์ต่างๆ พวกมันถูกเรียกว่า "คำใบ้" (hints) ตามที่ระบุในข้อกำหนด: +ใน JavaScript มีวิธีแปลงออบเจ็กต์เป็นค่าปฐมภูมิสามแบบที่เรียกว่า "คำใบ้" (hint) ซึ่งเกิดขึ้นในสถานการณ์ต่างๆ ดังนี้: `"string"` -: สำหรับการแปลงออบเจ็กต์เป็นสตริง เมื่อเรากำลังทำการดำเนินการกับออบเจ็กต์ที่คาดหวังสตริง เช่น `alert`: - - ```js - // แสดงผล - alert(obj); - - // ใช้ออบเจ็กต์เป็นคีย์ของคุณสมบัติ - anotherObj[obj] = 123; - ``` +: สำหรับการแปลงออบเจ็กต์เป็นสตริง เมื่อต้องการนำออบเจ็กต์ไปใช้ในบริบทที่คาดหวังสตริง เช่น `alert(obj)` `"number"` -: สำหรับการแปลงออบเจ็กต์เป็นตัวเลข เช่นเมื่อเรากำลังคำนวณทางคณิตศาสตร์: - - ```js - // แปลงโดยชัดแจ้ง - let num = Number(obj); - - // คณิตศาสตร์ (ยกเว้นบวกเลขฐานสอง) - let n = +obj; // บวกเอกภาคี - let delta = date1 - date2; - - // การเปรียบเทียบน้อยกว่า/มากกว่า - let greater = user1 > user2; - ``` - - ฟังก์ชันทางคณิตศาสตร์ในตัวส่วนใหญ่ก็รวมการแปลงดังกล่าวด้วย +: สำหรับการแปลงออบเจ็กต์เป็นตัวเลข เช่น การคำนวณทางคณิตศาสตร์ `let num = Number(obj)`, `let n = +obj`, `let delta = date1 - date2`, `let greater = user1 > user2` เป็นต้น ฟังก์ชันคณิตศาสตร์ส่วนใหญ่ก็ใช้การแปลงนี้ด้วย `"default"` -: เกิดขึ้นในกรณีที่พบน้อย เมื่อตัวดำเนินการ "ไม่แน่ใจ" ว่าคาดหวังข้อมูลชนิดใด - - ตัวอย่างเช่น บวกเลขฐานสอง `+` สามารถทำงานได้ทั้งกับสตริง (เชื่อมต่อพวกมัน) และตัวเลข (บวกพวกมัน) ดังนั้นถ้าการบวกเลขฐานสองได้รับออบเจ็กต์เป็นอาร์กิวเมนต์ มันจะใช้คำใบ้ `"default"` เพื่อแปลงออบเจ็กต์นั้น - - นอกจากนี้ ถ้าออบเจ็กต์ถูกเปรียบเทียบด้วย `==` กับสตริง ตัวเลข หรือสัญลักษณ์ มันจะไม่ชัดเจนว่าควรทำการแปลงแบบใด ดังนั้นคำใบ้ `"default"` จะถูกใช้ - - ```js - // การบวกเลขฐานสองใช้คำใบ้ "default" - let total = obj1 + obj2; - - // obj == number ใช้คำใบ้ "default" - if (user == 1) { ... }; - ``` +: เกิดขึ้นน้อยครั้ง เมื่อตัวดำเนินการ "ไม่แน่ใจ" ว่าคาดหวังข้อมูลชนิดใด เช่น การบวกเลขฐานสอง `+` ที่รับทั้งสตริงและตัวเลขได้ จึงใช้คำใบ้นี้เมื่อได้รับออบเจ็กต์ นอกจากนี้การเปรียบเทียบ `==` กับสตริง ตัวเลข หรือสัญลักษณ์ ก็ใช้คำใบ้ `"default"` เช่นกัน - ตัวดำเนินการเปรียบเทียบน้อยกว่าและมากกว่า เช่น `<` `>` ก็สามารถทำงานได้ทั้งกับสตริงและตัวเลขเช่นกัน แต่พวกมันใช้คำใบ้ `"number"` ไม่ใช่ `"default"` ด้วยเหตุผลทางประวัติศาสตร์ +ในทางปฏิบัติ สิ่งต่างๆ จะเรียบง่ายกว่านี้เล็กน้อย ออบเจ็กต์ส่วนใหญ่ยกเว้น Date จะใช้การแปลง `"default"` เหมือนกับ `"number"` และเราก็ควรทำแบบเดียวกัน แต่ก็ยังจำเป็นต้องรู้จักคำใบ้ทั้งสามแบบ เพราะเดี๋ยวเราจะเห็นว่ามันมีประโยชน์ยังไง -ในทางปฏิบัติ สิ่งต่างๆ เรียบง่ายกว่านั้นเล็กน้อย +**เมื่อต้องการแปลงออบเจ็กต์ JavaScript จะพยายามหาและเรียกเมท็อดสามแบบตามลำดับ ได้แก่:** -ออบเจ็กต์ในตัวทั้งหมด ยกเว้นหนึ่งกรณี (ออบเจ็กต์ `Date`) จะใช้การแปลง `"default"` แบบเดียวกับ `"number"` และเราก็ควรทำแบบเดียวกันนั้น - -แต่ก็ยังสำคัญที่จะต้องรู้เกี่ยวกับคำใบ้ทั้งสามแบบและเหตุผลที่พวกมันมีอยู่ - -**เพื่อดำเนินการแปลง JavaScript พยายามค้นหาและเรียกเมท็อดสามอย่างของออบเจ็กต์:** - -1. เรียก `obj[Symbol.toPrimitive](hint)` - เมท็อดที่มีคีย์สัญลักษณ์ `Symbol.toPrimitive` (สัญลักษณ์ของระบบ) ถ้ามีเมท็อดดังกล่าวอยู่ -2. มิฉะนั้น ถ้าคำใบ้เป็น `"string"`: - - ลองเรียก `obj.toString()` หรือ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ -3. มิฉะนั้น ถ้าคำใบ้เป็น `"number"` หรือ `"default"`: - - ลองเรียก `obj.valueOf()` หรือ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ +1. เรียก `obj[Symbol.toPrimitive](hint)` หากกำหนดเมท็อดดังกล่าวไว้ โดย `Symbol.toPrimitive` เป็นสัญลักษณ์ของระบบ (system symbol) +2. หากคำใบ้เป็น `"string"` จะลองเรียก `obj.toString()` หรือ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ +3. หากคำใบ้เป็น `"number"` หรือ `"default"` จะลองเรียก `obj.valueOf()` หรือ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ ## Symbol.toPrimitive -มาเริ่มต้นด้วยเมท็อดแรกกัน มีสัญลักษณ์ในตัวที่เรียกว่า `Symbol.toPrimitive` ซึ่งควรใช้เป็นชื่อเมท็อดการแปลง แบบนี้: +วิธีการแรกคือการใช้สัญลักษณ์ในตัว `Symbol.toPrimitive` เป็นชื่อเมท็อดสำหรับการแปลงออบเจ็กต์ ดังนี้: ```js obj[Symbol.toPrimitive] = function(hint) { - // โค้ดเพื่อแปลงออบเจ็กต์นี้เป็นค่าปฐมภูมิ - // ต้องคืนค่าปฐมภูมิ - // hint = "string", "number", หรือ "default" อย่างใดอย่างหนึ่ง + // โค้ดสำหรับแปลงออบเจ็กต์นี้เป็นค่าปฐมภูมิ + // ต้องคืนค่าเป็นค่าปฐมภูมิ + // hint เป็นหนึ่งใน "string", "number", "default" }; ``` -ถ้าเมท็อด `Symbol.toPrimitive` มีอยู่ มันจะถูกใช้สำหรับทุกคำใบ้ และไม่จำเป็นต้องใช้เมท็อดอื่นๆ อีก +หากมีเมท็อด `Symbol.toPrimitive` มันจะถูกใช้งานสำหรับทุกคำใบ้ และไม่จำเป็นต้องใช้เมท็อดอื่นอีก -ตัวอย่างเช่น ที่นี่ออบเจ็กต์ `user` ใช้เมท็อดนี้: +ตัวอย่างเช่น ที่นี่ `user` ใช้เมท็อดนี้: ```js let user = { @@ -129,25 +71,25 @@ alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` -อย่างที่เราเห็นจากโค้ด `user` จะกลายเป็นสตริงที่อธิบายตัวเองหรือจำนวนเงิน ขึ้นอยู่กับการแปลง เมท็อดเดียวคือ `user[Symbol.toPrimitive]` จัดการกับทุกกรณีการแปลง +จะเห็นได้ว่า `user` จะกลายเป็นสตริงหรือตัวเลขขึ้นอยู่กับการแปลง โดยเมท็อดเดียว `user[Symbol.toPrimitive]` จัดการทุกกรณีเลย -## toString/valueOf +## toString / valueOf -ถ้าไม่มี `Symbol.toPrimitive` JavaScript จะพยายามหาเมท็อด `toString` และ `valueOf`: +หากไม่มี `Symbol.toPrimitive` JavaScript จะมองหาเมท็อด `toString` และ `valueOf` แทน: -- สำหรับคำใบ้ `"string"`: เรียก `toString` ก่อน และถ้ามันไม่มีหรือคืนค่าออบเจ็กต์แทนค่าปฐมภูมิ ก็จะเรียก `valueOf` (ดังนั้น `toString` จะมีความสำคัญมากกว่าสำหรับการแปลงเป็นสตริง) -- สำหรับคำใบ้อื่นๆ: เรียก `valueOf` ก่อน และถ้ามันไม่มีหรือคืนค่าออบเจ็กต์แทนค่าปฐมภูมิ ก็จะเรียก `toString` (ดังนั้น `valueOf` จะมีความสำคัญมากกว่าสำหรับการคำนวณทางคณิตศาสตร์) +- สำหรับคำใบ้ `"string"` จะเรียก `toString` ก่อน หากไม่มีหรือคืนค่าเป็นออบเจ็กต์ก็จะไปเรียก `valueOf` แทน (เพราะฉะนั้น `toString` จะสำคัญกว่าสำหรับการแปลงเป็นสตริง) +- สำหรับคำใบ้ `"number"` หรือ `"default"` จะเรียก `valueOf` ก่อน หากไม่มีหรือคืนค่าเป็นออบเจ็กต์ก็จะไปเรียก `toString` แทน (เพราะฉะนั้น `valueOf` จะสำคัญกว่าสำหรับการคำนวณ) -เมท็อด `toString` และ `valueOf` มาจากสมัยโบราณ พวกมันไม่ใช่สัญลักษณ์ (สัญลักษณ์ยังไม่มีในอดีต) แต่เป็นเมท็อดชื่อสตริง "ปกติ" พวกมันให้วิธีการ "แบบเก่า" อีกแบบในการใช้การแปลง +เมท็อด `toString` และ `valueOf` มาจากสมัยเก่า ยังไม่มีแนวคิดของสัญลักษณ์ในตอนนั้น มันเป็นแค่เมท็อดที่ชื่อเป็นสตริงธรรมดา ที่ให้วิธีการแปลงแบบ "โบราณ" -เมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิเพื่อให้ทำงาน (ถ้ามีการกำหนด) +โดยเมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิ หาก `toString` หรือ `valueOf` คืนค่าเป็นออบเจ็กต์ มันจะถูกเพิกเฉยไปเลย (เหมือนกับว่าไม่ได้กำหนดเมท็อดนั้นไว้) -โดยค่าเริ่มต้น ออบเจ็กต์ธรรมดาจะมีเมท็อด `toString` และ `valueOf` ดังนี้: +ออบเจ็กต์ธรรมดาจะมีเมท็อด `toString` และ `valueOf` ดีฟอลต์ดังนี้: - `toString` คืนสตริง `"[object Object]"` - `valueOf` คืนออบเจ็กต์ตัวมันเอง -นี่คือตัวอย่าง: +ตัวอย่าง: ```js let user = {name: "John"}; @@ -156,25 +98,25 @@ alert(user); // [object Object] alert(user.valueOf() === user); // true ``` -ดังนั้นถ้าเราพยายามใช้ออบเจ็กต์เป็นสตริง เช่นใน `alert` หรืออะไรทำนองนั้น โดยค่าเริ่มต้นเราจะเห็น `[object Object]` +ดังนั้น หากเราพยายามใช้ออบเจ็กต์ในที่ๆ ต้องการสตริง อย่างเช่นใน `alert` โดยปกติมันจะแสดง `[object Object]` -`valueOf` เริ่มต้นถูกกล่าวถึงที่นี่เพื่อความครบถ้วนเท่านั้น เพื่อหลีกเลี่ยงความสับสน อย่างที่คุณเห็น มันคืนออบเจ็กต์ตัวมันเอง และดังนั้นจึงถูกเพิกเฉย อย่าถามฉันว่าทำไม -- นั่นเป็นเหตุผลทางประวัติศาสตร์ เราสามารถสมมติว่ามันไม่มีอยู่ก็ได้ +ส่วน `valueOf` ที่คืนออบเจ็กต์ตัวมันเองนั้น ไม่ค่อยมีประโยชน์ มันถูกพูดถึงที่นี่เพื่อความครบถ้วนเท่านั้น ซึ่งมันเป็นแบบนี้มาตั้งแต่แรกด้วยเหตุผลบางอย่างในอดีต สามารถถือได้ว่ามันไม่มีอยู่จริงๆ ก็ได้ -มาเพิ่มเมท็อดเหล่านี้เพื่อกำหนดการแปลงเองกัน +มาลองใช้เมท็อดเหล่านี้กำหนดวิธีการแปลงแบบของเรากัน -ตัวอย่างเช่น ที่นี่ `user` ทำแบบเดียวกับด้านบนโดยใช้ `toString` และ `valueOf` ร่วมกันแทน `Symbol.toPrimitive`: +ตัวอย่างเช่น ที่นี่ `user` ทำงานแบบเดียวกับตัวอย่างก่อนหน้า แต่ใช้ `toString` กับ `valueOf` แทน `Symbol.toPrimitive`: ```js let user = { name: "John", money: 1000, - // สำหรับคำใบ้ "string" + // สำหรับ hint "string" toString() { return `{name: "${this.name}"}`; }, - // สำหรับคำใบ้ "number" หรือ "default" + // สำหรับ hint "number" หรือ "default" valueOf() { return this.money; } @@ -182,95 +124,4 @@ let user = { alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 -alert(user + 500); // valueOf -> 1500 -``` - -อย่างที่เราเห็น พฤติกรรมจะเหมือนกับตัวอย่างก่อนหน้าที่ใช้ `Symbol.toPrimitive` - -บ่อยครั้งที่เราต้องการจุดเดียวที่ "รวมทุกอย่าง" เพื่อจัดการกับการแปลงเป็นค่าปฐมภูมิทั้งหมด ในกรณีนี้ เราสามารถใช้แค่ `toString` อย่างเดียว แบบนี้: - -```js -let user = { - name: "John", - - toString() { - return this.name; - } -}; - -alert(user); // toString -> John -alert(user + 500); // toString -> John500 -``` - -เมื่อไม่มี `Symbol.toPrimitive` และ `valueOf`, `toString` จะจัดการการแปลงเป็นค่าปฐมภูมิทั้งหมด - -### การแปลงสามารถคืนค่าปฐมภูมิชนิดใดก็ได้ - -สิ่งสำคัญที่ควรรู้เกี่ยวกับเมท็อดการแปลงเป็นค่าปฐมภูมิทั้งหมดคือ พวกมันไม่จำเป็นต้องคืนค่าปฐมภูมิที่ "ตรงตามคำใบ้" - -ไม่มีการควบคุมว่า `toString` จะคืนสตริงหรือไม่ หรือว่าเมท็อด `Symbol.toPrimitive` จะคืนตัวเลขสำหรับคำใบ้ `"number"` หรือไม่ - -สิ่งเดียวที่บังคับคือ เมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิ ไม่ใช่ออบเจ็กต์ - -```smart header="หมายเหตุทางประวัติศาสตร์" -ด้วยเหตุผลทางประวัติศาสตร์ ถ้า `toString` หรือ `valueOf` คืนออบเจ็กต์ จะไม่เกิด error แต่ค่าดังกล่าวจะถูกเพิกเฉย (เหมือนกับว่าเมท็อดไม่มีอยู่) นั่นเป็นเพราะในสมัยโบราณ ไม่มีแนวคิด "error" ที่ดีใน JavaScript - -ในทางตรงกันข้าม `Symbol.toPrimitive` เข้มงวดกว่า มัน *ต้อง* คืนค่าปฐมภูมิ มิฉะนั้นจะเกิด error -``` - -## การแปลงเพิ่มเติม - -ตามที่เรารู้แล้ว ตัวดำเนินการและฟังก์ชันหลายตัวทำการแปลงชนิดข้อมูล เช่น คูณ `*` จะแปลงตัวถูกดำเนินการเป็นตัวเลข - -ถ้าเราส่งออบเจ็กต์เป็นอาร์กิวเมนต์ จะมีสองขั้นตอนในการคำนวณ: -1. ออบเจ็กต์ถูกแปลงเป็นค่าปฐมภูมิ (โดยใช้กฎที่อธิบายไว้ด้านบน) -2. หากจำเป็นสำหรับการคำนวณต่อไป ค่าปฐมภูมิที่ได้จะถูกแปลงอีกด้วย - -ตัวอย่างเช่น: - -```js -let obj = { - // toString จัดการการแปลงทั้งหมดเมื่อไม่มีเมท็อดอื่น - toString() { - return "2"; - } -}; - -alert(obj * 2); // 4, ออบเจ็กต์ถูกแปลงเป็นค่าปฐมภูมิ "2" จากนั้นการคูณทำให้มันเป็นตัวเลข -``` - -1. การคูณ `obj * 2` แปลงออบเจ็กต์เป็นค่าปฐมภูมิก่อน (ซึ่งเป็นสตริง `"2"`) -2. จากนั้น `"2" * 2` กลายเป็น `2 * 2` (สตริงถูกแปลงเป็นตัวเลข) - -การบวกเลขฐานสองจะเชื่อมต่อสตริงในสถานการณ์เดียวกัน เพราะมันยอมรับสตริงด้วยความยินดี: - -```js -let obj = { - toString() { - return "2"; - } -}; - -alert(obj + 2); // 22 ("2" + 2), การแปลงเป็นค่าปฐมภูมิคืนสตริง => เชื่อมต่อ -``` - -## สรุป - -การแปลงออบเจ็กต์เป็นค่าปฐมภูมิจะถูกเรียกโดยอัตโนมัติโดยฟังก์ชันและตัวดำเนินการในตัวหลายตัวที่คาดหวังค่าปฐมภูมิ - -มีสามชนิด (คำใบ้) ของการแปลง: -- `"string"` (สำหรับ `alert` และการดำเนินการอื่นๆ ที่ต้องการสตริง) -- `"number"` (สำหรับการคำนวณทางคณิตศาสตร์) -- `"default"` (สำหรับตัวดำเนินการไม่กี่ตัว ปกติออบเจ็กต์จะใช้มันแบบเดียวกับ `"number"`) - -ข้อกำหนดระบุอย่างชัดเจนว่าตัวดำเนินการใดใช้คำใบ้ใด - -อัลกอริทึมการแปลงคือ: - -1. เรียก `obj[Symbol.toPrimitive](hint)` ถ้าเมท็อดมีอยู่ -2. มิฉะนั้น ถ้าคำใบ้เป็น `"string"`: - - ลองเรียก `obj.toString()` หรือ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ -3. มิฉะนั้น ถ้าคำใบ้เป็น `"number"` หรือ `"default"`: - - ลองเรียก `obj.valueOf()` หรือ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ - -ในทางปฏิบัติ มักจะเพียงพอที่จะใช้แค่ `obj.toString()` เป็นเมท็อด "รวมทุกอย่าง" สำหรับการแปลงเป็นสตริง ซึ่งควรคืนตัวแทนของออบเจ็กต์ที่ "อ่านได้โดยมนุษย์" เพื่อวัตถุประสงค์ในการบันทึกหรือการดีบั๊ก \ No newline at end of file +alert(user + 500); // valueOf -> 1500 \ No newline at end of file From e0d054a7732d25abde7e1b3cad51420d51b27cca Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:34:25 +0700 Subject: [PATCH 3/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index 0a07778bb..e5c0ba27a 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -124,4 +124,95 @@ let user = { alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 -alert(user + 500); // valueOf -> 1500 \ No newline at end of file +alert(user + 500); // valueOf -> 1500 +``` + +บ่อยครั้งที่เราต้องการจุดรวมศูนย์เดียวในการจัดการการแปลงออบเจ็กต์เป็นค่าปฐมภูมิทั้งหมด ในกรณีนี้ เรามักจะใช้แค่ `toString` อย่างเดียว เพื่อให้มันเป็นตัวจัดการหลัก ดังตัวอย่าง: + +```js +let user = { + name: "John", + + toString() { + return this.name; + } +}; + +alert(user); // toString -> John +alert(user + 500); // toString -> John500 +``` + +เมื่อไม่มี `Symbol.toPrimitive` และ `valueOf` เมท็อด `toString` จะจัดการการแปลงออบเจ็กต์เป็นค่าปฐมภูมิทั้งหมดเอง + +### การแปลงสามารถคืนค่าปฐมภูมิชนิดใดก็ได้ + +สิ่งสำคัญที่ต้องรู้เกี่ยวกับเมท็อดแปลงออบเจ็กต์ทั้งหมดก็คือ พวกมันไม่จำเป็นต้องคืนค่าปฐมภูมิตรงตามคำใบ้เสมอไป + +ไม่มีข้อบังคับว่า `toString` ต้องคืนสตริง หรือ `Symbol.toPrimitive` ต้องคืนตัวเลขเมื่อได้รับคำใบ้ `"number"` + +สิ่งเดียวที่ต้องทำคือ เมท็อดเหล่านั้นต้องคืนค่าปฐมภูมิ ไม่ใช่ออบเจ็กต์ + +```smart header="หมายเหตุทางประวัติศาสตร์" +ในอดีต หาก `toString` หรือ `valueOf` คืนออบเจ็กต์ ก็จะไม่เกิด error แต่ค่านั้นจะถูกเพิกเฉยไป (เหมือนกับว่าไม่มีเมท็อดนั้นอยู่) นั่นเป็นเพราะไม่มีแนวคิดเรื่อง "error" ที่ดีใน JavaScript เมื่อก่อน + +ในทางตรงข้าม `Symbol.toPrimitive` เข้มงวดกว่า มันจะต้องคืนค่าปฐมภูมิเท่านั้น ไม่งั้นจะเกิด error +``` + +## การแปลงเพิ่มเติม + +อย่างที่เราได้เรียนรู้ไปแล้ว ตัวดำเนินการและฟังก์ชันหลายตัวจะทำการแปลงชนิดข้อมูล เช่น การคูณ `*` จะแปลงตัวถูกดำเนินการเป็นตัวเลข + +เมื่อเราส่งออบเจ็กต์เป็นอาร์กิวเมนต์ จะมีสองขั้นตอนในการคำนวณ: +1. ออบเจ็กต์จะถูกแปลงเป็นค่าปฐมภูมิ (ตามกฎที่อธิบายไปข้างต้น) +2. หากจำเป็นต่อการคำนวณต่อไป ค่าปฐมภูมิที่ได้ก็จะถูกแปลงอีกด้วย + +ตัวอย่างเช่น: + +```js +let obj = { + // toString จัดการการแปลงทั้งหมดเมื่อไม่มีเมท็อดอื่น + toString() { + return "2"; + } +}; + +alert(obj * 2); // 4, ออบเจ็กต์ถูกแปลงเป็น "2", แล้วจึงเกิดการคูณซึ่งแปลงเป็นตัวเลข +``` + +1. การคูณ `obj * 2` ในตอนแรกจะแปลง `obj` เป็นค่าปฐมภูมิ ซึ่งจะได้เป็นสตริง `"2"` (จาก `toString`) +2. หลังจากนั้น `"2" * 2` จะกลายเป็น `2 * 2` (สตริงถูกแปลงเป็นตัวเลข) + +ส่วนการบวกด้วย `+` นั้น หากเป็นสตริงก็จะทำการต่อสตริงแทน ดังตัวอย่าง: + +```js +let obj = { + toString() { + return "2"; + } +}; + +alert(obj + 2); // "22" (แปลงเป็นสตริงจึงเกิดการต่อสตริง) +``` + +## สรุป + +การแปลงออบเจ็กต์เป็นค่าปฐมภูมิจะถูกเรียกใช้โดยอัตโนมัติโดยฟังก์ชันและตัวดำเนินการหลายตัวที่คาดหวังให้ข้อมูลเป็นค่าปฐมภูมิ + +มีสามรูปแบบ (หรือเรียกว่าคำใบ้) ของการแปลง ได้แก่: +- `"string"` (สำหรับ `alert` และการดำเนินการอื่นๆ ที่ต้องการสตริง) +- `"number"` (สำหรับการคำนวณทางคณิตศาสตร์) +- `"default"` (ตัวดำเนินการบางตัวเท่านั้น ปกติออบเจ็กต์จะใช้แบบเดียวกับ `"number"`) + +ซึ่งจะระบุไว้อย่างชัดเจนในข้อกำหนดว่า ตัวดำเนินการแต่ละตัวใช้คำใบ้แบบใด + +ขั้นตอนการแปลงมีดังนี้: + +1. เรียก `obj[Symbol.toPrimitive](hint)` หากกำหนดเมท็อดนี้ไว้ +2. ไม่งั้น หากคำใบ้เป็น `"string"`: + - ลองเรียก `obj.toString()` หรือ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ +3. ไม่งั้น หากคำใบ้เป็น `"number"` หรือ `"default"`: + - ลองเรียก `obj.valueOf()` หรือ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ + +ทุกเมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิ หากถูกกำหนดไว้ + +ในทางปฏิบัติ มักจะเพียงพอที่จะใช้แค่ `obj.toString()` เป็นเมท็อดหลักในการแปลงออบเจ็กต์เป็นสตริง เพื่ออธิบายข้อมูลของออบเจ็กต์ในรูปแบบที่มนุษย์อ่านง่าย ซึ่งจะใช้สำหรับการบันทึก (log) หรือการดีบั๊ก \ No newline at end of file From e76277353083858e1b02ab782d8086dfa70e290d Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:42:51 +0700 Subject: [PATCH 4/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 218 +----------------- 1 file changed, 9 insertions(+), 209 deletions(-) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index e5c0ba27a..9794321e6 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -1,218 +1,18 @@ # การแปลงออบเจ็กต์เป็นค่าปฐมภูมิ -เกิดอะไรขึ้นเมื่อนำออบเจ็กต์มาบวกกัน (`obj1 + obj2`), ลบกัน (`obj1 - obj2`) หรือแสดงผลด้วย `alert(obj)`? +เกิดอะไรขึ้นเมื่อเรานำออบเจ็กต์ไปบวก (`obj1 + obj2`) ลบ (`obj1 - obj2`) หรือแสดงผลด้วย `alert(obj)`? -JavaScript ไม่อนุญาตให้กำหนดวิธีการทำงานของตัวดำเนินการบนออบเจ็กต์เอง แตกต่างจากบางภาษาโปรแกรม เช่น Ruby หรือ C++ ที่เราสามารถใช้เมท็อดพิเศษของออบเจ็กต์เพื่อจัดการการดำเนินการต่างๆ ได้ +ใน JavaScript เราไม่สามารถกำหนดพฤติกรรมของตัวดำเนินการเมื่อใช้กับออบเจ็กต์ได้เอง แตกต่างจากบางภาษาเช่น Ruby หรือ C++ ที่สามารถใช้เมท็อดพิเศษเพื่อควบคุมการทำงานของตัวดำเนินการได้ -ในกรณีเหล่านี้ ออบเจ็กต์จะถูกแปลงเป็นค่าปฐมภูมิโดยอัตโนมัติก่อน จากนั้นการดำเนินการจะเกิดขึ้นบนค่าปฐมภูมิ และส่งผลเป็นค่าปฐมภูมิเช่นกัน +ในกรณีเหล่านี้ ออบเจ็กต์จะถูกแปลงเป็นค่าปฐมภูมิ (primitive value) โดยอัตโนมัติก่อน จากนั้นการดำเนินการจะเกิดขึ้นกับค่าปฐมภูมินั้น และส่งผลลัพธ์กลับมาเป็นค่าปฐมภูมิเช่นกัน -นี่ถือเป็นข้อจำกัดสำคัญ: ผลลัพธ์ของ `obj1 + obj2` (หรือการดำเนินการทางคณิตศาสตร์อื่นๆ) ไม่สามารถเป็นออบเจ็กต์ได้! +นี่เป็นข้อจำกัดที่สำคัญ: ผลลัพธ์ของ `obj1 + obj2` (หรือการดำเนินการทางคณิตศาสตร์อื่นๆ) ไม่สามารถเป็นออบเจ็กต์ได้ -เราจึงไม่สามารถสร้างออบเจ็กต์ที่แทนเวกเตอร์หรือเมทริกซ์ แล้วบวกมันเข้าด้วยกันโดยหวังให้ได้ผลลัพธ์เป็นออบเจ็กต์ที่ "รวม" กัน ข้อจำกัดนี้ทำให้ไม่อาจเขียนโค้ดในลักษณะนั้นได้เลย +ดังนั้น เราจึงไม่สามารถนิยามออบเจ็กต์ที่แทนเวกเตอร์หรือเมทริกซ์ แล้วคาดหวังให้การบวกออบเจ็กต์เหล่านั้นเข้าด้วยกัน ทำให้ได้เวกเตอร์หรือเมทริกซ์ใหม่ที่ "รวม" ค่าเข้าด้วยกัน ข้อจำกัดนี้ทำให้ไม่สามารถเขียนโค้ดในลักษณะนั้นได้เลย -ด้วยเหตุนี้ในโปรเจ็กต์จริงจึงไม่ค่อยมีการใช้งานการคำนวณทางคณิตศาสตร์กับออบเจ็กต์ หากมันเกิดขึ้น ก็มักจะเป็นเพราะข้อผิดพลาดในการเขียนโค้ดเสียมากกว่า +ด้วยเหตุนี้ การทำงานทางคณิตศาสตร์โดยใช้ออบเจ็กต์จึงมีใช้น้อยมากในโปรเจ็กต์จริง และถ้าเกิดขึ้นมักจะเป็นเพราะข้อผิดพลาดในการเขียนโค้ดมากกว่า -ในบทความนี้ เราจะกล่าวถึงวิธีที่ออบเจ็กต์ถูกแปลงเป็นค่าปฐมภูมิ และวิธีกำหนดการแปลงด้วยตัวเอง โดยมีจุดประสงค์สองประการคือ: +ในบทความนี้ เราจะเรียนรู้กระบวนการแปลงออบเจ็กต์เป็นค่าปฐมภูมิ และวิธีควบคุมกระบวนการนั้นด้วยตัวเอง โดยมีจุดประสงค์ 2 ประการ: -1. เพื่อให้เข้าใจสิ่งที่เกิดขึ้นเมื่อมีข้อผิดพลาดในการเขียนโค้ด ซึ่งทำให้เกิดการแปลงโดยไม่ได้ตั้งใจ -2. เพื่อรู้จักข้อยกเว้นที่การแปลงดังกล่าวสามารถทำได้และให้ผลลัพธ์ที่ถูกต้อง เช่น การลบหรือเปรียบเทียบวันที่ (Date) - -## กฎการแปลง - -ใน JavaScript มีวิธีแปลงออบเจ็กต์เป็นค่าปฐมภูมิสามแบบที่เรียกว่า "คำใบ้" (hint) ซึ่งเกิดขึ้นในสถานการณ์ต่างๆ ดังนี้: - -`"string"` -: สำหรับการแปลงออบเจ็กต์เป็นสตริง เมื่อต้องการนำออบเจ็กต์ไปใช้ในบริบทที่คาดหวังสตริง เช่น `alert(obj)` - -`"number"` -: สำหรับการแปลงออบเจ็กต์เป็นตัวเลข เช่น การคำนวณทางคณิตศาสตร์ `let num = Number(obj)`, `let n = +obj`, `let delta = date1 - date2`, `let greater = user1 > user2` เป็นต้น ฟังก์ชันคณิตศาสตร์ส่วนใหญ่ก็ใช้การแปลงนี้ด้วย - -`"default"` -: เกิดขึ้นน้อยครั้ง เมื่อตัวดำเนินการ "ไม่แน่ใจ" ว่าคาดหวังข้อมูลชนิดใด เช่น การบวกเลขฐานสอง `+` ที่รับทั้งสตริงและตัวเลขได้ จึงใช้คำใบ้นี้เมื่อได้รับออบเจ็กต์ นอกจากนี้การเปรียบเทียบ `==` กับสตริง ตัวเลข หรือสัญลักษณ์ ก็ใช้คำใบ้ `"default"` เช่นกัน - -ในทางปฏิบัติ สิ่งต่างๆ จะเรียบง่ายกว่านี้เล็กน้อย ออบเจ็กต์ส่วนใหญ่ยกเว้น Date จะใช้การแปลง `"default"` เหมือนกับ `"number"` และเราก็ควรทำแบบเดียวกัน แต่ก็ยังจำเป็นต้องรู้จักคำใบ้ทั้งสามแบบ เพราะเดี๋ยวเราจะเห็นว่ามันมีประโยชน์ยังไง - -**เมื่อต้องการแปลงออบเจ็กต์ JavaScript จะพยายามหาและเรียกเมท็อดสามแบบตามลำดับ ได้แก่:** - -1. เรียก `obj[Symbol.toPrimitive](hint)` หากกำหนดเมท็อดดังกล่าวไว้ โดย `Symbol.toPrimitive` เป็นสัญลักษณ์ของระบบ (system symbol) -2. หากคำใบ้เป็น `"string"` จะลองเรียก `obj.toString()` หรือ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ -3. หากคำใบ้เป็น `"number"` หรือ `"default"` จะลองเรียก `obj.valueOf()` หรือ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ - -## Symbol.toPrimitive - -วิธีการแรกคือการใช้สัญลักษณ์ในตัว `Symbol.toPrimitive` เป็นชื่อเมท็อดสำหรับการแปลงออบเจ็กต์ ดังนี้: - -```js -obj[Symbol.toPrimitive] = function(hint) { - // โค้ดสำหรับแปลงออบเจ็กต์นี้เป็นค่าปฐมภูมิ - // ต้องคืนค่าเป็นค่าปฐมภูมิ - // hint เป็นหนึ่งใน "string", "number", "default" -}; -``` - -หากมีเมท็อด `Symbol.toPrimitive` มันจะถูกใช้งานสำหรับทุกคำใบ้ และไม่จำเป็นต้องใช้เมท็อดอื่นอีก - -ตัวอย่างเช่น ที่นี่ `user` ใช้เมท็อดนี้: - -```js -let user = { - name: "John", - money: 1000, - - [Symbol.toPrimitive](hint) { - alert(`hint: ${hint}`); - return hint == "string" ? `{name: "${this.name}"}` : this.money; - } -}; - -// ตัวอย่างการแปลง: -alert(user); // hint: string -> {name: "John"} -alert(+user); // hint: number -> 1000 -alert(user + 500); // hint: default -> 1500 -``` - -จะเห็นได้ว่า `user` จะกลายเป็นสตริงหรือตัวเลขขึ้นอยู่กับการแปลง โดยเมท็อดเดียว `user[Symbol.toPrimitive]` จัดการทุกกรณีเลย - -## toString / valueOf - -หากไม่มี `Symbol.toPrimitive` JavaScript จะมองหาเมท็อด `toString` และ `valueOf` แทน: - -- สำหรับคำใบ้ `"string"` จะเรียก `toString` ก่อน หากไม่มีหรือคืนค่าเป็นออบเจ็กต์ก็จะไปเรียก `valueOf` แทน (เพราะฉะนั้น `toString` จะสำคัญกว่าสำหรับการแปลงเป็นสตริง) -- สำหรับคำใบ้ `"number"` หรือ `"default"` จะเรียก `valueOf` ก่อน หากไม่มีหรือคืนค่าเป็นออบเจ็กต์ก็จะไปเรียก `toString` แทน (เพราะฉะนั้น `valueOf` จะสำคัญกว่าสำหรับการคำนวณ) - -เมท็อด `toString` และ `valueOf` มาจากสมัยเก่า ยังไม่มีแนวคิดของสัญลักษณ์ในตอนนั้น มันเป็นแค่เมท็อดที่ชื่อเป็นสตริงธรรมดา ที่ให้วิธีการแปลงแบบ "โบราณ" - -โดยเมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิ หาก `toString` หรือ `valueOf` คืนค่าเป็นออบเจ็กต์ มันจะถูกเพิกเฉยไปเลย (เหมือนกับว่าไม่ได้กำหนดเมท็อดนั้นไว้) - -ออบเจ็กต์ธรรมดาจะมีเมท็อด `toString` และ `valueOf` ดีฟอลต์ดังนี้: - -- `toString` คืนสตริง `"[object Object]"` -- `valueOf` คืนออบเจ็กต์ตัวมันเอง - -ตัวอย่าง: - -```js -let user = {name: "John"}; - -alert(user); // [object Object] -alert(user.valueOf() === user); // true -``` - -ดังนั้น หากเราพยายามใช้ออบเจ็กต์ในที่ๆ ต้องการสตริง อย่างเช่นใน `alert` โดยปกติมันจะแสดง `[object Object]` - -ส่วน `valueOf` ที่คืนออบเจ็กต์ตัวมันเองนั้น ไม่ค่อยมีประโยชน์ มันถูกพูดถึงที่นี่เพื่อความครบถ้วนเท่านั้น ซึ่งมันเป็นแบบนี้มาตั้งแต่แรกด้วยเหตุผลบางอย่างในอดีต สามารถถือได้ว่ามันไม่มีอยู่จริงๆ ก็ได้ - -มาลองใช้เมท็อดเหล่านี้กำหนดวิธีการแปลงแบบของเรากัน - -ตัวอย่างเช่น ที่นี่ `user` ทำงานแบบเดียวกับตัวอย่างก่อนหน้า แต่ใช้ `toString` กับ `valueOf` แทน `Symbol.toPrimitive`: - -```js -let user = { - name: "John", - money: 1000, - - // สำหรับ hint "string" - toString() { - return `{name: "${this.name}"}`; - }, - - // สำหรับ hint "number" หรือ "default" - valueOf() { - return this.money; - } -}; - -alert(user); // toString -> {name: "John"} -alert(+user); // valueOf -> 1000 -alert(user + 500); // valueOf -> 1500 -``` - -บ่อยครั้งที่เราต้องการจุดรวมศูนย์เดียวในการจัดการการแปลงออบเจ็กต์เป็นค่าปฐมภูมิทั้งหมด ในกรณีนี้ เรามักจะใช้แค่ `toString` อย่างเดียว เพื่อให้มันเป็นตัวจัดการหลัก ดังตัวอย่าง: - -```js -let user = { - name: "John", - - toString() { - return this.name; - } -}; - -alert(user); // toString -> John -alert(user + 500); // toString -> John500 -``` - -เมื่อไม่มี `Symbol.toPrimitive` และ `valueOf` เมท็อด `toString` จะจัดการการแปลงออบเจ็กต์เป็นค่าปฐมภูมิทั้งหมดเอง - -### การแปลงสามารถคืนค่าปฐมภูมิชนิดใดก็ได้ - -สิ่งสำคัญที่ต้องรู้เกี่ยวกับเมท็อดแปลงออบเจ็กต์ทั้งหมดก็คือ พวกมันไม่จำเป็นต้องคืนค่าปฐมภูมิตรงตามคำใบ้เสมอไป - -ไม่มีข้อบังคับว่า `toString` ต้องคืนสตริง หรือ `Symbol.toPrimitive` ต้องคืนตัวเลขเมื่อได้รับคำใบ้ `"number"` - -สิ่งเดียวที่ต้องทำคือ เมท็อดเหล่านั้นต้องคืนค่าปฐมภูมิ ไม่ใช่ออบเจ็กต์ - -```smart header="หมายเหตุทางประวัติศาสตร์" -ในอดีต หาก `toString` หรือ `valueOf` คืนออบเจ็กต์ ก็จะไม่เกิด error แต่ค่านั้นจะถูกเพิกเฉยไป (เหมือนกับว่าไม่มีเมท็อดนั้นอยู่) นั่นเป็นเพราะไม่มีแนวคิดเรื่อง "error" ที่ดีใน JavaScript เมื่อก่อน - -ในทางตรงข้าม `Symbol.toPrimitive` เข้มงวดกว่า มันจะต้องคืนค่าปฐมภูมิเท่านั้น ไม่งั้นจะเกิด error -``` - -## การแปลงเพิ่มเติม - -อย่างที่เราได้เรียนรู้ไปแล้ว ตัวดำเนินการและฟังก์ชันหลายตัวจะทำการแปลงชนิดข้อมูล เช่น การคูณ `*` จะแปลงตัวถูกดำเนินการเป็นตัวเลข - -เมื่อเราส่งออบเจ็กต์เป็นอาร์กิวเมนต์ จะมีสองขั้นตอนในการคำนวณ: -1. ออบเจ็กต์จะถูกแปลงเป็นค่าปฐมภูมิ (ตามกฎที่อธิบายไปข้างต้น) -2. หากจำเป็นต่อการคำนวณต่อไป ค่าปฐมภูมิที่ได้ก็จะถูกแปลงอีกด้วย - -ตัวอย่างเช่น: - -```js -let obj = { - // toString จัดการการแปลงทั้งหมดเมื่อไม่มีเมท็อดอื่น - toString() { - return "2"; - } -}; - -alert(obj * 2); // 4, ออบเจ็กต์ถูกแปลงเป็น "2", แล้วจึงเกิดการคูณซึ่งแปลงเป็นตัวเลข -``` - -1. การคูณ `obj * 2` ในตอนแรกจะแปลง `obj` เป็นค่าปฐมภูมิ ซึ่งจะได้เป็นสตริง `"2"` (จาก `toString`) -2. หลังจากนั้น `"2" * 2` จะกลายเป็น `2 * 2` (สตริงถูกแปลงเป็นตัวเลข) - -ส่วนการบวกด้วย `+` นั้น หากเป็นสตริงก็จะทำการต่อสตริงแทน ดังตัวอย่าง: - -```js -let obj = { - toString() { - return "2"; - } -}; - -alert(obj + 2); // "22" (แปลงเป็นสตริงจึงเกิดการต่อสตริง) -``` - -## สรุป - -การแปลงออบเจ็กต์เป็นค่าปฐมภูมิจะถูกเรียกใช้โดยอัตโนมัติโดยฟังก์ชันและตัวดำเนินการหลายตัวที่คาดหวังให้ข้อมูลเป็นค่าปฐมภูมิ - -มีสามรูปแบบ (หรือเรียกว่าคำใบ้) ของการแปลง ได้แก่: -- `"string"` (สำหรับ `alert` และการดำเนินการอื่นๆ ที่ต้องการสตริง) -- `"number"` (สำหรับการคำนวณทางคณิตศาสตร์) -- `"default"` (ตัวดำเนินการบางตัวเท่านั้น ปกติออบเจ็กต์จะใช้แบบเดียวกับ `"number"`) - -ซึ่งจะระบุไว้อย่างชัดเจนในข้อกำหนดว่า ตัวดำเนินการแต่ละตัวใช้คำใบ้แบบใด - -ขั้นตอนการแปลงมีดังนี้: - -1. เรียก `obj[Symbol.toPrimitive](hint)` หากกำหนดเมท็อดนี้ไว้ -2. ไม่งั้น หากคำใบ้เป็น `"string"`: - - ลองเรียก `obj.toString()` หรือ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ -3. ไม่งั้น หากคำใบ้เป็น `"number"` หรือ `"default"`: - - ลองเรียก `obj.valueOf()` หรือ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ - -ทุกเมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิ หากถูกกำหนดไว้ - -ในทางปฏิบัติ มักจะเพียงพอที่จะใช้แค่ `obj.toString()` เป็นเมท็อดหลักในการแปลงออบเจ็กต์เป็นสตริง เพื่ออธิบายข้อมูลของออบเจ็กต์ในรูปแบบที่มนุษย์อ่านง่าย ซึ่งจะใช้สำหรับการบันทึก (log) หรือการดีบั๊ก \ No newline at end of file +1. เพื่อเข้าใจสิ่งที่เกิดขึ้นเมื่อมีข้อผิดพลาดในการเขียนโค้ด ที่ทำให้เกิดการแปลงโดยไม่ตั้งใจ +2. เพื่อศึกษากรณียกเว้นที่การแปลงดังกล่าวมีประโยชน์และให้ผลลัพธ์ที่ถูกต้อง เช่น การลบหรือเปรียบเทียบวันที่ (Date) \ No newline at end of file From d22f60678879ffd258bee42c659273d7ac77d441 Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:43:15 +0700 Subject: [PATCH 5/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index 9794321e6..ded76434b 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -15,4 +15,25 @@ ในบทความนี้ เราจะเรียนรู้กระบวนการแปลงออบเจ็กต์เป็นค่าปฐมภูมิ และวิธีควบคุมกระบวนการนั้นด้วยตัวเอง โดยมีจุดประสงค์ 2 ประการ: 1. เพื่อเข้าใจสิ่งที่เกิดขึ้นเมื่อมีข้อผิดพลาดในการเขียนโค้ด ที่ทำให้เกิดการแปลงโดยไม่ตั้งใจ -2. เพื่อศึกษากรณียกเว้นที่การแปลงดังกล่าวมีประโยชน์และให้ผลลัพธ์ที่ถูกต้อง เช่น การลบหรือเปรียบเทียบวันที่ (Date) \ No newline at end of file +2. เพื่อศึกษากรณียกเว้นที่การแปลงดังกล่าวมีประโยชน์และให้ผลลัพธ์ที่ถูกต้อง เช่น การลบหรือเปรียบเทียบวันที่ (Date) + +## ขั้นตอนการแปลง + +ใน JavaScript มี 3 ประเภทของการแปลงออบเจ็กต์เป็นค่าปฐมภูมิ ซึ่งเรียกว่า "hint" (คำใบ้) ขึ้นอยู่กับสถานการณ์ที่เกิดขึ้น ดังนี้: + +`"string"` +: สำหรับการแปลงเป็น string เมื่อเราต้องการนำออบเจ็กต์ไปใช้ในบริบทที่คาดหวังค่า string เช่น `alert(obj)` + +`"number"` +: สำหรับการแปลงเป็นตัวเลข เช่น ในการคำนวณทางคณิตศาสตร์ `let num = Number(obj)`, `let n = +obj`, `let delta = date1 - date2`, `let greater = user1 > user2` เป็นต้น ฟังก์ชันคณิตศาสตร์ส่วนใหญ่ก็ใช้การแปลงแบบนี้ด้วย + +`"default"` +: เกิดขึ้นน้อยครั้ง เมื่อตัวดำเนินการ "ไม่แน่ใจ" ว่าคาดหวังข้อมูลประเภทใด เช่น ตัวดำเนินการบวกเลขฐานสอง `+` ที่ยอมรับทั้ง string และตัวเลข ดังนั้นจึงใช้ hint นี้เมื่อได้รับออบเจ็กต์มา นอกจากนี้ตัวดำเนินการเปรียบเทียบ `==` กับ string, number หรือ symbol ก็ใช้ hint `"default"` เช่นกัน + +ในทางปฏิบัติ สถานการณ์อาจง่ายกว่านี้เล็กน้อย ออบเจ็กต์ส่วนใหญ่ (ยกเว้น Date) จะใช้การแปลงแบบ `"default"` เหมือนกับ `"number"` และเราก็ควรทำเช่นนั้น แต่ก็ยังจำเป็นต้องรู้จักทั้ง 3 ประเภท เพื่อให้เข้าใจว่ามันมีประโยชน์อย่างไร + +**เมื่อต้องการแปลงออบเจ็กต์เป็นค่าปฐมภูมิ JavaScript จะพยายามค้นหาและเรียกใช้เมท็อดต่างๆ ตามลำดับดังนี้:** + +1. เรียกใช้ `obj[Symbol.toPrimitive](hint)` หากมีการกำหนดเมท็อดนี้ไว้ โดย `Symbol.toPrimitive` เป็นสัญลักษณ์ในตัวของระบบ +2. มิฉะนั้น ถ้า hint เป็น `"string"` จะลองเรียก `obj.toString()` และ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ +3. มิฉะนั้น ถ้า hint เป็น `"number"` หรือ `"default"` จะลองเรียก `obj.valueOf()` และ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ \ No newline at end of file From a07ed97103b4c13f0dd0bcd10575a5347dc80e87 Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:43:37 +0700 Subject: [PATCH 6/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index ded76434b..badb61499 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -36,4 +36,39 @@ 1. เรียกใช้ `obj[Symbol.toPrimitive](hint)` หากมีการกำหนดเมท็อดนี้ไว้ โดย `Symbol.toPrimitive` เป็นสัญลักษณ์ในตัวของระบบ 2. มิฉะนั้น ถ้า hint เป็น `"string"` จะลองเรียก `obj.toString()` และ `obj.valueOf()` อย่างใดอย่างหนึ่งที่มีอยู่ -3. มิฉะนั้น ถ้า hint เป็น `"number"` หรือ `"default"` จะลองเรียก `obj.valueOf()` และ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ \ No newline at end of file +3. มิฉะนั้น ถ้า hint เป็น `"number"` หรือ `"default"` จะลองเรียก `obj.valueOf()` และ `obj.toString()` อย่างใดอย่างหนึ่งที่มีอยู่ + +## Symbol.toPrimitive + +วิธีแรกคือใช้ symbol ในตัว `Symbol.toPrimitive` เป็นชื่อเมท็อดสำหรับแปลงออบเจ็กต์ โดยใช้ดังนี้: + +```js +obj[Symbol.toPrimitive] = function(hint) { + // โค้ดสำหรับแปลงออบเจ็กต์นี้เป็นค่าปฐมภูมิ + // ต้องคืนค่าเป็นค่าปฐมภูมิ + // hint จะเป็นหนึ่งใน "string", "number", "default" +}; +``` + +ถ้ามีเมท็อด `Symbol.toPrimitive` มันจะถูกเรียกใช้สำหรับทุก hint และไม่จำเป็นต้องใช้เมท็อดอื่นอีก + +ตัวอย่างเช่น ออบเจ็กต์ `user` ใช้เมท็อดนี้: + +```js +let user = { + name: "John", + money: 1000, + + [Symbol.toPrimitive](hint) { + alert(`hint: ${hint}`); + return hint == "string" ? `{name: "${this.name}"}` : this.money; + } +}; + +// ตัวอย่างการแปลง: +alert(user); // hint: string -> {name: "John"} +alert(+user); // hint: number -> 1000 +alert(user + 500); // hint: default -> 1500 +``` + +จะเห็นว่า `user` กลายเป็น string หรือตัวเลข ขึ้นอยู่กับรูปแบบการแปลง โดยใช้แค่เมท็อดเดียว `user[Symbol.toPrimitive]` ในการจัดการทุกกรณี \ No newline at end of file From 4392a9a5cde5d7e8df46880ab67dc02c4ba29dfb Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:47:33 +0700 Subject: [PATCH 7/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index badb61499..d740b1293 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -71,4 +71,89 @@ alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` -จะเห็นว่า `user` กลายเป็น string หรือตัวเลข ขึ้นอยู่กับรูปแบบการแปลง โดยใช้แค่เมท็อดเดียว `user[Symbol.toPrimitive]` ในการจัดการทุกกรณี \ No newline at end of file +จะเห็นว่า `user` กลายเป็น string หรือตัวเลข ขึ้นอยู่กับรูปแบบการแปลง โดยใช้แค่เมท็อดเดียว `user[Symbol.toPrimitive]` ในการจัดการทุกกรณี + +## toString/valueOf + +ถ้าไม่มี `Symbol.toPrimitive` JavaScript จะมองหาเมท็อด `toString` และ `valueOf` แทน: + +- สำหรับ hint ชนิด `"string"` จะเรียก `toString` ก่อน หากไม่มีหรือคืนค่าเป็นออบเจ็กต์ จะไปเรียก `valueOf` แทน (ดังนั้น `toString` จึงมีความสำคัญมากกว่าในการแปลงเป็น string) +- สำหรับ hint ชนิด `"number"` หรือ `"default"` จะเรียก `valueOf` ก่อน หากไม่มีหรือคืนค่าเป็นออบเจ็กต์ จะไปเรียก `toString` แทน (ดังนั้น `valueOf` จึงมีความสำคัญมากกว่าในการคำนวณ) + +เมท็อด `toString` และ `valueOf` มาจากสมัยเก่า ก่อนที่จะมีแนวคิดเรื่อง symbol เป็นเมท็อดชื่อ string ธรรมดาที่ให้วิธีการแปลงแบบ "โบราณ" + +โดยเมท็อดเหล่านี้ต้องคืนค่าปฐมภูมิ หาก `toString` หรือ `valueOf` คืนค่าเป็นออบเจ็กต์ ก็จะถูกละเลยเสมือนไม่มีการกำหนดเมท็อดนั้นไว้เลย + +ออบเจ็กต์ทั่วไปมีเมท็อด `toString` และ `valueOf` ค่าเริ่มต้นดังนี้: + +- `toString` คืนค่าเป็น string `"[object Object]"` +- `valueOf` คืนออบเจ็กต์ตัวมันเอง + +ตัวอย่างเช่น: + +```js +let user = {name: "John"}; + +alert(user); // [object Object] +alert(user.valueOf() === user); // true +``` + +ดังนั้น หากใช้ออบเจ็กต์แทนที่ที่คาดหวังค่า string เช่นใน `alert` โดยปกติแล้วจะแสดงผลเป็น `[object Object]` + +ส่วน `valueOf` ที่คืนออบเจ็กต์ตัวมันเองนั้น ไม่มากมีประโยชน์ มีการกล่าวถึงไว้ที่นี่เพื่อความครบถ้วนเท่านั้น ซึ่งเป็นแบบนี้มาตั้งแต่สมัยแรกด้วยบางเหตุผล ส่วนใหญ่แล้วมักจะถือว่ามันไม่มีอยู่ + +มาลองใช้เมท็อดเหล่านี้ในการกำหนดพฤติกรรมการแปลงเป็นแบบของเราเองกัน + +ตัวอย่างเช่น ที่นี่ `user` จะทำงานแบบเดียวกับตัวอย่างก่อนหน้า แต่ใช้ `toString` กับ `valueOf` แทน `Symbol.toPrimitive`: + +```js +let user = { + name: "John", + money: 1000, + + // สำหรับ hint "string" + toString() { + return `{name: "${this.name}"}`; + }, + + // สำหรับ hint "number" หรือ "default" + valueOf() { + return this.money; + } +}; + +alert(user); // toString -> {name: "John"} +alert(+user); // valueOf -> 1000 +alert(user + 500); // valueOf -> 1500 +``` + +โดยทั่วไปแล้ว มักต้องการจุดรวมศูนย์เดียวในการจัดการการแปลงออบเจ็กต์ให้เป็นค่าปฐมภูมิทั้งหมด ในกรณีนี้เราจะใช้แค่ `toString` อย่างเดียว ทำให้มันเป็นตัวจัดการหลักในการแปลง ดังตัวอย่าง: + +```js +let user = { + name: "John", + + toString() { + return this.name; + } +}; + +alert(user); // toString -> John +alert(user + 500); // toString -> John500 +``` + +หากไม่มี `Symbol.toPrimitive` และ `valueOf` เมท็อด `toString` ก็จะรับผิดชอบการแปลงออบเจ็กต์เป็นค่าปฐมภูมิทั้งหมดเอง + +### การแปลงสามารถคืนค่าปฐมภูมิประเภทใดก็ได้ + +สิ่งสำคัญที่ต้องทราบเกี่ยวกับเมท็อดทั้งหมดที่ใช้ในการแปลงออบเจ็กต์ คือ พวกมันไม่จำเป็นต้องคืนค่าปฐมภูมิประเภทที่ตรงกับ hint เสมอไป + +ไม่มีข้อบังคับว่า `toString` ต้องคืนค่าเป็น string เสมอ หรือ `Symbol.toPrimitive` ต้องคืนตัวเลขเมื่อ hint เป็น `"number"` + +สิ่งเดียวที่จำเป็นคือ เมท็อดเหล่านั้นต้องคืนค่าปฐมภูมิ ไม่ใช่ออบเจ็กต์ + +```smart header="หมายเหตุทางประวัติศาสตร์" +สมัยก่อน ถ้า `toString` หรือ `valueOf` คืนออบเจ็กต์ ก็จะไม่เกิด error แต่ค่านั้นจะถูกละเลย (เสมือนไม่มีการกำหนดเมท็อดนั้นไว้) นั่นเป็นเพราะไม่มีแนวคิดเรื่อง "error" ที่ดีพอใน JavaScript สมัยนั้น + +ในทางตรงข้าม `Symbol.toPrimitive` มีกฎเข้มงวดกว่า มันต้องคืนค่าปฐมภูมิเท่านั้น ไม่เช่นนั้นจะเกิด error +``` \ No newline at end of file From a8421d00f08dbc9ea82643555d573b6f4f7e505f Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:47:52 +0700 Subject: [PATCH 8/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index d740b1293..b4d9cc6e2 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -156,4 +156,65 @@ alert(user + 500); // toString -> John500 สมัยก่อน ถ้า `toString` หรือ `valueOf` คืนออบเจ็กต์ ก็จะไม่เกิด error แต่ค่านั้นจะถูกละเลย (เสมือนไม่มีการกำหนดเมท็อดนั้นไว้) นั่นเป็นเพราะไม่มีแนวคิดเรื่อง "error" ที่ดีพอใน JavaScript สมัยนั้น ในทางตรงข้าม `Symbol.toPrimitive` มีกฎเข้มงวดกว่า มันต้องคืนค่าปฐมภูมิเท่านั้น ไม่เช่นนั้นจะเกิด error -``` \ No newline at end of file +``` + +## การแปลงอื่นๆ + +อย่างที่เราได้เรียนรู้มา มีตัวดำเนินการและฟังก์ชันหลายตัวที่ดำเนินการแปลงชนิดข้อมูล เช่น การคูณ `*` จะแปลงตัวถูกดำเนินการให้เป็นตัวเลข + +หากเราส่งออบเจ็กต์เป็นอาร์กิวเมนต์ การคำนวณจะประกอบด้วย 2 ขั้นตอน: +1. ออบเจ็กต์จะถูกแปลงให้เป็นค่าปฐมภูมิ (ตามกฎที่อธิบายข้างต้น) +2. หากจำเป็นสำหรับการดำเนินการต่อไป ค่าปฐมภูมินั้นจะถูกแปลงอีกครั้ง + +ตัวอย่างเช่น: + +```js +let obj = { + // toString จัดการการแปลงทั้งหมดเมื่อไม่มีเมท็อดอื่น + toString() { + return "2"; + } +}; + +alert(obj * 2); // 4 ออบเจ็กต์ถูกแปลงเป็น "2", จากนั้น "2" จึงถูกคูณด้วย 2 +``` + +1. การคูณ `obj * 2` ขั้นแรกจะแปลง `obj` ให้เป็นค่าปฐมภูมิ ซึ่งจะเป็น string `"2"` (จาก `toString`) +2. จากนั้น `"2" * 2` จะกลายเป็น `2 * 2` (string ถูกแปลงเป็นตัวเลข) + +บวกด้วย `+` ก็มีกฎพิเศษเหมือนกัน: + +```js +let obj = { + toString() { + return "2"; + } +}; + +alert(obj + 2); // 22 ("2" + 2), การแปลงเป็น string เกิดขึ้น +``` + +การบวก `obj + 2` ในตอนแรกจะแปลง `obj` เป็นค่าปฐมภูมิ (ซึ่งเป็น string `"2"`) จากนั้นจึงเกิดการต่อ string `"2" + 2 = "22"` + +ในทางตรงข้าม การลบ `-` และตัวดำเนินการเปรียบเทียบอื่นๆ เช่น `<` `>` จะแปลงทั้งสอง operand ให้เป็นตัวเลขเสมอ: + +```js +let obj = { + toString() { + return "2"; + } +}; + +alert(obj - 2); // 0 ("2" - 2), การแปลงเป็นตัวเลขเกิดขึ้น +``` + +นี่คือตารางสรุประบุว่าตัวดำเนินการใช้การแปลงแบบใด: + +| ตัวดำเนินการ | การแปลงตัวถูกดำเนินการ | +|--------------|----------------------| +| + | ทำการต่อ string ถ้าสามารถแปลงได้ เช่น `"1" + "2" = "12"`, `"1" + 2 = "12"` | +| - | แปลงเป็นตัวเลข | +| * / | แปลงเป็นตัวเลข | +| == | แปลงเป็นตัวเลข แต่ `null` กับ `undefined` จะถูกเปรียบเทียบโดยไม่มีการแปลง | +| < > >= <= | แปลงเป็นตัวเลข | +| + (unary) | แปลงเป็นตัวเลข | From b92a761a40489d05b4ef11ff62cf3f06ea654a00 Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 21 Apr 2024 19:48:02 +0700 Subject: [PATCH 9/9] Translate the "Object to primitive conversion" section to Thai and update examples --- .../09-object-toprimitive/article.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index b4d9cc6e2..7bdbfe8a9 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -218,3 +218,25 @@ alert(obj - 2); // 0 ("2" - 2), การแปลงเป็นตัวเล | == | แปลงเป็นตัวเลข แต่ `null` กับ `undefined` จะถูกเปรียบเทียบโดยไม่มีการแปลง | | < > >= <= | แปลงเป็นตัวเลข | | + (unary) | แปลงเป็นตัวเลข | + + +## สรุป + +การแปลงออบเจ็กต์เป็นค่าปฐมภูมิจะถูกเรียกใช้โดยอัตโนมัติ โดยฟังก์ชันและตัวดำเนินการหลายตัวที่คาดหวังให้ข้อมูลเป็นค่าปฐมภูมิ + +มีการแปลง 3 แบบ: +- `"string"` (สำหรับ `alert` และตัวดำเนินการที่ต้องการ string) +- `"number"` (สำหรับการคำนวณทางคณิตศาสตร์) +- `"default"` (สำหรับตัวดำเนินการบางตัวเท่านั้น) + +ซึ่งมีระบุไว้ชัดเจนในสเปคว่าตัวดำเนินการแต่ละตัวใช้การแปลงแบบใด + +ลำดับขั้นตอนการแปลงคือ: + +1. เรียกใช้ `obj[Symbol.toPrimitive](hint)` หากมีการกำหนดไว้ +2. มิฉะนั้น ถ้า hint คือ `"string"` + - ลองเรียก `obj.toString()` และ `obj.valueOf()` ตามลำดับ เลือกอันแรกที่ใช้ได้ +3. มิฉะนั้น ถ้า hint คือ `"number"` หรือ `"default"` + - ลองเรียก `obj.valueOf()` และ `obj.toString()` ตามลำดับ เลือกอันแรกที่ใช้ได้ + +ในทางปฏิบัติ การใช้เมท็อด `obj.toString()` เพียงอย่างเดียวเป็นตัวจัดการหลักในการแปลงออบเจ็กต์เป็น string มักจะพอเพียง และถูกใช้เพื่ออธิบายข้อมูลของออบเจ็กต์ในรูปแบบที่อ่านได้ง่าย โดยมักใช้ในการล็อก (log) และดีบั๊ก \ No newline at end of file