From c5c2d155cfe75d7f71c78ae71338a4bed7ddac6e Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 8 Mar 2026 00:01:12 +0700 Subject: [PATCH 1/2] docs: translate data-types section (8 articles) --- .../05-data-types/05-array-methods/article.md | 530 +++++++++--------- 1-js/05-data-types/06-iterable/article.md | 178 +++--- 1-js/05-data-types/07-map-set/article.md | 244 ++++---- .../08-weakmap-weakset/article.md | 188 +++---- .../01-sum-salaries/solution.md | 8 +- .../01-sum-salaries/task.md | 11 +- .../02-count-properties/task.md | 9 +- .../09-keys-values-entries/article.md | 64 +-- .../10-destructuring-assignment/article.md | 262 ++++----- .../11-date/3-weekday/_js.view/solution.js | 2 +- .../_js.view/solution.js | 22 +- .../8-format-date-relative/_js.view/test.js | 14 +- 1-js/05-data-types/11-date/article.md | 284 +++++----- 1-js/05-data-types/12-json/article.md | 244 ++++---- 14 files changed, 1029 insertions(+), 1031 deletions(-) diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 853645958..7c1d752da 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,111 +1,111 @@ -# Array methods +# เมธอดของอาร์เรย์ -Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. +อาร์เรย์มีเมธอดให้ใช้มากมาย เพื่อให้เข้าใจง่ายขึ้น ในบทนี้จะแบ่งเมธอดออกเป็นกลุ่มๆ -## Add/remove items +## เพิ่ม/ลบรายการ -We already know methods that add and remove items from the beginning or the end: +เราได้รู้จักเมธอดสำหรับเพิ่มและลบรายการจากต้นหรือท้ายอาร์เรย์ไปแล้ว: -- `arr.push(...items)` -- adds items to the end, -- `arr.pop()` -- extracts an item from the end, -- `arr.shift()` -- extracts an item from the beginning, -- `arr.unshift(...items)` -- adds items to the beginning. +- `arr.push(...items)` -- เพิ่มรายการที่ท้ายอาร์เรย์ +- `arr.pop()` -- ดึงรายการออกจากท้ายอาร์เรย์ +- `arr.shift()` -- ดึงรายการออกจากต้นอาร์เรย์ +- `arr.unshift(...items)` -- เพิ่มรายการที่ต้นอาร์เรย์ -Here are a few others. +มาดูเมธอดอื่นๆ เพิ่มเติมกัน ### splice -How to delete an element from the array? +จะลบสมาชิกออกจากอาร์เรย์ได้อย่างไร? -The arrays are objects, so we can try to use `delete`: +เนื่องจากอาร์เรย์เป็นออบเจ็กต์ เราอาจลองใช้ `delete`: ```js run let arr = ["I", "go", "home"]; -delete arr[1]; // remove "go" +delete arr[1]; // ลบ "go" alert( arr[1] ); // undefined -// now arr = ["I", , "home"]; +// ตอนนี้ arr = ["I", , "home"]; alert( arr.length ); // 3 ``` -The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`. +สมาชิกถูกลบแล้ว แต่อาร์เรย์ยังมี 3 สมาชิกอยู่ จะเห็นว่า `arr.length == 3` -That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now. +เป็นเรื่องปกติ เพราะ `delete obj.key` ลบค่าตาม `key` เท่านั้น สำหรับออบเจ็กต์ทั่วไปก็ใช้ได้ แต่สำหรับอาร์เรย์ เรามักต้องการให้สมาชิกที่เหลือเลื่อนมาเติมช่องที่ว่าง และอยากได้อาร์เรย์ที่สั้นลงด้วย -So, special methods should be used. +ดังนั้น จึงต้องใช้เมธอดพิเศษแทน -The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements. +เมธอด [arr.splice](mdn:js/Array/splice) เปรียบเสมือนมีดพับอเนกประสงค์ของอาร์เรย์ ทำได้ทุกอย่าง: แทรก ลบ และแทนที่สมาชิก -The syntax is: +ไวยากรณ์คือ: ```js arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` -It modifies `arr` starting from the index `start`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements. +เมธอดนี้แก้ไข `arr` โดยเริ่มจาก index `start`: ลบ `deleteCount` สมาชิก แล้วแทรก `elem1, ..., elemN` ลงในตำแหน่งนั้น และคืนค่าเป็นอาร์เรย์ของสมาชิกที่ถูกลบออก -This method is easy to grasp by examples. +วิธีที่ดีที่สุดในการทำความเข้าใจเมธอดนี้คือดูตัวอย่าง -Let's start with the deletion: +เริ่มจากการลบก่อน: ```js run let arr = ["I", "study", "JavaScript"]; *!* -arr.splice(1, 1); // from index 1 remove 1 element +arr.splice(1, 1); // จาก index 1 ลบ 1 สมาชิก */!* alert( arr ); // ["I", "JavaScript"] ``` -Easy, right? Starting from the index `1` it removed `1` element. +ง่ายใช่ไหม? เริ่มจาก index `1` แล้วลบ `1` สมาชิก -In the next example, we remove 3 elements and replace them with the other two: +ในตัวอย่างถัดไป เราลบ 3 สมาชิกแล้วแทนด้วยอีก 2 สมาชิก: ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; -// remove 3 first elements and replace them with another +// ลบ 3 สมาชิกแรก แล้วแทนด้วยสมาชิกใหม่ arr.splice(0, 3, "Let's", "dance"); -alert( arr ) // now [*!*"Let's", "dance"*/!*, "right", "now"] +alert( arr ) // ตอนนี้คือ [*!*"Let's", "dance"*/!*, "right", "now"] ``` -Here we can see that `splice` returns the array of removed elements: +ต่อไปมาดูว่า `splice` คืนค่าเป็นอาร์เรย์ของสมาชิกที่ถูกลบ: ```js run let arr = [*!*"I", "study",*/!* "JavaScript", "right", "now"]; -// remove 2 first elements +// ลบ 2 สมาชิกแรก let removed = arr.splice(0, 2); -alert( removed ); // "I", "study" <-- array of removed elements +alert( removed ); // "I", "study" <-- อาร์เรย์ของสมาชิกที่ถูกลบ ``` -The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: +เมธอด `splice` ยังสามารถแทรกสมาชิกโดยไม่ต้องลบอะไรเลย เพียงกำหนด `deleteCount` เป็น `0`: ```js run let arr = ["I", "study", "JavaScript"]; -// from index 2 -// delete 0 -// then insert "complex" and "language" +// จาก index 2 +// ลบ 0 สมาชิก +// แล้วแทรก "complex" และ "language" arr.splice(2, 0, "complex", "language"); alert( arr ); // "I", "study", "complex", "language", "JavaScript" ``` -````smart header="Negative indexes allowed" -Here and in other array methods, negative indexes are allowed. They specify the position from the end of the array, like here: +````smart header="ใช้ index ติดลบได้" +ในเมธอดนี้และเมธอดอาร์เรย์อื่นๆ สามารถใช้ index ติดลบได้ ซึ่งหมายถึงตำแหน่งนับจากท้ายอาร์เรย์ ดังนี้: ```js run let arr = [1, 2, 5]; -// from index -1 (one step from the end) -// delete 0 elements, -// then insert 3 and 4 +// จาก index -1 (หนึ่งขั้นจากท้าย) +// ลบ 0 สมาชิก +// แล้วแทรก 3 และ 4 arr.splice(-1, 0, 3, 4); alert( arr ); // 1,2,3,4,5 @@ -114,62 +114,62 @@ alert( arr ); // 1,2,3,4,5 ### slice -The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. +เมธอด [arr.slice](mdn:js/Array/slice) ง่ายกว่า `arr.splice` มาก แม้จะดูคล้ายกัน -The syntax is: +ไวยากรณ์คือ: ```js arr.slice([start], [end]) ``` -It returns a new array copying to it all items from index `start` to `end` (not including `end`). Both `start` and `end` can be negative, in that case position from array end is assumed. +เมธอดนี้คืนค่าเป็นอาร์เรย์ใหม่ โดยคัดลอกสมาชิกทุกตัวตั้งแต่ index `start` ถึง `end` (ไม่รวม `end`) ทั้ง `start` และ `end` ใช้ค่าติดลบได้ ซึ่งจะนับตำแหน่งจากท้ายอาร์เรย์ -It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. +คล้ายกับเมธอด `str.slice` ของสตริง แต่แทนที่จะได้ substring กลับได้เป็น subarray แทน -For instance: +ตัวอย่าง: ```js run let arr = ["t", "e", "s", "t"]; -alert( arr.slice(1, 3) ); // e,s (copy from 1 to 3) +alert( arr.slice(1, 3) ); // e,s (คัดลอกจาก index 1 ถึง 3) -alert( arr.slice(-2) ); // s,t (copy from -2 till the end) +alert( arr.slice(-2) ); // s,t (คัดลอกจาก index -2 ถึงท้ายอาร์เรย์) ``` -We can also call it without arguments: `arr.slice()` creates a copy of `arr`. That's often used to obtain a copy for further transformations that should not affect the original array. +เรายังสามารถเรียกใช้โดยไม่ระบุอาร์กิวเมนต์: `arr.slice()` จะสร้างสำเนาของ `arr` ซึ่งมักใช้เมื่อต้องการทำสำเนาสำหรับการแปลงข้อมูลที่ไม่ต้องการกระทบอาร์เรย์ต้นฉบับ ### concat -The method [arr.concat](mdn:js/Array/concat) creates a new array that includes values from other arrays and additional items. +เมธอด [arr.concat](mdn:js/Array/concat) สร้างอาร์เรย์ใหม่ที่รวมค่าจากอาร์เรย์อื่นและรายการเพิ่มเติม -The syntax is: +ไวยากรณ์คือ: ```js arr.concat(arg1, arg2...) ``` -It accepts any number of arguments -- either arrays or values. +รับอาร์กิวเมนต์กี่ตัวก็ได้ จะเป็นอาร์เรย์หรือค่าธรรมดาก็ได้ -The result is a new array containing items from `arr`, then `arg1`, `arg2` etc. +ผลลัพธ์คืออาร์เรย์ใหม่ที่ประกอบด้วยสมาชิกจาก `arr` ตามด้วย `arg1`, `arg2` เป็นต้น -If an argument `argN` is an array, then all its elements are copied. Otherwise, the argument itself is copied. +ถ้าอาร์กิวเมนต์ `argN` เป็นอาร์เรย์ สมาชิกทุกตัวจะถูกคัดลอก มิฉะนั้นจะคัดลอกอาร์กิวเมนต์นั้นทั้งก้อน -For instance: +ตัวอย่าง: ```js run let arr = [1, 2]; -// create an array from: arr and [3,4] +// สร้างอาร์เรย์จาก: arr และ [3,4] alert( arr.concat([3, 4]) ); // 1,2,3,4 -// create an array from: arr and [3,4] and [5,6] +// สร้างอาร์เรย์จาก: arr และ [3,4] และ [5,6] alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 -// create an array from: arr and [3,4], then add values 5 and 6 +// สร้างอาร์เรย์จาก: arr และ [3,4] แล้วเพิ่มค่า 5 และ 6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 ``` -Normally, it only copies elements from arrays. Other objects, even if they look like arrays, are added as a whole: +โดยปกติ เมธอดนี้จะคัดลอกแค่สมาชิกจากอาร์เรย์ ส่วนออบเจ็กต์อื่นๆ แม้จะดูคล้ายอาร์เรย์ จะถูกเพิ่มเข้าไปทั้งก้อน: ```js run let arr = [1, 2]; @@ -182,7 +182,7 @@ let arrayLike = { alert( arr.concat(arrayLike) ); // 1,2,[object Object] ``` -...But if an array-like object has a special `Symbol.isConcatSpreadable` property, then it's treated as an array by `concat`: its elements are added instead: +...แต่ถ้าออบเจ็กต์ array-like มีพร็อพเพอร์ตี้พิเศษชื่อ `Symbol.isConcatSpreadable` `concat` จะถือว่ามันเป็นอาร์เรย์และเพิ่มสมาชิกแทน: ```js run let arr = [1, 2]; @@ -199,49 +199,49 @@ let arrayLike = { alert( arr.concat(arrayLike) ); // 1,2,something,else ``` -## Iterate: forEach +## วนซ้ำด้วย forEach -The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for every element of the array. +เมธอด [arr.forEach](mdn:js/Array/forEach) ช่วยให้รันฟังก์ชันสำหรับสมาชิกทุกตัวในอาร์เรย์ -The syntax: +ไวยากรณ์: ```js arr.forEach(function(item, index, array) { - // ... do something with an item + // ... ทำบางอย่างกับ item }); ``` -For instance, this shows each element of the array: +ตัวอย่างเช่น โค้ดนี้แสดงสมาชิกทุกตัวในอาร์เรย์: ```js run -// for each element call alert +// เรียก alert สำหรับสมาชิกแต่ละตัว ["Bilbo", "Gandalf", "Nazgul"].forEach(alert); ``` -And this code is more elaborate about their positions in the target array: +และโค้ดนี้แสดงข้อมูลตำแหน่งของแต่ละสมาชิกในอาร์เรย์: ```js run ["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { - alert(`${item} is at index ${index} in ${array}`); + alert(`${item} อยู่ที่ index ${index} ใน ${array}`); }); ``` -The result of the function (if it returns any) is thrown away and ignored. +ค่าที่ฟังก์ชันคืนค่ากลับมา (ถ้ามี) จะถูกทิ้งและไม่นำมาใช้งาน -## Searching in array +## ค้นหาในอาร์เรย์ -Now let's cover methods that search in an array. +ทีนี้มาดูเมธอดสำหรับค้นหาข้อมูลในอาร์เรย์กัน -### indexOf/lastIndexOf and includes +### indexOf/lastIndexOf และ includes -The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/includes) have the similar syntax and do essentially the same as their string counterparts, but operate on items instead of characters: +เมธอด [arr.indexOf](mdn:js/Array/indexOf) และ [arr.includes](mdn:js/Array/includes) มีไวยากรณ์คล้ายกัน และทำงานคล้ายกับเมธอดของสตริง แต่ทำงานกับสมาชิกแทนที่จะเป็นตัวอักษร: -- `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`. -- `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found. +- `arr.indexOf(item, from)` -- ค้นหา `item` โดยเริ่มจาก index `from` คืนค่า index ที่พบ ถ้าไม่พบคืน `-1` +- `arr.includes(item, from)` -- ค้นหา `item` โดยเริ่มจาก index `from` คืน `true` ถ้าพบ -Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. +โดยทั่วไปเมธอดเหล่านี้ใช้กับอาร์กิวเมนต์เดียวคือ `item` ที่ต้องการค้นหา ซึ่งจะค้นหาตั้งแต่ต้น -For instance: +ตัวอย่าง: ```js run let arr = [1, 0, false]; @@ -253,140 +253,140 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` -Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero. +โปรดสังเกตว่า `indexOf` ใช้การเปรียบเทียบแบบเข้มงวด `===` ดังนั้นถ้าค้นหา `false` จะพบ `false` เท่านั้น ไม่ใช่ศูนย์ -If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred. +ถ้าต้องการแค่ตรวจสอบว่า `item` มีอยู่ในอาร์เรย์หรือไม่ โดยไม่ต้องการ index ให้ใช้ `arr.includes` แทน -The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left. +เมธอด [arr.lastIndexOf](mdn:js/Array/lastIndexOf) ทำงานเหมือน `indexOf` แต่ค้นหาจากขวาไปซ้าย ```js run let fruits = ['Apple', 'Orange', 'Apple'] -alert( fruits.indexOf('Apple') ); // 0 (first Apple) -alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) +alert( fruits.indexOf('Apple') ); // 0 (Apple ตัวแรก) +alert( fruits.lastIndexOf('Apple') ); // 2 (Apple ตัวสุดท้าย) ``` -````smart header="The `includes` method handles `NaN` correctly" -A minor, but noteworthy feature of `includes` is that it correctly handles `NaN`, unlike `indexOf`: +````smart header="เมธอด `includes` รองรับ `NaN` อย่างถูกต้อง" +ข้อดีเล็กน้อยแต่น่าสนใจของ `includes` คือรองรับ `NaN` ได้อย่างถูกต้อง ต่างจาก `indexOf`: ```js run const arr = [NaN]; -alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0) -alert( arr.includes(NaN) );// true (correct) +alert( arr.indexOf(NaN) ); // -1 (ผิด ควรได้ 0) +alert( arr.includes(NaN) );// true (ถูกต้อง) ``` -That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. +เพราะ `includes` ถูกเพิ่มเข้ามาใน JavaScript ในภายหลัง และใช้อัลกอริทึมเปรียบเทียบที่ทันสมัยกว่า ```` -### find and findIndex/findLastIndex +### find และ findIndex/findLastIndex -Imagine we have an array of objects. How do we find an object with a specific condition? +ลองนึกภาพว่าเรามีอาร์เรย์ของออบเจ็กต์ แล้วจะค้นหาออบเจ็กต์ที่ตรงตามเงื่อนไขได้อย่างไร? -Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy. +เมธอด [arr.find(fn)](mdn:js/Array/find) ช่วยได้พอดี -The syntax is: +ไวยากรณ์คือ: ```js let result = arr.find(function(item, index, array) { - // if true is returned, item is returned and iteration is stopped - // for falsy scenario returns undefined + // ถ้าคืนค่า true จะหยุดค้นหาและคืน item กลับ + // ถ้าไม่พบจะคืน undefined }); ``` -The function is called for elements of the array, one after another: +ฟังก์ชันจะถูกเรียกสำหรับสมาชิกทุกตัวในอาร์เรย์ ทีละตัว: -- `item` is the element. -- `index` is its index. -- `array` is the array itself. +- `item` คือสมาชิก +- `index` คือ index ของสมาชิก +- `array` คืออาร์เรย์เอง -If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. +ถ้าฟังก์ชันคืน `true` การค้นหาจะหยุดและคืน `item` กลับ ถ้าไม่พบอะไร จะคืน `undefined` -For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`: +ตัวอย่าง เรามีอาร์เรย์ของผู้ใช้ที่มีฟิลด์ `id` และ `name` แล้วหาผู้ใช้ที่มี `id == 1`: ```js run let users = [ - {id: 1, name: "John"}, - {id: 2, name: "Pete"}, - {id: 3, name: "Mary"} + {id: 1, name: "สมชาย"}, + {id: 2, name: "สมหญิง"}, + {id: 3, name: "มาลี"} ]; let user = users.find(item => item.id == 1); -alert(user.name); // John +alert(user.name); // สมชาย ``` -In real life, arrays of objects are a common thing, so the `find` method is very useful. +ในชีวิตจริง อาร์เรย์ของออบเจ็กต์เป็นเรื่องที่พบบ่อยมาก ดังนั้นเมธอด `find` จึงมีประโยชน์มาก -Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. That's typical, other arguments of this function are rarely used. +สังเกตว่าในตัวอย่างนี้ เราส่งฟังก์ชัน `item => item.id == 1` ที่มีอาร์กิวเมนต์เดียวให้ `find` ซึ่งเป็นรูปแบบทั่วไป อาร์กิวเมนต์อื่นๆ ของฟังก์ชันนี้ใช้น้อยมาก -The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. +เมธอด [arr.findIndex](mdn:js/Array/findIndex) มีไวยากรณ์เหมือนกัน แต่คืน index ของสมาชิกที่พบแทนตัวสมาชิก ถ้าไม่พบจะคืน `-1` -The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`. +เมธอด [arr.findLastIndex](mdn:js/Array/findLastIndex) คล้ายกับ `findIndex` แต่ค้นหาจากขวาไปซ้าย คล้ายกับ `lastIndexOf` -Here's an example: +ตัวอย่าง: ```js run let users = [ - {id: 1, name: "John"}, - {id: 2, name: "Pete"}, - {id: 3, name: "Mary"}, - {id: 4, name: "John"} + {id: 1, name: "สมชาย"}, + {id: 2, name: "สมหญิง"}, + {id: 3, name: "มาลี"}, + {id: 4, name: "สมชาย"} ]; -// Find the index of the first John -alert(users.findIndex(user => user.name == 'John')); // 0 +// หา index ของสมชายคนแรก +alert(users.findIndex(user => user.name == 'สมชาย')); // 0 -// Find the index of the last John -alert(users.findLastIndex(user => user.name == 'John')); // 3 +// หา index ของสมชายคนสุดท้าย +alert(users.findLastIndex(user => user.name == 'สมชาย')); // 3 ``` ### filter -The `find` method looks for a single (first) element that makes the function return `true`. +เมธอด `find` ค้นหาสมาชิกตัวเดียว (ตัวแรก) ที่ทำให้ฟังก์ชันคืน `true` -If there may be many, we can use [arr.filter(fn)](mdn:js/Array/filter). +ถ้าอาจมีหลายตัวที่ตรงเงื่อนไข ให้ใช้ [arr.filter(fn)](mdn:js/Array/filter) -The syntax is similar to `find`, but `filter` returns an array of all matching elements: +ไวยากรณ์คล้ายกับ `find` แต่ `filter` คืนอาร์เรย์ของสมาชิกทุกตัวที่ตรงเงื่อนไข: ```js let results = arr.filter(function(item, index, array) { - // if true item is pushed to results and the iteration continues - // returns empty array if nothing found + // ถ้าคืน true สมาชิกจะถูกเพิ่มใน results และวนต่อ + // คืนอาร์เรย์ว่างถ้าไม่พบ }); ``` -For instance: +ตัวอย่าง: ```js run let users = [ - {id: 1, name: "John"}, - {id: 2, name: "Pete"}, - {id: 3, name: "Mary"} + {id: 1, name: "สมชาย"}, + {id: 2, name: "สมหญิง"}, + {id: 3, name: "มาลี"} ]; -// returns array of the first two users +// คืนอาร์เรย์ของสองผู้ใช้แรก let someUsers = users.filter(item => item.id < 3); alert(someUsers.length); // 2 ``` -## Transform an array +## แปลงอาร์เรย์ -Let's move on to methods that transform and reorder an array. +มาดูเมธอดสำหรับแปลงและจัดเรียงอาร์เรย์กัน ### map -The [arr.map](mdn:js/Array/map) method is one of the most useful and often used. +เมธอด [arr.map](mdn:js/Array/map) เป็นหนึ่งในเมธอดที่มีประโยชน์และใช้บ่อยที่สุด -It calls the function for each element of the array and returns the array of results. +เมธอดนี้เรียกฟังก์ชันสำหรับสมาชิกแต่ละตัวในอาร์เรย์ แล้วคืนอาร์เรย์ของผลลัพธ์ -The syntax is: +ไวยากรณ์คือ: ```js let result = arr.map(function(item, index, array) { - // returns the new value instead of item + // คืนค่าใหม่แทน item }); ``` -For instance, here we transform each element into its length: +ตัวอย่าง เราแปลงสมาชิกแต่ละตัวให้เป็นความยาวของมัน: ```js run let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); @@ -395,42 +395,42 @@ alert(lengths); // 5,7,6 ### sort(fn) -The call to [arr.sort()](mdn:js/Array/sort) sorts the array *in place*, changing its element order. +การเรียก [arr.sort()](mdn:js/Array/sort) จะเรียงลำดับอาร์เรย์ *แบบ in-place* นั่นคือเปลี่ยนลำดับสมาชิกในอาร์เรย์เดิม -It also returns the sorted array, but the returned value is usually ignored, as `arr` itself is modified. +เมธอดนี้ยังคืนอาร์เรย์ที่เรียงแล้วด้วย แต่ค่าที่คืนมักถูกละเว้น เพราะ `arr` เองถูกแก้ไขแล้ว -For instance: +ตัวอย่าง: ```js run let arr = [ 1, 2, 15 ]; -// the method reorders the content of arr +// เมธอดนี้จัดเรียงเนื้อหาของ arr ใหม่ arr.sort(); alert( arr ); // *!*1, 15, 2*/!* ``` -Did you notice anything strange in the outcome? +สังเกตเห็นอะไรแปลกๆ ไหม? -The order became `1, 15, 2`. Incorrect. But why? +ลำดับกลายเป็น `1, 15, 2` ซึ่งไม่ถูกต้อง แต่ทำไม? -**The items are sorted as strings by default.** +**สมาชิกถูกเรียงลำดับในรูปแบบสตริงโดยค่าเริ่มต้น** -Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed `"2" > "15"`. +สมาชิกทุกตัวจะถูกแปลงเป็นสตริงก่อนเปรียบเทียบ สำหรับสตริงจะใช้การเรียงแบบ lexicographic ซึ่ง `"2" > "15"` นั่นเอง -To use our own sorting order, we need to supply a function as the argument of `arr.sort()`. +ถ้าต้องการกำหนดลำดับการเรียงเอง ต้องส่งฟังก์ชันเป็นอาร์กิวเมนต์ให้ `arr.sort()` -The function should compare two arbitrary values and return: +ฟังก์ชันนั้นควรเปรียบเทียบค่าสองค่าใดๆ และคืนค่า: ```js function compare(a, b) { - if (a > b) return 1; // if the first value is greater than the second - if (a == b) return 0; // if values are equal - if (a < b) return -1; // if the first value is less than the second + if (a > b) return 1; // ถ้าค่าแรกมากกว่าค่าที่สอง + if (a == b) return 0; // ถ้าค่าเท่ากัน + if (a < b) return -1; // ถ้าค่าแรกน้อยกว่าค่าที่สอง } ``` -For instance, to sort as numbers: +ตัวอย่าง เรียงลำดับเป็นตัวเลข: ```js run function compareNumeric(a, b) { @@ -448,13 +448,13 @@ arr.sort(compareNumeric); alert(arr); // *!*1, 2, 15*/!* ``` -Now it works as intended. +ตอนนี้ทำงานถูกต้องแล้ว -Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. +ลองคิดดูว่าเกิดอะไรขึ้น `arr` อาจเป็นอาร์เรย์ของอะไรก็ได้ใช่ไหม? อาจเป็นตัวเลข สตริง ออบเจ็กต์ หรืออะไรก็ตาม เรามี *ชุดของรายการ* บางอย่าง ในการเรียงลำดับ เราต้องมี *ฟังก์ชันเรียงลำดับ* ที่รู้วิธีเปรียบเทียบสมาชิก ค่าเริ่มต้นคือการเรียงแบบสตริง -The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. +เมธอด `arr.sort(fn)` ใช้อัลกอริทึมการเรียงลำดับทั่วไป เราไม่จำเป็นต้องรู้ว่ามันทำงานภายในอย่างไร (ส่วนใหญ่จะเป็น [quicksort](https://en.wikipedia.org/wiki/Quicksort) หรือ [Timsort](https://en.wikipedia.org/wiki/Timsort) ที่ปรับแต่งแล้ว) เมธอดจะวนผ่านอาร์เรย์ เปรียบเทียบสมาชิกด้วยฟังก์ชันที่ให้มา และจัดเรียงใหม่ สิ่งที่เราต้องทำมีแค่ระบุ `fn` สำหรับการเปรียบเทียบ -By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: +อนึ่ง ถ้าต้องการดูว่าสมาชิกไหนถูกเปรียบเทียบ แค่ alert ออกมาได้เลย: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -463,12 +463,12 @@ By the way, if we ever want to know which elements are compared -- nothing preve }); ``` -The algorithm may compare an element with multiple others in the process, but it tries to make as few comparisons as possible. +อัลกอริทึมอาจเปรียบเทียบสมาชิกหนึ่งตัวกับหลายตัว แต่จะพยายามเปรียบเทียบให้น้อยครั้งที่สุด -````smart header="A comparison function may return any number" -Actually, a comparison function is only required to return a positive number to say "greater" and a negative number to say "less". +````smart header="ฟังก์ชันเปรียบเทียบสามารถคืนค่าตัวเลขใดก็ได้" +จริงๆ แล้ว ฟังก์ชันเปรียบเทียบต้องคืนค่าบวกเพื่อบอกว่า "มากกว่า" และค่าลบเพื่อบอกว่า "น้อยกว่า" เท่านั้น -That allows to write shorter functions: +ทำให้เขียนฟังก์ชันได้สั้นลง: ```js run let arr = [ 1, 2, 15 ]; @@ -479,37 +479,37 @@ alert(arr); // *!*1, 2, 15*/!* ``` ```` -````smart header="Arrow functions for the best" -Remember [arrow functions](info:arrow-functions-basics)? We can use them here for neater sorting: +````smart header="ใช้ arrow function ให้กระชับ" +จำ [arrow function](info:arrow-functions-basics) ได้ไหม? เราใช้มันเพื่อเขียนการเรียงลำดับได้กระชับขึ้น: ```js arr.sort( (a, b) => a - b ); ``` -This works exactly the same as the longer version above. +ทำงานได้เหมือนกับเวอร์ชันยาวด้านบนทุกประการ ```` -````smart header="Use `localeCompare` for strings" -Remember [strings](info:string#correct-comparisons) comparison algorithm? It compares letters by their codes by default. +````smart header="ใช้ `localeCompare` สำหรับสตริง" +จำอัลกอริทึมการเปรียบเทียบ[สตริง](info:string#correct-comparisons) ได้ไหม? โดยค่าเริ่มต้นจะเปรียบเทียบตัวอักษรด้วยรหัสของมัน -For many alphabets, it's better to use `str.localeCompare` method to correctly sort letters, such as `Ö`. +สำหรับหลายภาษา ควรใช้เมธอด `str.localeCompare` เพื่อเรียงลำดับตัวอักษรให้ถูกต้อง เช่น `Ö` -For example, let's sort a few countries in German: +ตัวอย่าง ลองเรียงชื่อประเทศในภาษาเยอรมัน: ```js run let countries = ['Österreich', 'Andorra', 'Vietnam']; -alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (wrong) +alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (ผิด) -alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (correct!) +alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (ถูกต้อง!) ``` ```` ### reverse -The method [arr.reverse](mdn:js/Array/reverse) reverses the order of elements in `arr`. +เมธอด [arr.reverse](mdn:js/Array/reverse) กลับลำดับสมาชิกใน `arr` -For instance: +ตัวอย่าง: ```js run let arr = [1, 2, 3, 4, 5]; @@ -518,15 +518,15 @@ arr.reverse(); alert( arr ); // 5,4,3,2,1 ``` -It also returns the array `arr` after the reversal. +เมธอดนี้ยังคืนอาร์เรย์ `arr` หลังจากกลับลำดับแล้วด้วย -### split and join +### split และ join -Here's the situation from real life. We are writing a messaging app, and the person enters the comma-delimited list of receivers: `John, Pete, Mary`. But for us an array of names would be much more comfortable than a single string. How to get it? +ลองนึกถึงสถานการณ์จริง เรากำลังเขียนแอปส่งข้อความ และผู้ใช้พิมพ์รายชื่อผู้รับคั่นด้วยจุลภาค: `สมชาย, สมหญิง, มาลี` แต่สำหรับเรา อาร์เรย์ของชื่อสะดวกกว่าสตริงเดียวมาก จะแปลงได้อย่างไร? -The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`. +เมธอด [str.split(delim)](mdn:js/String/split) ทำงานนี้ได้พอดี โดยแยกสตริงออกเป็นอาร์เรย์โดยใช้ตัวคั่น `delim` -In the example below, we split by a comma followed by a space: +ในตัวอย่างด้านล่าง เราแยกด้วยจุลภาคตามด้วยเว้นวรรค: ```js run let names = 'Bilbo, Gandalf, Nazgul'; @@ -534,11 +534,11 @@ let names = 'Bilbo, Gandalf, Nazgul'; let arr = names.split(', '); for (let name of arr) { - alert( `A message to ${name}.` ); // A message to Bilbo (and other names) + alert( `ส่งข้อความถึง ${name}.` ); // ส่งข้อความถึง Bilbo (และชื่ออื่นๆ) } ``` -The `split` method has an optional second numeric argument -- a limit on the array length. If it is provided, then the extra elements are ignored. In practice it is rarely used though: +เมธอด `split` มีอาร์กิวเมนต์ตัวเลขตัวที่สองที่ไม่บังคับ ซึ่งเป็นขีดจำกัดความยาวของอาร์เรย์ ถ้าระบุ สมาชิกส่วนเกินจะถูกละเว้น แต่ในทางปฏิบัติใช้น้อยมาก: ```js run let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); @@ -546,8 +546,8 @@ let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); alert(arr); // Bilbo, Gandalf ``` -````smart header="Split into letters" -The call to `split(s)` with an empty `s` would split the string into an array of letters: +````smart header="แยกเป็นตัวอักษร" +การเรียก `split(s)` ด้วย `s` ว่างเปล่าจะแยกสตริงออกเป็นอาร์เรย์ของตัวอักษร: ```js run let str = "test"; @@ -556,27 +556,27 @@ alert( str.split('') ); // t,e,s,t ``` ```` -The call [arr.join(glue)](mdn:js/Array/join) does the reverse to `split`. It creates a string of `arr` items joined by `glue` between them. +การเรียก [arr.join(glue)](mdn:js/Array/join) ทำงานตรงข้ามกับ `split` โดยสร้างสตริงจากสมาชิกของ `arr` โดยเชื่อมด้วย `glue` -For instance: +ตัวอย่าง: ```js run let arr = ['Bilbo', 'Gandalf', 'Nazgul']; -let str = arr.join(';'); // glue the array into a string using ; +let str = arr.join(';'); // เชื่อมอาร์เรย์เป็นสตริงด้วย ; alert( str ); // Bilbo;Gandalf;Nazgul ``` ### reduce/reduceRight -When we need to iterate over an array -- we can use `forEach`, `for` or `for..of`. +เมื่อต้องวนซ้ำในอาร์เรย์ เราใช้ `forEach`, `for` หรือ `for..of` ได้ -When we need to iterate and return the data for each element -- we can use `map`. +เมื่อต้องวนซ้ำและคืนข้อมูลสำหรับแต่ละสมาชิก เราใช้ `map` ได้ -The methods [arr.reduce](mdn:js/Array/reduce) and [arr.reduceRight](mdn:js/Array/reduceRight) also belong to that breed, but are a little bit more intricate. They are used to calculate a single value based on the array. +เมธอด [arr.reduce](mdn:js/Array/reduce) และ [arr.reduceRight](mdn:js/Array/reduceRight) อยู่ในกลุ่มเดียวกัน แต่ซับซ้อนกว่าเล็กน้อย ใช้สำหรับคำนวณค่าเดียวจากอาร์เรย์ -The syntax is: +ไวยากรณ์คือ: ```js let value = arr.reduce(function(accumulator, item, index, array) { @@ -584,24 +584,24 @@ let value = arr.reduce(function(accumulator, item, index, array) { }, [initial]); ``` -The function is applied to all array elements one after another and "carries on" its result to the next call. +ฟังก์ชันจะถูกเรียกสำหรับสมาชิกทุกตัวตามลำดับ และ "ส่งต่อ" ผลลัพธ์ไปยังการเรียกครั้งถัดไป -Arguments: +อาร์กิวเมนต์: -- `accumulator` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided). -- `item` -- is the current array item. -- `index` -- is its position. -- `array` -- is the array. +- `accumulator` -- ผลลัพธ์จากการเรียกฟังก์ชันครั้งก่อน ถ้าเป็นการเรียกครั้งแรกจะเท่ากับ `initial` (ถ้าระบุ) +- `item` -- สมาชิกในอาร์เรย์ปัจจุบัน +- `index` -- ตำแหน่งของสมาชิก +- `array` -- อาร์เรย์เอง -As the function is applied, the result of the previous function call is passed to the next one as the first argument. +เมื่อฟังก์ชันถูกเรียก ผลลัพธ์จากการเรียกครั้งก่อนจะถูกส่งเป็นอาร์กิวเมนต์แรกของการเรียกครั้งถัดไป -So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`. +กล่าวคือ อาร์กิวเมนต์แรกเป็น accumulator ที่เก็บผลลัพธ์รวมของการทำงานทั้งหมดก่อนหน้า และในตอนท้ายจะกลายเป็นผลลัพธ์ของ `reduce` -Sounds complicated? +ฟังดูซับซ้อนใช่ไหม? -The easiest way to grasp that is by example. +วิธีที่ง่ายที่สุดในการเข้าใจคือดูตัวอย่าง -Here we get a sum of an array in one line: +ในตัวอย่างนี้เราหาผลรวมของอาร์เรย์ในบรรทัดเดียว: ```js run let arr = [1, 2, 3, 4, 5]; @@ -611,73 +611,73 @@ let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 ``` -The function passed to `reduce` uses only 2 arguments, that's typically enough. +ฟังก์ชันที่ส่งให้ `reduce` ใช้แค่ 2 อาร์กิวเมนต์ ซึ่งมักเพียงพอ -Let's see the details of what's going on. +มาดูรายละเอียดว่าเกิดอะไรขึ้น: -1. On the first run, `sum` is the `initial` value (the last argument of `reduce`), equals `0`, and `current` is the first array element, equals `1`. So the function result is `1`. -2. On the second run, `sum = 1`, we add the second array element (`2`) to it and return. -3. On the 3rd run, `sum = 3` and we add one more element to it, and so on... +1. ครั้งแรก `sum` คือค่า `initial` (อาร์กิวเมนต์สุดท้ายของ `reduce`) ซึ่งเท่ากับ `0` และ `current` คือสมาชิกแรกของอาร์เรย์ ซึ่งเท่ากับ `1` ดังนั้นผลลัพธ์ของฟังก์ชันคือ `1` +2. ครั้งที่สอง `sum = 1` เราบวกสมาชิกที่สอง (`2`) แล้วคืนค่ากลับ +3. ครั้งที่สาม `sum = 3` และบวกสมาชิกตัวถัดไป และต่อไปเรื่อยๆ... -The calculation flow: +ขั้นตอนการคำนวณ: ![](reduce.svg) -Or in the form of a table, where each row represents a function call on the next array element: +หรือในรูปแบบตาราง แต่ละแถวแสดงการเรียกฟังก์ชันสำหรับสมาชิกถัดไป: -| |`sum`|`current`|result| +| |`sum`|`current`|ผลลัพธ์| |---|-----|---------|---------| -|the first call|`0`|`1`|`1`| -|the second call|`1`|`2`|`3`| -|the third call|`3`|`3`|`6`| -|the fourth call|`6`|`4`|`10`| -|the fifth call|`10`|`5`|`15`| +|การเรียกครั้งที่ 1|`0`|`1`|`1`| +|การเรียกครั้งที่ 2|`1`|`2`|`3`| +|การเรียกครั้งที่ 3|`3`|`3`|`6`| +|การเรียกครั้งที่ 4|`6`|`4`|`10`| +|การเรียกครั้งที่ 5|`10`|`5`|`15`| -Here we can clearly see how the result of the previous call becomes the first argument of the next one. +จะเห็นชัดว่าผลลัพธ์ของการเรียกครั้งก่อนกลายเป็นอาร์กิวเมนต์แรกของการเรียกครั้งถัดไปอย่างไร -We also can omit the initial value: +เราสามารถละเว้นค่าเริ่มต้นได้: ```js run let arr = [1, 2, 3, 4, 5]; -// removed initial value from reduce (no 0) +// ลบค่าเริ่มต้นออกจาก reduce (ไม่มี 0) let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15 ``` -The result is the same. That's because if there's no initial, then `reduce` takes the first element of the array as the initial value and starts the iteration from the 2nd element. +ผลลัพธ์เหมือนกัน เพราะถ้าไม่มีค่าเริ่มต้น `reduce` จะใช้สมาชิกแรกของอาร์เรย์เป็นค่าเริ่มต้น และเริ่มวนซ้ำจากสมาชิกที่ 2 -The calculation table is the same as above, minus the first row. +ตารางการคำนวณก็เหมือนกับด้านบน แค่ขาดแถวแรก -But such use requires an extreme care. If the array is empty, then `reduce` call without initial value gives an error. +แต่การใช้แบบนี้ต้องระวังมาก ถ้าอาร์เรย์ว่างเปล่า การเรียก `reduce` โดยไม่มีค่าเริ่มต้นจะเกิดข้อผิดพลาด -Here's an example: +ดูตัวอย่าง: ```js run let arr = []; // Error: Reduce of empty array with no initial value -// if the initial value existed, reduce would return it for the empty arr. +// ถ้ามีค่าเริ่มต้น reduce จะคืนค่านั้นสำหรับอาร์เรย์ว่าง arr.reduce((sum, current) => sum + current); ``` -So it's advised to always specify the initial value. +ดังนั้น แนะนำให้ระบุค่าเริ่มต้นเสมอ -The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same but goes from right to left. +เมธอด [arr.reduceRight](mdn:js/Array/reduceRight) ทำงานเหมือนกันแต่วนจากขวาไปซ้าย ## Array.isArray -Arrays do not form a separate language type. They are based on objects. +อาร์เรย์ไม่ได้เป็น type แยกต่างหากในภาษา แต่อิงจากออบเจ็กต์ -So `typeof` does not help to distinguish a plain object from an array: +ดังนั้น `typeof` จึงไม่สามารถแยกออบเจ็กต์ธรรมดาจากอาร์เรย์ได้: ```js run alert(typeof {}); // object -alert(typeof []); // object (same) +alert(typeof []); // object (เหมือนกัน) ``` -...But arrays are used so often that there's a special method for that: [Array.isArray(value)](mdn:js/Array/isArray). It returns `true` if the `value` is an array, and `false` otherwise. +...แต่อาร์เรย์ถูกใช้บ่อยมากจนมีเมธอดพิเศษสำหรับตรวจสอบ: [Array.isArray(value)](mdn:js/Array/isArray) คืน `true` ถ้า `value` เป็นอาร์เรย์ และคืน `false` มิฉะนั้น ```js run alert(Array.isArray({})); // false @@ -685,25 +685,25 @@ alert(Array.isArray({})); // false alert(Array.isArray([])); // true ``` -## Most methods support "thisArg" +## เมธอดส่วนใหญ่รองรับ "thisArg" -Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`. +เกือบทุกเมธอดของอาร์เรย์ที่เรียกฟังก์ชัน เช่น `find`, `filter`, `map` (ยกเว้น `sort`) รับพารามิเตอร์เสริมตัวสุดท้ายที่เรียกว่า `thisArg` -That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it. +พารามิเตอร์นี้ไม่ได้อธิบายในส่วนก่อนหน้า เพราะใช้น้อยมาก แต่เพื่อความสมบูรณ์เราควรพูดถึงมัน -Here's the full syntax of these methods: +ไวยากรณ์เต็มรูปแบบของเมธอดเหล่านี้: ```js arr.find(func, thisArg); arr.filter(func, thisArg); arr.map(func, thisArg); // ... -// thisArg is the optional last argument +// thisArg คือพารามิเตอร์เสริมตัวสุดท้าย ``` -The value of `thisArg` parameter becomes `this` for `func`. +ค่าของพารามิเตอร์ `thisArg` จะกลายเป็น `this` ของ `func` -For example, here we use a method of `army` object as a filter, and `thisArg` passes the context: +ตัวอย่าง เราใช้เมธอดของออบเจ็กต์ `army` เป็นตัวกรอง และ `thisArg` ส่งบริบท: ```js run let army = { @@ -722,7 +722,7 @@ let users = [ ]; *!* -// find users, for who army.canJoin returns true +// หาผู้ใช้ที่ army.canJoin คืนค่า true let soldiers = users.filter(army.canJoin, army); */!* @@ -731,53 +731,53 @@ alert(soldiers[0].age); // 20 alert(soldiers[1].age); // 23 ``` -If in the example above we used `users.filter(army.canJoin)`, then `army.canJoin` would be called as a standalone function, with `this=undefined`, thus leading to an instant error. +ถ้าในตัวอย่างด้านบนเราใช้ `users.filter(army.canJoin)` แล้ว `army.canJoin` จะถูกเรียกเป็นฟังก์ชันแบบ standalone ที่มี `this=undefined` ซึ่งจะเกิดข้อผิดพลาดทันที -A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The latter is used more often, as it's a bit easier to understand for most people. +การเรียก `users.filter(army.canJoin, army)` สามารถแทนด้วย `users.filter(user => army.canJoin(user))` ซึ่งทำงานเหมือนกัน รูปแบบหลังใช้บ่อยกว่า เพราะเข้าใจง่ายกว่าสำหรับคนส่วนใหญ่ -## Summary +## สรุป -A cheat sheet of array methods: +สรุปเมธอดของอาร์เรย์: -- To add/remove elements: - - `push(...items)` -- adds items to the end, - - `pop()` -- extracts an item from the end, - - `shift()` -- extracts an item from the beginning, - - `unshift(...items)` -- adds items to the beginning. - - `splice(pos, deleteCount, ...items)` -- at index `pos` deletes `deleteCount` elements and inserts `items`. - - `slice(start, end)` -- creates a new array, copies elements from index `start` till `end` (not inclusive) into it. - - `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken. +- สำหรับเพิ่ม/ลบสมาชิก: + - `push(...items)` -- เพิ่มสมาชิกที่ท้ายอาร์เรย์ + - `pop()` -- ดึงสมาชิกออกจากท้ายอาร์เรย์ + - `shift()` -- ดึงสมาชิกออกจากต้นอาร์เรย์ + - `unshift(...items)` -- เพิ่มสมาชิกที่ต้นอาร์เรย์ + - `splice(pos, deleteCount, ...items)` -- ที่ index `pos` ลบ `deleteCount` สมาชิกและแทรก `items` + - `slice(start, end)` -- สร้างอาร์เรย์ใหม่ คัดลอกสมาชิกตั้งแต่ index `start` ถึง `end` (ไม่รวม) ลงไป + - `concat(...items)` -- คืนอาร์เรย์ใหม่: คัดลอกสมาชิกทั้งหมดจากอาร์เรย์ปัจจุบันและเพิ่ม `items` ต่อท้าย ถ้า `items` ตัวไหนเป็นอาร์เรย์ สมาชิกของมันจะถูกนำมาใช้ -- To search among elements: - - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found. - - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. - - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. - - `findIndex` is like `find`, but returns the index instead of a value. +- สำหรับค้นหาสมาชิก: + - `indexOf/lastIndexOf(item, pos)` -- ค้นหา `item` โดยเริ่มจากตำแหน่ง `pos` คืน index หรือ `-1` ถ้าไม่พบ + - `includes(value)` -- คืน `true` ถ้าอาร์เรย์มี `value` มิฉะนั้นคืน `false` + - `find/filter(func)` -- กรองสมาชิกผ่านฟังก์ชัน คืนค่าแรก/ทั้งหมดที่ทำให้ฟังก์ชันคืน `true` + - `findIndex` คล้ายกับ `find` แต่คืน index แทนค่า -- To iterate over elements: - - `forEach(func)` -- calls `func` for every element, does not return anything. +- สำหรับวนซ้ำสมาชิก: + - `forEach(func)` -- เรียก `func` สำหรับทุกสมาชิก ไม่คืนค่าใด -- To transform the array: - - `map(func)` -- creates a new array from results of calling `func` for every element. - - `sort(func)` -- sorts the array in-place, then returns it. - - `reverse()` -- reverses the array in-place, then returns it. - - `split/join` -- convert a string to array and back. - - `reduce/reduceRight(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. +- สำหรับแปลงอาร์เรย์: + - `map(func)` -- สร้างอาร์เรย์ใหม่จากผลลัพธ์ของการเรียก `func` สำหรับทุกสมาชิก + - `sort(func)` -- เรียงลำดับอาร์เรย์แบบ in-place แล้วคืนค่ากลับ + - `reverse()` -- กลับลำดับอาร์เรย์แบบ in-place แล้วคืนค่ากลับ + - `split/join` -- แปลงสตริงเป็นอาร์เรย์และกลับกัน + - `reduce/reduceRight(func, initial)` -- คำนวณค่าเดียวจากอาร์เรย์โดยเรียก `func` สำหรับทุกสมาชิกและส่งผลลัพธ์กลางระหว่างการเรียกแต่ละครั้ง -- Additionally: - - `Array.isArray(value)` checks `value` for being an array, if so returns `true`, otherwise `false`. +- นอกจากนี้: + - `Array.isArray(value)` ตรวจสอบว่า `value` เป็นอาร์เรย์หรือไม่ ถ้าใช่คืน `true` มิฉะนั้นคืน `false` -Please note that methods `sort`, `reverse` and `splice` modify the array itself. +โปรดสังเกตว่าเมธอด `sort`, `reverse` และ `splice` แก้ไขอาร์เรย์เดิมโดยตรง -These methods are the most used ones, they cover 99% of use cases. But there are few others: +เมธอดเหล่านี้เป็นเมธอดที่ใช้บ่อยที่สุด ครอบคลุม 99% ของกรณีการใช้งาน แต่ยังมีเมธอดอื่นๆ อีก: -- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) check the array. +- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) ตรวจสอบอาร์เรย์ - The function `fn` is called on each element of the array similar to `map`. If any/all results are `true`, returns `true`, otherwise `false`. + ฟังก์ชัน `fn` ถูกเรียกสำหรับสมาชิกแต่ละตัวในอาร์เรย์ คล้ายกับ `map` ถ้าผลลัพธ์บางตัว/ทุกตัวเป็น `true` จะคืน `true` มิฉะนั้นคืน `false` - These methods behave sort of like `||` and `&&` operators: if `fn` returns a truthy value, `arr.some()` immediately returns `true` and stops iterating over the rest of items; if `fn` returns a falsy value, `arr.every()` immediately returns `false` and stops iterating over the rest of items as well. + เมธอดเหล่านี้ทำงานคล้ายกับตัวดำเนินการ `||` และ `&&`: ถ้า `fn` คืนค่า truthy `arr.some()` จะคืน `true` ทันทีและหยุดวนซ้ำ ถ้า `fn` คืนค่า falsy `arr.every()` จะคืน `false` ทันทีและหยุดวนซ้ำเช่นกัน - We can use `every` to compare arrays: + เราสามารถใช้ `every` เพื่อเปรียบเทียบอาร์เรย์: ```js run function arraysEqual(arr1, arr2) { @@ -787,16 +787,16 @@ These methods are the most used ones, they cover 99% of use cases. But there are alert( arraysEqual([1, 2], [1, 2])); // true ``` -- [arr.fill(value, start, end)](mdn:js/Array/fill) -- fills the array with repeating `value` from index `start` to `end`. +- [arr.fill(value, start, end)](mdn:js/Array/fill) -- เติมอาร์เรย์ด้วยค่า `value` ซ้ำๆ ตั้งแต่ index `start` ถึง `end` -- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- copies its elements from position `start` till position `end` into *itself*, at position `target` (overwrites existing). +- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- คัดลอกสมาชิกจากตำแหน่ง `start` ถึง `end` ไปยัง *ตัวมันเอง* ที่ตำแหน่ง `target` (ทับค่าที่มีอยู่) -- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) create a new flat array from a multidimensional array. +- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) สร้างอาร์เรย์แบนใหม่จากอาร์เรย์หลายมิติ -For the full list, see the [manual](mdn:js/Array). +ดูรายการเต็มได้ที่ [เอกสารอ้างอิง](mdn:js/Array) -At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. +มองเผินๆ อาจรู้สึกว่ามีเมธอดเยอะมากจนจำยาก แต่จริงๆ แล้วไม่ได้ยากขนาดนั้น -Look through the cheat sheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods. +ผ่านตาสรุปเพื่อให้รู้ว่ามีอะไรบ้าง แล้วทำโจทย์ในบทนี้เพื่อฝึกฝน เพื่อให้มีประสบการณ์กับเมธอดของอาร์เรย์จริงๆ -Afterwards whenever you need to do something with an array, and you don't know how -- come here, look at the cheat sheet and find the right method. Examples will help you to write it correctly. Soon you'll automatically remember the methods, without specific efforts from your side. +หลังจากนั้น เมื่อใดก็ตามที่ต้องทำอะไรกับอาร์เรย์แต่ไม่รู้จะทำยังไง ก็มาดูสรุปตรงนี้แล้วหาเมธอดที่ใช่ ตัวอย่างจะช่วยให้เขียนได้อย่างถูกต้อง เดี๋ยวก็จะจำเมธอดต่างๆ ได้เองโดยไม่ต้องพยายาม diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index e2c0d4f97..c6f2a2c35 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -1,20 +1,20 @@ # Iterables -*Iterable* objects are a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop. +ออบเจ็กต์ที่เป็น *iterable* คือการขยายแนวคิดของอาร์เรย์ออกไป กล่าวคือ เป็นแนวคิดที่ช่วยให้เราสามารถใช้ออบเจ็กต์ใดก็ได้ใน `for..of` loop -Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable. +แน่นอนว่าอาร์เรย์เป็น iterable แต่ยังมี built-in object อื่นอีกมากมายที่เป็น iterable เช่นกัน อย่างสตริงก็เป็น iterable ด้วย -If an object isn't technically an array, but represents a collection (list, set) of something, then `for..of` is a great syntax to loop over it, so let's see how to make it work. +ถ้าออบเจ็กต์ไม่ใช่อาร์เรย์ในทางเทคนิค แต่แทนค่าคอลเล็กชัน (list, set) ของบางอย่าง การใช้ `for..of` ก็เป็นไวยากรณ์ที่ดีในการวนซ้ำ มาดูกันว่าจะทำให้มันใช้งานได้อย่างไร ## Symbol.iterator -We can easily grasp the concept of iterables by making one of our own. +ทำความเข้าใจแนวคิดของ iterable ได้ง่ายที่สุดด้วยการสร้างขึ้นมาเอง -For instance, we have an object that is not an array, but looks suitable for `for..of`. +สมมติว่าเรามีออบเจ็กต์ที่ไม่ใช่อาร์เรย์ แต่ดูเหมาะสมสำหรับ `for..of` -Like a `range` object that represents an interval of numbers: +เช่น ออบเจ็กต์ `range` ที่แทนช่วงของตัวเลข: ```js let range = { @@ -22,18 +22,18 @@ let range = { to: 5 }; -// We want the for..of to work: +// เราอยากให้ for..of ทำงานได้: // for(let num of range) ... num=1,2,3,4,5 ``` -To make the `range` object iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). +เพื่อให้ออบเจ็กต์ `range` เป็น iterable (และทำให้ `for..of` ใช้งานได้) เราต้องเพิ่มเมธอดชื่อ `Symbol.iterator` ให้กับออบเจ็กต์ (ซึ่งเป็น built-in symbol พิเศษที่มีไว้สำหรับเรื่องนี้โดยเฉพาะ) -1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`. -2. Onward, `for..of` works *only with that returned object*. -3. When `for..of` wants the next value, it calls `next()` on that object. -4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the loop is finished, otherwise `value` is the next value. +1. เมื่อ `for..of` เริ่มทำงาน จะเรียกเมธอดนั้นหนึ่งครั้ง (หรือเกิดข้อผิดพลาดถ้าไม่พบเมธอด) เมธอดนั้นต้องคืนค่าเป็น *iterator* ซึ่งก็คือออบเจ็กต์ที่มีเมธอด `next` +2. จากนั้น `for..of` จะทำงาน *กับออบเจ็กต์ที่ได้รับคืนมาเท่านั้น* +3. เมื่อ `for..of` ต้องการค่าถัดไป จะเรียก `next()` บนออบเจ็กต์นั้น +4. ผลลัพธ์ของ `next()` ต้องอยู่ในรูป `{done: Boolean, value: any}` โดย `done=true` หมายความว่าลูปสิ้นสุดแล้ว มิฉะนั้น `value` คือค่าถัดไป -Here's the full implementation for `range` with remarks: +นี่คือโค้ดที่ implement ครบถ้วนสำหรับ `range` พร้อมคำอธิบาย: ```js run let range = { @@ -41,18 +41,18 @@ let range = { to: 5 }; -// 1. call to for..of initially calls this +// 1. การเรียก for..of ครั้งแรกจะเรียกฟังก์ชันนี้ range[Symbol.iterator] = function() { - // ...it returns the iterator object: - // 2. Onward, for..of works only with the iterator object below, asking it for next values + // ...ซึ่งคืนค่าเป็นออบเจ็กต์ iterator: + // 2. จากนั้น for..of จะทำงานกับออบเจ็กต์ iterator ด้านล่างเท่านั้น โดยขอค่าถัดไปจากมัน return { current: this.from, last: this.to, - // 3. next() is called on each iteration by the for..of loop + // 3. next() จะถูกเรียกในแต่ละรอบของ for..of loop next() { - // 4. it should return the value as an object {done:.., value :...} + // 4. ต้องคืนค่าในรูปออบเจ็กต์ {done:.., value :...} if (this.current <= this.last) { return { done: false, value: this.current++ }; } else { @@ -62,22 +62,22 @@ range[Symbol.iterator] = function() { }; }; -// now it works! +// ตอนนี้ใช้งานได้แล้ว! for (let num of range) { - alert(num); // 1, then 2, 3, 4, 5 + alert(num); // 1, แล้วก็ 2, 3, 4, 5 } ``` -Please note the core feature of iterables: separation of concerns. +สิ่งสำคัญของ iterable คือการแยกหน้าที่กันอย่างชัดเจน (separation of concerns) -- The `range` itself does not have the `next()` method. -- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`, and its `next()` generates values for the iteration. +- ออบเจ็กต์ `range` เองไม่มีเมธอด `next()` +- แต่จะสร้างออบเจ็กต์อีกตัวที่เรียกว่า "iterator" ขึ้นมาจากการเรียก `range[Symbol.iterator]()` และเมธอด `next()` ของมันจะสร้างค่าสำหรับการวนซ้ำ -So, the iterator object is separate from the object it iterates over. +ดังนั้นออบเจ็กต์ iterator จึงแยกออกจากออบเจ็กต์ที่มันวนซ้ำ -Technically, we may merge them and use `range` itself as the iterator to make the code simpler. +ในทางเทคนิค เราสามารถรวมทั้งสองเข้าด้วยกัน และใช้ `range` เองเป็น iterator เพื่อให้โค้ดกระชับขึ้น -Like this: +แบบนี้: ```js run let range = { @@ -99,55 +99,55 @@ let range = { }; for (let num of range) { - alert(num); // 1, then 2, 3, 4, 5 + alert(num); // 1, แล้วก็ 2, 3, 4, 5 } ``` -Now `range[Symbol.iterator]()` returns the `range` object itself: it has the necessary `next()` method and remembers the current iteration progress in `this.current`. Shorter? Yes. And sometimes that's fine too. +ตอนนี้ `range[Symbol.iterator]()` คืนค่าเป็นออบเจ็กต์ `range` เอง ซึ่งมีเมธอด `next()` ที่จำเป็น และจำสถานะการวนซ้ำปัจจุบันไว้ใน `this.current` กระชับขึ้นไหม? ใช่เลย และบางครั้งก็เหมาะสมดีเช่นกัน -The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself. But two parallel for-ofs is a rare thing, even in async scenarios. +ข้อเสียคือตอนนี้จะมี `for..of` สองตัวรันพร้อมกันบนออบเจ็กต์เดียวกันไม่ได้ เพราะทั้งสองจะแบ่งปันสถานะการวนซ้ำร่วมกัน เนื่องจากมี iterator แค่ตัวเดียวคือออบเจ็กต์นั่นเอง แต่ว่า for-of แบบขนานสองตัวเป็นเรื่องที่พบได้น้อยมาก แม้แต่ในสถานการณ์ async ```smart header="Infinite iterators" -Infinite iterators are also possible. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful. +Infinite iterators ก็เป็นไปได้เช่นกัน ตัวอย่างเช่น `range` จะกลายเป็นอนันต์เมื่อกำหนด `range.to = Infinity` หรือเราอาจสร้างออบเจ็กต์ iterable ที่สร้างลำดับตัวเลขสุ่ม (pseudorandom) ต่อเนื่องไปเรื่อยๆ ก็ได้ ซึ่งก็มีประโยชน์เช่นกัน -There are no limitations on `next`, it can return more and more values, that's normal. +ไม่มีข้อจำกัดสำหรับ `next` มันสามารถคืนค่าได้เรื่อยๆ ซึ่งเป็นเรื่องปกติ -Of course, the `for..of` loop over such an iterable would be endless. But we can always stop it using `break`. +แน่นอนว่า `for..of` loop ที่วนซ้ำ iterable แบบนี้จะไม่มีวันสิ้นสุด แต่เราหยุดมันได้เสมอด้วย `break` ``` -## String is iterable +## สตริงเป็น iterable -Arrays and strings are most widely used built-in iterables. +อาร์เรย์และสตริงเป็น built-in iterables ที่ใช้บ่อยที่สุด -For a string, `for..of` loops over its characters: +สำหรับสตริง `for..of` จะวนซ้ำผ่านตัวอักษรแต่ละตัว: ```js run for (let char of "test") { - // triggers 4 times: once for each character - alert( char ); // t, then e, then s, then t + // ทำงาน 4 รอบ: หนึ่งรอบต่อหนึ่งตัวอักษร + alert( char ); // t, แล้วก็ e, แล้วก็ s, แล้วก็ t } ``` -And it works correctly with surrogate pairs! +และยังทำงานได้ถูกต้องกับ surrogate pairs อีกด้วย! ```js run let str = '𝒳😂'; for (let char of str) { - alert( char ); // 𝒳, and then 😂 + alert( char ); // 𝒳, แล้วก็ 😂 } ``` -## Calling an iterator explicitly +## การเรียก iterator โดยตรง -For deeper understanding, let's see how to use an iterator explicitly. +เพื่อความเข้าใจที่ลึกขึ้น มาดูวิธีใช้ iterator โดยตรงกัน -We'll iterate over a string in exactly the same way as `for..of`, but with direct calls. This code creates a string iterator and gets values from it "manually": +เราจะวนซ้ำสตริงแบบเดียวกันกับ `for..of` แต่ด้วยการเรียกโดยตรง โค้ดนี้สร้าง string iterator และดึงค่าออกมา "ด้วยมือ": ```js run let str = "Hello"; -// does the same as +// ทำงานเหมือนกับ // for (let char of str) alert(char); *!* @@ -157,49 +157,49 @@ let iterator = str[Symbol.iterator](); while (true) { let result = iterator.next(); if (result.done) break; - alert(result.value); // outputs characters one by one + alert(result.value); // แสดงตัวอักษรทีละตัว } ``` -That is rarely needed, but gives us more control over the process than `for..of`. For instance, we can split the iteration process: iterate a bit, then stop, do something else, and then resume later. +วิธีนี้ไม่ค่อยจำเป็นนัก แต่ให้การควบคุมกระบวนการมากกว่า `for..of` ตัวอย่างเช่น เราสามารถแบ่งกระบวนการวนซ้ำออกได้: วนซ้ำไปสักพัก หยุด ทำอย่างอื่น แล้วค่อยกลับมาทำต่อในภายหลัง -## Iterables and array-likes [#array-like] +## Iterables และ array-likes [#array-like] -Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion. +คำศัพท์สองคำนี้ดูเหมือนกัน แต่จริงๆ แล้วต่างกันมาก ควรทำความเข้าใจให้ดีเพื่อหลีกเลี่ยงความสับสน -- *Iterables* are objects that implement the `Symbol.iterator` method, as described above. -- *Array-likes* are objects that have indexes and `length`, so they look like arrays. +- *Iterables* คือออบเจ็กต์ที่ implement เมธอด `Symbol.iterator` ดังที่อธิบายไว้ข้างต้น +- *Array-likes* คือออบเจ็กต์ที่มี index และ `length` จึงดูเหมือนอาร์เรย์ -When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both. +เมื่อใช้ JavaScript สำหรับงานจริงในเบราว์เซอร์หรือสภาพแวดล้อมอื่น เราอาจพบออบเจ็กต์ที่เป็น iterable หรือ array-like หรือทั้งสองอย่าง -For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`). +ตัวอย่างเช่น สตริงเป็นทั้ง iterable (`for..of` ใช้ได้กับมัน) และ array-like (มี numeric index และ `length`) -But an iterable may not be array-like. And vice versa an array-like may not be iterable. +แต่ iterable อาจไม่ใช่ array-like ก็ได้ และในทางกลับกัน array-like ก็อาจไม่ใช่ iterable ก็ได้ -For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`. +ตัวอย่างเช่น `range` ในตัวอย่างข้างต้นเป็น iterable แต่ไม่ใช่ array-like เพราะไม่มีพร็อพเพอร์ตี้แบบ index และ `length` -And here's the object that is array-like, but not iterable: +และนี่คือออบเจ็กต์ที่เป็น array-like แต่ไม่ใช่ iterable: ```js run -let arrayLike = { // has indexes and length => array-like +let arrayLike = { // มี index และ length => array-like 0: "Hello", 1: "World", length: 2 }; *!* -// Error (no Symbol.iterator) +// เกิดข้อผิดพลาด (ไม่มี Symbol.iterator) for (let item of arrayLike) {} */!* ``` -Both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we have such an object and want to work with it as with an array. E.g. we would like to work with `range` using array methods. How to achieve that? +ทั้ง iterable และ array-like มักจะ *ไม่ใช่อาร์เรย์* ไม่มีเมธอด `push`, `pop` เป็นต้น ซึ่งค่อนข้างไม่สะดวกถ้าเรามีออบเจ็กต์แบบนั้นและต้องการทำงานกับมันเหมือนกับอาร์เรย์ เช่น ถ้าอยากใช้ array methods กับ `range` จะทำอย่างไร? ## Array.from -There's a universal method [Array.from](mdn:js/Array/from) that takes an iterable or array-like value and makes a "real" `Array` from it. Then we can call array methods on it. +มีเมธอด [Array.from](mdn:js/Array/from) ที่รับ iterable หรือ array-like และสร้างอาร์เรย์ "จริงๆ" จากมัน จากนั้นเราก็เรียก array methods ได้ -For instance: +ตัวอย่างเช่น: ```js run let arrayLike = { @@ -211,43 +211,43 @@ let arrayLike = { *!* let arr = Array.from(arrayLike); // (*) */!* -alert(arr.pop()); // World (method works) +alert(arr.pop()); // World (เมธอดทำงานได้แล้ว) ``` -`Array.from` at the line `(*)` takes the object, examines it for being an iterable or array-like, then makes a new array and copies all items to it. +`Array.from` ที่บรรทัด `(*)` รับออบเจ็กต์มา ตรวจสอบว่าเป็น iterable หรือ array-like แล้วสร้างอาร์เรย์ใหม่และคัดลอกทุกรายการเข้าไป -The same happens for an iterable: +เหมือนกันสำหรับ iterable: ```js run -// assuming that range is taken from the example above +// สมมติว่า range มาจากตัวอย่างข้างต้น let arr = Array.from(range); -alert(arr); // 1,2,3,4,5 (array toString conversion works) +alert(arr); // 1,2,3,4,5 (การแปลง array toString ทำงานได้) ``` -The full syntax for `Array.from` also allows us to provide an optional "mapping" function: +ไวยากรณ์ครบถ้วนของ `Array.from` ยังรองรับอาร์กิวเมนต์ "mapping" เพิ่มเติมได้: ```js Array.from(obj[, mapFn, thisArg]) ``` -The optional second argument `mapFn` can be a function that will be applied to each element before adding it to the array, and `thisArg` allows us to set `this` for it. +อาร์กิวเมนต์ที่สองแบบเลือกได้คือ `mapFn` ซึ่งเป็นฟังก์ชันที่จะถูกนำไปใช้กับแต่ละ element ก่อนที่จะเพิ่มเข้าไปในอาร์เรย์ และ `thisArg` ช่วยให้เรากำหนดค่า `this` ให้กับมันได้ -For instance: +ตัวอย่างเช่น: ```js run -// assuming that range is taken from the example above +// สมมติว่า range มาจากตัวอย่างข้างต้น -// square each number +// ยกกำลังสองตัวเลขแต่ละตัว let arr = Array.from(range, num => num * num); alert(arr); // 1,4,9,16,25 ``` -Here we use `Array.from` to turn a string into an array of characters: +ที่นี่เราใช้ `Array.from` เพื่อแปลงสตริงให้เป็นอาร์เรย์ของตัวอักษร: ```js run let str = '𝒳😂'; -// splits str into array of characters +// แยก str ออกเป็นอาร์เรย์ของตัวอักษร let chars = Array.from(str); alert(chars[0]); // 𝒳 @@ -255,14 +255,14 @@ alert(chars[1]); // 😂 alert(chars.length); // 2 ``` -Unlike `str.split`, it relies on the iterable nature of the string and so, just like `for..of`, correctly works with surrogate pairs. +ต่างจาก `str.split` ตรงที่อาศัยคุณสมบัติ iterable ของสตริง ดังนั้นเหมือนกับ `for..of` จึงทำงานได้ถูกต้องกับ surrogate pairs -Technically here it does the same as: +ในทางเทคนิค มันทำสิ่งเดียวกับ: ```js run let str = '𝒳😂'; -let chars = []; // Array.from internally does the same loop +let chars = []; // Array.from ทำลูปแบบเดียวกันภายใน for (let char of str) { chars.push(char); } @@ -270,9 +270,9 @@ for (let char of str) { alert(chars); ``` -...But it is shorter. +...แต่กระชับกว่า -We can even build surrogate-aware `slice` on it: +เรายังสามารถสร้างฟังก์ชัน `slice` ที่รองรับ surrogate pairs ได้อีกด้วย: ```js run function slice(str, start, end) { @@ -283,25 +283,25 @@ let str = '𝒳😂𩷶'; alert( slice(str, 1, 3) ); // 😂𩷶 -// the native method does not support surrogate pairs -alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs) +// เมธอดดั้งเดิมไม่รองรับ surrogate pairs +alert( str.slice(1, 3) ); // ขยะ (สองชิ้นจาก surrogate pairs ต่างคู่กัน) ``` -## Summary +## สรุป -Objects that can be used in `for..of` are called *iterable*. +ออบเจ็กต์ที่ใช้ใน `for..of` ได้เรียกว่า *iterable* -- Technically, iterables must implement the method named `Symbol.iterator`. - - The result of `obj[Symbol.iterator]()` is called an *iterator*. It handles further iteration process. - - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value. -- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly. -- Built-in iterables like strings or arrays, also implement `Symbol.iterator`. -- String iterator knows about surrogate pairs. +- ในทางเทคนิค iterable ต้องมีเมธอดชื่อ `Symbol.iterator` + - ผลลัพธ์ของ `obj[Symbol.iterator]()` เรียกว่า *iterator* ซึ่งจัดการกระบวนการวนซ้ำต่อไป + - iterator ต้องมีเมธอดชื่อ `next()` ที่คืนค่าเป็นออบเจ็กต์ `{done: Boolean, value: any}` โดย `done:true` บ่งบอกว่ากระบวนการวนซ้ำสิ้นสุดแล้ว มิฉะนั้น `value` คือค่าถัดไป +- เมธอด `Symbol.iterator` ถูกเรียกอัตโนมัติโดย `for..of` แต่เราก็เรียกมันโดยตรงได้เช่นกัน +- Built-in iterables เช่นสตริงหรืออาร์เรย์ก็มี `Symbol.iterator` ให้มาด้วย +- String iterator รู้จัก surrogate pairs -Objects that have indexed properties and `length` are called *array-like*. Such objects may also have other properties and methods, but lack the built-in methods of arrays. +ออบเจ็กต์ที่มีพร็อพเพอร์ตี้แบบ index และ `length` เรียกว่า *array-like* ออบเจ็กต์เหล่านี้อาจมีพร็อพเพอร์ตี้และเมธอดอื่นด้วย แต่ขาด built-in methods ของอาร์เรย์ -If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract. +ถ้าลองดูใน specification จะเห็นว่า built-in methods ส่วนใหญ่ถูกออกแบบมาให้ทำงานกับ iterable หรือ array-like แทน "real" arrays เพราะนั่นเป็นแนวคิดที่เป็นนามธรรมมากกว่า -`Array.from(obj[, mapFn, thisArg])` makes a real `Array` from an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. +`Array.from(obj[, mapFn, thisArg])` สร้างอาร์เรย์ "จริงๆ" จาก iterable หรือ array-like `obj` จากนั้นเราสามารถเรียก array methods บนมันได้ อาร์กิวเมนต์เพิ่มเติม `mapFn` และ `thisArg` ช่วยให้เราใช้ฟังก์ชันกับแต่ละรายการได้ diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index 37f5e48c2..5a72147af 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -1,97 +1,97 @@ -# Map and Set +# Map และ Set -Till now, we've learned about the following complex data structures: +ที่ผ่านมา เราได้เรียนรู้โครงสร้างข้อมูลที่ซับซ้อนดังนี้: -- Objects are used for storing keyed collections. -- Arrays are used for storing ordered collections. +- ออบเจ็กต์ ใช้สำหรับเก็บคอลเล็กชันแบบมี key +- อาร์เรย์ ใช้สำหรับเก็บคอลเล็กชันที่มีลำดับ -But that's not enough for real life. That's why `Map` and `Set` also exist. +แต่สำหรับงานจริงในชีวิตประจำวัน แค่นี้ยังไม่พอ นั่นจึงเป็นเหตุที่ `Map` และ `Set` ถือกำเนิดขึ้นมา ## Map -[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) คือคอลเล็กชันของข้อมูลแบบมี key คล้ายกับ `Object` แต่ข้อแตกต่างสำคัญคือ `Map` อนุญาตให้ใช้ key เป็น **ชนิดข้อมูลใดก็ได้** -Methods and properties are: +เมธอดและพร็อพเพอร์ตี้ที่มี: -- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map. -- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key. -- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. -- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element (the key/value pair) by the key. -- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. -- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- สร้าง map ใหม่ +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- บันทึกค่า value ด้วย key ที่กำหนด +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- คืนค่า value ตาม key ที่กำหนด หากไม่มี key นั้นจะคืนค่า `undefined` +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- คืนค่า `true` ถ้า key มีอยู่ใน map, `false` ถ้าไม่มี +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- ลบ element (คู่ key/value) ตาม key ที่กำหนด +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- ลบทุกอย่างออกจาก map +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- คืนค่าจำนวน element ปัจจุบัน -For instance: +ตัวอย่าง: ```js run let map = new Map(); -map.set('1', 'str1'); // a string key -map.set(1, 'num1'); // a numeric key -map.set(true, 'bool1'); // a boolean key +map.set('1', 'str1'); // key เป็น string +map.set(1, 'num1'); // key เป็นตัวเลข +map.set(true, 'bool1'); // key เป็น boolean -// remember the regular Object? it would convert keys to string -// Map keeps the type, so these two are different: +// จำ Object ธรรมดาได้ไหม? มันจะแปลง key ให้เป็น string เสมอ +// แต่ Map เก็บชนิดข้อมูลไว้ ดังนั้นสองตัวนี้ต่างกัน: alert( map.get(1) ); // 'num1' alert( map.get('1') ); // 'str1' alert( map.size ); // 3 ``` -As we can see, unlike objects, keys are not converted to strings. Any type of key is possible. +จะเห็นว่าต่างจาก Object ตรงที่ Map ไม่แปลง key ให้เป็น string key จะเป็นชนิดใดก็ได้ทั้งนั้น -```smart header="`map[key]` isn't the right way to use a `Map`" -Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (only string/symbol keys and so on). +```smart header="`map[key]` ไม่ใช่วิธีที่ถูกต้องในการใช้ `Map`" +แม้ว่า `map[key]` จะใช้ได้ เช่น `map[key] = 2` แต่นั่นคือการใช้ `map` เสมือน plain JavaScript object ทั่วไป ซึ่งมีข้อจำกัดทั้งหมดของ Object (key ได้แค่ string หรือ symbol เป็นต้น) -So we should use `map` methods: `set`, `get` and so on. +ดังนั้น ควรใช้เมธอดของ `map` แทน ได้แก่ `set`, `get` และอื่นๆ ``` -**Map can also use objects as keys.** +**Map ยังสามารถใช้ออบเจ็กต์เป็น key ได้ด้วย** -For instance: +ตัวอย่าง: ```js run let john = { name: "John" }; -// for every user, let's store their visits count +// เก็บจำนวนครั้งที่ผู้ใช้แต่ละคนเข้าเยี่ยมชม let visitsCountMap = new Map(); -// john is the key for the map +// john เป็น key ของ map visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` -Using objects as keys is one of the most notable and important `Map` features. The same does not count for `Object`. String as a key in `Object` is fine, but we can't use another `Object` as a key in `Object`. +การใช้ออบเจ็กต์เป็น key นี้คือหนึ่งในฟีเจอร์ที่โดดเด่นและสำคัญที่สุดของ `Map` ซึ่ง `Object` ทำไม่ได้ การใช้ string เป็น key ใน `Object` นั้นโอเค แต่เราไม่สามารถใช้ `Object` อื่นเป็น key ใน `Object` ได้ -Let's try: +ลองดูตัวอย่าง: ```js run let john = { name: "John" }; let ben = { name: "Ben" }; -let visitsCountObj = {}; // try to use an object +let visitsCountObj = {}; // ลองใช้ object ธรรมดา -visitsCountObj[ben] = 234; // try to use ben object as the key -visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced +visitsCountObj[ben] = 234; // ลองใช้ ben object เป็น key +visitsCountObj[john] = 123; // ลองใช้ john object เป็น key ซึ่งจะทับ ben object *!* -// That's what got written! -alert( visitsCountObj["[object Object]"] ); // 123 +// ผลลัพธ์ที่ได้จริงๆ คือ: +alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` -As `visitsCountObj` is an object, it converts all `Object` keys, such as `john` and `ben` above, to same string `"[object Object]"`. Definitely not what we want. +เนื่องจาก `visitsCountObj` เป็น object ธรรมดา มันจึงแปลง Object ทุกตัวที่เป็น key ไม่ว่าจะเป็น `john` หรือ `ben` ให้กลายเป็น string เดียวกันคือ `"[object Object]"` ซึ่งไม่ใช่สิ่งที่เราต้องการแน่นอน -```smart header="How `Map` compares keys" -To test keys for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well. +```smart header="`Map` เปรียบเทียบ key อย่างไร" +`Map` ใช้อัลกอริทึม [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero) ในการตรวจสอบความเท่ากันของ key ซึ่งคล้ายกับ strict equality `===` แต่ต่างตรงที่ `NaN` ถือว่าเท่ากับ `NaN` จึงสามารถใช้ `NaN` เป็น key ได้เช่นกัน -This algorithm can't be changed or customized. +อัลกอริทึมนี้ไม่สามารถเปลี่ยนหรือปรับแต่งได้ ``` -````smart header="Chaining" -Every `map.set` call returns the map itself, so we can "chain" the calls: +````smart header="การเชื่อมต่อเมธอด (Chaining)" +ทุกครั้งที่เรียก `map.set` จะได้รับ map กลับคืนมา จึงสามารถ "เชื่อม" การเรียกต่อกันได้แบบนี้: ```js map.set('1', 'str1') @@ -100,15 +100,15 @@ map.set('1', 'str1') ``` ```` -## Iteration over Map +## การวนซ้ำบน Map -For looping over a `map`, there are 3 methods: +สำหรับการวนซ้ำบน `map` มีเมธอดให้เลือก 3 แบบ: -- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- returns an iterable for keys, -- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- returns an iterable for values, -- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- คืนค่า iterable สำหรับ key ทั้งหมด +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- คืนค่า iterable สำหรับ value ทั้งหมด +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- คืนค่า iterable สำหรับ entry ในรูป `[key, value]` ซึ่งเป็นค่าที่ `for..of` ใช้โดยค่าเริ่มต้น -For instance: +ตัวอย่าง: ```js run let recipeMap = new Map([ @@ -117,41 +117,41 @@ let recipeMap = new Map([ ['onion', 50] ]); -// iterate over keys (vegetables) +// วนซ้ำบน key (ผัก) for (let vegetable of recipeMap.keys()) { alert(vegetable); // cucumber, tomatoes, onion } -// iterate over values (amounts) +// วนซ้ำบน value (ปริมาณ) for (let amount of recipeMap.values()) { alert(amount); // 500, 350, 50 } -// iterate over [key, value] entries -for (let entry of recipeMap) { // the same as of recipeMap.entries() - alert(entry); // cucumber,500 (and so on) +// วนซ้ำบน entry แบบ [key, value] +for (let entry of recipeMap) { // เหมือนกับ recipeMap.entries() + alert(entry); // cucumber,500 (และอื่นๆ) } ``` -```smart header="The insertion order is used" -The iteration goes in the same order as the values were inserted. `Map` preserves this order, unlike a regular `Object`. +```smart header="ลำดับการแทรกถูกนำมาใช้" +การวนซ้ำจะเป็นลำดับเดียวกับที่แทรกค่าเข้าไป `Map` รักษาลำดับนี้ไว้ ต่างจาก Object ธรรมดา ``` -Besides that, `Map` has a built-in `forEach` method, similar to `Array`: +นอกจากนี้ `Map` ยังมีเมธอด `forEach` แบบ built-in คล้ายกับ `Array`: ```js -// runs the function for each (key, value) pair +// เรียกฟังก์ชันสำหรับแต่ละคู่ (key, value) recipeMap.forEach( (value, key, map) => { - alert(`${key}: ${value}`); // cucumber: 500 etc + alert(`${key}: ${value}`); // cucumber: 500 เป็นต้น }); ``` -## Object.entries: Map from Object +## Object.entries: สร้าง Map จาก Object -When a `Map` is created, we can pass an array (or another iterable) with key/value pairs for initialization, like this: +เมื่อสร้าง `Map` เราสามารถส่งอาร์เรย์ (หรือ iterable อื่นๆ) ที่มีคู่ key/value เพื่อใช้กำหนดค่าเริ่มต้นได้ดังนี้: ```js run -// array of [key, value] pairs +// อาร์เรย์ของคู่ [key, value] let map = new Map([ ['1', 'str1'], [1, 'num1'], @@ -161,9 +161,9 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) that returns an array of key/value pairs for an object exactly in that format. +ถ้ามี plain object อยู่แล้วและต้องการสร้าง `Map` จากมัน เราสามารถใช้เมธอด built-in [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) ซึ่งคืนค่าอาร์เรย์ของคู่ key/value ในรูปแบบที่ต้องการพอดี -So we can create a map from an object like this: +ดังนั้น เราสร้าง map จาก object ได้แบบนี้: ```js run let obj = { @@ -178,14 +178,14 @@ let map = new Map(Object.entries(obj)); alert( map.get('name') ); // John ``` -Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs. +ที่นี่ `Object.entries` คืนค่าอาร์เรย์ของคู่ key/value: `[ ["name","John"], ["age", 30] ]` ซึ่งเป็นสิ่งที่ `Map` ต้องการ -## Object.fromEntries: Object from Map +## Object.fromEntries: สร้าง Object จาก Map -We've just seen how to create `Map` from a plain object with `Object.entries(obj)`. +เราเพิ่งเห็นวิธีสร้าง `Map` จาก plain object ด้วย `Object.entries(obj)` ไปแล้ว -There's `Object.fromEntries` method that does the reverse: given an array of `[key, value]` pairs, it creates an object from them: +ยังมีเมธอด `Object.fromEntries` ที่ทำสิ่งตรงกันข้าม: รับอาร์เรย์ของคู่ `[key, value]` แล้วสร้าง object จากมัน: ```js run let prices = Object.fromEntries([ @@ -194,16 +194,16 @@ let prices = Object.fromEntries([ ['meat', 4] ]); -// now prices = { banana: 1, orange: 2, meat: 4 } +// ตอนนี้ prices = { banana: 1, orange: 2, meat: 4 } alert(prices.orange); // 2 ``` -We can use `Object.fromEntries` to get a plain object from `Map`. +เราใช้ `Object.fromEntries` เพื่อแปลง `Map` กลับเป็น plain object ได้ -E.g. we store the data in a `Map`, but we need to pass it to a 3rd-party code that expects a plain object. +เช่น เก็บข้อมูลไว้ใน `Map` แต่ต้องส่งต่อให้โค้ดของ third-party ที่รับได้แค่ plain object -Here we go: +ทำได้แบบนี้: ```js run let map = new Map(); @@ -212,42 +212,42 @@ map.set('orange', 2); map.set('meat', 4); *!* -let obj = Object.fromEntries(map.entries()); // make a plain object (*) +let obj = Object.fromEntries(map.entries()); // แปลงเป็น plain object (*) */!* -// done! +// เสร็จแล้ว! // obj = { banana: 1, orange: 2, meat: 4 } alert(obj.orange); // 2 ``` -A call to `map.entries()` returns an iterable of key/value pairs, exactly in the right format for `Object.fromEntries`. +การเรียก `map.entries()` คืนค่า iterable ของคู่ key/value ในรูปแบบที่ `Object.fromEntries` ต้องการพอดี -We could also make line `(*)` shorter: +เราย่อบรรทัด `(*)` ให้สั้นลงได้อีก: ```js -let obj = Object.fromEntries(map); // omit .entries() +let obj = Object.fromEntries(map); // ละ .entries() ออก ``` -That's the same, because `Object.fromEntries` expects an iterable object as the argument. Not necessarily an array. And the standard iteration for `map` returns same key/value pairs as `map.entries()`. So we get a plain object with same key/values as the `map`. +ผลลัพธ์เหมือนกัน เพราะ `Object.fromEntries` รับ iterable object เป็น argument ไม่จำเป็นต้องเป็นอาร์เรย์เสมอไป และการวนซ้ำมาตรฐานของ `map` คืนค่าคู่ key/value เหมือนกับ `map.entries()` ทุกประการ ผลลัพธ์จึงเป็น plain object ที่มี key/value เดียวกับ `map` ## Set -A [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) is a special type collection - "set of values" (without keys), where each value may occur only once. +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) คือคอลเล็กชันชนิดพิเศษ — "ชุดของค่า" (ไม่มี key) ที่ **แต่ละค่าสามารถมีได้เพียงครั้งเดียว** -Its main methods are: +เมธอดหลักที่มี: -- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. -- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value, returns the set itself. -- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. -- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. -- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- สร้าง set ใหม่ ถ้าส่ง `iterable` มาด้วย (ปกติเป็นอาร์เรย์) จะคัดลอกค่าจากมันเข้า set +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- เพิ่มค่า และคืนค่า set กลับมา +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- ลบค่า คืนค่า `true` ถ้า value มีอยู่ตอนที่เรียก มิฉะนั้นคืน `false` +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- คืนค่า `true` ถ้าค่ามีอยู่ใน set มิฉะนั้นคืน `false` +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- ลบทุกอย่างออกจาก set +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- จำนวน element ทั้งหมด -The main feature is that repeated calls of `set.add(value)` with the same value don't do anything. That's the reason why each value appears in a `Set` only once. +จุดเด่นสำคัญคือการเรียก `set.add(value)` ซ้ำๆ ด้วยค่าเดิมจะไม่มีผลใดๆ นั่นเป็นเหตุให้แต่ละค่าปรากฏใน `Set` ได้เพียงครั้งเดียว -For example, we have visitors coming, and we'd like to remember everyone. But repeated visits should not lead to duplicates. A visitor must be "counted" only once. +สมมติว่ามีผู้เยี่ยมชมเข้ามา และเราต้องการจำทุกคน แต่การเข้าซ้ำไม่ควรทำให้มีข้อมูลซ้ำ ผู้เยี่ยมชมคนหนึ่งควร "นับ" แค่ครั้งเดียว -`Set` is just the right thing for that: +`Set` เหมาะกับงานนี้พอดี: ```js run let set = new Set(); @@ -256,76 +256,76 @@ let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; -// visits, some users come multiple times +// ผู้เยี่ยมชม บางคนมาหลายครั้ง set.add(john); set.add(pete); set.add(mary); set.add(john); set.add(mary); -// set keeps only unique values +// set เก็บเฉพาะค่าที่ไม่ซ้ำกัน alert( set.size ); // 3 for (let user of set) { - alert(user.name); // John (then Pete and Mary) + alert(user.name); // John (แล้วก็ Pete และ Mary) } ``` -The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. +อีกทางเลือกหนึ่งแทน `Set` อาจเป็นอาร์เรย์ของผู้ใช้ แล้วเขียนโค้ดตรวจสอบซ้ำทุกครั้งที่เพิ่มด้วย [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) แต่ประสิทธิภาพจะแย่กว่ามาก เพราะเมธอดนี้ต้องวนผ่านอาร์เรย์ทั้งหมดเพื่อตรวจสอบทุก element ในขณะที่ `Set` ถูกออกแบบมาให้ตรวจสอบความเป็นเอกลักษณ์ได้รวดเร็วกว่ามาก -## Iteration over Set +## การวนซ้ำบน Set -We can loop over a set either with `for..of` or using `forEach`: +วนซ้ำบน set ได้ด้วย `for..of` หรือ `forEach`: ```js run let set = new Set(["oranges", "apples", "bananas"]); for (let value of set) alert(value); -// the same with forEach: +// แบบเดียวกันกับ forEach: set.forEach((value, valueAgain, set) => { alert(value); }); ``` -Note the funny thing. The callback function passed in `forEach` has 3 arguments: a `value`, then *the same value* `valueAgain`, and then the target object. Indeed, the same value appears in the arguments twice. +สังเกตสิ่งที่น่าแปลกใจ — ฟังก์ชัน callback ที่ส่งให้ `forEach` มี 3 argument คือ `value`, *ค่าเดิมอีกครั้ง* `valueAgain`, และ object เป้าหมาย ซึ่งค่าเดิมปรากฏในอาร์กิวเมนต์ถึงสองครั้ง -That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But this may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +ที่เป็นแบบนี้เพื่อให้เข้ากันได้กับ `Map` ซึ่ง callback ของ `forEach` มีสาม argument ดูแปลกๆ นิดหน่อย แต่ช่วยให้สลับระหว่าง `Map` กับ `Set` ได้อย่างง่ายดายในบางกรณี -The same methods `Map` has for iterators are also supported: +เมธอดสำหรับ iterator ที่ `Map` มีก็รองรับใน `Set` ด้วย: -- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- returns an iterable object for values, -- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- same as `set.keys()`, for compatibility with `Map`, -- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- คืนค่า iterable object สำหรับ value ทั้งหมด +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- เหมือนกับ `set.keys()` ไว้รองรับความเข้ากันได้กับ `Map` +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- คืนค่า iterable object สำหรับ entry ในรูป `[value, value]` ไว้รองรับความเข้ากันได้กับ `Map` -## Summary +## สรุป -[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- is a collection of keyed values. +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- คือคอลเล็กชันของค่าแบบมี key -Methods and properties: +เมธอดและพร็อพเพอร์ตี้: -- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. -- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key, returns the map itself. -- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. -- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`. -- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. -- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- สร้าง map ใหม่ โดยมี `iterable` (เช่น อาร์เรย์) ของคู่ `[key,value]` สำหรับกำหนดค่าเริ่มต้นได้ (ไม่บังคับ) +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- บันทึกค่า value ด้วย key และคืนค่า map กลับมา +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- คืนค่า value ตาม key, `undefined` ถ้าไม่มี key นั้น +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- คืนค่า `true` ถ้า key มีอยู่, `false` ถ้าไม่มี +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- ลบ element ตาม key คืนค่า `true` ถ้า key มีอยู่ตอนที่เรียก มิฉะนั้นคืน `false` +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- ลบทุกอย่างออกจาก map +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- คืนค่าจำนวน element ปัจจุบัน -The differences from a regular `Object`: +ข้อแตกต่างจาก `Object` ธรรมดา: -- Any keys, objects can be keys. -- Additional convenient methods, the `size` property. +- key เป็นชนิดใดก็ได้ รวมถึงออบเจ็กต์ +- มีเมธอดอำนวยความสะดวกเพิ่มเติม และพร็อพเพอร์ตี้ `size` -[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- is a collection of unique values. +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- คือคอลเล็กชันของค่าที่ไม่ซ้ำกัน -Methods and properties: +เมธอดและพร็อพเพอร์ตี้: -- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, with optional `iterable` (e.g. array) of values for initialization. -- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value (does nothing if `value` exists), returns the set itself. -- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. -- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. -- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- สร้าง set ใหม่ โดยมี `iterable` (เช่น อาร์เรย์) ของค่าสำหรับกำหนดค่าเริ่มต้นได้ (ไม่บังคับ) +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- เพิ่มค่า (ไม่ทำอะไรถ้า value มีอยู่แล้ว) คืนค่า set กลับมา +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- ลบค่า คืนค่า `true` ถ้า value มีอยู่ตอนที่เรียก มิฉะนั้นคืน `false` +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- คืนค่า `true` ถ้าค่ามีอยู่ใน set มิฉะนั้นคืน `false` +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- ลบทุกอย่างออกจาก set +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- จำนวน element ทั้งหมด -Iteration over `Map` and `Set` is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number. +การวนซ้ำบน `Map` และ `Set` จะเป็นลำดับเดียวกับที่แทรกค่าเข้าไปเสมอ เราจึงพูดไม่ได้ว่าคอลเล็กชันเหล่านี้ไม่มีลำดับ แต่ก็ไม่สามารถจัดเรียงใหม่หรือดึง element โดยตรงจากหมายเลขลำดับได้ diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 9795017d4..415894cde 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,46 +1,46 @@ -# WeakMap and WeakSet +# WeakMap และ WeakSet -As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. +จากที่เรารู้กันในบท JavaScript engine จะเก็บค่าไว้ในหน่วยความจำตราบเท่าที่ค่านั้น "ยังเข้าถึงได้" และอาจถูกนำไปใช้งาน -For instance: +ตัวอย่างเช่น: ```js let john = { name: "John" }; -// the object can be accessed, john is the reference to it +// เข้าถึงออบเจ็กต์ได้ผ่าน john ซึ่งเป็น reference ของมัน -// overwrite the reference +// เขียนทับ reference john = null; *!* -// the object will be removed from memory +// ออบเจ็กต์จะถูกลบออกจากหน่วยความจำ */!* ``` -Usually, properties of an object or elements of an array or another data structure are considered reachable and kept in memory while that data structure is in memory. +โดยทั่วไปแล้ว พร็อพเพอร์ตี้ของออบเจ็กต์ หรือสมาชิกของอาร์เรย์ หรือโครงสร้างข้อมูลอื่นๆ จะถือว่า "เข้าถึงได้" และจะถูกเก็บไว้ในหน่วยความจำตราบเท่าที่โครงสร้างข้อมูลนั้นยังคงอยู่ -For instance, if we put an object into an array, then while the array is alive, the object will be alive as well, even if there are no other references to it. +ตัวอย่างเช่น ถ้าเราใส่ออบเจ็กต์ลงในอาร์เรย์ ตราบใดที่อาร์เรย์ยังคงอยู่ ออบเจ็กต์นั้นก็จะยังคงอยู่ด้วย แม้จะไม่มี reference อื่นชี้ไปที่มันแล้วก็ตาม -Like this: +แบบนี้: ```js let john = { name: "John" }; let array = [ john ]; -john = null; // overwrite the reference +john = null; // เขียนทับ reference *!* -// the object previously referenced by john is stored inside the array -// therefore it won't be garbage-collected -// we can get it as array[0] +// ออบเจ็กต์ที่ john เคยชี้ไป ยังคงอยู่ในอาร์เรย์ +// ดังนั้นมันจะไม่ถูก garbage-collected +// เราเข้าถึงมันได้ผ่าน array[0] */!* ``` -Similar to that, if we use an object as the key in a regular `Map`, then while the `Map` exists, that object exists as well. It occupies memory and may not be garbage collected. +ในทำนองเดียวกัน ถ้าเราใช้ออบเจ็กต์เป็น key ใน `Map` ปกติ ตราบใดที่ `Map` ยังคงอยู่ ออบเจ็กต์นั้นก็จะยังคงอยู่ด้วย มันจะครอบครองหน่วยความจำและไม่อาจถูก garbage collect -For instance: +ตัวอย่าง: ```js let john = { name: "John" }; @@ -48,36 +48,36 @@ let john = { name: "John" }; let map = new Map(); map.set(john, "..."); -john = null; // overwrite the reference +john = null; // เขียนทับ reference *!* -// john is stored inside the map, -// we can get it by using map.keys() +// john ยังคงอยู่ใน map +// เราเข้าถึงมันได้ผ่าน map.keys() */!* ``` -[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) ทำงานต่างออกไปในแง่นี้โดยสิ้นเชิง มันไม่ขัดขวางการ garbage-collect ของออบเจ็กต์ที่เป็น key -Let's see what it means on examples. +มาดูกันว่าหมายความว่าอะไรผ่านตัวอย่าง ## WeakMap -The first difference between [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is that keys must be objects, not primitive values: +ความแตกต่างประการแรกระหว่าง [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) และ [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) คือ key ต้องเป็นออบเจ็กต์เท่านั้น ไม่ใช่ค่า primitive: ```js run let weakMap = new WeakMap(); let obj = {}; -weakMap.set(obj, "ok"); // works fine (object key) +weakMap.set(obj, "ok"); // ใช้งานได้ปกติ (key เป็นออบเจ็กต์) *!* -// can't use a string as the key -weakMap.set("test", "Whoops"); // Error, because "test" is not an object +// ไม่สามารถใช้สตริงเป็น key ได้ +weakMap.set("test", "Whoops"); // เกิดข้อผิดพลาด เพราะ "test" ไม่ใช่ออบเจ็กต์ */!* ``` -Now, if we use an object as the key in it, and there are no other references to that object -- it will be removed from memory (and from the map) automatically. +ทีนี้ ถ้าเราใช้ออบเจ็กต์เป็น key และไม่มี reference อื่นชี้ไปที่ออบเจ็กต์นั้นอีกแล้ว ออบเจ็กต์นั้นจะถูกลบออกจากหน่วยความจำ (และจาก map) โดยอัตโนมัติ ```js let john = { name: "John" }; @@ -85,103 +85,103 @@ let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); -john = null; // overwrite the reference +john = null; // เขียนทับ reference -// john is removed from memory! +// john ถูกลบออกจากหน่วยความจำแล้ว! ``` -Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it will be automatically deleted from the map (and memory). +เปรียบเทียบกับตัวอย่าง `Map` ปกติข้างบน ถ้า `john` อยู่ใน `WeakMap` ในฐานะ key เพียงอย่างเดียว มันจะถูกลบออกจาก map (และหน่วยความจำ) โดยอัตโนมัติ -`WeakMap` does not support iteration and methods `keys()`, `values()`, `entries()`, so there's no way to get all keys or values from it. +`WeakMap` ไม่รองรับการวนซ้ำ และเมธอด `keys()`, `values()`, `entries()` ดังนั้นจึงไม่มีทางดึง key หรือ value ทั้งหมดออกมาได้ -`WeakMap` has only the following methods: +`WeakMap` มีเพียงเมธอดเหล่านี้: - [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) - [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) - [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) - [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) -Why such a limitation? That's for technical reasons. If an object has lost all other references (like `john` in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified *when the cleanup happens*. +ทำไมถึงมีข้อจำกัดเช่นนี้? เหตุผลเป็นเรื่องทางเทคนิค ถ้าออบเจ็กต์สูญเสีย reference ทั้งหมด (เหมือน `john` ในโค้ดข้างบน) มันจะถูก garbage collect โดยอัตโนมัติ แต่ในทางเทคนิคแล้ว ไม่มีการระบุแน่นอนว่า *การทำความสะอาดจะเกิดขึ้นเมื่อใด* -The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically, the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported. +JavaScript engine เป็นตัวตัดสินใจ อาจเลือกทำทันทีหรือรอก่อนแล้วค่อยทำในภายหลังเมื่อมีการลบมากขึ้น ดังนั้นในทางเทคนิค จำนวนสมาชิกปัจจุบันของ `WeakMap` จึงไม่แน่นอน engine อาจทำความสะอาดไปแล้วหรือยังไม่ได้ทำ หรือทำไปบางส่วน ด้วยเหตุนี้ เมธอดที่เข้าถึง key/value ทั้งหมดจึงไม่ได้รับการรองรับ -Now, where do we need such a data structure? +แล้วเราต้องการโครงสร้างข้อมูลแบบนี้ในสถานการณ์ไหน? -## Use case: additional data +## กรณีใช้งาน: ข้อมูลเสริม -The main area of application for `WeakMap` is an *additional data storage*. +การใช้งานหลักของ `WeakMap` คือการเก็บ *ข้อมูลเสริม* -If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then `WeakMap` is exactly what's needed. +ถ้าเรากำลังทำงานกับออบเจ็กต์ที่ "เป็นของ" โค้ดอื่น หรืออาจเป็น library ของบุคคลที่สาม และต้องการเก็บข้อมูลบางอย่างที่เชื่อมโยงกับมัน ซึ่งควรมีอยู่แค่ตราบเท่าที่ออบเจ็กต์นั้นยังมีชีวิตอยู่ — `WeakMap` คือสิ่งที่ต้องการพอดี -We put the data to a `WeakMap`, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well. +เราใส่ข้อมูลลงใน `WeakMap` โดยใช้ออบเจ็กต์เป็น key และเมื่อออบเจ็กต์ถูก garbage collect ข้อมูลนั้นก็จะหายไปโดยอัตโนมัติด้วย ```js -weakMap.set(john, "secret documents"); -// if john dies, secret documents will be destroyed automatically +weakMap.set(john, "เอกสารลับ"); +// ถ้า john ถูกลบ เอกสารลับก็จะถูกทำลายโดยอัตโนมัติ ``` -Let's look at an example. +มาดูตัวอย่างกัน -For instance, we have code that keeps a visit count for users. The information is stored in a map: a user object is the key and the visit count is the value. When a user leaves (its object gets garbage collected), we don't want to store their visit count anymore. +สมมติเรามีโค้ดที่เก็บจำนวนครั้งที่ผู้ใช้เข้าเยี่ยมชม ข้อมูลถูกเก็บใน map โดยใช้ออบเจ็กต์ user เป็น key และจำนวนครั้งเป็น value เมื่อผู้ใช้จากไป (ออบเจ็กต์ถูก garbage collect) เราก็ไม่อยากเก็บจำนวนครั้งของเขาอีกต่อไป -Here's an example of a counting function with `Map`: +นี่คือตัวอย่างฟังก์ชันนับการเข้าชมด้วย `Map`: ```js // 📁 visitsCount.js -let visitsCountMap = new Map(); // map: user => visits count +let visitsCountMap = new Map(); // map: user => จำนวนครั้งที่เข้าชม -// increase the visits count +// เพิ่มจำนวนครั้งที่เข้าชม function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` -And here's another part of the code, maybe another file using it: +และนี่คืออีกส่วนของโค้ด อาจเป็นไฟล์อื่นที่ใช้งานมัน: ```js // 📁 main.js let john = { name: "John" }; -countUser(john); // count his visits +countUser(john); // นับการเข้าชมของเขา -// later john leaves us +// ต่อมา john ออกจากระบบ john = null; ``` -Now, `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. +ตอนนี้ ออบเจ็กต์ `john` ควรจะถูก garbage collect แต่มันยังคงอยู่ในหน่วยความจำ เพราะเป็น key ใน `visitsCountMap` -We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures. +เราต้องทำความสะอาด `visitsCountMap` ทุกครั้งที่ลบผู้ใช้ออก ไม่เช่นนั้นมันจะกินหน่วยความจำไปเรื่อยๆ การทำความสะอาดแบบนี้อาจกลายเป็นงานน่าเบื่อในสถาปัตยกรรมที่ซับซ้อน -We can avoid it by switching to `WeakMap` instead: +เราหลีกเลี่ยงปัญหานี้ได้ด้วยการเปลี่ยนไปใช้ `WeakMap` แทน: ```js // 📁 visitsCount.js -let visitsCountMap = new WeakMap(); // weakmap: user => visits count +let visitsCountMap = new WeakMap(); // weakmap: user => จำนวนครั้งที่เข้าชม -// increase the visits count +// เพิ่มจำนวนครั้งที่เข้าชม function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` -Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable, by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`. +ทีนี้เราไม่ต้องทำความสะอาด `visitsCountMap` อีกต่อไป เมื่อออบเจ็กต์ `john` ไม่สามารถเข้าถึงได้แล้ว ยกเว้นในฐานะ key ของ `WeakMap` มันจะถูกลบออกจากหน่วยความจำพร้อมกับข้อมูลที่เชื่อมโยงกับ key นั้นใน `WeakMap` โดยอัตโนมัติ -## Use case: caching +## กรณีใช้งาน: caching -Another common example is caching. We can store ("cache") results from a function, so that future calls on the same object can reuse it. +อีกตัวอย่างที่พบบ่อยคือการทำ caching เราสามารถเก็บ ("cache") ผลลัพธ์จากฟังก์ชันไว้ เพื่อที่การเรียกครั้งต่อๆ ไปด้วยออบเจ็กต์เดิมจะได้นำผลลัพธ์เดิมมาใช้ได้เลย -To achieve that, we can use `Map` (not optimal scenario): +เราทำแบบนี้ได้ด้วย `Map` (แต่ยังไม่ดีที่สุด): ```js run // 📁 cache.js let cache = new Map(); -// calculate and remember the result +// คำนวณและจดจำผลลัพธ์ function process(obj) { if (!cache.has(obj)) { - let result = /* calculations of the result for */ obj; + let result = /* คำนวณผลลัพธ์สำหรับ */ obj; cache.set(obj, result); return result; @@ -191,26 +191,26 @@ function process(obj) { } *!* -// Now we use process() in another file: +// ทีนี้เราใช้ process() ในไฟล์อื่น: */!* // 📁 main.js -let obj = {/* let's say we have an object */}; +let obj = {/* สมมติว่าเรามีออบเจ็กต์นี้ */}; -let result1 = process(obj); // calculated +let result1 = process(obj); // คำนวณแล้ว -// ...later, from another place of the code... -let result2 = process(obj); // remembered result taken from cache +// ...ต่อมา จากที่อื่นในโค้ด... +let result2 = process(obj); // นำผลลัพธ์จาก cache มาใช้ -// ...later, when the object is not needed any more: +// ...ต่อมา เมื่อไม่ต้องการออบเจ็กต์แล้ว: obj = null; -alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!) +alert(cache.size); // 1 (อุ๊ย! ออบเจ็กต์ยังอยู่ใน cache กินหน่วยความจำ!) ``` -For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more. +เมื่อเรียก `process(obj)` ด้วยออบเจ็กต์เดิมหลายครั้ง ครั้งแรกเท่านั้นที่จะคำนวณผลลัพธ์ ครั้งต่อๆ ไปจะดึงมาจาก `cache` แต่ข้อเสียคือเราต้องทำความสะอาด `cache` เมื่อไม่ต้องการออบเจ็กต์แล้ว -If we replace `Map` with `WeakMap`, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected. +ถ้าเราแทน `Map` ด้วย `WeakMap` ปัญหานี้จะหมดไป ผลลัพธ์ที่ cache ไว้จะถูกลบออกจากหน่วยความจำโดยอัตโนมัติหลังจากออบเจ็กต์ถูก garbage collect ```js run // 📁 cache.js @@ -218,10 +218,10 @@ If we replace `Map` with `WeakMap`, then this problem disappears. The cached res let cache = new WeakMap(); */!* -// calculate and remember the result +// คำนวณและจดจำผลลัพธ์ function process(obj) { if (!cache.has(obj)) { - let result = /* calculate the result for */ obj; + let result = /* คำนวณผลลัพธ์สำหรับ */ obj; cache.set(obj, result); return result; @@ -231,30 +231,30 @@ function process(obj) { } // 📁 main.js -let obj = {/* some object */}; +let obj = {/* ออบเจ็กต์ตัวหนึ่ง */}; let result1 = process(obj); let result2 = process(obj); -// ...later, when the object is not needed any more: +// ...ต่อมา เมื่อไม่ต้องการออบเจ็กต์แล้ว: obj = null; -// Can't get cache.size, as it's a WeakMap, -// but it's 0 or soon be 0 -// When obj gets garbage collected, cached data will be removed as well +// ไม่สามารถดู cache.size ได้ เพราะเป็น WeakMap +// แต่ค่าจะเป็น 0 หรือจะเป็น 0 ในไม่ช้า +// เมื่อ obj ถูก garbage collect ข้อมูล cache ก็จะถูกลบตามไปด้วย ``` ## WeakSet -[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) behaves similarly: +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) ทำงานในลักษณะเดียวกัน: -- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). -- An object exists in the set while it is reachable from somewhere else. -- Like `Set`, it supports [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) and [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), but not `size`, `keys()` and no iterations. +- คล้ายกับ `Set` แต่เราเพิ่มได้เฉพาะออบเจ็กต์ใน `WeakSet` เท่านั้น (ไม่ใช่ค่า primitive) +- ออบเจ็กต์จะอยู่ใน set ตราบเท่าที่ยังมีที่อื่นเข้าถึงมันได้ +- เหมือน `Set` รองรับ [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) และ [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete) แต่ไม่มี `size`, `keys()` และไม่รองรับการวนซ้ำ -Being "weak", it also serves as additional storage. But not for arbitrary data, rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. +ด้วยความเป็น "weak" มันจึงเหมาะสำหรับเก็บข้อมูลเสริมเช่นกัน แต่ไม่ใช่ข้อมูลทั่วไป แต่เป็นข้อเท็จจริงแบบ "ใช่/ไม่ใช่" การที่ออบเจ็กต์อยู่ใน `WeakSet` อาจหมายความว่าบางอย่างเกี่ยวกับออบเจ็กต์นั้น -For instance, we can add users to `WeakSet` to keep track of those who visited our site: +ตัวอย่างเช่น เราสามารถเพิ่ม user ลงใน `WeakSet` เพื่อติดตามว่าใครเคยเข้าเยี่ยมชมเว็บของเรา: ```js run let visitedSet = new WeakSet(); @@ -263,33 +263,33 @@ let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; -visitedSet.add(john); // John visited us -visitedSet.add(pete); // Then Pete -visitedSet.add(john); // John again +visitedSet.add(john); // John เข้าเยี่ยมชม +visitedSet.add(pete); // แล้ว Pete ก็เข้ามา +visitedSet.add(john); // John เข้ามาอีกครั้ง -// visitedSet has 2 users now +// ตอนนี้ visitedSet มี 2 user -// check if John visited? +// ตรวจสอบว่า John เคยเข้าเยี่ยมชมหรือยัง? alert(visitedSet.has(john)); // true -// check if Mary visited? +// ตรวจสอบว่า Mary เคยเข้าเยี่ยมชมหรือยัง? alert(visitedSet.has(mary)); // false john = null; -// visitedSet will be cleaned automatically +// visitedSet จะถูกทำความสะอาดโดยอัตโนมัติ ``` -The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and the inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. +ข้อจำกัดที่เด่นชัดที่สุดของ `WeakMap` และ `WeakSet` คือการไม่รองรับการวนซ้ำ และไม่สามารถดึงเนื้อหาทั้งหมดในขณะนั้นออกมาได้ ดูเหมือนจะไม่สะดวก แต่ก็ไม่ได้ขัดขวาง `WeakMap/WeakSet` จากการทำหน้าที่หลัก นั่นคือเป็นที่เก็บข้อมูล "เสริม" สำหรับออบเจ็กต์ที่ถูกเก็บและจัดการในที่อื่น -## Summary +## สรุป -[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) คือคอลเล็กชันคล้าย `Map` ที่รองรับเฉพาะออบเจ็กต์เป็น key และจะลบออบเจ็กต์พร้อมกับค่าที่เชื่อมโยงโดยอัตโนมัติเมื่อออบเจ็กต์นั้นไม่สามารถเข้าถึงได้จากที่อื่นอีกแล้ว -[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) คือคอลเล็กชันคล้าย `Set` ที่เก็บได้เฉพาะออบเจ็กต์ และจะลบออบเจ็กต์โดยอัตโนมัติเมื่อไม่สามารถเข้าถึงได้จากที่อื่นอีกแล้ว -Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector. +ข้อดีหลักของทั้งสองคือมัน reference ออบเจ็กต์แบบ "อ่อนแอ" ทำให้ garbage collector ลบออบเจ็กต์เหล่านั้นได้อย่างง่ายดาย -That comes at the cost of not having support for `clear`, `size`, `keys`, `values`... +แลกมาด้วยการไม่รองรับ `clear`, `size`, `keys`, `values`... -`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "primary" object storage. Once the object is removed from the primary storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. +`WeakMap` และ `WeakSet` ถูกใช้เป็นโครงสร้างข้อมูล "รอง" เพิ่มเติมจากที่เก็บออบเจ็กต์ "หลัก" เมื่อออบเจ็กต์ถูกลบออกจากที่เก็บหลัก หากพบมันในฐานะ key ของ `WeakMap` หรืออยู่ใน `WeakSet` เท่านั้น มันก็จะถูกทำความสะอาดโดยอัตโนมัติ diff --git a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md index 27a7b418a..40fa34e5b 100644 --- a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md +++ b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/solution.md @@ -17,12 +17,12 @@ let salaries = { alert( sumSalaries(salaries) ); // 650 ``` -Or, optionally, we could also get the sum using `Object.values` and `reduce`: +หรืออีกทางหนึ่ง เราสามารถใช้ `Object.values` ร่วมกับ `reduce` เพื่อหาผลรวมได้เช่นกัน: ```js -// reduce loops over array of salaries, -// adding them up -// and returns the result +// reduce วนซ้ำผ่านอาร์เรย์ของเงินเดือน +// แล้วบวกสะสมทีละตัว +// และคืนค่าผลลัพธ์สุดท้าย function sumSalaries(salaries) { return Object.values(salaries).reduce((a, b) => a + b, 0) // 650 } diff --git a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md index 211357d03..1d1e5fd8d 100644 --- a/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md +++ b/1-js/05-data-types/09-keys-values-entries/01-sum-salaries/task.md @@ -2,15 +2,15 @@ importance: 5 --- -# Sum the properties +# รวมเงินเดือน -There is a `salaries` object with arbitrary number of salaries. +มีออบเจ็กต์ `salaries` ที่เก็บเงินเดือนจำนวนไม่แน่นอน -Write the function `sumSalaries(salaries)` that returns the sum of all salaries using `Object.values` and the `for..of` loop. +เขียนฟังก์ชัน `sumSalaries(salaries)` ที่คืนค่าผลรวมของเงินเดือนทั้งหมด โดยใช้ `Object.values` และลูป `for..of` -If `salaries` is empty, then the result must be `0`. +ถ้า `salaries` ว่างเปล่า ผลลัพธ์ต้องเป็น `0` -For instance: +ตัวอย่าง: ```js let salaries = { @@ -21,4 +21,3 @@ let salaries = { alert( sumSalaries(salaries) ); // 650 ``` - diff --git a/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md b/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md index d7aebb1fa..544f4059a 100644 --- a/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md +++ b/1-js/05-data-types/09-keys-values-entries/02-count-properties/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Count properties +# นับพร็อพเพอร์ตี้ -Write a function `count(obj)` that returns the number of properties in the object: +เขียนฟังก์ชัน `count(obj)` ที่คืนค่าจำนวนพร็อพเพอร์ตี้ในออบเจ็กต์: ```js let user = { @@ -15,7 +15,6 @@ let user = { alert( count(user) ); // 2 ``` -Try to make the code as short as possible. - -P.S. Ignore symbolic properties, count only "regular" ones. +พยายามเขียนโค้ดให้สั้นที่สุด +หมายเหตุ: ไม่ต้องนับพร็อพเพอร์ตี้ที่เป็น Symbol นับเฉพาะพร็อพเพอร์ตี้ปกติเท่านั้น diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index bef678f53..2b2a56c1d 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -1,42 +1,42 @@ # Object.keys, values, entries -Let's step away from the individual data structures and talk about the iterations over them. +มาหยุดพักจากโครงสร้างข้อมูลแต่ละตัวสักครู่ แล้วมาพูดถึงการวนซ้ำผ่านโครงสร้างเหล่านั้นกัน -In the previous chapter we saw methods `map.keys()`, `map.values()`, `map.entries()`. +ในบทที่แล้ว เราเห็นเมธอด `map.keys()`, `map.values()`, `map.entries()` กันมาแล้ว -These methods are generic, there is a common agreement to use them for data structures. If we ever create a data structure of our own, we should implement them too. +เมธอดเหล่านี้เป็น "กฎสากล" ที่ตกลงกันว่าควรนำไปใช้กับโครงสร้างข้อมูลทุกประเภท ถ้าเราสร้างโครงสร้างข้อมูลขึ้นมาเอง ก็ควรนำไปใช้ด้วยเช่นกัน -They are supported for: +เมธอดเหล่านี้รองรับใน: - `Map` - `Set` - `Array` -Plain objects also support similar methods, but the syntax is a bit different. +ออบเจ็กต์ธรรมดาก็มีเมธอดในแบบเดียวกัน แต่ไวยากรณ์จะแตกต่างออกไปเล็กน้อย ## Object.keys, values, entries -For plain objects, the following methods are available: +สำหรับออบเจ็กต์ธรรมดา มีเมธอดต่อไปนี้: -- [Object.keys(obj)](mdn:js/Object/keys) -- returns an array of keys. -- [Object.values(obj)](mdn:js/Object/values) -- returns an array of values. -- [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of `[key, value]` pairs. +- [Object.keys(obj)](mdn:js/Object/keys) -- คืนค่าอาร์เรย์ของ key ทั้งหมด +- [Object.values(obj)](mdn:js/Object/values) -- คืนค่าอาร์เรย์ของ value ทั้งหมด +- [Object.entries(obj)](mdn:js/Object/entries) -- คืนค่าอาร์เรย์ของคู่ `[key, value]` -Please note the distinctions (compared to map for example): +สังเกตความแตกต่าง (เทียบกับ Map): | | Map | Object | |-------------|------------------|--------------| -| Call syntax | `map.keys()` | `Object.keys(obj)`, but not `obj.keys()` | -| Returns | iterable | "real" Array | +| วิธีเรียกใช้ | `map.keys()` | `Object.keys(obj)` ไม่ใช่ `obj.keys()` | +| คืนค่า | iterable | อาร์เรย์จริงๆ | -The first difference is that we have to call `Object.keys(obj)`, and not `obj.keys()`. +ความแตกต่างแรกคือ เราต้องเรียก `Object.keys(obj)` ไม่ใช่ `obj.keys()` -Why so? The main reason is flexibility. Remember, objects are a base of all complex structures in JavaScript. So we may have an object of our own like `data` that implements its own `data.values()` method. And we still can call `Object.values(data)` on it. +ทำไมถึงเป็นแบบนี้? เหตุผลหลักคือความยืดหยุ่น ออบเจ็กต์เป็นพื้นฐานของโครงสร้างที่ซับซ้อนทุกอย่างใน JavaScript ดังนั้น เราอาจมีออบเจ็กต์ของเราเองเช่น `data` ที่มีเมธอด `data.values()` เป็นของตัวเอง แต่เรายังสามารถเรียก `Object.values(data)` กับมันได้อยู่ดี -The second difference is that `Object.*` methods return "real" array objects, not just an iterable. That's mainly for historical reasons. +ความแตกต่างที่สองคือ เมธอดในกลุ่ม `Object.*` คืนค่าเป็นอาร์เรย์จริงๆ ไม่ใช่แค่ iterable เหตุผลส่วนใหญ่เป็นเรื่องของประวัติศาสตร์การพัฒนาภาษา -For instance: +ตัวอย่างเช่น: ```js let user = { @@ -49,7 +49,7 @@ let user = { - `Object.values(user) = ["John", 30]` - `Object.entries(user) = [ ["name","John"], ["age",30] ]` -Here's an example of using `Object.values` to loop over property values: +ตัวอย่างการใช้ `Object.values` เพื่อวนซ้ำผ่าน value ของพร็อพเพอร์ตี้: ```js run let user = { @@ -57,30 +57,30 @@ let user = { age: 30 }; -// loop over values +// วนซ้ำผ่าน value for (let value of Object.values(user)) { - alert(value); // John, then 30 + alert(value); // John แล้วก็ 30 } ``` -```warn header="Object.keys/values/entries ignore symbolic properties" -Just like a `for..in` loop, these methods ignore properties that use `Symbol(...)` as keys. +```warn header="Object.keys/values/entries ไม่สนใจพร็อพเพอร์ตี้ที่เป็น Symbol" +เช่นเดียวกับลูป `for..in` เมธอดเหล่านี้จะข้ามพร็อพเพอร์ตี้ที่ใช้ `Symbol(...)` เป็น key -Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, there exist a method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys. +โดยทั่วไปนี่เป็นเรื่องที่สะดวกดี แต่ถ้าต้องการ symbolic key ด้วย ก็มีเมธอดแยกต่างหากชื่อ [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) ที่คืนค่าเป็นอาร์เรย์ของ symbolic key เท่านั้น นอกจากนี้ยังมีเมธอด [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) ที่คืนค่า key *ทั้งหมด* ``` -## Transforming objects +## การแปลงออบเจ็กต์ -Objects lack many methods that exist for arrays, e.g. `map`, `filter` and others. +ออบเจ็กต์ขาดเมธอดหลายอย่างที่อาร์เรย์มี เช่น `map`, `filter` และอื่นๆ -If we'd like to apply them, then we can use `Object.entries` followed by `Object.fromEntries`: +ถ้าอยากใช้เมธอดเหล่านั้น ให้ใช้ `Object.entries` ตามด้วย `Object.fromEntries` ดังนี้: -1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`. -2. Use array methods on that array, e.g. `map`, to transform these key/value pairs. -3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object. +1. ใช้ `Object.entries(obj)` เพื่อดึงอาร์เรย์ของคู่ key/value จาก `obj` +2. ใช้เมธอดของอาร์เรย์กับอาร์เรย์นั้น เช่น `map` เพื่อแปลงคู่ key/value +3. ใช้ `Object.fromEntries(array)` กับอาร์เรย์ผลลัพธ์เพื่อแปลงกลับเป็นออบเจ็กต์ -For example, we have an object with prices, and would like to double them: +ตัวอย่างเช่น ถ้ามีออบเจ็กต์ราคาสินค้า และต้องการคูณราคาทุกอย่างด้วย 2: ```js run let prices = { @@ -91,8 +91,8 @@ let prices = { *!* let doublePrices = Object.fromEntries( - // convert prices to array, map each key/value pair into another pair - // and then fromEntries gives back the object + // แปลง prices เป็นอาร์เรย์ แล้ว map แต่ละคู่ key/value ให้เป็นคู่ใหม่ + // จากนั้น fromEntries แปลงกลับเป็นออบเจ็กต์ Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) ); */!* @@ -100,4 +100,4 @@ let doublePrices = Object.fromEntries( alert(doublePrices.meat); // 8 ``` -It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. +อาจดูซับซ้อนในตอนแรก แต่พอใช้สักครั้งสองครั้งก็จะเข้าใจได้เอง เทคนิคนี้ช่วยให้เราต่อโซ่การแปลงข้อมูลได้อย่างทรงพลังมาก diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index c8693b2c4..ddad59791 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -1,28 +1,28 @@ # Destructuring assignment -The two most used data structures in JavaScript are `Object` and `Array`. +โครงสร้างข้อมูลที่ใช้บ่อยที่สุดสองอย่างใน JavaScript คือ `Object` และ `Array` -- Objects allow us to create a single entity that stores data items by key. -- Arrays allow us to gather data items into an ordered list. +- ออบเจ็กต์ช่วยให้เราสร้างหน่วยเดียวที่เก็บข้อมูลโดยใช้ key +- อาร์เรย์ช่วยให้เราเก็บข้อมูลเป็นรายการที่มีลำดับ -However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties. +อย่างไรก็ตาม เวลาที่เราส่งสิ่งเหล่านี้ไปให้ฟังก์ชัน บางครั้งก็ไม่ต้องการข้อมูลทั้งหมด อาจต้องการแค่บางส่วนเท่านั้น -*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. +*Destructuring assignment* คือไวยากรณ์พิเศษที่ช่วยให้เรา "แกะ" อาร์เรย์หรือออบเจ็กต์ออกเป็นตัวแปรหลายตัวได้ในทีเดียว ซึ่งบางครั้งสะดวกกว่ามาก -Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. +destructuring ยังทำงานได้ดีกับฟังก์ชันที่มีพารามิเตอร์จำนวนมาก มีค่าเริ่มต้น และอื่นๆ อีกมาก เดี๋ยวเราจะเห็นกัน ## Array destructuring -Here's an example of how an array is destructured into variables: +ตัวอย่างการแตกอาร์เรย์ออกเป็นตัวแปร: ```js -// we have an array with a name and surname +// มีอาร์เรย์ที่เก็บชื่อและนามสกุล let arr = ["John", "Smith"] *!* // destructuring assignment -// sets firstName = arr[0] -// and surname = arr[1] +// กำหนด firstName = arr[0] +// และ surname = arr[1] let [firstName, surname] = arr; */!* @@ -30,9 +30,9 @@ alert(firstName); // John alert(surname); // Smith ``` -Now we can work with variables instead of array members. +ทีนี้เราทำงานกับตัวแปรได้เลย แทนที่จะอ้างอิง index ของอาร์เรย์ -It looks great when combined with `split` or other array-returning methods: +ดูดีมากเมื่อใช้ร่วมกับ `split` หรือเมธอดอื่นๆ ที่คืนค่าเป็นอาร์เรย์: ```js run let [firstName, surname] = "John Smith".split(' '); @@ -40,12 +40,12 @@ alert(firstName); // John alert(surname); // Smith ``` -As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better. +ไวยากรณ์นี้เรียบง่ายดี แต่ก็มีรายละเอียดที่น่าสนใจอยู่บ้าง มาดูตัวอย่างเพิ่มเติมเพื่อทำความเข้าใจให้ลึกขึ้นกัน -````smart header="\"Destructuring\" does not mean \"destructive\"." -It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified. +````smart header="\"Destructuring\" ไม่ได้แปลว่า \"ทำลาย\"" +เรียกว่า "destructuring assignment" เพราะมัน "แตกโครงสร้าง" โดยการคัดลอกค่าไปใส่ตัวแปร แต่อาร์เรย์ต้นฉบับไม่ได้ถูกแก้ไขแต่อย่างใด -It's just a shorter way to write: +มันเป็นแค่วิธีเขียนที่สั้นกว่านี้: ```js // let [firstName, surname] = arr; let firstName = arr[0]; @@ -53,37 +53,37 @@ let surname = arr[1]; ``` ```` -````smart header="Ignore elements using commas" -Unwanted elements of the array can also be thrown away via an extra comma: +````smart header="ข้ามสมาชิกที่ไม่ต้องการด้วยเครื่องหมายจุลภาค" +สมาชิกของอาร์เรย์ที่ไม่ต้องการสามารถข้ามได้ด้วยเครื่องหมายจุลภาคเพิ่มเติม: ```js run *!* -// second element is not needed +// ไม่ต้องการสมาชิกตัวที่สอง let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; */!* alert( title ); // Consul ``` -In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them). +ในโค้ดด้านบน สมาชิกตัวที่สองของอาร์เรย์ถูกข้ามไป ตัวที่สามถูกกำหนดให้กับ `title` และสมาชิกที่เหลือก็ถูกข้ามเช่นกัน (เพราะไม่มีตัวแปรรับ) ```` -````smart header="Works with any iterable on the right-side" +````smart header="ใช้ได้กับ iterable ใดๆ ทางขวามือ" -...Actually, we can use it with any iterable, not only arrays: +จริงๆ แล้ว เราใช้ได้กับ iterable ใดก็ได้ ไม่ใช่แค่อาร์เรย์: ```js let [a, b, c] = "abc"; // ["a", "b", "c"] let [one, two, three] = new Set([1, 2, 3]); ``` -That works, because internally a destructuring assignment works by iterating over the right value. It's a kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values. +ใช้ได้เพราะภายในแล้ว destructuring assignment ทำงานโดยการ iterate ค่าทางขวา มันคือน้ำตาลทางไวยากรณ์ของการใช้ `for..of` กับค่าทางขวาของ `=` แล้วกำหนดค่าให้ตัวแปร ```` -````smart header="Assign to anything on the left-side" -We can use any "assignables" on the left side. +````smart header="กำหนดให้สิ่งใดก็ได้ทางซ้ายมือ" +เราใช้ "สิ่งที่รับการกำหนดค่าได้" ทางซ้ายมือก็ได้ -For instance, an object property: +เช่น พร็อพเพอร์ตี้ของออบเจ็กต์: ```js run let user = {}; [user.name, user.surname] = "John Smith".split(' '); @@ -94,10 +94,10 @@ alert(user.surname); // Smith ```` -````smart header="Looping with .entries()" -In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method. +````smart header="วนลูปด้วย .entries()" +ในบทก่อน เราได้เห็นเมธอด [Object.entries(obj)](mdn:js/Object/entries) แล้ว -We can use it with destructuring to loop over the keys-and-values of an object: +เราใช้มันร่วมกับ destructuring เพื่อวนลูปผ่าน key-value ของออบเจ็กต์ได้: ```js run let user = { @@ -105,15 +105,15 @@ let user = { age: 30 }; -// loop over the keys-and-values +// วนลูปผ่าน key-value *!* for (let [key, value] of Object.entries(user)) { */!* - alert(`${key}:${value}`); // name:John, then age:30 + alert(`${key}:${value}`); // name:John แล้วก็ age:30 } ``` -The similar code for a `Map` is simpler, as it's iterable: +โค้ดที่คล้ายกันสำหรับ `Map` จะง่ายกว่า เพราะมัน iterable อยู่แล้ว: ```js run let user = new Map(); @@ -121,73 +121,73 @@ user.set("name", "John"); user.set("age", "30"); *!* -// Map iterates as [key, value] pairs, very convenient for destructuring +// Map iterate เป็นคู่ [key, value] สะดวกมากสำหรับ destructuring for (let [key, value] of user) { */!* - alert(`${key}:${value}`); // name:John, then age:30 + alert(`${key}:${value}`); // name:John แล้วก็ age:30 } ``` ```` -````smart header="Swap variables trick" -There's a well-known trick for swapping values of two variables using a destructuring assignment: +````smart header="เทคนิคสลับค่าตัวแปร" +มีเทคนิคที่รู้จักกันดีในการสลับค่าของตัวแปรสองตัวโดยใช้ destructuring assignment: ```js run let guest = "Jane"; let admin = "Pete"; -// Let's swap the values: make guest=Pete, admin=Jane +// สลับค่า: ให้ guest=Pete, admin=Jane *!* [guest, admin] = [admin, guest]; */!* -alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!) +alert(`${guest} ${admin}`); // Pete Jane (สลับสำเร็จ!) ``` -Here we create a temporary array of two variables and immediately destructure it in swapped order. +เราสร้างอาร์เรย์ชั่วคราวที่มีสองตัวแปร แล้ว destructure มันในลำดับที่สลับกัน -We can swap more than two variables this way. +วิธีนี้ใช้สลับมากกว่าสองตัวแปรก็ได้ ```` ### The rest '...' -Usually, if the array is longer than the list at the left, the "extra" items are omitted. +โดยปกติแล้ว ถ้าอาร์เรย์ยาวกว่ารายการตัวแปรทางซ้าย สมาชิก "ส่วนเกิน" จะถูกละทิ้ง -For example, here only two items are taken, and the rest is just ignored: +ตัวอย่างเช่น ที่นี่นำแค่สองรายการ ที่เหลือถูกละทิ้ง: ```js run let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; alert(name1); // Julius alert(name2); // Caesar -// Further items aren't assigned anywhere +// รายการที่เหลือไม่ได้ถูกกำหนดให้ที่ไหน ``` -If we'd like also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`: +ถ้าอยากเก็บทุกอย่างที่เหลือด้วย เราสามารถเพิ่มพารามิเตอร์อีกตัวที่รับ "ส่วนที่เหลือ" โดยใช้จุดสามจุด `"..."`: ```js run let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* -// rest is an array of items, starting from the 3rd one +// rest คืออาร์เรย์ของสมาชิก เริ่มตั้งแต่ตัวที่ 3 alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 */!* ``` -The value of `rest` is the array of the remaining array elements. +ค่าของ `rest` คืออาร์เรย์ของสมาชิกที่เหลือ -We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment. +เราตั้งชื่อตัวแปรอื่นแทน `rest` ได้ แค่ต้องมีจุดสามจุดอยู่ข้างหน้าและวางไว้เป็นตัวสุดท้ายใน destructuring assignment: ```js run let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; -// now titles = ["Consul", "of the Roman Republic"] +// ตอนนี้ titles = ["Consul", "of the Roman Republic"] ``` ### Default values -If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined: +ถ้าอาร์เรย์สั้นกว่ารายการตัวแปรทางซ้าย จะไม่เกิด error แต่ค่าที่ขาดหายไปจะเป็น undefined: ```js run *!* @@ -198,45 +198,45 @@ alert(firstName); // undefined alert(surname); // undefined ``` -If we want a "default" value to replace the missing one, we can provide it using `=`: +ถ้าต้องการค่า "เริ่มต้น" มาแทนที่ค่าที่ขาดหายไป สามารถระบุได้ด้วย `=`: ```js run *!* -// default values +// ค่าเริ่มต้น let [name = "Guest", surname = "Anonymous"] = ["Julius"]; */!* -alert(name); // Julius (from array) -alert(surname); // Anonymous (default used) +alert(name); // Julius (มาจากอาร์เรย์) +alert(surname); // Anonymous (ใช้ค่าเริ่มต้น) ``` -Default values can be more complex expressions or even function calls. They are evaluated only if the value is not provided. +ค่าเริ่มต้นอาจเป็นนิพจน์ที่ซับซ้อนหรือแม้แต่การเรียกฟังก์ชันก็ได้ และจะถูกประเมินผลเฉพาะเมื่อไม่มีค่าให้เท่านั้น -For instance, here we use the `prompt` function for two defaults: +ตัวอย่างเช่น เราใช้ฟังก์ชัน `prompt` สำหรับค่าเริ่มต้นสองตัว: ```js run -// runs only prompt for surname +// เรียก prompt เฉพาะสำหรับ surname เท่านั้น let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"]; -alert(name); // Julius (from array) -alert(surname); // whatever prompt gets +alert(name); // Julius (มาจากอาร์เรย์) +alert(surname); // แล้วแต่ที่ป้อนใน prompt ``` -Please note: the `prompt` will run only for the missing value (`surname`). +สังเกตว่า `prompt` จะทำงานเฉพาะกับค่าที่ขาดหาย (`surname`) เท่านั้น ## Object destructuring -The destructuring assignment also works with objects. +destructuring assignment ใช้ได้กับออบเจ็กต์ด้วย -The basic syntax is: +ไวยากรณ์พื้นฐานคือ: ```js let {var1, var2} = {var1:…, var2:…} ``` -We should have an existing object on the right side, that we want to split into variables. The left side contains an object-like "pattern" for corresponding properties. In the simplest case, that's a list of variable names in `{...}`. +ทางขวามือเป็นออบเจ็กต์ที่มีอยู่แล้วที่เราต้องการแตกออกเป็นตัวแปร ส่วนทางซ้ายมือมี "รูปแบบ" ที่คล้ายออบเจ็กต์สำหรับพร็อพเพอร์ตี้ที่ต้องการ ในรูปแบบง่ายที่สุดก็คือรายชื่อตัวแปรใน `{...}` -For instance: +ตัวอย่างเช่น: ```js run let options = { @@ -254,18 +254,18 @@ alert(width); // 100 alert(height); // 200 ``` -Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. +พร็อพเพอร์ตี้ `options.title`, `options.width` และ `options.height` ถูกกำหนดให้กับตัวแปรที่ตรงกัน -The order does not matter. This works too: +ลำดับไม่สำคัญ แบบนี้ก็ใช้ได้: ```js -// changed the order in let {...} +// เปลี่ยนลำดับใน let {...} let {height, width, title} = { title: "Menu", height: 200, width: 100 } ``` -The pattern on the left side may be more complex and specify the mapping between properties and variables. +รูปแบบทางซ้ายมืออาจซับซ้อนกว่านี้ได้ โดยระบุการจับคู่ระหว่างพร็อพเพอร์ตี้กับตัวแปร -If we want to assign a property to a variable with another name, for instance, make `options.width` go into the variable named `w`, then we can set the variable name using a colon: +ถ้าต้องการกำหนดพร็อพเพอร์ตี้ให้กับตัวแปรที่มีชื่อต่างกัน เช่น ให้ `options.width` ไปอยู่ในตัวแปรชื่อ `w` เราระบุชื่อตัวแปรได้ด้วยเครื่องหมายทวิภาค: ```js run let options = { @@ -275,7 +275,7 @@ let options = { }; *!* -// { sourceProperty: targetVariable } +// { พร็อพเพอร์ตี้ต้นทาง: ตัวแปรปลายทาง } let {width: w, height: h, title} = options; */!* @@ -288,9 +288,9 @@ alert(w); // 100 alert(h); // 200 ``` -The colon shows "what : goes where". In the example above the property `width` goes to `w`, property `height` goes to `h`, and `title` is assigned to the same name. +เครื่องหมายทวิภาคหมายถึง "อะไร : ไปที่ไหน" ในตัวอย่างด้านบน พร็อพเพอร์ตี้ `width` ไปที่ `w`, `height` ไปที่ `h` และ `title` ถูกกำหนดให้กับชื่อเดิม -For potentially missing properties we can set default values using `"="`, like this: +สำหรับพร็อพเพอร์ตี้ที่อาจไม่มีอยู่ เราตั้งค่าเริ่มต้นได้ด้วย `"="` แบบนี้: ```js run let options = { @@ -306,9 +306,9 @@ alert(width); // 100 alert(height); // 200 ``` -Just like with arrays or function parameters, default values can be any expressions or even function calls. They will be evaluated if the value is not provided. +เช่นเดียวกับอาร์เรย์หรือพารามิเตอร์ของฟังก์ชัน ค่าเริ่มต้นอาจเป็นนิพจน์หรือการเรียกฟังก์ชันก็ได้ และจะถูกประเมินผลเฉพาะเมื่อไม่มีค่าให้ -In the code below `prompt` asks for `width`, but not for `title`: +ในโค้ดด้านล่าง `prompt` ถามค่าสำหรับ `width` แต่ไม่ถามสำหรับ `title`: ```js run let options = { @@ -320,10 +320,10 @@ let {width = prompt("width?"), title = prompt("title?")} = options; */!* alert(title); // Menu -alert(width); // (whatever the result of prompt is) +alert(width); // (แล้วแต่ค่าที่ได้จาก prompt) ``` -We also can combine both the colon and equality: +เราใช้ทั้งเครื่องหมายทวิภาคและเครื่องหมายเท่ากับร่วมกันก็ได้: ```js run let options = { @@ -339,7 +339,7 @@ alert(w); // 100 alert(h); // 200 ``` -If we have a complex object with many properties, we can extract only what we need: +ถ้ามีออบเจ็กต์ที่ซับซ้อนและมีหลายพร็อพเพอร์ตี้ เราดึงเฉพาะที่ต้องการมาได้: ```js run let options = { @@ -348,7 +348,7 @@ let options = { height: 200 }; -// only extract title as a variable +// ดึงเฉพาะ title มาเป็นตัวแปร let { title } = options; alert(title); // Menu @@ -356,11 +356,11 @@ alert(title); // Menu ### The rest pattern "..." -What if the object has more properties than we have variables? Can we take some and then assign the "rest" somewhere? +แล้วถ้าออบเจ็กต์มีพร็อพเพอร์ตี้มากกว่าจำนวนตัวแปรที่เรามีล่ะ? เราเก็บบางส่วนแล้วกำหนด "ส่วนที่เหลือ" ไว้ที่ไหนได้ไหม? -We can use the rest pattern, just like we did with arrays. It's not supported by some older browsers (IE, use Babel to polyfill it), but works in modern ones. +เราใช้ rest pattern ได้เหมือนกับที่ทำกับอาร์เรย์ ซึ่งบราวเซอร์เก่าบางตัว (IE) ไม่รองรับ แต่ใช้ Babel แก้ได้ บราวเซอร์สมัยใหม่ใช้ได้ทั้งนั้น -It looks like this: +ดูตัวอย่าง: ```js run let options = { @@ -370,46 +370,46 @@ let options = { }; *!* -// title = property named title -// rest = object with the rest of properties +// title = พร็อพเพอร์ตี้ชื่อ title +// rest = ออบเจ็กต์ที่เก็บพร็อพเพอร์ตี้ที่เหลือ let {title, ...rest} = options; */!* -// now title="Menu", rest={height: 200, width: 100} +// ตอนนี้ title="Menu", rest={height: 200, width: 100} alert(rest.height); // 200 alert(rest.width); // 100 ``` -````smart header="Gotcha if there's no `let`" -In the examples above variables were declared right in the assignment: `let {…} = {…}`. Of course, we could use existing variables too, without `let`. But there's a catch. +````smart header="ข้อควรระวังถ้าไม่มี `let`" +ในตัวอย่างด้านบน ตัวแปรถูกประกาศไว้ในการกำหนดค่า: `let {…} = {…}` แน่นอนว่าเราใช้ตัวแปรที่มีอยู่แล้วได้ โดยไม่ต้องมี `let` แต่มีข้อระวัง -This won't work: +แบบนี้จะไม่ทำงาน: ```js run let title, width, height; -// error in this line +// เกิดข้อผิดพลาดในบรรทัดนี้ {title, width, height} = {title: "Menu", width: 200, height: 100}; ``` -The problem is that JavaScript treats `{...}` in the main code flow (not inside another expression) as a code block. Such code blocks can be used to group statements, like this: +ปัญหาคือ JavaScript ตีความ `{...}` ในโค้ดหลัก (ที่ไม่ได้อยู่ในนิพจน์อื่น) ว่าเป็น code block ซึ่งใช้จัดกลุ่มคำสั่งได้แบบนี้: ```js run { - // a code block + // code block let message = "Hello"; // ... alert( message ); } ``` -So here JavaScript assumes that we have a code block, that's why there's an error. We want destructuring instead. +ดังนั้น JavaScript จึงสันนิษฐานว่าเป็น code block และเกิด error ทั้งๆ ที่เราต้องการ destructuring -To show JavaScript that it's not a code block, we can wrap the expression in parentheses `(...)`: +เพื่อบอก JavaScript ว่าไม่ใช่ code block เราครอบนิพจน์ด้วยวงเล็บ `(...)`: ```js run let title, width, height; -// okay now +// โอเคแล้ว *!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*; alert( title ); // Menu @@ -418,9 +418,9 @@ alert( title ); // Menu ## Nested destructuring -If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. +ถ้าออบเจ็กต์หรืออาร์เรย์มีออบเจ็กต์หรืออาร์เรย์ซ้อนกันอยู่ข้างใน เราใช้รูปแบบทางซ้ายมือที่ซับซ้อนขึ้นเพื่อดึงข้อมูลจากส่วนที่ลึกกว่าได้ -In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern on the left side of the assignment has the same structure to extract values from them: +ในโค้ดด้านล่าง `options` มีออบเจ็กต์อีกตัวอยู่ใน `size` และมีอาร์เรย์ใน `items` รูปแบบทางซ้ายมือของการกำหนดค่ามีโครงสร้างเดียวกันเพื่อดึงค่าออกมา: ```js run let options = { @@ -432,14 +432,14 @@ let options = { extra: true }; -// destructuring assignment split in multiple lines for clarity +// destructuring assignment แบ่งเขียนหลายบรรทัดเพื่อความชัดเจน let { - size: { // put size here + size: { // ใส่ size ที่นี่ width, height }, - items: [item1, item2], // assign items here - title = "Menu" // not present in the object (default value is used) + items: [item1, item2], // กำหนด items ที่นี่ + title = "Menu" // ไม่มีในออบเจ็กต์ (ใช้ค่าเริ่มต้น) } = options; alert(title); // Menu @@ -449,19 +449,19 @@ alert(item1); // Cake alert(item2); // Donut ``` -All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables: +พร็อพเพอร์ตี้ทั้งหมดของออบเจ็กต์ `options` ยกเว้น `extra` ที่ไม่อยู่ทางซ้ายมือ ถูกกำหนดให้กับตัวแปรที่ตรงกัน: ![](destructuring-complex.svg) -Finally, we have `width`, `height`, `item1`, `item2` and `title` from the default value. +สุดท้ายเราได้ตัวแปร `width`, `height`, `item1`, `item2` และ `title` จากค่าเริ่มต้น -Note that there are no variables for `size` and `items`, as we take their content instead. +สังเกตว่าไม่มีตัวแปรสำหรับ `size` และ `items` เพราะเราดึงเนื้อหาข้างในออกมาแทน ## Smart function parameters -There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, an item list and so on. +มีบางครั้งที่ฟังก์ชันมีพารามิเตอร์จำนวนมาก ส่วนใหญ่เป็นพารามิเตอร์ optional ซึ่งพบบ่อยในส่วน user interface ลองนึกภาพฟังก์ชันที่สร้างเมนู อาจมีความกว้าง ความสูง หัวข้อ รายการ และอื่นๆ -Here's a bad way to write such a function: +วิธีเขียนฟังก์ชันแบบนี้ที่ไม่ดีคือ: ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -469,32 +469,32 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` -In real-life, the problem is how to remember the order of arguments. Usually, IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. +ปัญหาในทางปฏิบัติคือต้องจำลำดับของอาร์กิวเมนต์ ปกติ IDE จะช่วยเราได้ โดยเฉพาะถ้าโค้ดมี documentation ที่ดี แต่ก็ยังยุ่งยาก อีกปัญหาคือจะเรียกฟังก์ชันยังไงเมื่อพารามิเตอร์ส่วนใหญ่ใช้ค่าเริ่มต้นได้ -Like this? +แบบนี้เหรอ? ```js -// undefined where default values are fine +// undefined ตรงที่ค่าเริ่มต้นใช้ได้ showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) ``` -That's ugly. And becomes unreadable when we deal with more parameters. +ดูน่าเกลียดมาก และยิ่งอ่านไม่รู้เรื่องเลยถ้ามีพารามิเตอร์มากขึ้น -Destructuring comes to the rescue! +destructuring มาช่วยได้! -We can pass parameters as an object, and the function immediately destructurizes them into variables: +เราส่งพารามิเตอร์เป็นออบเจ็กต์ แล้วให้ฟังก์ชัน destructure เป็นตัวแปรทันที: ```js run -// we pass object to function +// ส่งออบเจ็กต์ไปให้ฟังก์ชัน let options = { title: "My menu", items: ["Item1", "Item2"] }; -// ...and it immediately expands it to variables +// ...แล้วมันแตกออกเป็นตัวแปรทันที function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []}*/!*) { - // title, items – taken from options, - // width, height – defaults used + // title, items – มาจาก options + // width, height – ใช้ค่าเริ่มต้น alert( `${title} ${width} ${height}` ); // My Menu 200 100 alert( items ); // Item1, Item2 } @@ -502,7 +502,7 @@ function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []} showMenu(options); ``` -We can also use more complex destructuring with nested objects and colon mappings: +เราใช้ destructuring ที่ซับซ้อนกว่านี้ได้ด้วย ทั้งออบเจ็กต์ซ้อนและการแมปด้วยทวิภาค: ```js run let options = { @@ -513,9 +513,9 @@ let options = { *!* function showMenu({ title = "Untitled", - width: w = 100, // width goes to w - height: h = 200, // height goes to h - items: [item1, item2] // items first element goes to item1, second to item2 + width: w = 100, // width ไปที่ w + height: h = 200, // height ไปที่ h + items: [item1, item2] // สมาชิกแรกของ items ไปที่ item1 ตัวที่สองไปที่ item2 }) { */!* alert( `${title} ${w} ${h}` ); // My Menu 100 200 @@ -526,7 +526,7 @@ function showMenu({ showMenu(options); ``` -The full syntax is the same as for a destructuring assignment: +ไวยากรณ์แบบเต็มเหมือนกับ destructuring assignment: ```js function({ incomingProperty: varName = defaultValue @@ -534,17 +534,17 @@ function({ }) ``` -Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. +สำหรับออบเจ็กต์ของพารามิเตอร์ จะมีตัวแปร `varName` สำหรับพร็อพเพอร์ตี้ `incomingProperty` โดยมีค่าเริ่มต้นเป็น `defaultValue` -Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object: +พึงระวังว่า destructuring แบบนี้สันนิษฐานว่า `showMenu()` ต้องได้รับอาร์กิวเมนต์ ถ้าต้องการให้ทุกค่าใช้ค่าเริ่มต้น ต้องระบุออบเจ็กต์ว่าง: ```js -showMenu({}); // ok, all values are default +showMenu({}); // โอเค ทุกค่าใช้ค่าเริ่มต้น -showMenu(); // this would give an error +showMenu(); // แบบนี้จะ error ``` -We can fix this by making `{}` the default value for the whole object of parameters: +แก้ได้โดยกำหนดให้ `{}` เป็นค่าเริ่มต้นของออบเจ็กต์พารามิเตอร์ทั้งหมด: ```js run function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { @@ -554,26 +554,26 @@ function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { showMenu(); // Menu 100 200 ``` -In the code above, the whole arguments object is `{}` by default, so there's always something to destructurize. +ในโค้ดด้านบน ออบเจ็กต์อาร์กิวเมนต์ทั้งหมดจะเป็น `{}` โดยค่าเริ่มต้น จึงมีของให้ destructure เสมอ ## Summary -- Destructuring assignment allows for instantly mapping an object or array onto many variables. -- The full object syntax: +- Destructuring assignment ช่วยให้แมปออบเจ็กต์หรืออาร์เรย์ไปยังตัวแปรหลายตัวได้ทันที +- ไวยากรณ์แบบเต็มสำหรับออบเจ็กต์: ```js let {prop : varName = defaultValue, ...rest} = object ``` - This means that property `prop` should go into the variable `varName` and, if no such property exists, then the `default` value should be used. + หมายความว่าพร็อพเพอร์ตี้ `prop` จะไปที่ตัวแปร `varName` และถ้าไม่มีพร็อพเพอร์ตี้นั้น จะใช้ค่า `default` - Object properties that have no mapping are copied to the `rest` object. + พร็อพเพอร์ตี้ที่ไม่มีการแมปจะถูกคัดลอกไปยังออบเจ็กต์ `rest` -- The full array syntax: +- ไวยากรณ์แบบเต็มสำหรับอาร์เรย์: ```js let [item1 = defaultValue, item2, ...rest] = array ``` - The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`. + สมาชิกแรกไปที่ `item1` ตัวที่สองไปที่ `item2` ที่เหลือทั้งหมดสร้างเป็นอาร์เรย์ `rest` -- It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one. +- ดึงข้อมูลจากอาร์เรย์/ออบเจ็กต์ที่ซ้อนกันได้ โดยรูปแบบทางซ้ายมือต้องมีโครงสร้างเดียวกับทางขวามือ diff --git a/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js b/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js index fb9e3d2a4..843d91616 100644 --- a/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js +++ b/1-js/05-data-types/11-date/3-weekday/_js.view/solution.js @@ -2,7 +2,7 @@ function getLocalDay(date) { let day = date.getDay(); - if (day == 0) { // weekday 0 (sunday) is 7 in european + if (day == 0) { // วันที่ 0 (อาทิตย์) นับเป็น 7 ในระบบยุโรป day = 7; } diff --git a/1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js index 4695354a5..3b3989438 100644 --- a/1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js +++ b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/solution.js @@ -1,24 +1,24 @@ function formatDate(date) { - let diff = new Date() - date; // the difference in milliseconds + let diff = new Date() - date; // ความต่างในหน่วยมิลลิวินาที - if (diff < 1000) { // less than 1 second - return 'right now'; + if (diff < 1000) { // น้อยกว่า 1 วินาที + return 'เมื่อกี้นี้'; } - let sec = Math.floor(diff / 1000); // convert diff to seconds + let sec = Math.floor(diff / 1000); // แปลงความต่างเป็นวินาที if (sec < 60) { - return sec + ' sec. ago'; + return sec + ' วินาทีที่แล้ว'; } - let min = Math.floor(diff / 60000); // convert diff to minutes + let min = Math.floor(diff / 60000); // แปลงความต่างเป็นนาที if (min < 60) { - return min + ' min. ago'; + return min + ' นาทีที่แล้ว'; } - // format the date - // add leading zeroes to single-digit day/month/hours/minutes + // จัดรูปแบบวันที่ + // เติมศูนย์นำหน้าสำหรับวัน/เดือน/ชั่วโมง/นาทีที่มีหลักเดียว let d = date; d = [ '0' + d.getDate(), @@ -26,8 +26,8 @@ function formatDate(date) { '' + d.getFullYear(), '0' + d.getHours(), '0' + d.getMinutes() - ].map(component => component.slice(-2)); // take last 2 digits of every component + ].map(component => component.slice(-2)); // นำ 2 หลักสุดท้ายของแต่ละส่วน - // join the components into date + // รวมส่วนประกอบเป็นวันที่ return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':'); } diff --git a/1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js index 9b4cb2f58..1b81355c6 100644 --- a/1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js +++ b/1-js/05-data-types/11-date/8-format-date-relative/_js.view/test.js @@ -1,17 +1,17 @@ describe("formatDate", function() { - it("shows 1ms ago as \"right now\"", function() { - assert.equal(formatDate(new Date(new Date - 1)), 'right now'); + it("แสดง 1ms ที่แล้วเป็น \"เมื่อกี้นี้\"", function() { + assert.equal(formatDate(new Date(new Date - 1)), 'เมื่อกี้นี้'); }); - it('"30 seconds ago"', function() { - assert.equal(formatDate(new Date(new Date - 30 * 1000)), "30 sec. ago"); + it('"30 วินาทีที่แล้ว"', function() { + assert.equal(formatDate(new Date(new Date - 30 * 1000)), "30 วินาทีที่แล้ว"); }); - it('"5 minutes ago"', function() { - assert.equal(formatDate(new Date(new Date - 5 * 60 * 1000)), "5 min. ago"); + it('"5 นาทีที่แล้ว"', function() { + assert.equal(formatDate(new Date(new Date - 5 * 60 * 1000)), "5 นาทีที่แล้ว"); }); - it("older dates as DD.MM.YY HH:mm", function() { + it("วันที่เก่ากว่าแสดงในรูปแบบ DD.MM.YY HH:mm", function() { assert.equal(formatDate(new Date(2014, 2, 1, 11, 22, 33)), "01.03.14 11:22"); }); diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index 6958a3a97..64f6fa8d2 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -1,141 +1,141 @@ -# Date and time +# วันที่และเวลา -Let's meet a new built-in object: [Date](mdn:js/Date). It stores the date, time and provides methods for date/time management. +มาทำความรู้จักกับออบเจ็กต์ built-in ตัวใหม่: [Date](mdn:js/Date) ออบเจ็กต์นี้จัดเก็บวันที่และเวลา และมีเมธอดสำหรับจัดการข้อมูลวันที่/เวลา -For instance, we can use it to store creation/modification times, to measure time, or just to print out the current date. +ตัวอย่างเช่น เราสามารถใช้มันเพื่อบันทึกเวลาที่สร้างหรือแก้ไขไฟล์ วัดระยะเวลา หรือแค่แสดงวันที่ปัจจุบันก็ได้ -## Creation +## การสร้าง -To create a new `Date` object call `new Date()` with one of the following arguments: +ในการสร้างออบเจ็กต์ `Date` ใหม่ ให้เรียก `new Date()` พร้อมอาร์กิวเมนต์ในรูปแบบใดรูปแบบหนึ่งดังนี้: `new Date()` -: Without arguments -- create a `Date` object for the current date and time: +: ไม่มีอาร์กิวเมนต์ -- สร้างออบเจ็กต์ `Date` สำหรับวันที่และเวลาปัจจุบัน: ```js run let now = new Date(); - alert( now ); // shows current date/time + alert( now ); // แสดงวันที่/เวลาปัจจุบัน ``` `new Date(milliseconds)` -: Create a `Date` object with the time equal to number of milliseconds (1/1000 of a second) passed after the Jan 1st of 1970 UTC+0. +: สร้างออบเจ็กต์ `Date` โดยกำหนดเวลาจากจำนวนมิลลิวินาที (1/1000 ของวินาที) ที่ผ่านมานับตั้งแต่วันที่ 1 มกราคม 1970 UTC+0 ```js run - // 0 means 01.01.1970 UTC+0 + // 0 หมายถึง 01.01.1970 UTC+0 let Jan01_1970 = new Date(0); alert( Jan01_1970 ); - // now add 24 hours, get 02.01.1970 UTC+0 + // เพิ่ม 24 ชั่วโมง ได้ 02.01.1970 UTC+0 let Jan02_1970 = new Date(24 * 3600 * 1000); alert( Jan02_1970 ); ``` - An integer number representing the number of milliseconds that has passed since the beginning of 1970 is called a *timestamp*. + ตัวเลขจำนวนเต็มที่แสดงจำนวนมิลลิวินาทีที่ผ่านมาตั้งแต่ต้นปี 1970 เรียกว่า *timestamp* - It's a lightweight numeric representation of a date. We can always create a date from a timestamp using `new Date(timestamp)` and convert the existing `Date` object to a timestamp using the `date.getTime()` method (see below). + มันเป็นการแทนค่าวันที่ในรูปแบบตัวเลขที่เบาและกะทัดรัด เราสามารถสร้างวันที่จาก timestamp ได้ตลอดเวลาด้วย `new Date(timestamp)` และแปลงออบเจ็กต์ `Date` เป็น timestamp ด้วยเมธอด `date.getTime()` (ดูรายละเอียดด้านล่าง) - Dates before 01.01.1970 have negative timestamps, e.g.: + วันที่ก่อน 01.01.1970 จะมี timestamp เป็นค่าลบ เช่น: ```js run - // 31 Dec 1969 + // 31 ธ.ค. 1969 let Dec31_1969 = new Date(-24 * 3600 * 1000); alert( Dec31_1969 ); ``` `new Date(datestring)` -: If there is a single argument, and it's a string, then it is parsed automatically. The algorithm is the same as `Date.parse` uses, we'll cover it later. +: ถ้ามีอาร์กิวเมนต์เพียงตัวเดียวและเป็นสตริง จะถูก parse อัตโนมัติ โดยใช้อัลกอริทึมเดียวกับที่ `Date.parse` ใช้ ซึ่งจะกล่าวถึงในภายหลัง ```js run let date = new Date("2017-01-26"); alert(date); - // The time is not set, so it's assumed to be midnight GMT and - // is adjusted according to the timezone the code is run in - // So the result could be + // ไม่ได้กำหนดเวลา ระบบจึงถือว่าเป็นเที่ยงคืน GMT และ + // ปรับตามเขตเวลาที่รันโค้ดอยู่ + // ผลลัพธ์อาจเป็น // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) - // or + // หรือ // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time) ``` `new Date(year, month, date, hours, minutes, seconds, ms)` -: Create the date with the given components in the local time zone. Only the first two arguments are obligatory. +: สร้างวันที่ด้วยค่าที่กำหนดในเขตเวลาท้องถิ่น โดยอาร์กิวเมนต์สองตัวแรกเท่านั้นที่จำเป็น - - The `year` should have 4 digits. For compatibility, 2 digits are also accepted and considered `19xx`, e.g. `98` is the same as `1998` here, but always using 4 digits is strongly encouraged. - - The `month` count starts with `0` (Jan), up to `11` (Dec). - - The `date` parameter is actually the day of month, if absent then `1` is assumed. - - If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`. + - `year` ควรมี 4 หลัก เพื่อความเข้ากันได้ ยังรับ 2 หลักได้และถือว่าเป็น `19xx` เช่น `98` เทียบเท่ากับ `1998` แต่แนะนำให้ใช้ 4 หลักเสมอ + - `month` เริ่มนับจาก `0` (ม.ค.) ไปถึง `11` (ธ.ค.) + - พารามิเตอร์ `date` คือวันที่ในเดือน ถ้าไม่ระบุจะใช้ค่า `1` + - ถ้าไม่ระบุ `hours/minutes/seconds/ms` จะมีค่าเป็น `0` - For instance: + ตัวอย่าง: ```js - new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00 - new Date(2011, 0, 1); // the same, hours etc are 0 by default + new Date(2011, 0, 1, 0, 0, 0, 0); // 1 ม.ค. 2011, 00:00:00 + new Date(2011, 0, 1); // เหมือนกัน ค่า hours เป็นต้นจะเป็น 0 โดยค่าเริ่มต้น ``` - The maximal precision is 1 ms (1/1000 sec): + ความละเอียดสูงสุดคือ 1 ms (1/1000 วินาที): ```js run let date = new Date(2011, 0, 1, 2, 3, 4, 567); alert( date ); // 1.01.2011, 02:03:04.567 ``` -## Access date components +## การเข้าถึงส่วนประกอบของวันที่ -There are methods to access the year, month and so on from the `Date` object: +มีเมธอดสำหรับดึงค่าปี เดือน และข้อมูลอื่นๆ จากออบเจ็กต์ `Date`: [getFullYear()](mdn:js/Date/getFullYear) -: Get the year (4 digits) +: ดึงปี (4 หลัก) [getMonth()](mdn:js/Date/getMonth) -: Get the month, **from 0 to 11**. +: ดึงเดือน **ตั้งแต่ 0 ถึง 11** [getDate()](mdn:js/Date/getDate) -: Get the day of month, from 1 to 31, the name of the method does look a little bit strange. +: ดึงวันที่ในเดือน ตั้งแต่ 1 ถึง 31 ชื่อเมธอดนี้ดูแปลกๆ นิดหน่อย [getHours()](mdn:js/Date/getHours), [getMinutes()](mdn:js/Date/getMinutes), [getSeconds()](mdn:js/Date/getSeconds), [getMilliseconds()](mdn:js/Date/getMilliseconds) -: Get the corresponding time components. +: ดึงค่าส่วนประกอบของเวลาที่สอดคล้องกัน -```warn header="Not `getYear()`, but `getFullYear()`" -Many JavaScript engines implement a non-standard method `getYear()`. This method is deprecated. It returns 2-digit year sometimes. Please never use it. There is `getFullYear()` for the year. +```warn header="ไม่ใช่ `getYear()` แต่ใช้ `getFullYear()`" +JavaScript engine หลายตัวมีเมธอดที่ไม่ได้มาตรฐานชื่อ `getYear()` เมธอดนี้ถูก deprecated แล้วและบางครั้งคืนค่าปีแค่ 2 หลัก ขอให้หลีกเลี่ยงอย่างเด็ดขาด ให้ใช้ `getFullYear()` แทน ``` -Additionally, we can get a day of week: +นอกจากนี้ยังดึงวันในสัปดาห์ได้: [getDay()](mdn:js/Date/getDay) -: Get the day of week, from `0` (Sunday) to `6` (Saturday). The first day is always Sunday, in some countries that's not so, but can't be changed. +: ดึงวันในสัปดาห์ ตั้งแต่ `0` (อาทิตย์) ถึง `6` (เสาร์) วันแรกเป็นวันอาทิตย์เสมอ บางประเทศอาจไม่ใช่แบบนี้ แต่ก็ไม่สามารถเปลี่ยนแปลงได้ -**All the methods above return the components relative to the local time zone.** +**เมธอดทั้งหมดข้างต้นคืนค่าส่วนประกอบตามเขตเวลาท้องถิ่น** -There are also their UTC-counterparts, that return day, month, year and so on for the time zone UTC+0: [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay). Just insert the `"UTC"` right after `"get"`. +ยังมีเวอร์ชัน UTC สำหรับแต่ละเมธอด ซึ่งคืนค่าวัน เดือน ปีและอื่นๆ สำหรับเขตเวลา UTC+0: [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay) แค่เพิ่ม `"UTC"` ต่อจาก `"get"` ก็พอ -If your local time zone is shifted relative to UTC, then the code below shows different hours: +ถ้าเขตเวลาท้องถิ่นของคุณต่างจาก UTC โค้ดด้านล่างจะแสดงชั่วโมงที่ต่างกัน: ```js run -// current date +// วันที่ปัจจุบัน let date = new Date(); -// the hour in your current time zone +// ชั่วโมงในเขตเวลาปัจจุบัน alert( date.getHours() ); -// the hour in UTC+0 time zone (London time without daylight savings) +// ชั่วโมงในเขตเวลา UTC+0 (เวลาลอนดอนไม่มีการปรับเวลาฤดูร้อน) alert( date.getUTCHours() ); ``` -Besides the given methods, there are two special ones that do not have a UTC-variant: +นอกจากเมธอดที่กล่าวมา ยังมีอีกสองเมธอดพิเศษที่ไม่มีเวอร์ชัน UTC: [getTime()](mdn:js/Date/getTime) -: Returns the timestamp for the date -- a number of milliseconds passed from the January 1st of 1970 UTC+0. +: คืนค่า timestamp ของวันที่นั้น -- จำนวนมิลลิวินาทีที่ผ่านมาตั้งแต่วันที่ 1 มกราคม 1970 UTC+0 [getTimezoneOffset()](mdn:js/Date/getTimezoneOffset) -: Returns the difference between UTC and the local time zone, in minutes: +: คืนค่าความต่างระหว่าง UTC กับเขตเวลาท้องถิ่น เป็นหน่วยนาที: ```js run - // if you are in timezone UTC-1, outputs 60 - // if you are in timezone UTC+3, outputs -180 + // ถ้าอยู่ในเขตเวลา UTC-1 จะแสดง 60 + // ถ้าอยู่ในเขตเวลา UTC+3 จะแสดง -180 alert( new Date().getTimezoneOffset() ); ``` -## Setting date components +## การกำหนดค่าส่วนประกอบของวันที่ -The following methods allow to set date/time components: +เมธอดต่อไปนี้ใช้สำหรับกำหนดค่าส่วนประกอบของวันที่/เวลา: - [`setFullYear(year, [month], [date])`](mdn:js/Date/setFullYear) - [`setMonth(month, [date])`](mdn:js/Date/setMonth) @@ -144,38 +144,38 @@ The following methods allow to set date/time components: - [`setMinutes(min, [sec], [ms])`](mdn:js/Date/setMinutes) - [`setSeconds(sec, [ms])`](mdn:js/Date/setSeconds) - [`setMilliseconds(ms)`](mdn:js/Date/setMilliseconds) -- [`setTime(milliseconds)`](mdn:js/Date/setTime) (sets the whole date by milliseconds since 01.01.1970 UTC) +- [`setTime(milliseconds)`](mdn:js/Date/setTime) (กำหนดวันที่ทั้งหมดด้วยมิลลิวินาทีตั้งแต่ 01.01.1970 UTC) -Every one of them except `setTime()` has a UTC-variant, for instance: `setUTCHours()`. +ทุกเมธอดยกเว้น `setTime()` มีเวอร์ชัน UTC เช่น `setUTCHours()` -As we can see, some methods can set multiple components at once, for example `setHours`. The components that are not mentioned are not modified. +จะเห็นว่าบางเมธอดสามารถกำหนดหลายค่าพร้อมกันได้ เช่น `setHours` ส่วนค่าที่ไม่ได้ระบุจะไม่ถูกเปลี่ยน -For instance: +ตัวอย่าง: ```js run let today = new Date(); today.setHours(0); -alert(today); // still today, but the hour is changed to 0 +alert(today); // ยังเป็นวันนี้อยู่ แต่ชั่วโมงเปลี่ยนเป็น 0 today.setHours(0, 0, 0, 0); -alert(today); // still today, now 00:00:00 sharp. +alert(today); // ยังเป็นวันนี้อยู่ แต่ตอนนี้เป็น 00:00:00 พอดี ``` -## Autocorrection +## การแก้ไขค่าอัตโนมัติ -The *autocorrection* is a very handy feature of `Date` objects. We can set out-of-range values, and it will auto-adjust itself. +*การแก้ไขค่าอัตโนมัติ (autocorrection)* เป็นฟีเจอร์ที่มีประโยชน์มากของออบเจ็กต์ `Date` เราสามารถกำหนดค่าที่เกินขอบเขตได้ และมันจะปรับค่าให้ถูกต้องเอง -For instance: +ตัวอย่าง: ```js run -let date = new Date(2013, 0, *!*32*/!*); // 32 Jan 2013 ?!? -alert(date); // ...is 1st Feb 2013! +let date = new Date(2013, 0, *!*32*/!*); // 32 ม.ค. 2013 ?!? +alert(date); // ...กลายเป็น 1 ก.พ. 2013! ``` -Out-of-range date components are distributed automatically. +ส่วนประกอบที่เกินขอบเขตจะถูกกระจายออกไปอัตโนมัติ -Let's say we need to increase the date "28 Feb 2016" by 2 days. It may be "2 Mar" or "1 Mar" in case of a leap-year. We don't need to think about it. Just add 2 days. The `Date` object will do the rest: +สมมติว่าต้องการเพิ่ม 2 วันให้กับ "28 ก.พ. 2016" ผลลัพธ์อาจเป็น "2 มี.ค." หรือ "1 มี.ค." ถ้าปีนั้นเป็นปีอธิกสุรทิน เราไม่ต้องคิดเรื่องนี้เลย แค่บวก 2 วัน ออบเจ็กต์ `Date` จัดการให้เอง: ```js run let date = new Date(2016, 1, 28); @@ -183,112 +183,112 @@ let date = new Date(2016, 1, 28); date.setDate(date.getDate() + 2); */!* -alert( date ); // 1 Mar 2016 +alert( date ); // 1 มี.ค. 2016 ``` -That feature is often used to get the date after the given period of time. For instance, let's get the date for "70 seconds after now": +ฟีเจอร์นี้มักใช้เพื่อหาวันที่หลังจากช่วงเวลาที่กำหนด เช่น มาหาวันที่ "70 วินาทีหลังจากนี้": ```js run let date = new Date(); date.setSeconds(date.getSeconds() + 70); -alert( date ); // shows the correct date +alert( date ); // แสดงวันที่ที่ถูกต้อง ``` -We can also set zero or even negative values. For example: +นอกจากนี้ยังกำหนดค่าศูนย์หรือค่าลบได้ด้วย เช่น: ```js run -let date = new Date(2016, 0, 2); // 2 Jan 2016 +let date = new Date(2016, 0, 2); // 2 ม.ค. 2016 -date.setDate(1); // set day 1 of month +date.setDate(1); // กำหนดเป็นวันที่ 1 ของเดือน alert( date ); -date.setDate(0); // min day is 1, so the last day of the previous month is assumed -alert( date ); // 31 Dec 2015 +date.setDate(0); // วันที่น้อยสุดคือ 1 ระบบจึงถือว่าเป็นวันสุดท้ายของเดือนก่อนหน้า +alert( date ); // 31 ธ.ค. 2015 ``` -## Date to number, date diff +## วันที่เป็นตัวเลข และการลบวันที่ -When a `Date` object is converted to number, it becomes the timestamp same as `date.getTime()`: +เมื่อแปลงออบเจ็กต์ `Date` เป็นตัวเลข จะได้ค่า timestamp เหมือนกับ `date.getTime()`: ```js run let date = new Date(); -alert(+date); // the number of milliseconds, same as date.getTime() +alert(+date); // จำนวนมิลลิวินาที เหมือนกับ date.getTime() ``` -The important side effect: dates can be subtracted, the result is their difference in ms. +ผลข้างเคียงที่สำคัญคือ วันที่สามารถนำมาลบกันได้ และได้ผลลัพธ์เป็นความต่างในหน่วย ms -That can be used for time measurements: +ใช้ประโยชน์ได้ในการวัดเวลา: ```js run -let start = new Date(); // start measuring time +let start = new Date(); // เริ่มจับเวลา -// do the job +// ทำงานบางอย่าง for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } -let end = new Date(); // end measuring time +let end = new Date(); // หยุดจับเวลา -alert( `The loop took ${end - start} ms` ); +alert( `ลูปใช้เวลา ${end - start} ms` ); ``` ## Date.now() -If we only want to measure time, we don't need the `Date` object. +ถ้าต้องการแค่วัดเวลา ไม่จำเป็นต้องสร้างออบเจ็กต์ `Date` เลย -There's a special method `Date.now()` that returns the current timestamp. +มีเมธอดพิเศษ `Date.now()` ที่คืนค่า timestamp ปัจจุบัน -It is semantically equivalent to `new Date().getTime()`, but it doesn't create an intermediate `Date` object. So it's faster and doesn't put pressure on garbage collection. +ความหมายเทียบเท่ากับ `new Date().getTime()` แต่ไม่สร้างออบเจ็กต์ `Date` ชั่วคราว จึงเร็วกว่าและไม่กดดันการเก็บขยะ (garbage collection) -It is used mostly for convenience or when performance matters, like in games in JavaScript or other specialized applications. +มักใช้เพื่อความสะดวกหรือเมื่อประสิทธิภาพสำคัญ เช่นในเกม JavaScript หรือแอปพลิเคชันเฉพาะทาง -So this is probably better: +วิธีนี้จึงน่าจะดีกว่า: ```js run *!* -let start = Date.now(); // milliseconds count from 1 Jan 1970 +let start = Date.now(); // นับมิลลิวินาทีตั้งแต่ 1 ม.ค. 1970 */!* -// do the job +// ทำงานบางอย่าง for (let i = 0; i < 100000; i++) { let doSomething = i * i * i; } *!* -let end = Date.now(); // done +let end = Date.now(); // เสร็จแล้ว */!* -alert( `The loop took ${end - start} ms` ); // subtract numbers, not dates +alert( `ลูปใช้เวลา ${end - start} ms` ); // ลบตัวเลข ไม่ใช่วันที่ ``` ## Benchmarking -If we want a reliable benchmark of CPU-hungry function, we should be careful. +ถ้าต้องการทำ benchmark ที่เชื่อถือได้สำหรับฟังก์ชันที่ใช้ CPU มาก ต้องระมัดระวัง -For instance, let's measure two functions that calculate the difference between two dates: which one is faster? +ลองวัดสองฟังก์ชันที่คำนวณความต่างระหว่างวันที่สองวัน: ฟังก์ชันไหนเร็วกว่า? -Such performance measurements are often called "benchmarks". +การวัดประสิทธิภาพแบบนี้มักเรียกว่า "benchmark" ```js -// we have date1 and date2, which function faster returns their difference in ms? +// มี date1 และ date2 ฟังก์ชันไหนคืนค่าความต่างในหน่วย ms ได้เร็วกว่า? function diffSubtract(date1, date2) { return date2 - date1; } -// or +// หรือ function diffGetTime(date1, date2) { return date2.getTime() - date1.getTime(); } ``` -These two do exactly the same thing, but one of them uses an explicit `date.getTime()` to get the date in ms, and the other one relies on a date-to-number transform. Their result is always the same. +ทั้งสองทำสิ่งเดียวกันทุกประการ แต่ฟังก์ชันหนึ่งใช้ `date.getTime()` อย่างชัดเจนเพื่อดึงค่าวันที่เป็น ms ส่วนอีกฟังก์ชันอาศัยการแปลงวันที่เป็นตัวเลข ผลลัพธ์เหมือนกันเสมอ -So, which one is faster? +แล้วฟังก์ชันไหนเร็วกว่ากัน? -The first idea may be to run them many times in a row and measure the time difference. For our case, functions are very simple, so we have to do it at least 100000 times. +ความคิดแรกอาจเป็นการรันซ้ำหลายๆ ครั้งติดกันแล้ววัดความต่างของเวลา สำหรับกรณีนี้ฟังก์ชันเรียบง่ายมาก จึงต้องรันอย่างน้อย 100,000 ครั้ง -Let's measure: +มาวัดกัน: ```js run function diffSubtract(date1, date2) { @@ -308,23 +308,23 @@ function bench(f) { return Date.now() - start; } -alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' ); -alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' ); +alert( 'เวลาของ diffSubtract: ' + bench(diffSubtract) + 'ms' ); +alert( 'เวลาของ diffGetTime: ' + bench(diffGetTime) + 'ms' ); ``` -Wow! Using `getTime()` is so much faster! That's because there's no type conversion, it is much easier for engines to optimize. +ว้าว! การใช้ `getTime()` เร็วกว่ามากเลย! เพราะไม่มีการแปลงประเภท engine จึง optimize ได้ง่ายกว่า -Okay, we have something. But that's not a good benchmark yet. +โอเค เราได้ข้อมูลมาแล้ว แต่นี่ยังไม่ใช่ benchmark ที่ดี -Imagine that at the time of running `bench(diffSubtract)` CPU was doing something in parallel, and it was taking resources. And by the time of running `bench(diffGetTime)` that work has finished. +ลองนึกภาพว่าตอนที่รัน `bench(diffSubtract)` CPU กำลังทำงานอื่นอยู่พร้อมกันและกินทรัพยากรไป พอถึงเวลารัน `bench(diffGetTime)` งานนั้นเสร็จแล้ว -A pretty real scenario for a modern multi-process OS. +เป็นสถานการณ์จริงที่พบได้บ่อยในระบบปฏิบัติการแบบ multi-process สมัยใหม่ -As a result, the first benchmark will have less CPU resources than the second. That may lead to wrong results. +ผลก็คือ benchmark แรกจะได้ทรัพยากร CPU น้อยกว่า benchmark ที่สอง ซึ่งอาจนำไปสู่ผลลัพธ์ที่ผิดพลาดได้ -**For more reliable benchmarking, the whole pack of benchmarks should be rerun multiple times.** +**เพื่อให้ benchmark น่าเชื่อถือกว่า ควรรันชุด benchmark ทั้งหมดหลายๆ รอบ** -For example, like this: +ตัวอย่าง: ```js run function diffSubtract(date1, date2) { @@ -348,53 +348,53 @@ let time1 = 0; let time2 = 0; *!* -// run bench(diffSubtract) and bench(diffGetTime) each 10 times alternating +// รัน bench(diffSubtract) และ bench(diffGetTime) สลับกัน 10 รอบ for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } */!* -alert( 'Total time for diffSubtract: ' + time1 ); -alert( 'Total time for diffGetTime: ' + time2 ); +alert( 'เวลารวมของ diffSubtract: ' + time1 ); +alert( 'เวลารวมของ diffGetTime: ' + time2 ); ``` -Modern JavaScript engines start applying advanced optimizations only to "hot code" that executes many times (no need to optimize rarely executed things). So, in the example above, first executions are not well-optimized. We may want to add a heat-up run: +JavaScript engine สมัยใหม่จะเริ่มใช้การ optimize ขั้นสูงเฉพาะกับ "hot code" ที่ทำงานซ้ำหลายครั้ง (ไม่จำเป็นต้อง optimize สิ่งที่รันไม่บ่อย) ดังนั้นในตัวอย่างข้างต้น การรันครั้งแรกๆ ยังไม่ได้รับการ optimize เราอาจต้องการเพิ่มการ "อุ่นเครื่อง" ก่อน: ```js -// added for "heating up" prior to the main loop +// เพิ่มเพื่อ "อุ่นเครื่อง" ก่อนเข้าลูปหลัก bench(diffSubtract); bench(diffGetTime); -// now benchmark +// เริ่ม benchmark จริง for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); } ``` -```warn header="Be careful doing microbenchmarking" -Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. +```warn header="ระวังการทำ microbenchmarking" +JavaScript engine สมัยใหม่ทำการ optimize หลายอย่าง ซึ่งอาจบิดเบือนผลของ "การทดสอบเทียม" เมื่อเทียบกับ "การใช้งานจริง" โดยเฉพาะเมื่อ benchmark สิ่งที่เล็กมาก เช่น วิธีที่ตัวดำเนินการทำงาน หรือฟังก์ชัน built-in ดังนั้นถ้าต้องการเข้าใจประสิทธิภาพอย่างจริงจัง โปรดศึกษาว่า JavaScript engine ทำงานอย่างไร แล้วจะพบว่าแทบไม่จำเป็นต้องใช้ microbenchmark เลย -The great pack of articles about V8 can be found at . +บทความดีๆ เกี่ยวกับ V8 สามารถหาได้ที่ ``` -## Date.parse from a string +## Date.parse จากสตริง -The method [Date.parse(str)](mdn:js/Date/parse) can read a date from a string. +เมธอด [Date.parse(str)](mdn:js/Date/parse) สามารถอ่านวันที่จากสตริงได้ -The string format should be: `YYYY-MM-DDTHH:mm:ss.sssZ`, where: +รูปแบบสตริงควรเป็น: `YYYY-MM-DDTHH:mm:ss.sssZ` โดยที่: -- `YYYY-MM-DD` -- is the date: year-month-day. -- The character `"T"` is used as the delimiter. -- `HH:mm:ss.sss` -- is the time: hours, minutes, seconds and milliseconds. -- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` would mean UTC+0. +- `YYYY-MM-DD` -- คือวันที่: ปี-เดือน-วัน +- อักขระ `"T"` ใช้เป็นตัวคั่น +- `HH:mm:ss.sss` -- คือเวลา: ชั่วโมง นาที วินาที และมิลลิวินาที +- ส่วน `'Z'` ที่เป็น optional ระบุเขตเวลาในรูปแบบ `+-hh:mm` ถ้าเป็นตัวอักษร `Z` เพียงตัวเดียว หมายถึง UTC+0 -Shorter variants are also possible, like `YYYY-MM-DD` or `YYYY-MM` or even `YYYY`. +รูปแบบย่อก็ได้เช่นกัน เช่น `YYYY-MM-DD` หรือ `YYYY-MM` หรือแม้แต่ `YYYY` -The call to `Date.parse(str)` parses the string in the given format and returns the timestamp (number of milliseconds from 1 Jan 1970 UTC+0). If the format is invalid, returns `NaN`. +การเรียก `Date.parse(str)` จะ parse สตริงตามรูปแบบที่กำหนดและคืนค่า timestamp (จำนวนมิลลิวินาทีตั้งแต่ 1 ม.ค. 1970 UTC+0) ถ้ารูปแบบไม่ถูกต้อง จะคืนค่า `NaN` -For instance: +ตัวอย่าง: ```js run let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); @@ -402,7 +402,7 @@ let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); alert(ms); // 1327611110417 (timestamp) ``` -We can instantly create a `new Date` object from the timestamp: +เราสามารถสร้างออบเจ็กต์ `new Date` จาก timestamp ได้ทันที: ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); @@ -410,24 +410,24 @@ let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); alert(date); ``` -## Summary +## สรุป -- Date and time in JavaScript are represented with the [Date](mdn:js/Date) object. We can't create "only date" or "only time": `Date` objects always carry both. -- Months are counted from zero (yes, January is a zero month). -- Days of week in `getDay()` are also counted from zero (that's Sunday). -- `Date` auto-corrects itself when out-of-range components are set. Good for adding/subtracting days/months/hours. -- Dates can be subtracted, giving their difference in milliseconds. That's because a `Date` becomes the timestamp when converted to a number. -- Use `Date.now()` to get the current timestamp fast. +- วันที่และเวลาใน JavaScript ใช้ออบเจ็กต์ [Date](mdn:js/Date) เราไม่สามารถสร้าง "แค่วันที่" หรือ "แค่เวลา" ได้: ออบเจ็กต์ `Date` มีทั้งสองเสมอ +- เดือนนับจากศูนย์ (ใช่ มกราคมคือเดือนที่ศูนย์) +- วันในสัปดาห์ใน `getDay()` ก็นับจากศูนย์เช่นกัน (นั่นคือวันอาทิตย์) +- `Date` จะแก้ไขค่าตัวเองอัตโนมัติเมื่อกำหนดค่าที่เกินขอบเขต สะดวกสำหรับการบวก/ลบวัน/เดือน/ชั่วโมง +- วันที่สามารถลบกันได้ ได้ผลต่างในหน่วยมิลลิวินาที เพราะออบเจ็กต์ `Date` กลายเป็น timestamp เมื่อแปลงเป็นตัวเลข +- ใช้ `Date.now()` เพื่อดึง timestamp ปัจจุบันได้อย่างรวดเร็ว -Note that unlike many other systems, timestamps in JavaScript are in milliseconds, not in seconds. +โปรดทราบว่าต่างจากระบบอื่นๆ หลายระบบ timestamp ใน JavaScript มีหน่วยเป็นมิลลิวินาที ไม่ใช่วินาที -Sometimes we need more precise time measurements. JavaScript itself does not have a way to measure time in microseconds (1 millionth of a second), but most environments provide it. For instance, browser has [performance.now()](mdn:api/Performance/now) that gives the number of milliseconds from the start of page loading with microsecond precision (3 digits after the point): +บางครั้งเราต้องการวัดเวลาที่แม่นยำกว่านี้ JavaScript เองไม่มีวิธีวัดเวลาในระดับไมโครวินาที (1 ในล้านของวินาที) แต่สภาพแวดล้อมส่วนใหญ่มีให้ เช่น เบราว์เซอร์มี [performance.now()](mdn:api/Performance/now) ที่คืนค่าจำนวนมิลลิวินาทีตั้งแต่เริ่มโหลดหน้าเว็บพร้อมความแม่นยำระดับไมโครวินาที (3 ตำแหน่งหลังจุดทศนิยม): ```js run -alert(`Loading started ${performance.now()}ms ago`); -// Something like: "Loading started 34731.26000000001ms ago" -// .26 is microseconds (260 microseconds) -// more than 3 digits after the decimal point are precision errors, only the first 3 are correct +alert(`โหลดเริ่มมาแล้ว ${performance.now()}ms`); +// บางอย่างเช่น: "โหลดเริ่มมาแล้ว 34731.26000000001ms" +// .26 คือไมโครวินาที (260 ไมโครวินาที) +// ตัวเลขหลังจุดทศนิยมมากกว่า 3 ตำแหน่งคือความผิดพลาดของความแม่นยำ ถูกต้องแค่ 3 ตำแหน่งแรก ``` -Node.js has `microtime` module and other ways. Technically, almost any device and environment allows to get more precision, it's just not in `Date`. +Node.js มีโมดูล `microtime` และวิธีอื่นๆ ในทางเทคนิค แทบทุกอุปกรณ์และสภาพแวดล้อมสามารถให้ความแม่นยำสูงกว่านี้ได้ เพียงแต่ยังไม่อยู่ใน `Date` diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index 133ffb353..c5bf5209f 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -1,10 +1,10 @@ -# JSON methods, toJSON +# เมธอด JSON และ toJSON -Let's say we have a complex object, and we'd like to convert it into a string, to send it over a network, or just to output it for logging purposes. +สมมติว่าเรามีออบเจ็กต์ที่ซับซ้อนและต้องการแปลงเป็นสตริง เพื่อส่งผ่านเครือข่ายหรือเพื่อ log ข้อมูล -Naturally, such a string should include all important properties. +แน่นอนว่าสตริงนั้นควรรวมพร็อพเพอร์ตี้ที่สำคัญทั้งหมดไว้ด้วย -We could implement the conversion like this: +เราอาจทำการแปลงแบบนี้: ```js run let user = { @@ -21,20 +21,20 @@ let user = { alert(user); // {name: "John", age: 30} ``` -...But in the process of development, new properties are added, old properties are renamed and removed. Updating such `toString` every time can become a pain. We could try to loop over properties in it, but what if the object is complex and has nested objects in properties? We'd need to implement their conversion as well. +...แต่ระหว่างพัฒนา อาจมีการเพิ่มพร็อพเพอร์ตี้ใหม่ เปลี่ยนชื่อ หรือลบพร็อพเพอร์ตี้เก่าออก การอัปเดต `toString` ทุกครั้งอาจกลายเป็นเรื่องยุ่งยาก เราอาจลองวนลูปผ่านพร็อพเพอร์ตี้ แต่ถ้าออบเจ็กต์ซับซ้อนและมีออบเจ็กต์ซ้อนอยู่ข้างใน ก็ต้องแปลงออบเจ็กต์เหล่านั้นด้วย -Luckily, there's no need to write the code to handle all this. The task has been solved already. +โชคดีที่ไม่จำเป็นต้องเขียนโค้ดจัดการทั้งหมดนี้เอง เพราะมีวิธีที่แก้ปัญหานี้ไว้แล้ว ## JSON.stringify -The [JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](https://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever. +[JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) เป็นรูปแบบทั่วไปสำหรับแสดงค่าและออบเจ็กต์ ถูกกำหนดไว้ในมาตรฐาน [RFC 4627](https://tools.ietf.org/html/rfc4627) เดิมทีสร้างขึ้นสำหรับ JavaScript แต่ภาษาอื่นๆ อีกมากก็มีไลบรารีรองรับ JSON ด้วย ทำให้ใช้ JSON แลกเปลี่ยนข้อมูลได้ง่าย เมื่อฝั่ง client ใช้ JavaScript และ server เขียนด้วย Ruby/PHP/Java หรืออะไรก็ตาม -JavaScript provides methods: +JavaScript มีเมธอดสำหรับจัดการ: -- `JSON.stringify` to convert objects into JSON. -- `JSON.parse` to convert JSON back into an object. +- `JSON.stringify` — แปลงออบเจ็กต์เป็น JSON +- `JSON.parse` — แปลง JSON กลับเป็นออบเจ็กต์ -For instance, here we `JSON.stringify` a student: +ลองดูตัวอย่างการใช้ `JSON.stringify` กับข้อมูลนักเรียน: ```js run let student = { name: 'John', @@ -48,11 +48,11 @@ let student = { let json = JSON.stringify(student); */!* -alert(typeof json); // we've got a string! +alert(typeof json); // เราได้สตริงแล้ว! alert(json); *!* -/* JSON-encoded object: +/* ออบเจ็กต์ที่เข้ารหัสเป็น JSON: { "name": "John", "age": 30, @@ -64,35 +64,35 @@ alert(json); */!* ``` -The method `JSON.stringify(student)` takes the object and converts it into a string. +เมธอด `JSON.stringify(student)` รับออบเจ็กต์แล้วแปลงเป็นสตริง -The resulting `json` string is called a *JSON-encoded* or *serialized* or *stringified* or *marshalled* object. We are ready to send it over the wire or put into a plain data store. +สตริง `json` ที่ได้นี้เรียกว่าออบเจ็กต์ที่ถูก *JSON-encoded* หรือ *serialized* หรือ *stringified* หรือ *marshalled* และพร้อมส่งผ่านเครือข่ายหรือเก็บไว้ใน data store ทั่วไปแล้ว -Please note that a JSON-encoded object has several important differences from the object literal: +สิ่งสำคัญคือ ออบเจ็กต์ที่ถูก JSON-encoded มีความแตกต่างจาก object literal ทั่วไปหลายอย่าง: -- Strings use double quotes. No single quotes or backticks in JSON. So `'John'` becomes `"John"`. -- Object property names are double-quoted also. That's obligatory. So `age:30` becomes `"age":30`. +- สตริงใช้เครื่องหมายคำพูดคู่ ใน JSON ไม่มีคำพูดเดี่ยวหรือ backtick ดังนั้น `'John'` จะกลายเป็น `"John"` +- ชื่อพร็อพเพอร์ตี้ก็ใส่เครื่องหมายคำพูดคู่ด้วย นั่นเป็นกฎบังคับ ดังนั้น `age:30` จะกลายเป็น `"age":30` -`JSON.stringify` can be applied to primitives as well. +`JSON.stringify` ยังใช้กับค่า primitive ได้ด้วย -JSON supports following data types: +JSON รองรับชนิดข้อมูลดังนี้: -- Objects `{ ... }` -- Arrays `[ ... ]` -- Primitives: - - strings, - - numbers, - - boolean values `true/false`, - - `null`. +- ออบเจ็กต์ `{ ... }` +- อาร์เรย์ `[ ... ]` +- Primitive: + - สตริง, + - ตัวเลข, + - บูลีน `true/false`, + - `null` -For instance: +ตัวอย่าง: ```js run -// a number in JSON is just a number +// ตัวเลขใน JSON ก็คือตัวเลขธรรมดา alert( JSON.stringify(1) ) // 1 -// a string in JSON is still a string, but double-quoted +// สตริงใน JSON ยังคงเป็นสตริง แต่ใส่เครื่องหมายคำพูดคู่ alert( JSON.stringify('test') ) // "test" alert( JSON.stringify(true) ); // true @@ -100,31 +100,31 @@ alert( JSON.stringify(true) ); // true alert( JSON.stringify([1, 2, 3]) ); // [1,2,3] ``` -JSON is data-only language-independent specification, so some JavaScript-specific object properties are skipped by `JSON.stringify`. +JSON เป็นสเปซิฟิเคชันที่เป็นอิสระจากภาษาโปรแกรมและเก็บได้แต่ข้อมูล ดังนั้น `JSON.stringify` จะข้ามพร็อพเพอร์ตี้บางอย่างที่เป็น JavaScript เฉพาะ -Namely: +โดยเฉพาะ: -- Function properties (methods). -- Symbolic keys and values. -- Properties that store `undefined`. +- พร็อพเพอร์ตี้ที่เป็นฟังก์ชัน (เมธอด) +- คีย์และค่าที่เป็น Symbol +- พร็อพเพอร์ตี้ที่เก็บค่า `undefined` ```js run let user = { - sayHi() { // ignored + sayHi() { // ถูกข้าม alert("Hello"); }, - [Symbol("id")]: 123, // ignored - something: undefined // ignored + [Symbol("id")]: 123, // ถูกข้าม + something: undefined // ถูกข้าม }; -alert( JSON.stringify(user) ); // {} (empty object) +alert( JSON.stringify(user) ); // {} (ออบเจ็กต์ว่างเปล่า) ``` -Usually that's fine. If that's not what we want, then soon we'll see how to customize the process. +ส่วนใหญ่แล้วก็ไม่มีปัญหา แต่ถ้าไม่ต้องการแบบนี้ เดี๋ยวเราจะดูวิธีปรับแต่งกระบวนการนี้ -The great thing is that nested objects are supported and converted automatically. +สิ่งที่ดีคือออบเจ็กต์ที่ซ้อนกันก็รองรับและแปลงโดยอัตโนมัติ -For instance: +ตัวอย่าง: ```js run let meetup = { @@ -138,7 +138,7 @@ let meetup = { }; alert( JSON.stringify(meetup) ); -/* The whole structure is stringified: +/* โครงสร้างทั้งหมดถูกแปลงเป็น string: { "title":"Conference", "room":{"number":23,"participants":["john","ann"]}, @@ -146,9 +146,9 @@ alert( JSON.stringify(meetup) ); */ ``` -The important limitation: there must be no circular references. +ข้อจำกัดสำคัญคือต้องไม่มี circular reference -For instance: +ตัวอย่าง: ```js run let room = { @@ -160,41 +160,41 @@ let meetup = { participants: ["john", "ann"] }; -meetup.place = room; // meetup references room -room.occupiedBy = meetup; // room references meetup +meetup.place = room; // meetup อ้างอิงถึง room +room.occupiedBy = meetup; // room อ้างอิงถึง meetup *!* JSON.stringify(meetup); // Error: Converting circular structure to JSON */!* ``` -Here, the conversion fails, because of circular reference: `room.occupiedBy` references `meetup`, and `meetup.place` references `room`: +การแปลงล้มเหลวเพราะมี circular reference: `room.occupiedBy` อ้างอิง `meetup` และ `meetup.place` อ้างอิง `room`: ![](json-meetup.svg) -## Excluding and transforming: replacer +## การกรองและแปลงค่า: replacer -The full syntax of `JSON.stringify` is: +ไวยากรณ์เต็มของ `JSON.stringify` คือ: ```js let json = JSON.stringify(value[, replacer, space]) ``` value -: A value to encode. +: ค่าที่ต้องการเข้ารหัส replacer -: Array of properties to encode or a mapping function `function(key, value)`. +: อาร์เรย์ของพร็อพเพอร์ตี้ที่ต้องการเข้ารหัส หรือฟังก์ชัน `function(key, value)` space -: Amount of space to use for formatting +: จำนวน space สำหรับการจัดรูปแบบ -Most of the time, `JSON.stringify` is used with the first argument only. But if we need to fine-tune the replacement process, like to filter out circular references, we can use the second argument of `JSON.stringify`. +ส่วนใหญ่ `JSON.stringify` ใช้กับอาร์กิวเมนต์แรกเท่านั้น แต่ถ้าต้องการปรับแต่งกระบวนการแทนที่ เช่น กรอง circular reference ออก ก็ใช้อาร์กิวเมนต์ที่สองได้ -If we pass an array of properties to it, only these properties will be encoded. +ถ้าส่งอาร์เรย์ของพร็อพเพอร์ตี้เข้าไป จะเข้ารหัสเฉพาะพร็อพเพอร์ตี้เหล่านั้น -For instance: +ตัวอย่าง: ```js run let room = { @@ -204,18 +204,18 @@ let room = { let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], - place: room // meetup references room + place: room // meetup อ้างอิงถึง room }; -room.occupiedBy = meetup; // room references meetup +room.occupiedBy = meetup; // room อ้างอิงถึง meetup alert( JSON.stringify(meetup, *!*['title', 'participants']*/!*) ); // {"title":"Conference","participants":[{},{}]} ``` -Here we are probably too strict. The property list is applied to the whole object structure. So the objects in `participants` are empty, because `name` is not in the list. +ดูเหมือนจะเข้มงวดเกินไปหน่อย เพราะรายการพร็อพเพอร์ตี้นี้ถูกนำไปใช้กับโครงสร้างออบเจ็กต์ทั้งหมด ทำให้ออบเจ็กต์ใน `participants` ว่างเปล่า เนื่องจาก `name` ไม่อยู่ในรายการ -Let's include in the list every property except `room.occupiedBy` that would cause the circular reference: +มาใส่พร็อพเพอร์ตี้ทุกตัวยกเว้น `room.occupiedBy` ที่จะทำให้เกิด circular reference: ```js run let room = { @@ -225,10 +225,10 @@ let room = { let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], - place: room // meetup references room + place: room // meetup อ้างอิงถึง room }; -room.occupiedBy = meetup; // room references meetup +room.occupiedBy = meetup; // room อ้างอิงถึง meetup alert( JSON.stringify(meetup, *!*['title', 'participants', 'place', 'name', 'number']*/!*) ); /* @@ -240,13 +240,13 @@ alert( JSON.stringify(meetup, *!*['title', 'participants', 'place', 'name', 'num */ ``` -Now everything except `occupiedBy` is serialized. But the list of properties is quite long. +ตอนนี้ทุกอย่างยกเว้น `occupiedBy` ถูก serialize แล้ว แต่รายการพร็อพเพอร์ตี้ค่อนข้างยาว -Fortunately, we can use a function instead of an array as the `replacer`. +โชคดีที่เราใช้ฟังก์ชันแทนอาร์เรย์เป็น `replacer` ได้ -The function will be called for every `(key, value)` pair and should return the "replaced" value, which will be used instead of the original one. Or `undefined` if the value is to be skipped. +ฟังก์ชันนี้จะถูกเรียกสำหรับทุกคู่ `(key, value)` และควรคืนค่าที่ "แทนที่" ซึ่งจะถูกใช้แทนค่าเดิม หรือคืน `undefined` ถ้าต้องการข้ามค่านั้น -In our case, we can return `value` "as is" for everything except `occupiedBy`. To ignore `occupiedBy`, the code below returns `undefined`: +ในกรณีของเรา เราคืน `value` "ตามเดิม" สำหรับทุกอย่างยกเว้น `occupiedBy` และส่ง `undefined` เพื่อข้ามมัน: ```js run let room = { @@ -256,17 +256,17 @@ let room = { let meetup = { title: "Conference", participants: [{name: "John"}, {name: "Alice"}], - place: room // meetup references room + place: room // meetup อ้างอิงถึง room }; -room.occupiedBy = meetup; // room references meetup +room.occupiedBy = meetup; // room อ้างอิงถึง meetup alert( JSON.stringify(meetup, function replacer(key, value) { alert(`${key}: ${value}`); return (key == 'occupiedBy') ? undefined : value; })); -/* key:value pairs that come to replacer: +/* คู่ key:value ที่ส่งให้ replacer: : [object Object] title: Conference participants: [object Object],[object Object] @@ -280,20 +280,20 @@ occupiedBy: [object Object] */ ``` -Please note that `replacer` function gets every key/value pair including nested objects and array items. It is applied recursively. The value of `this` inside `replacer` is the object that contains the current property. +สังเกตว่าฟังก์ชัน `replacer` ได้รับทุกคู่ key/value รวมถึงออบเจ็กต์ที่ซ้อนกันและรายการในอาร์เรย์ด้วย โดยทำงานแบบ recursive และค่าของ `this` ภายใน `replacer` คือออบเจ็กต์ที่มีพร็อพเพอร์ตี้ปัจจุบันอยู่ -The first call is special. It is made using a special "wrapper object": `{"": meetup}`. In other words, the first `(key, value)` pair has an empty key, and the value is the target object as a whole. That's why the first line is `":[object Object]"` in the example above. +การเรียกครั้งแรกพิเศษเป็น เพราะใช้ "wrapper object" พิเศษ: `{"": meetup}` กล่าวอีกอย่างคือ คู่ `(key, value)` แรกมี key ว่างเปล่า และ value คือออบเจ็กต์เป้าหมายทั้งหมด นั่นเป็นเหตุผลที่บรรทัดแรกในตัวอย่างข้างต้นเป็น `":[object Object]"` -The idea is to provide as much power for `replacer` as possible: it has a chance to analyze and replace/skip even the whole object if necessary. +แนวคิดคือเพื่อให้ `replacer` มีพลังมากที่สุดเท่าที่จะเป็นไปได้ โดยมีโอกาสวิเคราะห์และแทนที่หรือข้ามแม้แต่ออบเจ็กต์ทั้งหมดหากจำเป็น -## Formatting: space +## การจัดรูปแบบ: space -The third argument of `JSON.stringify(value, replacer, space)` is the number of spaces to use for pretty formatting. +อาร์กิวเมนต์ที่สามของ `JSON.stringify(value, replacer, space)` คือจำนวน space สำหรับการจัดรูปแบบที่อ่านง่าย -Previously, all stringified objects had no indents and extra spaces. That's fine if we want to send an object over a network. The `space` argument is used exclusively for a nice output. +ก่อนหน้านี้ ออบเจ็กต์ที่ stringify แล้วทั้งหมดไม่มีการเยื้องหรือ space เพิ่มเติม ซึ่งก็เพียงพอถ้าต้องการส่งออบเจ็กต์ผ่านเครือข่าย แต่อาร์กิวเมนต์ `space` ใช้สำหรับแสดงผลให้อ่านง่ายขึ้นเท่านั้น -Here `space = 2` tells JavaScript to show nested objects on multiple lines, with indentation of 2 spaces inside an object: +ตรงนี้ `space = 2` บอกให้ JavaScript แสดงออบเจ็กต์ที่ซ้อนกันบนหลายบรรทัด โดยเยื้อง 2 space ข้างใน: ```js run let user = { @@ -306,7 +306,7 @@ let user = { }; alert(JSON.stringify(user, null, 2)); -/* two-space indents: +/* เยื้องด้วย 2 space: { "name": "John", "age": 25, @@ -317,7 +317,7 @@ alert(JSON.stringify(user, null, 2)); } */ -/* for JSON.stringify(user, null, 4) the result would be more indented: +/* สำหรับ JSON.stringify(user, null, 4) ผลลัพธ์จะเยื้องมากกว่า: { "name": "John", "age": 25, @@ -329,15 +329,15 @@ alert(JSON.stringify(user, null, 2)); */ ``` -The third argument can also be a string. In this case, the string is used for indentation instead of a number of spaces. +อาร์กิวเมนต์ที่สามยังเป็นสตริงได้ด้วย ในกรณีนั้นสตริงจะถูกใช้แทนจำนวน space สำหรับการเยื้อง -The `space` parameter is used solely for logging and nice-output purposes. +พารามิเตอร์ `space` ใช้เพื่อการ log และแสดงผลที่อ่านง่ายเท่านั้น -## Custom "toJSON" +## toJSON แบบกำหนดเอง -Like `toString` for string conversion, an object may provide method `toJSON` for to-JSON conversion. `JSON.stringify` automatically calls it if available. +เหมือนกับ `toString` สำหรับการแปลงเป็นสตริง ออบเจ็กต์อาจมีเมธอด `toJSON` สำหรับการแปลงเป็น JSON ซึ่ง `JSON.stringify` จะเรียกโดยอัตโนมัติถ้ามีอยู่ -For instance: +ตัวอย่าง: ```js run let room = { @@ -362,9 +362,9 @@ alert( JSON.stringify(meetup) ); */ ``` -Here we can see that `date` `(1)` became a string. That's because all dates have a built-in `toJSON` method which returns such kind of string. +จะเห็นว่า `date` `(1)` กลายเป็นสตริง เพราะออบเจ็กต์ Date ทั้งหมดมีเมธอด `toJSON` ในตัวที่คืนค่าสตริงในรูปแบบนั้น -Now let's add a custom `toJSON` for our object `room` `(2)`: +ทีนี้มาเพิ่ม `toJSON` แบบกำหนดเองให้ออบเจ็กต์ `room` `(2)`: ```js run let room = { @@ -396,28 +396,28 @@ alert( JSON.stringify(meetup) ); */ ``` -As we can see, `toJSON` is used both for the direct call `JSON.stringify(room)` and when `room` is nested in another encoded object. +จะเห็นว่า `toJSON` ถูกใช้ทั้งกับการเรียกโดยตรง `JSON.stringify(room)` และเมื่อ `room` ซ้อนอยู่ในออบเจ็กต์ที่เข้ารหัสอื่น ## JSON.parse -To decode a JSON-string, we need another method named [JSON.parse](mdn:js/JSON/parse). +ในการ decode สตริง JSON เราต้องใช้เมธอด [JSON.parse](mdn:js/JSON/parse) -The syntax: +ไวยากรณ์: ```js let value = JSON.parse(str[, reviver]); ``` str -: JSON-string to parse. +: สตริง JSON ที่ต้องการ parse reviver -: Optional function(key,value) that will be called for each `(key, value)` pair and can transform the value. +: ฟังก์ชัน function(key,value) ที่ไม่บังคับ จะถูกเรียกสำหรับทุกคู่ `(key, value)` และสามารถแปลงค่าได้ -For instance: +ตัวอย่าง: ```js run -// stringified array +// อาร์เรย์ที่ถูก stringify let numbers = "[0, 1, 2, 3]"; numbers = JSON.parse(numbers); @@ -425,7 +425,7 @@ numbers = JSON.parse(numbers); alert( numbers[1] ); // 1 ``` -Or for nested objects: +หรือสำหรับออบเจ็กต์ที่ซ้อนกัน: ```js run let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; @@ -435,40 +435,40 @@ let user = JSON.parse(userData); alert( user.friends[1] ); // 1 ``` -The JSON may be as complex as necessary, objects and arrays can include other objects and arrays. But they must obey the same JSON format. +JSON อาจซับซ้อนแค่ไหนก็ได้ ออบเจ็กต์และอาร์เรย์สามารถมีออบเจ็กต์และอาร์เรย์อื่นซ้อนอยู่ได้ แต่ต้องเป็นไปตามรูปแบบ JSON เดียวกัน -Here are typical mistakes in hand-written JSON (sometimes we have to write it for debugging purposes): +นี่คือข้อผิดพลาดที่พบบ่อยใน JSON ที่เขียนด้วยมือ (บางครั้งต้องเขียนเองเพื่อ debug): ```js let json = `{ - *!*name*/!*: "John", // mistake: property name without quotes - "surname": *!*'Smith'*/!*, // mistake: single quotes in value (must be double) - *!*'isAdmin'*/!*: false // mistake: single quotes in key (must be double) - "birthday": *!*new Date(2000, 2, 3)*/!*, // mistake: no "new" is allowed, only bare values - "friends": [0,1,2,3] // here all fine + *!*name*/!*: "John", // ข้อผิดพลาด: ชื่อพร็อพเพอร์ตี้ไม่มีเครื่องหมายคำพูด + "surname": *!*'Smith'*/!*, // ข้อผิดพลาด: ใช้คำพูดเดี่ยวสำหรับค่า (ต้องใช้คู่) + *!*'isAdmin'*/!*: false // ข้อผิดพลาด: ใช้คำพูดเดี่ยวสำหรับ key (ต้องใช้คู่) + "birthday": *!*new Date(2000, 2, 3)*/!*, // ข้อผิดพลาด: ไม่อนุญาต "new" ใช้ได้แค่ค่าพื้นฐาน + "friends": [0,1,2,3] // ตรงนี้ถูกต้อง }`; ``` -Besides, JSON does not support comments. Adding a comment to JSON makes it invalid. +นอกจากนี้ JSON ไม่รองรับ comment ถ้าใส่ comment ใน JSON จะทำให้ไม่ valid -There's another format named [JSON5](https://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. +มีรูปแบบอื่นชื่อ [JSON5](https://json5.org/) ที่อนุญาต unquoted keys, comment ฯลฯ แต่เป็น library แยกต่างหาก ไม่ได้อยู่ในสเปซิฟิเคชันของภาษา -The regular JSON is that strict not because its developers are lazy, but to allow easy, reliable and very fast implementations of the parsing algorithm. +JSON ที่ใช้กันทั่วไปเข้มงวดขนาดนี้ ไม่ใช่เพราะผู้พัฒนาขี้เกียจ แต่เพื่อให้สามารถสร้างอัลกอริทึม parsing ที่ง่าย เชื่อถือได้ และรวดเร็วมาก -## Using reviver +## การใช้ reviver -Imagine, we got a stringified `meetup` object from the server. +สมมติว่าเราได้รับออบเจ็กต์ `meetup` ที่ถูก stringify มาจาก server -It looks like this: +ข้อมูลนั้นมีหน้าตาแบบนี้: ```js -// title: (meetup title), date: (meetup date) +// title: (ชื่อการประชุม), date: (วันที่ประชุม) let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; ``` -...And now we need to *deserialize* it, to turn back into JavaScript object. +...และตอนนี้เราต้องการ *deserialize* มัน เพื่อแปลงกลับเป็นออบเจ็กต์ JavaScript -Let's do it by calling `JSON.parse`: +มาทำด้วยการเรียก `JSON.parse`: ```js run let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; @@ -476,15 +476,15 @@ let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; let meetup = JSON.parse(str); *!* -alert( meetup.date.getDate() ); // Error! +alert( meetup.date.getDate() ); // เกิดข้อผิดพลาด! */!* ``` -Whoops! An error! +อุ๊ย! เกิดข้อผิดพลาด! -The value of `meetup.date` is a string, not a `Date` object. How could `JSON.parse` know that it should transform that string into a `Date`? +ค่าของ `meetup.date` เป็นสตริง ไม่ใช่ออบเจ็กต์ `Date` แล้ว `JSON.parse` จะรู้ได้อย่างไรว่าควรแปลงสตริงนั้นเป็น `Date`? -Let's pass to `JSON.parse` the reviving function as the second argument, that returns all values "as is", but `date` will become a `Date`: +มาส่งฟังก์ชัน reviving ให้กับ `JSON.parse` เป็นอาร์กิวเมนต์ที่สอง โดยคืนค่าทุกอย่าง "ตามเดิม" ยกเว้น `date` จะกลายเป็น `Date`: ```js run let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; @@ -496,10 +496,10 @@ let meetup = JSON.parse(str, function(key, value) { }); */!* -alert( meetup.date.getDate() ); // now works! +alert( meetup.date.getDate() ); // ทำงานได้แล้ว! ``` -By the way, that works for nested objects as well: +อนึ่ง วิธีนี้ใช้ได้กับออบเจ็กต์ที่ซ้อนกันด้วย: ```js run let schedule = `{ @@ -515,16 +515,16 @@ schedule = JSON.parse(schedule, function(key, value) { }); *!* -alert( schedule.meetups[1].date.getDate() ); // works! +alert( schedule.meetups[1].date.getDate() ); // ทำงานได้! */!* ``` -## Summary +## สรุป -- JSON is a data format that has its own independent standard and libraries for most programming languages. -- JSON supports plain objects, arrays, strings, numbers, booleans, and `null`. -- JavaScript provides methods [JSON.stringify](mdn:js/JSON/stringify) to serialize into JSON and [JSON.parse](mdn:js/JSON/parse) to read from JSON. -- Both methods support transformer functions for smart reading/writing. -- If an object has `toJSON`, then it is called by `JSON.stringify`. +- JSON เป็นรูปแบบข้อมูลที่มีมาตรฐานเป็นของตัวเองและมีไลบรารีรองรับในภาษาโปรแกรมส่วนใหญ่ +- JSON รองรับออบเจ็กต์ธรรมดา อาร์เรย์ สตริง ตัวเลข บูลีน และ `null` +- JavaScript มีเมธอด [JSON.stringify](mdn:js/JSON/stringify) สำหรับ serialize เป็น JSON และ [JSON.parse](mdn:js/JSON/parse) สำหรับอ่านจาก JSON +- ทั้งสองเมธอดรองรับฟังก์ชัน transformer สำหรับการอ่าน/เขียนอย่างชาญฉลาด +- ถ้าออบเจ็กต์มี `toJSON` จะถูกเรียกโดย `JSON.stringify` From f4db4de4d9348389fa9f5caa4a3d3269e4d8a731 Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 8 Mar 2026 00:41:53 +0700 Subject: [PATCH 2/2] docs: improve thai in data-types section (8 articles) --- .../05-data-types/05-array-methods/article.md | 188 +++++++++--------- 1-js/05-data-types/06-iterable/article.md | 106 +++++----- 1-js/05-data-types/07-map-set/article.md | 66 +++--- .../08-weakmap-weakset/article.md | 74 +++---- .../09-keys-values-entries/article.md | 30 +-- .../10-destructuring-assignment/article.md | 154 +++++++------- 1-js/05-data-types/11-date/article.md | 100 +++++----- 1-js/05-data-types/12-json/article.md | 109 +++++----- 8 files changed, 396 insertions(+), 431 deletions(-) diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 7c1d752da..bb9dbda56 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -32,11 +32,11 @@ alert( arr.length ); // 3 สมาชิกถูกลบแล้ว แต่อาร์เรย์ยังมี 3 สมาชิกอยู่ จะเห็นว่า `arr.length == 3` -เป็นเรื่องปกติ เพราะ `delete obj.key` ลบค่าตาม `key` เท่านั้น สำหรับออบเจ็กต์ทั่วไปก็ใช้ได้ แต่สำหรับอาร์เรย์ เรามักต้องการให้สมาชิกที่เหลือเลื่อนมาเติมช่องที่ว่าง และอยากได้อาร์เรย์ที่สั้นลงด้วย +เป็นเรื่องปกติ เพราะ `delete obj.key` ลบค่าตาม `key` เท่านั้น สำหรับออบเจ็กต์ทั่วไปก็ไม่มีปัญหา แต่กับอาร์เรย์ เรามักต้องการให้สมาชิกที่เหลือเลื่อนมาเติมช่องว่าง และอยากได้อาร์เรย์ที่สั้นลงด้วย -ดังนั้น จึงต้องใช้เมธอดพิเศษแทน +ดังนั้นจึงต้องใช้เมธอดพิเศษแทน -เมธอด [arr.splice](mdn:js/Array/splice) เปรียบเสมือนมีดพับอเนกประสงค์ของอาร์เรย์ ทำได้ทุกอย่าง: แทรก ลบ และแทนที่สมาชิก +เมธอด [arr.splice](mdn:js/Array/splice) เปรียบได้กับมีดพับอเนกประสงค์ของอาร์เรย์ — แทรก ลบ และแทนที่สมาชิกได้ครบในเมธอดเดียว ไวยากรณ์คือ: @@ -44,9 +44,9 @@ alert( arr.length ); // 3 arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` -เมธอดนี้แก้ไข `arr` โดยเริ่มจาก index `start`: ลบ `deleteCount` สมาชิก แล้วแทรก `elem1, ..., elemN` ลงในตำแหน่งนั้น และคืนค่าเป็นอาร์เรย์ของสมาชิกที่ถูกลบออก +เมธอดนี้แก้ไข `arr` โดยเริ่มจาก index `start`: ลบ `deleteCount` สมาชิก แล้วแทรก `elem1, ..., elemN` ลงในตำแหน่งนั้น จากนั้นคืนค่าเป็นอาร์เรย์ของสมาชิกที่ถูกลบออก -วิธีที่ดีที่สุดในการทำความเข้าใจเมธอดนี้คือดูตัวอย่าง +มาดูตัวอย่างกัน จะเข้าใจได้เร็วกว่าอ่านอธิบาย เริ่มจากการลบก่อน: @@ -73,7 +73,7 @@ arr.splice(0, 3, "Let's", "dance"); alert( arr ) // ตอนนี้คือ [*!*"Let's", "dance"*/!*, "right", "now"] ``` -ต่อไปมาดูว่า `splice` คืนค่าเป็นอาร์เรย์ของสมาชิกที่ถูกลบ: +คราวนี้ดูค่าที่ `splice` คืนกลับมา — เป็นอาร์เรย์ของสมาชิกที่ถูกลบออก: ```js run let arr = [*!*"I", "study",*/!* "JavaScript", "right", "now"]; @@ -84,7 +84,7 @@ let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- อาร์เรย์ของสมาชิกที่ถูกลบ ``` -เมธอด `splice` ยังสามารถแทรกสมาชิกโดยไม่ต้องลบอะไรเลย เพียงกำหนด `deleteCount` เป็น `0`: +`splice` ยังแทรกสมาชิกได้โดยไม่ต้องลบอะไรเลย แค่กำหนด `deleteCount` เป็น `0`: ```js run let arr = ["I", "study", "JavaScript"]; @@ -98,7 +98,7 @@ alert( arr ); // "I", "study", "complex", "language", "JavaScript" ``` ````smart header="ใช้ index ติดลบได้" -ในเมธอดนี้และเมธอดอาร์เรย์อื่นๆ สามารถใช้ index ติดลบได้ ซึ่งหมายถึงตำแหน่งนับจากท้ายอาร์เรย์ ดังนี้: +เมธอดนี้ (และเมธอดอาร์เรย์อื่นๆ) รองรับ index ติดลบ ซึ่งหมายถึงตำแหน่งนับจากท้ายอาร์เรย์: ```js run let arr = [1, 2, 5]; @@ -114,7 +114,7 @@ alert( arr ); // 1,2,3,4,5 ### slice -เมธอด [arr.slice](mdn:js/Array/slice) ง่ายกว่า `arr.splice` มาก แม้จะดูคล้ายกัน +เมธอด [arr.slice](mdn:js/Array/slice) ง่ายกว่า `arr.splice` มาก แม้ชื่อจะดูคล้ายกัน ไวยากรณ์คือ: @@ -122,9 +122,9 @@ alert( arr ); // 1,2,3,4,5 arr.slice([start], [end]) ``` -เมธอดนี้คืนค่าเป็นอาร์เรย์ใหม่ โดยคัดลอกสมาชิกทุกตัวตั้งแต่ index `start` ถึง `end` (ไม่รวม `end`) ทั้ง `start` และ `end` ใช้ค่าติดลบได้ ซึ่งจะนับตำแหน่งจากท้ายอาร์เรย์ +คืนค่าเป็นอาร์เรย์ใหม่ โดยคัดลอกสมาชิกตั้งแต่ index `start` ถึง `end` (ไม่รวม `end`) ทั้ง `start` และ `end` ใช้ค่าติดลบได้ ซึ่งนับตำแหน่งจากท้ายอาร์เรย์ -คล้ายกับเมธอด `str.slice` ของสตริง แต่แทนที่จะได้ substring กลับได้เป็น subarray แทน +ลักษณะคล้ายกับ `str.slice` ของสตริง ต่างแค่ได้ subarray กลับมาแทน substring ตัวอย่าง: @@ -136,11 +136,11 @@ alert( arr.slice(1, 3) ); // e,s (คัดลอกจาก index 1 ถึง alert( arr.slice(-2) ); // s,t (คัดลอกจาก index -2 ถึงท้ายอาร์เรย์) ``` -เรายังสามารถเรียกใช้โดยไม่ระบุอาร์กิวเมนต์: `arr.slice()` จะสร้างสำเนาของ `arr` ซึ่งมักใช้เมื่อต้องการทำสำเนาสำหรับการแปลงข้อมูลที่ไม่ต้องการกระทบอาร์เรย์ต้นฉบับ +เรียกโดยไม่ระบุอาร์กิวเมนต์ก็ได้: `arr.slice()` จะสร้างสำเนาของ `arr` ทั้งอาร์เรย์ ซึ่งมักใช้เมื่อต้องการแปลงข้อมูลโดยไม่ให้กระทบอาร์เรย์ต้นฉบับ ### concat -เมธอด [arr.concat](mdn:js/Array/concat) สร้างอาร์เรย์ใหม่ที่รวมค่าจากอาร์เรย์อื่นและรายการเพิ่มเติม +เมธอด [arr.concat](mdn:js/Array/concat) สร้างอาร์เรย์ใหม่โดยรวมค่าจากอาร์เรย์อื่นหรือค่าทั่วไปเข้าด้วยกัน ไวยากรณ์คือ: @@ -150,9 +150,9 @@ arr.concat(arg1, arg2...) รับอาร์กิวเมนต์กี่ตัวก็ได้ จะเป็นอาร์เรย์หรือค่าธรรมดาก็ได้ -ผลลัพธ์คืออาร์เรย์ใหม่ที่ประกอบด้วยสมาชิกจาก `arr` ตามด้วย `arg1`, `arg2` เป็นต้น +ผลลัพธ์เป็นอาร์เรย์ใหม่ที่มีสมาชิกจาก `arr` ตามด้วย `arg1`, `arg2` และต่อๆ ไป -ถ้าอาร์กิวเมนต์ `argN` เป็นอาร์เรย์ สมาชิกทุกตัวจะถูกคัดลอก มิฉะนั้นจะคัดลอกอาร์กิวเมนต์นั้นทั้งก้อน +ถ้า `argN` เป็นอาร์เรย์ สมาชิกทุกตัวจะถูกคัดลอกเข้ามา แต่ถ้าไม่ใช่อาร์เรย์ ค่านั้นจะถูกเพิ่มทั้งก้อน ตัวอย่าง: @@ -169,7 +169,7 @@ alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 ``` -โดยปกติ เมธอดนี้จะคัดลอกแค่สมาชิกจากอาร์เรย์ ส่วนออบเจ็กต์อื่นๆ แม้จะดูคล้ายอาร์เรย์ จะถูกเพิ่มเข้าไปทั้งก้อน: +โดยปกติ `concat` จะคัดลอกแค่สมาชิกจากอาร์เรย์ ส่วนออบเจ็กต์อื่นๆ แม้จะดูคล้ายอาร์เรย์ จะถูกเพิ่มเข้าไปทั้งก้อน: ```js run let arr = [1, 2]; @@ -182,7 +182,7 @@ let arrayLike = { alert( arr.concat(arrayLike) ); // 1,2,[object Object] ``` -...แต่ถ้าออบเจ็กต์ array-like มีพร็อพเพอร์ตี้พิเศษชื่อ `Symbol.isConcatSpreadable` `concat` จะถือว่ามันเป็นอาร์เรย์และเพิ่มสมาชิกแทน: +...แต่ถ้าออบเจ็กต์ array-like มีพร็อพเพอร์ตี้พิเศษชื่อ `Symbol.isConcatSpreadable` แล้วล่ะก็ `concat` จะถือว่าเป็นอาร์เรย์และแตกสมาชิกออกมาแทน: ```js run let arr = [1, 2]; @@ -201,7 +201,7 @@ alert( arr.concat(arrayLike) ); // 1,2,something,else ## วนซ้ำด้วย forEach -เมธอด [arr.forEach](mdn:js/Array/forEach) ช่วยให้รันฟังก์ชันสำหรับสมาชิกทุกตัวในอาร์เรย์ +เมธอด [arr.forEach](mdn:js/Array/forEach) ใช้รันฟังก์ชันสำหรับสมาชิกทุกตัวในอาร์เรย์ ไวยากรณ์: ```js @@ -225,7 +225,7 @@ arr.forEach(function(item, index, array) { }); ``` -ค่าที่ฟังก์ชันคืนค่ากลับมา (ถ้ามี) จะถูกทิ้งและไม่นำมาใช้งาน +ค่าที่ฟังก์ชันคืนกลับมา (ถ้ามี) จะถูกทิ้ง ไม่นำมาใช้งาน ## ค้นหาในอาร์เรย์ @@ -234,12 +234,12 @@ arr.forEach(function(item, index, array) { ### indexOf/lastIndexOf และ includes -เมธอด [arr.indexOf](mdn:js/Array/indexOf) และ [arr.includes](mdn:js/Array/includes) มีไวยากรณ์คล้ายกัน และทำงานคล้ายกับเมธอดของสตริง แต่ทำงานกับสมาชิกแทนที่จะเป็นตัวอักษร: +เมธอด [arr.indexOf](mdn:js/Array/indexOf) และ [arr.includes](mdn:js/Array/includes) มีไวยากรณ์คล้ายกัน และทำงานแบบเดียวกับเมธอดของสตริง แต่ทำงานกับสมาชิกแทนตัวอักษร: -- `arr.indexOf(item, from)` -- ค้นหา `item` โดยเริ่มจาก index `from` คืนค่า index ที่พบ ถ้าไม่พบคืน `-1` +- `arr.indexOf(item, from)` -- ค้นหา `item` โดยเริ่มจาก index `from` คืน index ที่พบ ถ้าไม่พบคืน `-1` - `arr.includes(item, from)` -- ค้นหา `item` โดยเริ่มจาก index `from` คืน `true` ถ้าพบ -โดยทั่วไปเมธอดเหล่านี้ใช้กับอาร์กิวเมนต์เดียวคือ `item` ที่ต้องการค้นหา ซึ่งจะค้นหาตั้งแต่ต้น +ส่วนใหญ่ใช้แค่อาร์กิวเมนต์เดียวคือ `item` ที่ต้องการค้นหา ซึ่งจะค้นหาตั้งแต่ต้น ตัวอย่าง: @@ -253,9 +253,9 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` -โปรดสังเกตว่า `indexOf` ใช้การเปรียบเทียบแบบเข้มงวด `===` ดังนั้นถ้าค้นหา `false` จะพบ `false` เท่านั้น ไม่ใช่ศูนย์ +สังเกตว่า `indexOf` ใช้การเปรียบเทียบแบบ strict `===` ดังนั้นถ้าค้นหา `false` จะพบ `false` เท่านั้น ไม่ใช่ศูนย์ -ถ้าต้องการแค่ตรวจสอบว่า `item` มีอยู่ในอาร์เรย์หรือไม่ โดยไม่ต้องการ index ให้ใช้ `arr.includes` แทน +ถ้าต้องการแค่ตรวจสอบว่ามี `item` อยู่หรือไม่ โดยไม่ต้องรู้ index ให้ใช้ `arr.includes` แทน เมธอด [arr.lastIndexOf](mdn:js/Array/lastIndexOf) ทำงานเหมือน `indexOf` แต่ค้นหาจากขวาไปซ้าย @@ -267,7 +267,7 @@ alert( fruits.lastIndexOf('Apple') ); // 2 (Apple ตัวสุดท้าย ``` ````smart header="เมธอด `includes` รองรับ `NaN` อย่างถูกต้อง" -ข้อดีเล็กน้อยแต่น่าสนใจของ `includes` คือรองรับ `NaN` ได้อย่างถูกต้อง ต่างจาก `indexOf`: +จุดเด่นเล็กๆ ของ `includes` คือจัดการ `NaN` ได้ถูกต้อง ต่างจาก `indexOf`: ```js run const arr = [NaN]; @@ -279,9 +279,9 @@ alert( arr.includes(NaN) );// true (ถูกต้อง) ### find และ findIndex/findLastIndex -ลองนึกภาพว่าเรามีอาร์เรย์ของออบเจ็กต์ แล้วจะค้นหาออบเจ็กต์ที่ตรงตามเงื่อนไขได้อย่างไร? +สมมติว่าเรามีอาร์เรย์ของออบเจ็กต์ แล้วอยากค้นหาออบเจ็กต์ที่ตรงตามเงื่อนไข -เมธอด [arr.find(fn)](mdn:js/Array/find) ช่วยได้พอดี +เมธอด [arr.find(fn)](mdn:js/Array/find) ทำแบบนั้นได้พอดี ไวยากรณ์คือ: ```js @@ -291,13 +291,13 @@ let result = arr.find(function(item, index, array) { }); ``` -ฟังก์ชันจะถูกเรียกสำหรับสมาชิกทุกตัวในอาร์เรย์ ทีละตัว: +ฟังก์ชันจะถูกเรียกสำหรับสมาชิกทุกตัวทีละตัว: -- `item` คือสมาชิก -- `index` คือ index ของสมาชิก -- `array` คืออาร์เรย์เอง +- `item` -- สมาชิก +- `index` -- index ของสมาชิก +- `array` -- อาร์เรย์เอง -ถ้าฟังก์ชันคืน `true` การค้นหาจะหยุดและคืน `item` กลับ ถ้าไม่พบอะไร จะคืน `undefined` +ถ้าฟังก์ชันคืน `true` การค้นหาหยุดทันทีและคืน `item` กลับ ถ้าวนครบแล้วยังไม่พบ จะคืน `undefined` ตัวอย่าง เรามีอาร์เรย์ของผู้ใช้ที่มีฟิลด์ `id` และ `name` แล้วหาผู้ใช้ที่มี `id == 1`: @@ -313,13 +313,13 @@ let user = users.find(item => item.id == 1); alert(user.name); // สมชาย ``` -ในชีวิตจริง อาร์เรย์ของออบเจ็กต์เป็นเรื่องที่พบบ่อยมาก ดังนั้นเมธอด `find` จึงมีประโยชน์มาก +อาร์เรย์ของออบเจ็กต์เป็นสิ่งที่เจอบ่อยมากในงานจริง `find` จึงมีประโยชน์มาก -สังเกตว่าในตัวอย่างนี้ เราส่งฟังก์ชัน `item => item.id == 1` ที่มีอาร์กิวเมนต์เดียวให้ `find` ซึ่งเป็นรูปแบบทั่วไป อาร์กิวเมนต์อื่นๆ ของฟังก์ชันนี้ใช้น้อยมาก +สังเกตว่าตัวอย่างนี้ส่งฟังก์ชัน `item => item.id == 1` ที่มีอาร์กิวเมนต์เดียว ซึ่งเป็นรูปแบบที่ใช้กันทั่วไป อาร์กิวเมนต์อื่นๆ (`index`, `array`) ใช้น้อยมาก -เมธอด [arr.findIndex](mdn:js/Array/findIndex) มีไวยากรณ์เหมือนกัน แต่คืน index ของสมาชิกที่พบแทนตัวสมาชิก ถ้าไม่พบจะคืน `-1` +เมธอด [arr.findIndex](mdn:js/Array/findIndex) มีไวยากรณ์เหมือนกัน แต่คืน index ของสมาชิกที่พบแทนตัวสมาชิก ถ้าไม่พบคืน `-1` -เมธอด [arr.findLastIndex](mdn:js/Array/findLastIndex) คล้ายกับ `findIndex` แต่ค้นหาจากขวาไปซ้าย คล้ายกับ `lastIndexOf` +เมธอด [arr.findLastIndex](mdn:js/Array/findLastIndex) คล้ายกับ `findIndex` แต่ค้นหาจากขวาไปซ้าย เช่นเดียวกับ `lastIndexOf` ตัวอย่าง: @@ -340,11 +340,11 @@ alert(users.findLastIndex(user => user.name == 'สมชาย')); // 3 ### filter -เมธอด `find` ค้นหาสมาชิกตัวเดียว (ตัวแรก) ที่ทำให้ฟังก์ชันคืน `true` +`find` คืนแค่สมาชิกตัวแรกที่ตรงเงื่อนไข -ถ้าอาจมีหลายตัวที่ตรงเงื่อนไข ให้ใช้ [arr.filter(fn)](mdn:js/Array/filter) +ถ้าต้องการสมาชิกทุกตัวที่ตรงเงื่อนไข ให้ใช้ [arr.filter(fn)](mdn:js/Array/filter) แทน -ไวยากรณ์คล้ายกับ `find` แต่ `filter` คืนอาร์เรย์ของสมาชิกทุกตัวที่ตรงเงื่อนไข: +ไวยากรณ์คล้ายกับ `find` แต่ `filter` คืนอาร์เรย์ของสมาชิกทั้งหมดที่ผ่านเงื่อนไข: ```js let results = arr.filter(function(item, index, array) { @@ -374,9 +374,9 @@ alert(someUsers.length); // 2 ### map -เมธอด [arr.map](mdn:js/Array/map) เป็นหนึ่งในเมธอดที่มีประโยชน์และใช้บ่อยที่สุด +[arr.map](mdn:js/Array/map) เป็นหนึ่งในเมธอดที่ใช้บ่อยที่สุด -เมธอดนี้เรียกฟังก์ชันสำหรับสมาชิกแต่ละตัวในอาร์เรย์ แล้วคืนอาร์เรย์ของผลลัพธ์ +เรียกฟังก์ชันสำหรับสมาชิกแต่ละตัว แล้วคืนอาร์เรย์ของผลลัพธ์ ไวยากรณ์คือ: @@ -386,7 +386,7 @@ let result = arr.map(function(item, index, array) { }); ``` -ตัวอย่าง เราแปลงสมาชิกแต่ละตัวให้เป็นความยาวของมัน: +ตัวอย่าง แปลงสมาชิกแต่ละตัวให้เป็นความยาว: ```js run let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); @@ -395,9 +395,9 @@ alert(lengths); // 5,7,6 ### sort(fn) -การเรียก [arr.sort()](mdn:js/Array/sort) จะเรียงลำดับอาร์เรย์ *แบบ in-place* นั่นคือเปลี่ยนลำดับสมาชิกในอาร์เรย์เดิม +[arr.sort()](mdn:js/Array/sort) เรียงลำดับอาร์เรย์ *แบบ in-place* คือเปลี่ยนลำดับสมาชิกในอาร์เรย์เดิมเลย -เมธอดนี้ยังคืนอาร์เรย์ที่เรียงแล้วด้วย แต่ค่าที่คืนมักถูกละเว้น เพราะ `arr` เองถูกแก้ไขแล้ว +คืนค่าเป็นอาร์เรย์ที่เรียงแล้วด้วย แต่มักละเว้น เพราะ `arr` เองถูกแก้ไขไปแล้ว ตัวอย่าง: @@ -410,17 +410,17 @@ arr.sort(); alert( arr ); // *!*1, 15, 2*/!* ``` -สังเกตเห็นอะไรแปลกๆ ไหม? +เห็นอะไรแปลกๆ ไหม? -ลำดับกลายเป็น `1, 15, 2` ซึ่งไม่ถูกต้อง แต่ทำไม? +ลำดับกลายเป็น `1, 15, 2` ซึ่งไม่ถูกต้อง ทำไมถึงเป็นแบบนั้น? -**สมาชิกถูกเรียงลำดับในรูปแบบสตริงโดยค่าเริ่มต้น** +**โดยค่าเริ่มต้น สมาชิกจะถูกเรียงในรูปแบบสตริง** -สมาชิกทุกตัวจะถูกแปลงเป็นสตริงก่อนเปรียบเทียบ สำหรับสตริงจะใช้การเรียงแบบ lexicographic ซึ่ง `"2" > "15"` นั่นเอง +สมาชิกทุกตัวจะถูกแปลงเป็นสตริงก่อนเปรียบเทียบ แล้วเรียงแบบ lexicographic ซึ่งถือว่า `"2" > "15"` นั่นเอง ถ้าต้องการกำหนดลำดับการเรียงเอง ต้องส่งฟังก์ชันเป็นอาร์กิวเมนต์ให้ `arr.sort()` -ฟังก์ชันนั้นควรเปรียบเทียบค่าสองค่าใดๆ และคืนค่า: +ฟังก์ชันนั้นควรรับค่าสองค่า เปรียบเทียบ และคืนผลดังนี้: ```js function compare(a, b) { @@ -450,11 +450,11 @@ alert(arr); // *!*1, 2, 15*/!* ตอนนี้ทำงานถูกต้องแล้ว -ลองคิดดูว่าเกิดอะไรขึ้น `arr` อาจเป็นอาร์เรย์ของอะไรก็ได้ใช่ไหม? อาจเป็นตัวเลข สตริง ออบเจ็กต์ หรืออะไรก็ตาม เรามี *ชุดของรายการ* บางอย่าง ในการเรียงลำดับ เราต้องมี *ฟังก์ชันเรียงลำดับ* ที่รู้วิธีเปรียบเทียบสมาชิก ค่าเริ่มต้นคือการเรียงแบบสตริง +ลองคิดดู — `arr` อาจเป็นอาร์เรย์ของอะไรก็ได้ ตัวเลข สตริง ออบเจ็กต์ หรืออะไรก็ตาม เรามี *ชุดของรายการ* และต้องการ *ฟังก์ชันเรียงลำดับ* ที่รู้วิธีเปรียบเทียบสมาชิก ค่าเริ่มต้นคือการเรียงแบบสตริง -เมธอด `arr.sort(fn)` ใช้อัลกอริทึมการเรียงลำดับทั่วไป เราไม่จำเป็นต้องรู้ว่ามันทำงานภายในอย่างไร (ส่วนใหญ่จะเป็น [quicksort](https://en.wikipedia.org/wiki/Quicksort) หรือ [Timsort](https://en.wikipedia.org/wiki/Timsort) ที่ปรับแต่งแล้ว) เมธอดจะวนผ่านอาร์เรย์ เปรียบเทียบสมาชิกด้วยฟังก์ชันที่ให้มา และจัดเรียงใหม่ สิ่งที่เราต้องทำมีแค่ระบุ `fn` สำหรับการเปรียบเทียบ +`arr.sort(fn)` ใช้อัลกอริทึมการเรียงลำดับทั่วไป ไม่ต้องรู้ว่าทำงานภายในอย่างไร (ส่วนใหญ่เป็น [quicksort](https://en.wikipedia.org/wiki/Quicksort) หรือ [Timsort](https://en.wikipedia.org/wiki/Timsort) ที่ปรับแต่งแล้ว) — แค่ระบุ `fn` สำหรับการเปรียบเทียบก็พอ -อนึ่ง ถ้าต้องการดูว่าสมาชิกไหนถูกเปรียบเทียบ แค่ alert ออกมาได้เลย: +ถ้าอยากดูว่าสมาชิกไหนถูกเปรียบเทียบกัน ลอง alert ดูได้เลย: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -463,12 +463,12 @@ alert(arr); // *!*1, 2, 15*/!* }); ``` -อัลกอริทึมอาจเปรียบเทียบสมาชิกหนึ่งตัวกับหลายตัว แต่จะพยายามเปรียบเทียบให้น้อยครั้งที่สุด +อัลกอริทึมอาจเปรียบเทียบสมาชิกหนึ่งตัวกับหลายตัว แต่จะพยายามทำให้น้อยครั้งที่สุด -````smart header="ฟังก์ชันเปรียบเทียบสามารถคืนค่าตัวเลขใดก็ได้" -จริงๆ แล้ว ฟังก์ชันเปรียบเทียบต้องคืนค่าบวกเพื่อบอกว่า "มากกว่า" และค่าลบเพื่อบอกว่า "น้อยกว่า" เท่านั้น +````smart header="ฟังก์ชันเปรียบเทียบคืนค่าตัวเลขใดก็ได้" +จริงๆ แล้ว ฟังก์ชันเปรียบเทียบต้องการแค่ค่าบวกเพื่อบอก "มากกว่า" และค่าลบเพื่อบอก "น้อยกว่า" เท่านั้น -ทำให้เขียนฟังก์ชันได้สั้นลง: +เขียนสั้นลงได้แบบนี้: ```js run let arr = [ 1, 2, 15 ]; @@ -480,21 +480,21 @@ alert(arr); // *!*1, 2, 15*/!* ```` ````smart header="ใช้ arrow function ให้กระชับ" -จำ [arrow function](info:arrow-functions-basics) ได้ไหม? เราใช้มันเพื่อเขียนการเรียงลำดับได้กระชับขึ้น: +จำ [arrow function](info:arrow-functions-basics) ได้ไหม? เขียนการเรียงลำดับให้สั้นกว่านี้ได้เลย: ```js arr.sort( (a, b) => a - b ); ``` -ทำงานได้เหมือนกับเวอร์ชันยาวด้านบนทุกประการ +ทำงานเหมือนเวอร์ชันยาวด้านบนทุกประการ ```` ````smart header="ใช้ `localeCompare` สำหรับสตริง" -จำอัลกอริทึมการเปรียบเทียบ[สตริง](info:string#correct-comparisons) ได้ไหม? โดยค่าเริ่มต้นจะเปรียบเทียบตัวอักษรด้วยรหัสของมัน +จำได้ไหมว่าการเปรียบเทียบ[สตริง](info:string#correct-comparisons) โดยค่าเริ่มต้นจะเปรียบเทียบด้วยรหัสของตัวอักษร? -สำหรับหลายภาษา ควรใช้เมธอด `str.localeCompare` เพื่อเรียงลำดับตัวอักษรให้ถูกต้อง เช่น `Ö` +สำหรับหลายภาษา ควรใช้ `str.localeCompare` เพื่อเรียงตัวอักษรให้ถูกต้อง เช่น ตัวอักษร `Ö` ในภาษาเยอรมัน -ตัวอย่าง ลองเรียงชื่อประเทศในภาษาเยอรมัน: +ลองดูตัวอย่าง: ```js run let countries = ['Österreich', 'Andorra', 'Vietnam']; @@ -509,8 +509,6 @@ alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich, เมธอด [arr.reverse](mdn:js/Array/reverse) กลับลำดับสมาชิกใน `arr` -ตัวอย่าง: - ```js run let arr = [1, 2, 3, 4, 5]; arr.reverse(); @@ -518,15 +516,15 @@ arr.reverse(); alert( arr ); // 5,4,3,2,1 ``` -เมธอดนี้ยังคืนอาร์เรย์ `arr` หลังจากกลับลำดับแล้วด้วย +คืนค่าเป็น `arr` หลังจากกลับลำดับแล้วด้วย ### split และ join -ลองนึกถึงสถานการณ์จริง เรากำลังเขียนแอปส่งข้อความ และผู้ใช้พิมพ์รายชื่อผู้รับคั่นด้วยจุลภาค: `สมชาย, สมหญิง, มาลี` แต่สำหรับเรา อาร์เรย์ของชื่อสะดวกกว่าสตริงเดียวมาก จะแปลงได้อย่างไร? +ลองนึกถึงสถานการณ์จริง สมมติว่าเรากำลังเขียนแอปส่งข้อความ และผู้ใช้พิมพ์รายชื่อผู้รับคั่นด้วยจุลภาค: `สมชาย, สมหญิง, มาลี` แต่เราต้องการอาร์เรย์ของชื่อมากกว่าสตริงเดียว จะแปลงได้อย่างไร? -เมธอด [str.split(delim)](mdn:js/String/split) ทำงานนี้ได้พอดี โดยแยกสตริงออกเป็นอาร์เรย์โดยใช้ตัวคั่น `delim` +เมธอด [str.split(delim)](mdn:js/String/split) ทำแบบนั้นได้พอดี — แยกสตริงออกเป็นอาร์เรย์โดยใช้ตัวคั่น `delim` -ในตัวอย่างด้านล่าง เราแยกด้วยจุลภาคตามด้วยเว้นวรรค: +ตัวอย่างด้านล่างแยกด้วยจุลภาคตามด้วยเว้นวรรค: ```js run let names = 'Bilbo, Gandalf, Nazgul'; @@ -538,7 +536,7 @@ for (let name of arr) { } ``` -เมธอด `split` มีอาร์กิวเมนต์ตัวเลขตัวที่สองที่ไม่บังคับ ซึ่งเป็นขีดจำกัดความยาวของอาร์เรย์ ถ้าระบุ สมาชิกส่วนเกินจะถูกละเว้น แต่ในทางปฏิบัติใช้น้อยมาก: +`split` มีอาร์กิวเมนต์ตัวที่สองที่ไม่บังคับ ใช้จำกัดความยาวของอาร์เรย์ สมาชิกส่วนเกินจะถูกตัดทิ้ง แต่ในทางปฏิบัติแทบไม่ค่อยใช้: ```js run let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2); @@ -547,7 +545,7 @@ alert(arr); // Bilbo, Gandalf ``` ````smart header="แยกเป็นตัวอักษร" -การเรียก `split(s)` ด้วย `s` ว่างเปล่าจะแยกสตริงออกเป็นอาร์เรย์ของตัวอักษร: +เรียก `split('')` ด้วยสตริงว่างจะได้อาร์เรย์ของตัวอักษรทีละตัว: ```js run let str = "test"; @@ -556,7 +554,7 @@ alert( str.split('') ); // t,e,s,t ``` ```` -การเรียก [arr.join(glue)](mdn:js/Array/join) ทำงานตรงข้ามกับ `split` โดยสร้างสตริงจากสมาชิกของ `arr` โดยเชื่อมด้วย `glue` +[arr.join(glue)](mdn:js/Array/join) ทำงานตรงข้ามกับ `split` — สร้างสตริงจากสมาชิกของ `arr` โดยเชื่อมด้วย `glue` ตัวอย่าง: @@ -570,11 +568,11 @@ alert( str ); // Bilbo;Gandalf;Nazgul ### reduce/reduceRight -เมื่อต้องวนซ้ำในอาร์เรย์ เราใช้ `forEach`, `for` หรือ `for..of` ได้ +ถ้าแค่ต้องการวนซ้ำอาร์เรย์ เราใช้ `forEach`, `for` หรือ `for..of` ได้ -เมื่อต้องวนซ้ำและคืนข้อมูลสำหรับแต่ละสมาชิก เราใช้ `map` ได้ +ถ้าต้องการวนและแปลงค่าสมาชิกแต่ละตัว ใช้ `map` -เมธอด [arr.reduce](mdn:js/Array/reduce) และ [arr.reduceRight](mdn:js/Array/reduceRight) อยู่ในกลุ่มเดียวกัน แต่ซับซ้อนกว่าเล็กน้อย ใช้สำหรับคำนวณค่าเดียวจากอาร์เรย์ +เมธอด [arr.reduce](mdn:js/Array/reduce) และ [arr.reduceRight](mdn:js/Array/reduceRight) อยู่ในกลุ่มเดียวกัน แต่ซับซ้อนขึ้นอีกนิด ใช้สำหรับคำนวณค่าเดียวจากอาร์เรย์ทั้งชุด ไวยากรณ์คือ: @@ -584,24 +582,20 @@ let value = arr.reduce(function(accumulator, item, index, array) { }, [initial]); ``` -ฟังก์ชันจะถูกเรียกสำหรับสมาชิกทุกตัวตามลำดับ และ "ส่งต่อ" ผลลัพธ์ไปยังการเรียกครั้งถัดไป +ฟังก์ชันจะถูกเรียกสำหรับสมาชิกทุกตัวตามลำดับ แล้ว "ส่งต่อ" ผลลัพธ์ไปยังการเรียกครั้งถัดไปเรื่อยๆ อาร์กิวเมนต์: -- `accumulator` -- ผลลัพธ์จากการเรียกฟังก์ชันครั้งก่อน ถ้าเป็นการเรียกครั้งแรกจะเท่ากับ `initial` (ถ้าระบุ) -- `item` -- สมาชิกในอาร์เรย์ปัจจุบัน +- `accumulator` -- ผลลัพธ์จากการเรียกครั้งก่อน ถ้าเป็นครั้งแรกจะเท่ากับ `initial` (ถ้าระบุไว้) +- `item` -- สมาชิกปัจจุบัน - `index` -- ตำแหน่งของสมาชิก - `array` -- อาร์เรย์เอง -เมื่อฟังก์ชันถูกเรียก ผลลัพธ์จากการเรียกครั้งก่อนจะถูกส่งเป็นอาร์กิวเมนต์แรกของการเรียกครั้งถัดไป +พูดง่ายๆ ก็คือ `accumulator` เก็บผลรวมสะสมจากทุกการเรียกก่อนหน้า และในตอนท้ายจะกลายเป็นผลลัพธ์ของ `reduce` -กล่าวคือ อาร์กิวเมนต์แรกเป็น accumulator ที่เก็บผลลัพธ์รวมของการทำงานทั้งหมดก่อนหน้า และในตอนท้ายจะกลายเป็นผลลัพธ์ของ `reduce` +ฟังดูซับซ้อนใช่ไหม? มาดูตัวอย่างแล้วจะเข้าใจเอง -ฟังดูซับซ้อนใช่ไหม? - -วิธีที่ง่ายที่สุดในการเข้าใจคือดูตัวอย่าง - -ในตัวอย่างนี้เราหาผลรวมของอาร์เรย์ในบรรทัดเดียว: +ตัวอย่างนี้หาผลรวมของอาร์เรย์ในบรรทัดเดียว: ```js run let arr = [1, 2, 3, 4, 5]; @@ -611,13 +605,13 @@ let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 ``` -ฟังก์ชันที่ส่งให้ `reduce` ใช้แค่ 2 อาร์กิวเมนต์ ซึ่งมักเพียงพอ +ฟังก์ชันที่ส่งให้ `reduce` ใช้แค่ 2 อาร์กิวเมนต์ ซึ่งส่วนใหญ่ก็เพียงพอ -มาดูรายละเอียดว่าเกิดอะไรขึ้น: +ไล่ดูแต่ละขั้นตอน: -1. ครั้งแรก `sum` คือค่า `initial` (อาร์กิวเมนต์สุดท้ายของ `reduce`) ซึ่งเท่ากับ `0` และ `current` คือสมาชิกแรกของอาร์เรย์ ซึ่งเท่ากับ `1` ดังนั้นผลลัพธ์ของฟังก์ชันคือ `1` -2. ครั้งที่สอง `sum = 1` เราบวกสมาชิกที่สอง (`2`) แล้วคืนค่ากลับ -3. ครั้งที่สาม `sum = 3` และบวกสมาชิกตัวถัดไป และต่อไปเรื่อยๆ... +1. ครั้งแรก `sum` คือค่า `initial` (อาร์กิวเมนต์สุดท้ายของ `reduce`) ซึ่งคือ `0` และ `current` คือสมาชิกแรก คือ `1` ผลลัพธ์จึงเป็น `1` +2. ครั้งที่สอง `sum = 1` บวกสมาชิกที่สอง (`2`) ได้ `3` +3. ครั้งที่สาม `sum = 3` บวกสมาชิกถัดไป และต่อๆ ไปเรื่อยๆ... ขั้นตอนการคำนวณ: @@ -633,9 +627,9 @@ alert(result); // 15 |การเรียกครั้งที่ 4|`6`|`4`|`10`| |การเรียกครั้งที่ 5|`10`|`5`|`15`| -จะเห็นชัดว่าผลลัพธ์ของการเรียกครั้งก่อนกลายเป็นอาร์กิวเมนต์แรกของการเรียกครั้งถัดไปอย่างไร +จะเห็นชัดว่าผลลัพธ์ของแต่ละครั้งถูกส่งต่อเป็นอาร์กิวเมนต์แรกของการเรียกครั้งถัดไป -เราสามารถละเว้นค่าเริ่มต้นได้: +ละเว้นค่าเริ่มต้นได้ด้วย: ```js run let arr = [1, 2, 3, 4, 5]; @@ -646,13 +640,11 @@ let result = arr.reduce((sum, current) => sum + current); alert( result ); // 15 ``` -ผลลัพธ์เหมือนกัน เพราะถ้าไม่มีค่าเริ่มต้น `reduce` จะใช้สมาชิกแรกของอาร์เรย์เป็นค่าเริ่มต้น และเริ่มวนซ้ำจากสมาชิกที่ 2 - -ตารางการคำนวณก็เหมือนกับด้านบน แค่ขาดแถวแรก +ผลลัพธ์เหมือนกัน เพราะถ้าไม่มีค่าเริ่มต้น `reduce` จะใช้สมาชิกตัวแรกเป็นค่าเริ่มต้น แล้วเริ่มวนจากสมาชิกตัวที่สอง -แต่การใช้แบบนี้ต้องระวังมาก ถ้าอาร์เรย์ว่างเปล่า การเรียก `reduce` โดยไม่มีค่าเริ่มต้นจะเกิดข้อผิดพลาด +ตารางการคำนวณก็เหมือนด้านบน เพียงแต่ขาดแถวแรก -ดูตัวอย่าง: +แต่ต้องระวัง — ถ้าอาร์เรย์ว่างเปล่า การเรียก `reduce` โดยไม่ระบุค่าเริ่มต้นจะเกิดข้อผิดพลาด ```js run let arr = []; @@ -662,7 +654,7 @@ let arr = []; arr.reduce((sum, current) => sum + current); ``` -ดังนั้น แนะนำให้ระบุค่าเริ่มต้นเสมอ +ดังนั้น แนะนำให้ระบุค่าเริ่มต้นไว้เสมอ เมธอด [arr.reduceRight](mdn:js/Array/reduceRight) ทำงานเหมือนกันแต่วนจากขวาไปซ้าย @@ -670,14 +662,14 @@ arr.reduce((sum, current) => sum + current); อาร์เรย์ไม่ได้เป็น type แยกต่างหากในภาษา แต่อิงจากออบเจ็กต์ -ดังนั้น `typeof` จึงไม่สามารถแยกออบเจ็กต์ธรรมดาจากอาร์เรย์ได้: +เพราะฉะนั้น `typeof` จึงแยกออบเจ็กต์ธรรมดาจากอาร์เรย์ไม่ได้: ```js run alert(typeof {}); // object alert(typeof []); // object (เหมือนกัน) ``` -...แต่อาร์เรย์ถูกใช้บ่อยมากจนมีเมธอดพิเศษสำหรับตรวจสอบ: [Array.isArray(value)](mdn:js/Array/isArray) คืน `true` ถ้า `value` เป็นอาร์เรย์ และคืน `false` มิฉะนั้น +...แต่เพราะอาร์เรย์ใช้บ่อยมาก จึงมีเมธอดพิเศษสำหรับตรวจสอบโดยเฉพาะ: [Array.isArray(value)](mdn:js/Array/isArray) คืน `true` ถ้า `value` เป็นอาร์เรย์ และคืน `false` ถ้าไม่ใช่ ```js run alert(Array.isArray({})); // false diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index c6f2a2c35..6531b6562 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -1,18 +1,18 @@ # Iterables -ออบเจ็กต์ที่เป็น *iterable* คือการขยายแนวคิดของอาร์เรย์ออกไป กล่าวคือ เป็นแนวคิดที่ช่วยให้เราสามารถใช้ออบเจ็กต์ใดก็ได้ใน `for..of` loop +*Iterable* คือแนวคิดที่ขยายออกมาจากอาร์เรย์ — ช่วยให้เราเอาออบเจ็กต์ใดก็ได้ไปใช้ใน `for..of` loop ได้ -แน่นอนว่าอาร์เรย์เป็น iterable แต่ยังมี built-in object อื่นอีกมากมายที่เป็น iterable เช่นกัน อย่างสตริงก็เป็น iterable ด้วย +อาร์เรย์เป็น iterable อยู่แล้ว แต่ built-in object อื่นๆ ก็เป็น iterable ด้วย อย่างสตริงก็เช่นกัน -ถ้าออบเจ็กต์ไม่ใช่อาร์เรย์ในทางเทคนิค แต่แทนค่าคอลเล็กชัน (list, set) ของบางอย่าง การใช้ `for..of` ก็เป็นไวยากรณ์ที่ดีในการวนซ้ำ มาดูกันว่าจะทำให้มันใช้งานได้อย่างไร +ถ้ามีออบเจ็กต์ที่ไม่ใช่อาร์เรย์ แต่แทนคอลเล็กชัน (list, set) ของบางอย่าง `for..of` ก็เป็นวิธีวนซ้ำที่ดีมาก มาดูกันว่าทำให้ใช้งานได้อย่างไร ## Symbol.iterator -ทำความเข้าใจแนวคิดของ iterable ได้ง่ายที่สุดด้วยการสร้างขึ้นมาเอง +วิธีที่เข้าใจง่ายที่สุดคือลองสร้าง iterable ขึ้นมาเอง -สมมติว่าเรามีออบเจ็กต์ที่ไม่ใช่อาร์เรย์ แต่ดูเหมาะสมสำหรับ `for..of` +สมมติว่ามีออบเจ็กต์ที่ไม่ใช่อาร์เรย์ แต่เหมาะกับ `for..of` เช่น ออบเจ็กต์ `range` ที่แทนช่วงของตัวเลข: @@ -26,14 +26,14 @@ let range = { // for(let num of range) ... num=1,2,3,4,5 ``` -เพื่อให้ออบเจ็กต์ `range` เป็น iterable (และทำให้ `for..of` ใช้งานได้) เราต้องเพิ่มเมธอดชื่อ `Symbol.iterator` ให้กับออบเจ็กต์ (ซึ่งเป็น built-in symbol พิเศษที่มีไว้สำหรับเรื่องนี้โดยเฉพาะ) +การทำให้ `range` เป็น iterable (เพื่อให้ `for..of` ใช้งานได้) ต้องเพิ่มเมธอดชื่อ `Symbol.iterator` ลงในออบเจ็กต์ ซึ่งเป็น built-in symbol พิเศษสำหรับเรื่องนี้โดยเฉพาะ -1. เมื่อ `for..of` เริ่มทำงาน จะเรียกเมธอดนั้นหนึ่งครั้ง (หรือเกิดข้อผิดพลาดถ้าไม่พบเมธอด) เมธอดนั้นต้องคืนค่าเป็น *iterator* ซึ่งก็คือออบเจ็กต์ที่มีเมธอด `next` -2. จากนั้น `for..of` จะทำงาน *กับออบเจ็กต์ที่ได้รับคืนมาเท่านั้น* -3. เมื่อ `for..of` ต้องการค่าถัดไป จะเรียก `next()` บนออบเจ็กต์นั้น -4. ผลลัพธ์ของ `next()` ต้องอยู่ในรูป `{done: Boolean, value: any}` โดย `done=true` หมายความว่าลูปสิ้นสุดแล้ว มิฉะนั้น `value` คือค่าถัดไป +1. ตอนที่ `for..of` เริ่มทำงาน จะเรียกเมธอดนั้นหนึ่งครั้ง (ถ้าไม่พบจะเกิดข้อผิดพลาด) เมธอดต้องคืนค่าเป็น *iterator* — ออบเจ็กต์ที่มีเมธอด `next` +2. จากนั้น `for..of` จะทำงาน *กับออบเจ็กต์ที่คืนมาเท่านั้น* +3. ทุกครั้งที่ต้องการค่าถัดไป `for..of` จะเรียก `next()` บนออบเจ็กต์นั้น +4. ผลลัพธ์จาก `next()` ต้องอยู่ในรูป `{done: Boolean, value: any}` — `done=true` แปลว่าลูปจบแล้ว ถ้ายังไม่จบ `value` คือค่าถัดไป -นี่คือโค้ดที่ implement ครบถ้วนสำหรับ `range` พร้อมคำอธิบาย: +ด้านล่างคือโค้ดครบถ้วนสำหรับ `range` พร้อมคำอธิบาย: ```js run let range = { @@ -68,14 +68,14 @@ for (let num of range) { } ``` -สิ่งสำคัญของ iterable คือการแยกหน้าที่กันอย่างชัดเจน (separation of concerns) +จุดสำคัญของ iterable คือการแยกหน้าที่กันอย่างชัดเจน (separation of concerns) -- ออบเจ็กต์ `range` เองไม่มีเมธอด `next()` -- แต่จะสร้างออบเจ็กต์อีกตัวที่เรียกว่า "iterator" ขึ้นมาจากการเรียก `range[Symbol.iterator]()` และเมธอด `next()` ของมันจะสร้างค่าสำหรับการวนซ้ำ +- ตัว `range` เองไม่มีเมธอด `next()` +- แต่เมื่อเรียก `range[Symbol.iterator]()` จะสร้างออบเจ็กต์ "iterator" ขึ้นมาอีกตัว แล้วเมธอด `next()` ของมันทำหน้าที่สร้างค่าในการวนซ้ำ -ดังนั้นออบเจ็กต์ iterator จึงแยกออกจากออบเจ็กต์ที่มันวนซ้ำ +กล่าวคือ iterator แยกออกจากออบเจ็กต์ที่กำลังวนซ้ำ -ในทางเทคนิค เราสามารถรวมทั้งสองเข้าด้วยกัน และใช้ `range` เองเป็น iterator เพื่อให้โค้ดกระชับขึ้น +ถ้าต้องการให้โค้ดกระชับขึ้น ก็รวมทั้งสองเข้าด้วยกันได้ โดยให้ `range` เป็น iterator ของตัวเองไปเลย แบบนี้: @@ -103,24 +103,24 @@ for (let num of range) { } ``` -ตอนนี้ `range[Symbol.iterator]()` คืนค่าเป็นออบเจ็กต์ `range` เอง ซึ่งมีเมธอด `next()` ที่จำเป็น และจำสถานะการวนซ้ำปัจจุบันไว้ใน `this.current` กระชับขึ้นไหม? ใช่เลย และบางครั้งก็เหมาะสมดีเช่นกัน +ตอนนี้ `range[Symbol.iterator]()` คืนค่าเป็นออบเจ็กต์ `range` เอง ซึ่งมีเมธอด `next()` ครบ และเก็บสถานะการวนซ้ำไว้ใน `this.current` กระชับขึ้นใช่ไหม? ใช่เลย และบางบริบทก็เหมาะดี -ข้อเสียคือตอนนี้จะมี `for..of` สองตัวรันพร้อมกันบนออบเจ็กต์เดียวกันไม่ได้ เพราะทั้งสองจะแบ่งปันสถานะการวนซ้ำร่วมกัน เนื่องจากมี iterator แค่ตัวเดียวคือออบเจ็กต์นั่นเอง แต่ว่า for-of แบบขนานสองตัวเป็นเรื่องที่พบได้น้อยมาก แม้แต่ในสถานการณ์ async +ข้อเสียคือ `for..of` สองตัวรันพร้อมกันบนออบเจ็กต์เดียวไม่ได้ เพราะทั้งคู่จะแชร์สถานะร่วมกัน — มี iterator แค่ตัวเดียวคือตัวออบเจ็กต์นั่นเอง แต่ก็ไม่ค่อยมีใครวนแบบขนานสองตัวพร้อมกันอยู่แล้ว แม้แต่ในสถานการณ์ async ```smart header="Infinite iterators" -Infinite iterators ก็เป็นไปได้เช่นกัน ตัวอย่างเช่น `range` จะกลายเป็นอนันต์เมื่อกำหนด `range.to = Infinity` หรือเราอาจสร้างออบเจ็กต์ iterable ที่สร้างลำดับตัวเลขสุ่ม (pseudorandom) ต่อเนื่องไปเรื่อยๆ ก็ได้ ซึ่งก็มีประโยชน์เช่นกัน +Infinite iterators เป็นไปได้เช่นกัน เช่น `range` จะกลายเป็นอนันต์เมื่อกำหนด `range.to = Infinity` หรือสร้างออบเจ็กต์ iterable ที่ผลิตลำดับตัวเลขสุ่ม (pseudorandom) ต่อเนื่องไปเรื่อยๆ ก็ได้ ซึ่งมีประโยชน์ในหลายกรณี -ไม่มีข้อจำกัดสำหรับ `next` มันสามารถคืนค่าได้เรื่อยๆ ซึ่งเป็นเรื่องปกติ +`next` ไม่มีข้อจำกัด คืนค่าได้เรื่อยๆ ถือเป็นเรื่องปกติ -แน่นอนว่า `for..of` loop ที่วนซ้ำ iterable แบบนี้จะไม่มีวันสิ้นสุด แต่เราหยุดมันได้เสมอด้วย `break` +แน่นอนว่า `for..of` ที่วนซ้ำ iterable แบบนี้จะไม่มีวันจบ แต่หยุดได้เสมอด้วย `break` ``` ## สตริงเป็น iterable -อาร์เรย์และสตริงเป็น built-in iterables ที่ใช้บ่อยที่สุด +อาร์เรย์กับสตริงเป็น built-in iterables ที่ใช้บ่อยที่สุด -สำหรับสตริง `for..of` จะวนซ้ำผ่านตัวอักษรแต่ละตัว: +สำหรับสตริง `for..of` จะวนผ่านตัวอักษรทีละตัว: ```js run for (let char of "test") { @@ -140,9 +140,9 @@ for (let char of str) { ## การเรียก iterator โดยตรง -เพื่อความเข้าใจที่ลึกขึ้น มาดูวิธีใช้ iterator โดยตรงกัน +อยากเข้าใจให้ลึกขึ้น ลองใช้ iterator โดยตรงดูกัน -เราจะวนซ้ำสตริงแบบเดียวกันกับ `for..of` แต่ด้วยการเรียกโดยตรง โค้ดนี้สร้าง string iterator และดึงค่าออกมา "ด้วยมือ": +เราจะวนสตริงแบบเดียวกับ `for..of` แต่เรียกเองด้วยมือ โค้ดด้านล่างสร้าง string iterator แล้วดึงค่าออกมาทีละตัว: ```js run let str = "Hello"; @@ -161,24 +161,24 @@ while (true) { } ``` -วิธีนี้ไม่ค่อยจำเป็นนัก แต่ให้การควบคุมกระบวนการมากกว่า `for..of` ตัวอย่างเช่น เราสามารถแบ่งกระบวนการวนซ้ำออกได้: วนซ้ำไปสักพัก หยุด ทำอย่างอื่น แล้วค่อยกลับมาทำต่อในภายหลัง +วิธีนี้ไม่ค่อยจำเป็นนัก แต่ควบคุมได้มากกว่า `for..of` เช่น วนไปสักพัก แล้วหยุด ไปทำอย่างอื่นก่อน แล้วค่อยกลับมาวนต่อทีหลัง ## Iterables และ array-likes [#array-like] -คำศัพท์สองคำนี้ดูเหมือนกัน แต่จริงๆ แล้วต่างกันมาก ควรทำความเข้าใจให้ดีเพื่อหลีกเลี่ยงความสับสน +สองคำนี้ดูคล้ายกัน แต่จริงๆ ต่างกันมาก ควรเข้าใจให้ชัดเพื่อไม่ให้สับสน -- *Iterables* คือออบเจ็กต์ที่ implement เมธอด `Symbol.iterator` ดังที่อธิบายไว้ข้างต้น -- *Array-likes* คือออบเจ็กต์ที่มี index และ `length` จึงดูเหมือนอาร์เรย์ +- *Iterables* คือออบเจ็กต์ที่ implement เมธอด `Symbol.iterator` ตามที่อธิบายไว้ข้างต้น +- *Array-likes* คือออบเจ็กต์ที่มี index และ `length` ทำให้ดูเหมือนอาร์เรย์ -เมื่อใช้ JavaScript สำหรับงานจริงในเบราว์เซอร์หรือสภาพแวดล้อมอื่น เราอาจพบออบเจ็กต์ที่เป็น iterable หรือ array-like หรือทั้งสองอย่าง +ในงานจริงบนเบราว์เซอร์หรือสภาพแวดล้อมอื่น เราอาจเจอออบเจ็กต์ที่เป็น iterable หรือ array-like หรือทั้งสองอย่างพร้อมกัน -ตัวอย่างเช่น สตริงเป็นทั้ง iterable (`for..of` ใช้ได้กับมัน) และ array-like (มี numeric index และ `length`) +สตริงเป็นตัวอย่างที่ดี — เป็นทั้ง iterable (`for..of` ใช้ได้) และ array-like (มี numeric index และ `length`) -แต่ iterable อาจไม่ใช่ array-like ก็ได้ และในทางกลับกัน array-like ก็อาจไม่ใช่ iterable ก็ได้ +แต่ iterable ไม่จำเป็นต้องเป็น array-like และกลับกัน array-like ก็ไม่จำเป็นต้องเป็น iterable -ตัวอย่างเช่น `range` ในตัวอย่างข้างต้นเป็น iterable แต่ไม่ใช่ array-like เพราะไม่มีพร็อพเพอร์ตี้แบบ index และ `length` +`range` ในตัวอย่างข้างต้นเป็น iterable แต่ไม่ใช่ array-like เพราะไม่มีพร็อพเพอร์ตี้ index และ `length` -และนี่คือออบเจ็กต์ที่เป็น array-like แต่ไม่ใช่ iterable: +ด้านล่างคือออบเจ็กต์ที่เป็น array-like แต่ไม่ใช่ iterable: ```js run let arrayLike = { // มี index และ length => array-like @@ -193,11 +193,11 @@ for (let item of arrayLike) {} */!* ``` -ทั้ง iterable และ array-like มักจะ *ไม่ใช่อาร์เรย์* ไม่มีเมธอด `push`, `pop` เป็นต้น ซึ่งค่อนข้างไม่สะดวกถ้าเรามีออบเจ็กต์แบบนั้นและต้องการทำงานกับมันเหมือนกับอาร์เรย์ เช่น ถ้าอยากใช้ array methods กับ `range` จะทำอย่างไร? +ทั้ง iterable และ array-like มักจะ *ไม่ใช่อาร์เรย์* ไม่มีเมธอด `push`, `pop` หรืออื่นๆ ซึ่งไม่สะดวกเลยถ้าอยากใช้งานเหมือนอาร์เรย์ เช่น อยากใช้ array methods กับ `range` จะทำอย่างไรดี? ## Array.from -มีเมธอด [Array.from](mdn:js/Array/from) ที่รับ iterable หรือ array-like และสร้างอาร์เรย์ "จริงๆ" จากมัน จากนั้นเราก็เรียก array methods ได้ +[Array.from](mdn:js/Array/from) คือเมธอดที่รับ iterable หรือ array-like แล้วสร้างอาร์เรย์ "จริงๆ" ออกมา จากนั้นก็เรียก array methods ได้ตามปกติ ตัวอย่างเช่น: @@ -214,9 +214,9 @@ let arr = Array.from(arrayLike); // (*) alert(arr.pop()); // World (เมธอดทำงานได้แล้ว) ``` -`Array.from` ที่บรรทัด `(*)` รับออบเจ็กต์มา ตรวจสอบว่าเป็น iterable หรือ array-like แล้วสร้างอาร์เรย์ใหม่และคัดลอกทุกรายการเข้าไป +`Array.from` ที่บรรทัด `(*)` รับออบเจ็กต์มาตรวจว่าเป็น iterable หรือ array-like แล้วสร้างอาร์เรย์ใหม่และคัดลอกทุกรายการเข้าไป -เหมือนกันสำหรับ iterable: +กับ iterable ก็ทำได้เหมือนกัน: ```js run // สมมติว่า range มาจากตัวอย่างข้างต้น @@ -224,12 +224,12 @@ let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (การแปลง array toString ทำงานได้) ``` -ไวยากรณ์ครบถ้วนของ `Array.from` ยังรองรับอาร์กิวเมนต์ "mapping" เพิ่มเติมได้: +`Array.from` ยังรองรับอาร์กิวเมนต์ "mapping" เพิ่มเติม: ```js Array.from(obj[, mapFn, thisArg]) ``` -อาร์กิวเมนต์ที่สองแบบเลือกได้คือ `mapFn` ซึ่งเป็นฟังก์ชันที่จะถูกนำไปใช้กับแต่ละ element ก่อนที่จะเพิ่มเข้าไปในอาร์เรย์ และ `thisArg` ช่วยให้เรากำหนดค่า `this` ให้กับมันได้ +`mapFn` คืออาร์กิวเมนต์ที่สองซึ่งเลือกใส่หรือไม่ก็ได้ — เป็นฟังก์ชันที่จะถูกเรียกกับแต่ละ element ก่อนเพิ่มเข้าอาร์เรย์ และ `thisArg` ช่วยกำหนด `this` ให้กับฟังก์ชันนั้นได้ ตัวอย่างเช่น: @@ -242,7 +242,7 @@ let arr = Array.from(range, num => num * num); alert(arr); // 1,4,9,16,25 ``` -ที่นี่เราใช้ `Array.from` เพื่อแปลงสตริงให้เป็นอาร์เรย์ของตัวอักษร: +ทีนี้ลองใช้ `Array.from` แปลงสตริงเป็นอาร์เรย์ของตัวอักษรดูบ้าง: ```js run let str = '𝒳😂'; @@ -255,9 +255,9 @@ alert(chars[1]); // 😂 alert(chars.length); // 2 ``` -ต่างจาก `str.split` ตรงที่อาศัยคุณสมบัติ iterable ของสตริง ดังนั้นเหมือนกับ `for..of` จึงทำงานได้ถูกต้องกับ surrogate pairs +ต่างจาก `str.split` ตรงที่อาศัยคุณสมบัติ iterable ของสตริง จึงเหมือนกับ `for..of` คือรองรับ surrogate pairs ได้ถูกต้อง -ในทางเทคนิค มันทำสิ่งเดียวกับ: +จริงๆ แล้ว ภายในมันทำแบบเดียวกับ: ```js run let str = '𝒳😂'; @@ -270,9 +270,9 @@ for (let char of str) { alert(chars); ``` -...แต่กระชับกว่า +...แต่กระชับกว่านั่นเอง -เรายังสามารถสร้างฟังก์ชัน `slice` ที่รองรับ surrogate pairs ได้อีกด้วย: +นอกจากนี้ยังสร้างฟังก์ชัน `slice` ที่รองรับ surrogate pairs ได้ด้วย: ```js run function slice(str, start, end) { @@ -293,15 +293,15 @@ alert( str.slice(1, 3) ); // ขยะ (สองชิ้นจาก surrogate ออบเจ็กต์ที่ใช้ใน `for..of` ได้เรียกว่า *iterable* - ในทางเทคนิค iterable ต้องมีเมธอดชื่อ `Symbol.iterator` - - ผลลัพธ์ของ `obj[Symbol.iterator]()` เรียกว่า *iterator* ซึ่งจัดการกระบวนการวนซ้ำต่อไป - - iterator ต้องมีเมธอดชื่อ `next()` ที่คืนค่าเป็นออบเจ็กต์ `{done: Boolean, value: any}` โดย `done:true` บ่งบอกว่ากระบวนการวนซ้ำสิ้นสุดแล้ว มิฉะนั้น `value` คือค่าถัดไป -- เมธอด `Symbol.iterator` ถูกเรียกอัตโนมัติโดย `for..of` แต่เราก็เรียกมันโดยตรงได้เช่นกัน -- Built-in iterables เช่นสตริงหรืออาร์เรย์ก็มี `Symbol.iterator` ให้มาด้วย -- String iterator รู้จัก surrogate pairs + - ผลลัพธ์จากการเรียก `obj[Symbol.iterator]()` คือ *iterator* ซึ่งรับช่วงดูแลกระบวนการวนซ้ำต่อไป + - iterator ต้องมีเมธอด `next()` ที่คืนค่าเป็น `{done: Boolean, value: any}` — `done:true` หมายความว่าวนซ้ำจบแล้ว ถ้ายังไม่จบ `value` คือค่าถัดไป +- `for..of` เรียก `Symbol.iterator` อัตโนมัติ แต่เรียกโดยตรงก็ได้เช่นกัน +- Built-in iterables เช่นสตริงและอาร์เรย์มี `Symbol.iterator` ติดมาด้วย +- String iterator รองรับ surrogate pairs -ออบเจ็กต์ที่มีพร็อพเพอร์ตี้แบบ index และ `length` เรียกว่า *array-like* ออบเจ็กต์เหล่านี้อาจมีพร็อพเพอร์ตี้และเมธอดอื่นด้วย แต่ขาด built-in methods ของอาร์เรย์ +ออบเจ็กต์ที่มีพร็อพเพอร์ตี้แบบ index และ `length` เรียกว่า *array-like* อาจมีพร็อพเพอร์ตี้และเมธอดอื่นด้วย แต่ไม่มี built-in methods ของอาร์เรย์ -ถ้าลองดูใน specification จะเห็นว่า built-in methods ส่วนใหญ่ถูกออกแบบมาให้ทำงานกับ iterable หรือ array-like แทน "real" arrays เพราะนั่นเป็นแนวคิดที่เป็นนามธรรมมากกว่า +ลองดูใน specification จะเห็นว่า built-in methods ส่วนใหญ่ออกแบบมาให้ทำงานกับ iterable หรือ array-like แทน "real" arrays เพราะเป็นแนวคิดที่ abstract กว่า -`Array.from(obj[, mapFn, thisArg])` สร้างอาร์เรย์ "จริงๆ" จาก iterable หรือ array-like `obj` จากนั้นเราสามารถเรียก array methods บนมันได้ อาร์กิวเมนต์เพิ่มเติม `mapFn` และ `thisArg` ช่วยให้เราใช้ฟังก์ชันกับแต่ละรายการได้ +`Array.from(obj[, mapFn, thisArg])` สร้างอาร์เรย์จริงๆ จาก iterable หรือ array-like `obj` แล้วใช้ array methods ได้ทันที อาร์กิวเมนต์เสริม `mapFn` และ `thisArg` ช่วยให้ใส่ฟังก์ชัน mapping กับแต่ละรายการได้ diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index 5a72147af..96c35a667 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -1,16 +1,16 @@ # Map และ Set -ที่ผ่านมา เราได้เรียนรู้โครงสร้างข้อมูลที่ซับซ้อนดังนี้: +ก่อนหน้านี้เราได้รู้จักโครงสร้างข้อมูลสองแบบแล้ว: -- ออบเจ็กต์ ใช้สำหรับเก็บคอลเล็กชันแบบมี key -- อาร์เรย์ ใช้สำหรับเก็บคอลเล็กชันที่มีลำดับ +- ออบเจ็กต์ — เก็บข้อมูลแบบมี key +- อาร์เรย์ — เก็บข้อมูลแบบมีลำดับ -แต่สำหรับงานจริงในชีวิตประจำวัน แค่นี้ยังไม่พอ นั่นจึงเป็นเหตุที่ `Map` และ `Set` ถือกำเนิดขึ้นมา +แต่สำหรับงานจริงๆ บางครั้งสองแบบนี้ก็ยังไม่พอ `Map` และ `Set` จึงเกิดขึ้นมาเพื่อเติมเต็มจุดนั้น ## Map -[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) คือคอลเล็กชันของข้อมูลแบบมี key คล้ายกับ `Object` แต่ข้อแตกต่างสำคัญคือ `Map` อนุญาตให้ใช้ key เป็น **ชนิดข้อมูลใดก็ได้** +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) คือคอลเล็กชันของข้อมูลแบบมี key คล้ายกับ `Object` แต่ข้อแตกต่างสำคัญคือ `Map` ให้ใช้ key เป็น **ชนิดข้อมูลใดก็ได้** เมธอดและพร็อพเพอร์ตี้ที่มี: @@ -39,7 +39,7 @@ alert( map.get('1') ); // 'str1' alert( map.size ); // 3 ``` -จะเห็นว่าต่างจาก Object ตรงที่ Map ไม่แปลง key ให้เป็น string key จะเป็นชนิดใดก็ได้ทั้งนั้น +ต่างจาก Object ตรงนี้แหละ — Map ไม่แปลง key เป็น string key จะเป็นชนิดใดก็ได้ทั้งนั้น ```smart header="`map[key]` ไม่ใช่วิธีที่ถูกต้องในการใช้ `Map`" แม้ว่า `map[key]` จะใช้ได้ เช่น `map[key] = 2` แต่นั่นคือการใช้ `map` เสมือน plain JavaScript object ทั่วไป ซึ่งมีข้อจำกัดทั้งหมดของ Object (key ได้แค่ string หรือ symbol เป็นต้น) @@ -47,7 +47,7 @@ alert( map.size ); // 3 ดังนั้น ควรใช้เมธอดของ `map` แทน ได้แก่ `set`, `get` และอื่นๆ ``` -**Map ยังสามารถใช้ออบเจ็กต์เป็น key ได้ด้วย** +**Map ยังใช้ออบเจ็กต์เป็น key ได้ด้วย** ตัวอย่าง: @@ -63,7 +63,7 @@ visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` -การใช้ออบเจ็กต์เป็น key นี้คือหนึ่งในฟีเจอร์ที่โดดเด่นและสำคัญที่สุดของ `Map` ซึ่ง `Object` ทำไม่ได้ การใช้ string เป็น key ใน `Object` นั้นโอเค แต่เราไม่สามารถใช้ `Object` อื่นเป็น key ใน `Object` ได้ +นี่คือหนึ่งในฟีเจอร์ที่โดดเด่นที่สุดของ `Map` — ใช้ออบเจ็กต์เป็น key ได้เลย ซึ่ง `Object` ทำไม่ได้ เพราะ Object ธรรมดาใช้ string เป็น key ได้ แต่ไม่สามารถใช้ Object อื่นเป็น key ได้ ลองดูตัวอย่าง: @@ -82,16 +82,16 @@ alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` -เนื่องจาก `visitsCountObj` เป็น object ธรรมดา มันจึงแปลง Object ทุกตัวที่เป็น key ไม่ว่าจะเป็น `john` หรือ `ben` ให้กลายเป็น string เดียวกันคือ `"[object Object]"` ซึ่งไม่ใช่สิ่งที่เราต้องการแน่นอน +เนื่องจาก `visitsCountObj` เป็น object ธรรมดา ไม่ว่าจะใส่ `john` หรือ `ben` เป็น key มันจะแปลงให้เป็น string เดียวกันคือ `"[object Object]"` ซึ่งไม่ใช่สิ่งที่ต้องการแน่นอน ```smart header="`Map` เปรียบเทียบ key อย่างไร" -`Map` ใช้อัลกอริทึม [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero) ในการตรวจสอบความเท่ากันของ key ซึ่งคล้ายกับ strict equality `===` แต่ต่างตรงที่ `NaN` ถือว่าเท่ากับ `NaN` จึงสามารถใช้ `NaN` เป็น key ได้เช่นกัน +`Map` ใช้อัลกอริทึม [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero) ในการเปรียบเทียบ key ซึ่งคล้ายกับ strict equality `===` แต่ต่างตรงที่ `NaN` ถือว่าเท่ากับ `NaN` จึงใช้ `NaN` เป็น key ได้เช่นกัน อัลกอริทึมนี้ไม่สามารถเปลี่ยนหรือปรับแต่งได้ ``` ````smart header="การเชื่อมต่อเมธอด (Chaining)" -ทุกครั้งที่เรียก `map.set` จะได้รับ map กลับคืนมา จึงสามารถ "เชื่อม" การเรียกต่อกันได้แบบนี้: +ทุกครั้งที่เรียก `map.set` จะได้รับ map กลับคืนมา จึงเชื่อมการเรียกต่อกันได้แบบนี้: ```js map.set('1', 'str1') @@ -102,7 +102,7 @@ map.set('1', 'str1') ## การวนซ้ำบน Map -สำหรับการวนซ้ำบน `map` มีเมธอดให้เลือก 3 แบบ: +วนซ้ำบน `map` ได้ด้วยเมธอดสามแบบ: - [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- คืนค่า iterable สำหรับ key ทั้งหมด - [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- คืนค่า iterable สำหรับ value ทั้งหมด @@ -148,7 +148,7 @@ recipeMap.forEach( (value, key, map) => { ## Object.entries: สร้าง Map จาก Object -เมื่อสร้าง `Map` เราสามารถส่งอาร์เรย์ (หรือ iterable อื่นๆ) ที่มีคู่ key/value เพื่อใช้กำหนดค่าเริ่มต้นได้ดังนี้: +ตอนสร้าง `Map` เราส่งอาร์เรย์ (หรือ iterable อื่นๆ) ที่มีคู่ key/value เพื่อกำหนดค่าเริ่มต้นได้เลย: ```js run // อาร์เรย์ของคู่ [key, value] @@ -161,9 +161,9 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -ถ้ามี plain object อยู่แล้วและต้องการสร้าง `Map` จากมัน เราสามารถใช้เมธอด built-in [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) ซึ่งคืนค่าอาร์เรย์ของคู่ key/value ในรูปแบบที่ต้องการพอดี +ถ้ามี plain object อยู่แล้วและต้องการแปลงเป็น `Map` ใช้เมธอด built-in [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) ได้เลย มันคืนค่าอาร์เรย์ของคู่ key/value ในรูปแบบที่ต้องการพอดี -ดังนั้น เราสร้าง map จาก object ได้แบบนี้: +สร้าง map จาก object ได้แบบนี้: ```js run let obj = { @@ -178,14 +178,12 @@ let map = new Map(Object.entries(obj)); alert( map.get('name') ); // John ``` -ที่นี่ `Object.entries` คืนค่าอาร์เรย์ของคู่ key/value: `[ ["name","John"], ["age", 30] ]` ซึ่งเป็นสิ่งที่ `Map` ต้องการ +`Object.entries` คืนค่าอาร์เรย์ `[ ["name","John"], ["age", 30] ]` ซึ่งเป็นรูปแบบที่ `Map` ต้องการพอดี ## Object.fromEntries: สร้าง Object จาก Map -เราเพิ่งเห็นวิธีสร้าง `Map` จาก plain object ด้วย `Object.entries(obj)` ไปแล้ว - -ยังมีเมธอด `Object.fromEntries` ที่ทำสิ่งตรงกันข้าม: รับอาร์เรย์ของคู่ `[key, value]` แล้วสร้าง object จากมัน: +ถ้า `Object.entries` แปลง object เป็น Map ได้ `Object.fromEntries` ก็ทำสิ่งตรงกันข้าม — รับอาร์เรย์ของคู่ `[key, value]` แล้วสร้าง object กลับออกมา: ```js run let prices = Object.fromEntries([ @@ -199,11 +197,7 @@ let prices = Object.fromEntries([ alert(prices.orange); // 2 ``` -เราใช้ `Object.fromEntries` เพื่อแปลง `Map` กลับเป็น plain object ได้ - -เช่น เก็บข้อมูลไว้ใน `Map` แต่ต้องส่งต่อให้โค้ดของ third-party ที่รับได้แค่ plain object - -ทำได้แบบนี้: +ใช้ `Object.fromEntries` แปลง `Map` กลับเป็น plain object ได้ เช่น เก็บข้อมูลไว้ใน `Map` แต่ต้องส่งให้โค้ดของ third-party ที่รับแค่ plain object — ทำแบบนี้: ```js run let map = new Map(); @@ -221,18 +215,18 @@ let obj = Object.fromEntries(map.entries()); // แปลงเป็น plain o alert(obj.orange); // 2 ``` -การเรียก `map.entries()` คืนค่า iterable ของคู่ key/value ในรูปแบบที่ `Object.fromEntries` ต้องการพอดี +`map.entries()` คืนค่า iterable ของคู่ key/value ในรูปแบบที่ `Object.fromEntries` ต้องการพอดี -เราย่อบรรทัด `(*)` ให้สั้นลงได้อีก: +ย่อบรรทัด `(*)` ให้สั้นลงได้อีก: ```js let obj = Object.fromEntries(map); // ละ .entries() ออก ``` -ผลลัพธ์เหมือนกัน เพราะ `Object.fromEntries` รับ iterable object เป็น argument ไม่จำเป็นต้องเป็นอาร์เรย์เสมอไป และการวนซ้ำมาตรฐานของ `map` คืนค่าคู่ key/value เหมือนกับ `map.entries()` ทุกประการ ผลลัพธ์จึงเป็น plain object ที่มี key/value เดียวกับ `map` +ผลลัพธ์เหมือนกัน เพราะ `Object.fromEntries` รับ iterable object เป็น argument ไม่จำเป็นต้องเป็นอาร์เรย์เสมอไป และการวนซ้ำมาตรฐานของ `map` คืนค่าคู่ key/value เหมือนกับ `map.entries()` ทุกประการ ## Set -[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) คือคอลเล็กชันชนิดพิเศษ — "ชุดของค่า" (ไม่มี key) ที่ **แต่ละค่าสามารถมีได้เพียงครั้งเดียว** +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) คือคอลเล็กชันชนิดพิเศษ — "ชุดของค่า" (ไม่มี key) ที่ **แต่ละค่าปรากฏได้เพียงครั้งเดียว** เมธอดหลักที่มี: @@ -243,11 +237,9 @@ let obj = Object.fromEntries(map); // ละ .entries() ออก - [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- ลบทุกอย่างออกจาก set - [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- จำนวน element ทั้งหมด -จุดเด่นสำคัญคือการเรียก `set.add(value)` ซ้ำๆ ด้วยค่าเดิมจะไม่มีผลใดๆ นั่นเป็นเหตุให้แต่ละค่าปรากฏใน `Set` ได้เพียงครั้งเดียว - -สมมติว่ามีผู้เยี่ยมชมเข้ามา และเราต้องการจำทุกคน แต่การเข้าซ้ำไม่ควรทำให้มีข้อมูลซ้ำ ผู้เยี่ยมชมคนหนึ่งควร "นับ" แค่ครั้งเดียว +จุดเด่นคือการเรียก `set.add(value)` ซ้ำด้วยค่าเดิมจะไม่มีผลใดๆ นั่นเองที่ทำให้แต่ละค่าปรากฏใน `Set` ได้เพียงครั้งเดียว -`Set` เหมาะกับงานนี้พอดี: +สมมติมีผู้เยี่ยมชมเข้ามา และต้องการจำทุกคน — แต่ไม่อยากให้คนเดิมซ้ำกัน ผู้เยี่ยมชมคนหนึ่งควรนับแค่ครั้งเดียว `Set` เหมาะกับงานนี้พอดี: ```js run let set = new Set(); @@ -271,7 +263,7 @@ for (let user of set) { } ``` -อีกทางเลือกหนึ่งแทน `Set` อาจเป็นอาร์เรย์ของผู้ใช้ แล้วเขียนโค้ดตรวจสอบซ้ำทุกครั้งที่เพิ่มด้วย [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) แต่ประสิทธิภาพจะแย่กว่ามาก เพราะเมธอดนี้ต้องวนผ่านอาร์เรย์ทั้งหมดเพื่อตรวจสอบทุก element ในขณะที่ `Set` ถูกออกแบบมาให้ตรวจสอบความเป็นเอกลักษณ์ได้รวดเร็วกว่ามาก +อีกทางหนึ่งคือใช้อาร์เรย์แล้วตรวจสอบซ้ำทุกครั้งที่เพิ่มด้วย [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) แต่ประสิทธิภาพจะแย่กว่ามาก เพราะ `arr.find` ต้องวนอ่านอาร์เรย์ทั้งหมดทุกครั้ง ส่วน `Set` ถูกออกแบบมาให้ตรวจสอบความเป็นเอกลักษณ์ได้รวดเร็วกว่ามาก ## การวนซ้ำบน Set @@ -288,11 +280,11 @@ set.forEach((value, valueAgain, set) => { }); ``` -สังเกตสิ่งที่น่าแปลกใจ — ฟังก์ชัน callback ที่ส่งให้ `forEach` มี 3 argument คือ `value`, *ค่าเดิมอีกครั้ง* `valueAgain`, และ object เป้าหมาย ซึ่งค่าเดิมปรากฏในอาร์กิวเมนต์ถึงสองครั้ง +สังเกตสิ่งที่น่าแปลกใจ — callback ที่ส่งให้ `forEach` มี 3 argument คือ `value`, *ค่าเดิมอีกครั้ง* `valueAgain`, และ object เป้าหมาย ค่าเดิมปรากฏถึงสองครั้ง ดูแปลกๆ ใช่ไหม? -ที่เป็นแบบนี้เพื่อให้เข้ากันได้กับ `Map` ซึ่ง callback ของ `forEach` มีสาม argument ดูแปลกๆ นิดหน่อย แต่ช่วยให้สลับระหว่าง `Map` กับ `Set` ได้อย่างง่ายดายในบางกรณี +เหตุผลคือเพื่อความเข้ากันได้กับ `Map` ซึ่ง callback ของ `forEach` มีสาม argument เหมือนกัน ข้อดีคือช่วยให้สลับระหว่าง `Map` กับ `Set` ได้ง่ายในบางกรณี -เมธอดสำหรับ iterator ที่ `Map` มีก็รองรับใน `Set` ด้วย: +เมธอด iterator ที่ `Map` มีก็รองรับใน `Set` ด้วย: - [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- คืนค่า iterable object สำหรับ value ทั้งหมด - [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- เหมือนกับ `set.keys()` ไว้รองรับความเข้ากันได้กับ `Map` @@ -328,4 +320,4 @@ set.forEach((value, valueAgain, set) => { - [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- ลบทุกอย่างออกจาก set - [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- จำนวน element ทั้งหมด -การวนซ้ำบน `Map` และ `Set` จะเป็นลำดับเดียวกับที่แทรกค่าเข้าไปเสมอ เราจึงพูดไม่ได้ว่าคอลเล็กชันเหล่านี้ไม่มีลำดับ แต่ก็ไม่สามารถจัดเรียงใหม่หรือดึง element โดยตรงจากหมายเลขลำดับได้ +การวนซ้ำบน `Map` และ `Set` จะเป็นลำดับเดียวกับที่แทรกค่าเข้าไปเสมอ จึงพูดไม่ได้ว่าคอลเล็กชันเหล่านี้ไม่มีลำดับ — แต่ก็ไม่สามารถจัดเรียงใหม่หรือดึง element โดยตรงจากหมายเลขลำดับได้ diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 415894cde..7d86f0dfd 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,7 +1,7 @@ # WeakMap และ WeakSet -จากที่เรารู้กันในบท JavaScript engine จะเก็บค่าไว้ในหน่วยความจำตราบเท่าที่ค่านั้น "ยังเข้าถึงได้" และอาจถูกนำไปใช้งาน +จากที่เรารู้กันในบท ค่าจะอยู่ในหน่วยความจำตราบเท่าที่ยัง "เข้าถึงได้" และอาจถูกนำไปใช้งาน ตัวอย่างเช่น: @@ -18,9 +18,9 @@ john = null; */!* ``` -โดยทั่วไปแล้ว พร็อพเพอร์ตี้ของออบเจ็กต์ หรือสมาชิกของอาร์เรย์ หรือโครงสร้างข้อมูลอื่นๆ จะถือว่า "เข้าถึงได้" และจะถูกเก็บไว้ในหน่วยความจำตราบเท่าที่โครงสร้างข้อมูลนั้นยังคงอยู่ +พร็อพเพอร์ตี้ของออบเจ็กต์ สมาชิกของอาร์เรย์ หรือข้อมูลในโครงสร้างข้อมูลอื่นๆ ล้วนถือว่า "เข้าถึงได้" ตราบเท่าที่โครงสร้างนั้นยังอยู่ในหน่วยความจำ -ตัวอย่างเช่น ถ้าเราใส่ออบเจ็กต์ลงในอาร์เรย์ ตราบใดที่อาร์เรย์ยังคงอยู่ ออบเจ็กต์นั้นก็จะยังคงอยู่ด้วย แม้จะไม่มี reference อื่นชี้ไปที่มันแล้วก็ตาม +เช่น ถ้าใส่ออบเจ็กต์ลงในอาร์เรย์ ออบเจ็กต์นั้นจะอยู่ตลอดตราบที่อาร์เรย์ยังอยู่ แม้จะไม่มี reference อื่นชี้ไปที่มันเลยก็ตาม แบบนี้: @@ -33,12 +33,12 @@ john = null; // เขียนทับ reference *!* // ออบเจ็กต์ที่ john เคยชี้ไป ยังคงอยู่ในอาร์เรย์ -// ดังนั้นมันจะไม่ถูก garbage-collected +// ดังนั้นจะไม่ถูก garbage-collected // เราเข้าถึงมันได้ผ่าน array[0] */!* ``` -ในทำนองเดียวกัน ถ้าเราใช้ออบเจ็กต์เป็น key ใน `Map` ปกติ ตราบใดที่ `Map` ยังคงอยู่ ออบเจ็กต์นั้นก็จะยังคงอยู่ด้วย มันจะครอบครองหน่วยความจำและไม่อาจถูก garbage collect +ถ้าใช้ออบเจ็กต์เป็น key ใน `Map` ปกติก็เหมือนกัน — ตราบใดที่ `Map` ยังอยู่ ออบเจ็กต์นั้นก็อยู่ด้วย ครอบครองหน่วยความจำและไม่ถูก garbage collect ตัวอย่าง: @@ -56,13 +56,13 @@ john = null; // เขียนทับ reference */!* ``` -[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) ทำงานต่างออกไปในแง่นี้โดยสิ้นเชิง มันไม่ขัดขวางการ garbage-collect ของออบเจ็กต์ที่เป็น key +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) ต่างออกไปโดยสิ้นเชิง — ไม่ขัดขวางการ garbage-collect ของออบเจ็กต์ที่เป็น key เลย มาดูกันว่าหมายความว่าอะไรผ่านตัวอย่าง ## WeakMap -ความแตกต่างประการแรกระหว่าง [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) และ [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) คือ key ต้องเป็นออบเจ็กต์เท่านั้น ไม่ใช่ค่า primitive: +ความต่างแรกระหว่าง [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) กับ [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) คือ key ต้องเป็นออบเจ็กต์เท่านั้น ใช้ค่า primitive ไม่ได้: ```js run let weakMap = new WeakMap(); @@ -77,7 +77,7 @@ weakMap.set("test", "Whoops"); // เกิดข้อผิดพลาด เ */!* ``` -ทีนี้ ถ้าเราใช้ออบเจ็กต์เป็น key และไม่มี reference อื่นชี้ไปที่ออบเจ็กต์นั้นอีกแล้ว ออบเจ็กต์นั้นจะถูกลบออกจากหน่วยความจำ (และจาก map) โดยอัตโนมัติ +ถ้าใช้ออบเจ็กต์เป็น key แล้วไม่มี reference อื่นชี้ไปที่ออบเจ็กต์นั้นอีก ออบเจ็กต์นั้นจะถูกลบออกจากหน่วยความจำ (และจาก map) โดยอัตโนมัติ ```js let john = { name: "John" }; @@ -90,39 +90,39 @@ john = null; // เขียนทับ reference // john ถูกลบออกจากหน่วยความจำแล้ว! ``` -เปรียบเทียบกับตัวอย่าง `Map` ปกติข้างบน ถ้า `john` อยู่ใน `WeakMap` ในฐานะ key เพียงอย่างเดียว มันจะถูกลบออกจาก map (และหน่วยความจำ) โดยอัตโนมัติ +เทียบกับตัวอย่าง `Map` ปกติข้างบน — ถ้า `john` เป็น key ใน `WeakMap` เพียงที่เดียว จะถูกลบออกจาก map และหน่วยความจำโดยอัตโนมัติ -`WeakMap` ไม่รองรับการวนซ้ำ และเมธอด `keys()`, `values()`, `entries()` ดังนั้นจึงไม่มีทางดึง key หรือ value ทั้งหมดออกมาได้ +`WeakMap` ไม่รองรับการวนซ้ำ และไม่มีเมธอด `keys()`, `values()`, `entries()` จึงไม่มีทางดึง key หรือ value ทั้งหมดออกมาได้ -`WeakMap` มีเพียงเมธอดเหล่านี้: +มีแค่สี่เมธอดเท่านั้น: - [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) - [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) - [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) - [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) -ทำไมถึงมีข้อจำกัดเช่นนี้? เหตุผลเป็นเรื่องทางเทคนิค ถ้าออบเจ็กต์สูญเสีย reference ทั้งหมด (เหมือน `john` ในโค้ดข้างบน) มันจะถูก garbage collect โดยอัตโนมัติ แต่ในทางเทคนิคแล้ว ไม่มีการระบุแน่นอนว่า *การทำความสะอาดจะเกิดขึ้นเมื่อใด* +ทำไมถึงจำกัดแค่นี้? เหตุผลเป็นเรื่องทางเทคนิค — ถ้าออบเจ็กต์สูญเสีย reference ทั้งหมด (เหมือน `john` ในโค้ดข้างบน) JavaScript engine จะ garbage collect ออบเจ็กต์นั้น แต่ไม่มีการระบุแน่นอนว่า *จะเกิดขึ้นเมื่อไหร่* -JavaScript engine เป็นตัวตัดสินใจ อาจเลือกทำทันทีหรือรอก่อนแล้วค่อยทำในภายหลังเมื่อมีการลบมากขึ้น ดังนั้นในทางเทคนิค จำนวนสมาชิกปัจจุบันของ `WeakMap` จึงไม่แน่นอน engine อาจทำความสะอาดไปแล้วหรือยังไม่ได้ทำ หรือทำไปบางส่วน ด้วยเหตุนี้ เมธอดที่เข้าถึง key/value ทั้งหมดจึงไม่ได้รับการรองรับ +engine เป็นตัวตัดสินใจเอง อาจทำทันทีหรือรอก่อนแล้วค่อยทำทีหลังพร้อมกันหลายๆ ตัว จำนวนสมาชิกใน `WeakMap` ณ ขณะหนึ่งจึงไม่แน่นอน — อาจทำความสะอาดไปแล้ว ยังไม่ได้ทำ หรือทำไปแค่บางส่วน นั่นจึงเป็นเหตุผลที่เมธอดสำหรับเข้าถึง key/value ทั้งหมดไม่ได้รับการรองรับ -แล้วเราต้องการโครงสร้างข้อมูลแบบนี้ในสถานการณ์ไหน? +แล้วโครงสร้างข้อมูลแบบนี้ใช้ในสถานการณ์ไหนบ้าง? ## กรณีใช้งาน: ข้อมูลเสริม การใช้งานหลักของ `WeakMap` คือการเก็บ *ข้อมูลเสริม* -ถ้าเรากำลังทำงานกับออบเจ็กต์ที่ "เป็นของ" โค้ดอื่น หรืออาจเป็น library ของบุคคลที่สาม และต้องการเก็บข้อมูลบางอย่างที่เชื่อมโยงกับมัน ซึ่งควรมีอยู่แค่ตราบเท่าที่ออบเจ็กต์นั้นยังมีชีวิตอยู่ — `WeakMap` คือสิ่งที่ต้องการพอดี +สมมติเรากำลังทำงานกับออบเจ็กต์ที่ "เป็นของ" โค้ดอื่น หรืออาจเป็น library ของบุคคลที่สาม แล้วต้องการเก็บข้อมูลบางอย่างที่ผูกกับออบเจ็กต์นั้น โดยให้ข้อมูลนั้นหายไปพร้อมออบเจ็กต์ — `WeakMap` เหมาะกับงานนี้พอดี -เราใส่ข้อมูลลงใน `WeakMap` โดยใช้ออบเจ็กต์เป็น key และเมื่อออบเจ็กต์ถูก garbage collect ข้อมูลนั้นก็จะหายไปโดยอัตโนมัติด้วย +ใส่ข้อมูลลงใน `WeakMap` โดยใช้ออบเจ็กต์เป็น key พอออบเจ็กต์ถูก garbage collect ข้อมูลนั้นก็หายไปเองโดยอัตโนมัติ ```js weakMap.set(john, "เอกสารลับ"); // ถ้า john ถูกลบ เอกสารลับก็จะถูกทำลายโดยอัตโนมัติ ``` -มาดูตัวอย่างกัน +มาดูตัวอย่างจริงกัน -สมมติเรามีโค้ดที่เก็บจำนวนครั้งที่ผู้ใช้เข้าเยี่ยมชม ข้อมูลถูกเก็บใน map โดยใช้ออบเจ็กต์ user เป็น key และจำนวนครั้งเป็น value เมื่อผู้ใช้จากไป (ออบเจ็กต์ถูก garbage collect) เราก็ไม่อยากเก็บจำนวนครั้งของเขาอีกต่อไป +สมมติมีโค้ดที่เก็บจำนวนครั้งที่ผู้ใช้เข้าเยี่ยมชม ข้อมูลถูกเก็บใน map โดยใช้ออบเจ็กต์ user เป็น key พอผู้ใช้จากไป (ออบเจ็กต์ถูก garbage collect) เราก็ไม่ต้องการจำนวนครั้งของเขาแล้ว นี่คือตัวอย่างฟังก์ชันนับการเข้าชมด้วย `Map`: @@ -149,11 +149,11 @@ countUser(john); // นับการเข้าชมของเขา john = null; ``` -ตอนนี้ ออบเจ็กต์ `john` ควรจะถูก garbage collect แต่มันยังคงอยู่ในหน่วยความจำ เพราะเป็น key ใน `visitsCountMap` +ตอนนี้ออบเจ็กต์ `john` ควรถูก garbage collect แต่ยังอยู่ในหน่วยความจำเพราะเป็น key ใน `visitsCountMap` -เราต้องทำความสะอาด `visitsCountMap` ทุกครั้งที่ลบผู้ใช้ออก ไม่เช่นนั้นมันจะกินหน่วยความจำไปเรื่อยๆ การทำความสะอาดแบบนี้อาจกลายเป็นงานน่าเบื่อในสถาปัตยกรรมที่ซับซ้อน +ทุกครั้งที่ลบผู้ใช้ออก เราต้องทำความสะอาด `visitsCountMap` ด้วย ไม่งั้นจะกินหน่วยความจำไปเรื่อยๆ ในสถาปัตยกรรมที่ซับซ้อน งานแบบนี้น่าเบื่อมาก -เราหลีกเลี่ยงปัญหานี้ได้ด้วยการเปลี่ยนไปใช้ `WeakMap` แทน: +ใช้ `WeakMap` แทนก็แก้ปัญหาได้ทันที: ```js // 📁 visitsCount.js @@ -166,13 +166,13 @@ function countUser(user) { } ``` -ทีนี้เราไม่ต้องทำความสะอาด `visitsCountMap` อีกต่อไป เมื่อออบเจ็กต์ `john` ไม่สามารถเข้าถึงได้แล้ว ยกเว้นในฐานะ key ของ `WeakMap` มันจะถูกลบออกจากหน่วยความจำพร้อมกับข้อมูลที่เชื่อมโยงกับ key นั้นใน `WeakMap` โดยอัตโนมัติ +ไม่ต้องทำความสะอาด `visitsCountMap` อีกต่อไป พอออบเจ็กต์ `john` ไม่สามารถเข้าถึงได้จากที่อื่นนอกจากใน `WeakMap` ก็จะถูกลบออกจากหน่วยความจำพร้อมกับข้อมูลที่ผูกกับ key นั้นโดยอัตโนมัติ ## กรณีใช้งาน: caching -อีกตัวอย่างที่พบบ่อยคือการทำ caching เราสามารถเก็บ ("cache") ผลลัพธ์จากฟังก์ชันไว้ เพื่อที่การเรียกครั้งต่อๆ ไปด้วยออบเจ็กต์เดิมจะได้นำผลลัพธ์เดิมมาใช้ได้เลย +อีกกรณีที่พบบ่อยคือการทำ caching — เก็บผลลัพธ์จากฟังก์ชันไว้ เพื่อที่การเรียกครั้งต่อๆ ไปด้วยออบเจ็กต์เดิมจะได้นำผลลัพธ์เดิมมาใช้ได้เลยโดยไม่ต้องคำนวณใหม่ -เราทำแบบนี้ได้ด้วย `Map` (แต่ยังไม่ดีที่สุด): +ทำแบบนี้ด้วย `Map` (แต่ยังไม่ดีที่สุด): ```js run // 📁 cache.js @@ -208,9 +208,9 @@ obj = null; alert(cache.size); // 1 (อุ๊ย! ออบเจ็กต์ยังอยู่ใน cache กินหน่วยความจำ!) ``` -เมื่อเรียก `process(obj)` ด้วยออบเจ็กต์เดิมหลายครั้ง ครั้งแรกเท่านั้นที่จะคำนวณผลลัพธ์ ครั้งต่อๆ ไปจะดึงมาจาก `cache` แต่ข้อเสียคือเราต้องทำความสะอาด `cache` เมื่อไม่ต้องการออบเจ็กต์แล้ว +เรียก `process(obj)` ด้วยออบเจ็กต์เดิมกี่ครั้งก็ตาม ครั้งแรกเท่านั้นที่คำนวณจริง ครั้งต่อๆ ไปดึงจาก `cache` ได้เลย แต่ปัญหาคือต้องทำความสะอาด `cache` เองเมื่อไม่ต้องการออบเจ็กต์แล้ว -ถ้าเราแทน `Map` ด้วย `WeakMap` ปัญหานี้จะหมดไป ผลลัพธ์ที่ cache ไว้จะถูกลบออกจากหน่วยความจำโดยอัตโนมัติหลังจากออบเจ็กต์ถูก garbage collect +แทน `Map` ด้วย `WeakMap` ปัญหานี้หายไปเลย ผลลัพธ์ใน cache จะถูกลบออกจากหน่วยความจำอัตโนมัติหลังออบเจ็กต์ถูก garbage collect ```js run // 📁 cache.js @@ -246,15 +246,15 @@ obj = null; ## WeakSet -[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) ทำงานในลักษณะเดียวกัน: +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) ทำงานในทำนองเดียวกัน: -- คล้ายกับ `Set` แต่เราเพิ่มได้เฉพาะออบเจ็กต์ใน `WeakSet` เท่านั้น (ไม่ใช่ค่า primitive) -- ออบเจ็กต์จะอยู่ใน set ตราบเท่าที่ยังมีที่อื่นเข้าถึงมันได้ -- เหมือน `Set` รองรับ [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) และ [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete) แต่ไม่มี `size`, `keys()` และไม่รองรับการวนซ้ำ +- คล้ายกับ `Set` แต่เพิ่มได้เฉพาะออบเจ็กต์เท่านั้น (ไม่ใช่ค่า primitive) +- ออบเจ็กต์จะอยู่ใน set ตราบเท่าที่ยังมีที่อื่นเข้าถึงได้ +- รองรับ [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) และ [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete) เหมือน `Set` แต่ไม่มี `size`, `keys()` และไม่รองรับการวนซ้ำ -ด้วยความเป็น "weak" มันจึงเหมาะสำหรับเก็บข้อมูลเสริมเช่นกัน แต่ไม่ใช่ข้อมูลทั่วไป แต่เป็นข้อเท็จจริงแบบ "ใช่/ไม่ใช่" การที่ออบเจ็กต์อยู่ใน `WeakSet` อาจหมายความว่าบางอย่างเกี่ยวกับออบเจ็กต์นั้น +ด้วยความเป็น "weak" จึงเหมาะสำหรับเก็บข้อมูลเสริมเช่นกัน แต่ไม่ใช่ข้อมูลทั่วไป — เหมาะกับข้อเท็จจริงแบบ "ใช่/ไม่ใช่" การที่ออบเจ็กต์อยู่ใน `WeakSet` บ่งบอกว่าบางอย่างเกี่ยวกับออบเจ็กต์นั้นเป็นความจริง -ตัวอย่างเช่น เราสามารถเพิ่ม user ลงใน `WeakSet` เพื่อติดตามว่าใครเคยเข้าเยี่ยมชมเว็บของเรา: +ตัวอย่างเช่น เพิ่ม user ลงใน `WeakSet` เพื่อติดตามว่าใครเคยเข้าเยี่ยมชมเว็บของเรา: ```js run let visitedSet = new WeakSet(); @@ -280,16 +280,16 @@ john = null; // visitedSet จะถูกทำความสะอาดโดยอัตโนมัติ ``` -ข้อจำกัดที่เด่นชัดที่สุดของ `WeakMap` และ `WeakSet` คือการไม่รองรับการวนซ้ำ และไม่สามารถดึงเนื้อหาทั้งหมดในขณะนั้นออกมาได้ ดูเหมือนจะไม่สะดวก แต่ก็ไม่ได้ขัดขวาง `WeakMap/WeakSet` จากการทำหน้าที่หลัก นั่นคือเป็นที่เก็บข้อมูล "เสริม" สำหรับออบเจ็กต์ที่ถูกเก็บและจัดการในที่อื่น +ข้อจำกัดที่เห็นชัดที่สุดของ `WeakMap` และ `WeakSet` คือไม่รองรับการวนซ้ำ และดึงเนื้อหาทั้งหมดออกมาไม่ได้ แต่นั่นก็ไม่ได้ขัดขวางการทำหน้าที่หลักของมัน — เป็นที่เก็บข้อมูล "เสริม" สำหรับออบเจ็กต์ที่ถูกเก็บและจัดการในที่อื่น ## สรุป -[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) คือคอลเล็กชันคล้าย `Map` ที่รองรับเฉพาะออบเจ็กต์เป็น key และจะลบออบเจ็กต์พร้อมกับค่าที่เชื่อมโยงโดยอัตโนมัติเมื่อออบเจ็กต์นั้นไม่สามารถเข้าถึงได้จากที่อื่นอีกแล้ว +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) คือคอลเล็กชันคล้าย `Map` ที่รับเฉพาะออบเจ็กต์เป็น key และจะลบออบเจ็กต์พร้อมค่าที่ผูกกันโดยอัตโนมัติเมื่อไม่สามารถเข้าถึงได้จากที่อื่นอีก -[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) คือคอลเล็กชันคล้าย `Set` ที่เก็บได้เฉพาะออบเจ็กต์ และจะลบออบเจ็กต์โดยอัตโนมัติเมื่อไม่สามารถเข้าถึงได้จากที่อื่นอีกแล้ว +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) คือคอลเล็กชันคล้าย `Set` ที่เก็บได้เฉพาะออบเจ็กต์ และลบออบเจ็กต์โดยอัตโนมัติเมื่อไม่สามารถเข้าถึงได้จากที่อื่นอีก -ข้อดีหลักของทั้งสองคือมัน reference ออบเจ็กต์แบบ "อ่อนแอ" ทำให้ garbage collector ลบออบเจ็กต์เหล่านั้นได้อย่างง่ายดาย +ข้อดีหลักของทั้งสองคือ reference ออบเจ็กต์แบบ "อ่อนแอ" ทำให้ garbage collector ลบออบเจ็กต์เหล่านั้นได้อย่างสะดวก แลกมาด้วยการไม่รองรับ `clear`, `size`, `keys`, `values`... -`WeakMap` และ `WeakSet` ถูกใช้เป็นโครงสร้างข้อมูล "รอง" เพิ่มเติมจากที่เก็บออบเจ็กต์ "หลัก" เมื่อออบเจ็กต์ถูกลบออกจากที่เก็บหลัก หากพบมันในฐานะ key ของ `WeakMap` หรืออยู่ใน `WeakSet` เท่านั้น มันก็จะถูกทำความสะอาดโดยอัตโนมัติ +`WeakMap` และ `WeakSet` ถูกใช้เป็นโครงสร้างข้อมูล "รอง" เพิ่มเติมจากที่เก็บออบเจ็กต์ "หลัก" พอออบเจ็กต์ถูกลบออกจากที่เก็บหลัก หากพบมันในฐานะ key ของ `WeakMap` หรืออยู่ใน `WeakSet` เพียงที่เดียว ก็จะถูกทำความสะอาดโดยอัตโนมัติ diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index 2b2a56c1d..1deedc9b3 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -1,19 +1,19 @@ # Object.keys, values, entries -มาหยุดพักจากโครงสร้างข้อมูลแต่ละตัวสักครู่ แล้วมาพูดถึงการวนซ้ำผ่านโครงสร้างเหล่านั้นกัน +มาพักจากโครงสร้างข้อมูลแต่ละตัวสักครู่ แล้วมาคุยเรื่องการวนซ้ำผ่านข้อมูลเหล่านั้นกัน -ในบทที่แล้ว เราเห็นเมธอด `map.keys()`, `map.values()`, `map.entries()` กันมาแล้ว +บทที่แล้วเราได้รู้จัก `map.keys()`, `map.values()`, `map.entries()` กันไปแล้ว -เมธอดเหล่านี้เป็น "กฎสากล" ที่ตกลงกันว่าควรนำไปใช้กับโครงสร้างข้อมูลทุกประเภท ถ้าเราสร้างโครงสร้างข้อมูลขึ้นมาเอง ก็ควรนำไปใช้ด้วยเช่นกัน +เมธอดพวกนี้ถือเป็นแบบแผนสากล — ทุกโครงสร้างข้อมูลควรมีเมธอดเหล่านี้ ถ้าสร้างโครงสร้างข้อมูลขึ้นมาเอง ก็ควรมีเมธอดเหล่านี้ด้วยเช่นกัน -เมธอดเหล่านี้รองรับใน: +รองรับในโครงสร้างต่อไปนี้: - `Map` - `Set` - `Array` -ออบเจ็กต์ธรรมดาก็มีเมธอดในแบบเดียวกัน แต่ไวยากรณ์จะแตกต่างออกไปเล็กน้อย +ออบเจ็กต์ธรรมดาก็มีเมธอดในแบบเดียวกัน แต่ไวยากรณ์ต่างออกไปเล็กน้อย ## Object.keys, values, entries @@ -30,11 +30,11 @@ | วิธีเรียกใช้ | `map.keys()` | `Object.keys(obj)` ไม่ใช่ `obj.keys()` | | คืนค่า | iterable | อาร์เรย์จริงๆ | -ความแตกต่างแรกคือ เราต้องเรียก `Object.keys(obj)` ไม่ใช่ `obj.keys()` +ข้อแรกคือต้องเรียก `Object.keys(obj)` ไม่ใช่ `obj.keys()` -ทำไมถึงเป็นแบบนี้? เหตุผลหลักคือความยืดหยุ่น ออบเจ็กต์เป็นพื้นฐานของโครงสร้างที่ซับซ้อนทุกอย่างใน JavaScript ดังนั้น เราอาจมีออบเจ็กต์ของเราเองเช่น `data` ที่มีเมธอด `data.values()` เป็นของตัวเอง แต่เรายังสามารถเรียก `Object.values(data)` กับมันได้อยู่ดี +ทำไมถึงเป็นแบบนี้? เพราะต้องการความยืดหยุ่น จำไว้ว่าออบเจ็กต์คือรากฐานของทุกโครงสร้างใน JavaScript เราอาจสร้างออบเจ็กต์อย่าง `data` ที่มีเมธอด `data.values()` เป็นของตัวเอง — แต่ยังเรียก `Object.values(data)` ได้เสมอ -ความแตกต่างที่สองคือ เมธอดในกลุ่ม `Object.*` คืนค่าเป็นอาร์เรย์จริงๆ ไม่ใช่แค่ iterable เหตุผลส่วนใหญ่เป็นเรื่องของประวัติศาสตร์การพัฒนาภาษา +ข้อสองคือ `Object.*` คืนค่าเป็นอาร์เรย์จริงๆ ไม่ใช่แค่ iterable เหตุผลส่วนใหญ่เป็นเรื่องของประวัติศาสตร์ภาษา ตัวอย่างเช่น: @@ -57,7 +57,7 @@ let user = { age: 30 }; -// วนซ้ำผ่าน value +// วนซ้ำผ่าน value ทั้งหมด for (let value of Object.values(user)) { alert(value); // John แล้วก็ 30 } @@ -66,19 +66,19 @@ for (let value of Object.values(user)) { ```warn header="Object.keys/values/entries ไม่สนใจพร็อพเพอร์ตี้ที่เป็น Symbol" เช่นเดียวกับลูป `for..in` เมธอดเหล่านี้จะข้ามพร็อพเพอร์ตี้ที่ใช้ `Symbol(...)` เป็น key -โดยทั่วไปนี่เป็นเรื่องที่สะดวกดี แต่ถ้าต้องการ symbolic key ด้วย ก็มีเมธอดแยกต่างหากชื่อ [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) ที่คืนค่าเป็นอาร์เรย์ของ symbolic key เท่านั้น นอกจากนี้ยังมีเมธอด [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) ที่คืนค่า key *ทั้งหมด* +ส่วนใหญ่ก็สะดวกดี แต่ถ้าต้องการ symbolic key ด้วย ใช้ [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) ซึ่งคืนค่าเป็นอาร์เรย์ของ symbolic key โดยเฉพาะ หรือจะใช้ [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) ที่คืนค่า key *ทั้งหมด* ก็ได้ ``` ## การแปลงออบเจ็กต์ -ออบเจ็กต์ขาดเมธอดหลายอย่างที่อาร์เรย์มี เช่น `map`, `filter` และอื่นๆ +ออบเจ็กต์ไม่มีเมธอดหลายตัวที่อาร์เรย์มี เช่น `map`, `filter` เป็นต้น -ถ้าอยากใช้เมธอดเหล่านั้น ให้ใช้ `Object.entries` ตามด้วย `Object.fromEntries` ดังนี้: +ถ้าอยากใช้เมธอดพวกนั้น ให้จับคู่ `Object.entries` กับ `Object.fromEntries` ดังนี้: 1. ใช้ `Object.entries(obj)` เพื่อดึงอาร์เรย์ของคู่ key/value จาก `obj` -2. ใช้เมธอดของอาร์เรย์กับอาร์เรย์นั้น เช่น `map` เพื่อแปลงคู่ key/value -3. ใช้ `Object.fromEntries(array)` กับอาร์เรย์ผลลัพธ์เพื่อแปลงกลับเป็นออบเจ็กต์ +2. ใช้เมธอดของอาร์เรย์ เช่น `map` เพื่อแปลงคู่ key/value ตามที่ต้องการ +3. ใช้ `Object.fromEntries(array)` แปลงอาร์เรย์ผลลัพธ์กลับเป็นออบเจ็กต์ ตัวอย่างเช่น ถ้ามีออบเจ็กต์ราคาสินค้า และต้องการคูณราคาทุกอย่างด้วย 2: @@ -100,4 +100,4 @@ let doublePrices = Object.fromEntries( alert(doublePrices.meat); // 8 ``` -อาจดูซับซ้อนในตอนแรก แต่พอใช้สักครั้งสองครั้งก็จะเข้าใจได้เอง เทคนิคนี้ช่วยให้เราต่อโซ่การแปลงข้อมูลได้อย่างทรงพลังมาก +ดูซับซ้อนตอนแรก แต่ลองใช้สักครั้งสองครั้งก็จะคุ้นเอง เทคนิคนี้ต่อโซ่การแปลงข้อมูลได้อย่างมีพลัง diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index ddad59791..47d678208 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -2,14 +2,14 @@ โครงสร้างข้อมูลที่ใช้บ่อยที่สุดสองอย่างใน JavaScript คือ `Object` และ `Array` -- ออบเจ็กต์ช่วยให้เราสร้างหน่วยเดียวที่เก็บข้อมูลโดยใช้ key -- อาร์เรย์ช่วยให้เราเก็บข้อมูลเป็นรายการที่มีลำดับ +- ออบเจ็กต์ใช้เก็บข้อมูลแบบ key-value เป็นหน่วยเดียว +- อาร์เรย์ใช้เก็บข้อมูลเป็นรายการที่มีลำดับ -อย่างไรก็ตาม เวลาที่เราส่งสิ่งเหล่านี้ไปให้ฟังก์ชัน บางครั้งก็ไม่ต้องการข้อมูลทั้งหมด อาจต้องการแค่บางส่วนเท่านั้น +แต่ในทางปฏิบัติ เวลาส่งสิ่งเหล่านี้ไปให้ฟังก์ชัน มักต้องการแค่บางส่วน ไม่จำเป็นต้องใช้ทั้งหมด -*Destructuring assignment* คือไวยากรณ์พิเศษที่ช่วยให้เรา "แกะ" อาร์เรย์หรือออบเจ็กต์ออกเป็นตัวแปรหลายตัวได้ในทีเดียว ซึ่งบางครั้งสะดวกกว่ามาก +*Destructuring assignment* คือไวยากรณ์พิเศษที่ช่วยให้ "แกะ" อาร์เรย์หรือออบเจ็กต์ออกเป็นตัวแปรหลายตัวได้ในคราวเดียว สะดวกกว่าการอ้างอิง index หรือ key ทีละตัวมาก -destructuring ยังทำงานได้ดีกับฟังก์ชันที่มีพารามิเตอร์จำนวนมาก มีค่าเริ่มต้น และอื่นๆ อีกมาก เดี๋ยวเราจะเห็นกัน +นอกจากนี้ destructuring ยังทำงานได้ดีกับฟังก์ชันที่มีพารามิเตอร์จำนวนมาก รวมถึงการกำหนดค่าเริ่มต้น เดี๋ยวจะเห็นกันเองว่าทรงพลังแค่ไหน ## Array destructuring @@ -30,9 +30,9 @@ alert(firstName); // John alert(surname); // Smith ``` -ทีนี้เราทำงานกับตัวแปรได้เลย แทนที่จะอ้างอิง index ของอาร์เรย์ +ทีนี้ก็ทำงานกับตัวแปรได้เลย ไม่ต้องอ้างอิง index ของอาร์เรย์อีกต่อไป -ดูดีมากเมื่อใช้ร่วมกับ `split` หรือเมธอดอื่นๆ ที่คืนค่าเป็นอาร์เรย์: +ใช้ร่วมกับ `split` หรือเมธอดอื่นๆ ที่คืนค่าเป็นอาร์เรย์ก็ได้เลย: ```js run let [firstName, surname] = "John Smith".split(' '); @@ -40,12 +40,12 @@ alert(firstName); // John alert(surname); // Smith ``` -ไวยากรณ์นี้เรียบง่ายดี แต่ก็มีรายละเอียดที่น่าสนใจอยู่บ้าง มาดูตัวอย่างเพิ่มเติมเพื่อทำความเข้าใจให้ลึกขึ้นกัน +ไวยากรณ์เรียบง่ายดี แต่มีรายละเอียดที่น่าสนใจซ่อนอยู่ มาดูตัวอย่างเพิ่มเติมกัน ````smart header="\"Destructuring\" ไม่ได้แปลว่า \"ทำลาย\"" -เรียกว่า "destructuring assignment" เพราะมัน "แตกโครงสร้าง" โดยการคัดลอกค่าไปใส่ตัวแปร แต่อาร์เรย์ต้นฉบับไม่ได้ถูกแก้ไขแต่อย่างใด +ชื่อ "destructuring assignment" มาจากการที่มัน "แตกโครงสร้าง" ด้วยการคัดลอกค่าลงตัวแปร — อาร์เรย์ต้นฉบับไม่ได้ถูกแตะเลย -มันเป็นแค่วิธีเขียนที่สั้นกว่านี้: +พูดง่ายๆ ก็คือ มันแค่เขียนสั้นลงจากนี้: ```js // let [firstName, surname] = arr; let firstName = arr[0]; @@ -54,7 +54,7 @@ let surname = arr[1]; ```` ````smart header="ข้ามสมาชิกที่ไม่ต้องการด้วยเครื่องหมายจุลภาค" -สมาชิกของอาร์เรย์ที่ไม่ต้องการสามารถข้ามได้ด้วยเครื่องหมายจุลภาคเพิ่มเติม: +ใส่จุลภาคเพิ่มเพื่อข้ามสมาชิกที่ไม่ต้องการ: ```js run *!* @@ -65,23 +65,24 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic alert( title ); // Consul ``` -ในโค้ดด้านบน สมาชิกตัวที่สองของอาร์เรย์ถูกข้ามไป ตัวที่สามถูกกำหนดให้กับ `title` และสมาชิกที่เหลือก็ถูกข้ามเช่นกัน (เพราะไม่มีตัวแปรรับ) +สมาชิกตัวที่สองถูกข้ามไป ตัวที่สามไปที่ `title` ส่วนที่เหลือก็ตกไปเพราะไม่มีตัวแปรรับ ```` ````smart header="ใช้ได้กับ iterable ใดๆ ทางขวามือ" -จริงๆ แล้ว เราใช้ได้กับ iterable ใดก็ได้ ไม่ใช่แค่อาร์เรย์: +ทางขวามือไม่จำเป็นต้องเป็นอาร์เรย์เสมอไป ใช้ได้กับ iterable ใดก็ได้: ```js let [a, b, c] = "abc"; // ["a", "b", "c"] let [one, two, three] = new Set([1, 2, 3]); ``` -ใช้ได้เพราะภายในแล้ว destructuring assignment ทำงานโดยการ iterate ค่าทางขวา มันคือน้ำตาลทางไวยากรณ์ของการใช้ `for..of` กับค่าทางขวาของ `=` แล้วกำหนดค่าให้ตัวแปร + +เป็นเพราะ destructuring assignment ทำงานด้วยการ iterate ค่าทางขวา — พูดง่ายๆ คือเป็นน้ำตาลทางไวยากรณ์ของการใช้ `for..of` กับค่าทางขวาของ `=` แล้วกำหนดค่าให้ตัวแปรทีละตัวนั่นเอง ```` ````smart header="กำหนดให้สิ่งใดก็ได้ทางซ้ายมือ" -เราใช้ "สิ่งที่รับการกำหนดค่าได้" ทางซ้ายมือก็ได้ +ทางซ้ายมือไม่จำเป็นต้องเป็นตัวแปรเสมอไป ใช้ "สิ่งที่รับการกำหนดค่าได้" ก็ได้ เช่น พร็อพเพอร์ตี้ของออบเจ็กต์: ```js run @@ -95,9 +96,7 @@ alert(user.surname); // Smith ```` ````smart header="วนลูปด้วย .entries()" -ในบทก่อน เราได้เห็นเมธอด [Object.entries(obj)](mdn:js/Object/entries) แล้ว - -เราใช้มันร่วมกับ destructuring เพื่อวนลูปผ่าน key-value ของออบเจ็กต์ได้: +จำเมธอด [Object.entries(obj)](mdn:js/Object/entries) จากบทก่อนได้ไหม? นำมาใช้ร่วมกับ destructuring เพื่อวนลูปผ่าน key-value ของออบเจ็กต์ได้เลย: ```js run let user = { @@ -113,7 +112,7 @@ for (let [key, value] of Object.entries(user)) { } ``` -โค้ดที่คล้ายกันสำหรับ `Map` จะง่ายกว่า เพราะมัน iterable อยู่แล้ว: +กรณีของ `Map` จะง่ายกว่าอีก เพราะ Map เป็น iterable อยู่แล้ว: ```js run let user = new Map(); @@ -130,7 +129,7 @@ for (let [key, value] of user) { ```` ````smart header="เทคนิคสลับค่าตัวแปร" -มีเทคนิคที่รู้จักกันดีในการสลับค่าของตัวแปรสองตัวโดยใช้ destructuring assignment: +destructuring มีเทคนิคที่รู้จักกันดีสำหรับสลับค่าตัวแปรสองตัว: ```js run let guest = "Jane"; @@ -144,16 +143,14 @@ let admin = "Pete"; alert(`${guest} ${admin}`); // Pete Jane (สลับสำเร็จ!) ``` -เราสร้างอาร์เรย์ชั่วคราวที่มีสองตัวแปร แล้ว destructure มันในลำดับที่สลับกัน +สร้างอาร์เรย์ชั่วคราวขึ้นมาหนึ่งตัว แล้ว destructure กลับในลำดับที่สลับกัน ง่ายมากใช่ไหม? -วิธีนี้ใช้สลับมากกว่าสองตัวแปรก็ได้ +ใช้วิธีเดิมนี้สลับมากกว่าสองตัวแปรก็ได้เช่นกัน ```` ### The rest '...' -โดยปกติแล้ว ถ้าอาร์เรย์ยาวกว่ารายการตัวแปรทางซ้าย สมาชิก "ส่วนเกิน" จะถูกละทิ้ง - -ตัวอย่างเช่น ที่นี่นำแค่สองรายการ ที่เหลือถูกละทิ้ง: +ถ้าอาร์เรย์ยาวกว่ารายการตัวแปรทางซ้าย สมาชิก "ส่วนเกิน" จะถูกละทิ้งโดยปริยาย ```js run let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; @@ -163,7 +160,7 @@ alert(name2); // Caesar // รายการที่เหลือไม่ได้ถูกกำหนดให้ที่ไหน ``` -ถ้าอยากเก็บทุกอย่างที่เหลือด้วย เราสามารถเพิ่มพารามิเตอร์อีกตัวที่รับ "ส่วนที่เหลือ" โดยใช้จุดสามจุด `"..."`: +อยากเก็บส่วนที่เหลือไว้ด้วยไหม? ใช้จุดสามจุด `"..."` เพิ่มตัวแปรที่รับ "ส่วนที่เหลือ" ได้เลย: ```js run let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; @@ -176,9 +173,9 @@ alert(rest.length); // 2 */!* ``` -ค่าของ `rest` คืออาร์เรย์ของสมาชิกที่เหลือ +`rest` จะได้ค่าเป็นอาร์เรย์ของสมาชิกที่เหลือทั้งหมด -เราตั้งชื่อตัวแปรอื่นแทน `rest` ได้ แค่ต้องมีจุดสามจุดอยู่ข้างหน้าและวางไว้เป็นตัวสุดท้ายใน destructuring assignment: +จะตั้งชื่ออื่นแทน `rest` ก็ได้ ขอแค่ใส่จุดสามจุดข้างหน้าและวางไว้เป็นตัวสุดท้ายเท่านั้น: ```js run let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; @@ -187,7 +184,7 @@ let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Ro ### Default values -ถ้าอาร์เรย์สั้นกว่ารายการตัวแปรทางซ้าย จะไม่เกิด error แต่ค่าที่ขาดหายไปจะเป็น undefined: +ถ้าอาร์เรย์สั้นกว่ารายการตัวแปรทางซ้าย จะไม่เกิด error แต่ตัวแปรที่ไม่มีค่าจะเป็น undefined: ```js run *!* @@ -198,7 +195,7 @@ alert(firstName); // undefined alert(surname); // undefined ``` -ถ้าต้องการค่า "เริ่มต้น" มาแทนที่ค่าที่ขาดหายไป สามารถระบุได้ด้วย `=`: +ถ้าต้องการค่า "เริ่มต้น" สำรองไว้แทน ระบุได้ด้วย `=`: ```js run *!* @@ -210,9 +207,9 @@ alert(name); // Julius (มาจากอาร์เรย์) alert(surname); // Anonymous (ใช้ค่าเริ่มต้น) ``` -ค่าเริ่มต้นอาจเป็นนิพจน์ที่ซับซ้อนหรือแม้แต่การเรียกฟังก์ชันก็ได้ และจะถูกประเมินผลเฉพาะเมื่อไม่มีค่าให้เท่านั้น +ค่าเริ่มต้นจะเป็นนิพจน์ที่ซับซ้อนหรือการเรียกฟังก์ชันก็ได้ โดยจะถูกประเมินผลก็ต่อเมื่อไม่มีค่ามาให้เท่านั้น -ตัวอย่างเช่น เราใช้ฟังก์ชัน `prompt` สำหรับค่าเริ่มต้นสองตัว: +เช่น ใช้ `prompt` เป็นค่าเริ่มต้น: ```js run // เรียก prompt เฉพาะสำหรับ surname เท่านั้น @@ -222,21 +219,21 @@ alert(name); // Julius (มาจากอาร์เรย์) alert(surname); // แล้วแต่ที่ป้อนใน prompt ``` -สังเกตว่า `prompt` จะทำงานเฉพาะกับค่าที่ขาดหาย (`surname`) เท่านั้น +`prompt` จะทำงานเฉพาะกับค่าที่หายไป (`surname`) เท่านั้น — `name` มีค่าอยู่แล้ว จึงไม่ถาม ## Object destructuring -destructuring assignment ใช้ได้กับออบเจ็กต์ด้วย +ออบเจ็กต์ก็ destructure ได้เช่นกัน -ไวยากรณ์พื้นฐานคือ: +ไวยากรณ์พื้นฐาน: ```js let {var1, var2} = {var1:…, var2:…} ``` -ทางขวามือเป็นออบเจ็กต์ที่มีอยู่แล้วที่เราต้องการแตกออกเป็นตัวแปร ส่วนทางซ้ายมือมี "รูปแบบ" ที่คล้ายออบเจ็กต์สำหรับพร็อพเพอร์ตี้ที่ต้องการ ในรูปแบบง่ายที่สุดก็คือรายชื่อตัวแปรใน `{...}` +ทางขวาคือออบเจ็กต์ที่ต้องการแตกออก ทางซ้ายคือ "รูปแบบ" ที่ระบุพร็อพเพอร์ตี้ที่ต้องการ ในรูปแบบง่ายที่สุดคือรายชื่อตัวแปรอยู่ใน `{...}` -ตัวอย่างเช่น: +ตัวอย่าง: ```js run let options = { @@ -254,18 +251,18 @@ alert(width); // 100 alert(height); // 200 ``` -พร็อพเพอร์ตี้ `options.title`, `options.width` และ `options.height` ถูกกำหนดให้กับตัวแปรที่ตรงกัน +พร็อพเพอร์ตี้ `options.title`, `options.width` และ `options.height` จะไปอยู่ในตัวแปรที่ชื่อตรงกัน -ลำดับไม่สำคัญ แบบนี้ก็ใช้ได้: +ลำดับไม่สำคัญ แบบนี้ก็ได้ผลเหมือนกัน: ```js // เปลี่ยนลำดับใน let {...} let {height, width, title} = { title: "Menu", height: 200, width: 100 } ``` -รูปแบบทางซ้ายมืออาจซับซ้อนกว่านี้ได้ โดยระบุการจับคู่ระหว่างพร็อพเพอร์ตี้กับตัวแปร +รูปแบบทางซ้ายซับซ้อนกว่านี้ได้ด้วย — ระบุการจับคู่ระหว่างพร็อพเพอร์ตี้กับตัวแปรได้เลย -ถ้าต้องการกำหนดพร็อพเพอร์ตี้ให้กับตัวแปรที่มีชื่อต่างกัน เช่น ให้ `options.width` ไปอยู่ในตัวแปรชื่อ `w` เราระบุชื่อตัวแปรได้ด้วยเครื่องหมายทวิภาค: +เช่น ถ้าอยากให้ `options.width` ไปที่ตัวแปรชื่อ `w` ใช้เครื่องหมายทวิภาคระบุชื่อตัวแปรได้: ```js run let options = { @@ -288,9 +285,9 @@ alert(w); // 100 alert(h); // 200 ``` -เครื่องหมายทวิภาคหมายถึง "อะไร : ไปที่ไหน" ในตัวอย่างด้านบน พร็อพเพอร์ตี้ `width` ไปที่ `w`, `height` ไปที่ `h` และ `title` ถูกกำหนดให้กับชื่อเดิม +เครื่องหมายทวิภาคอ่านว่า "อะไร : ไปที่ไหน" — `width` ไปที่ `w`, `height` ไปที่ `h`, `title` ใช้ชื่อเดิม -สำหรับพร็อพเพอร์ตี้ที่อาจไม่มีอยู่ เราตั้งค่าเริ่มต้นได้ด้วย `"="` แบบนี้: +พร็อพเพอร์ตี้ที่อาจไม่มีในออบเจ็กต์ก็กำหนดค่าเริ่มต้นด้วย `"="` ได้: ```js run let options = { @@ -306,9 +303,9 @@ alert(width); // 100 alert(height); // 200 ``` -เช่นเดียวกับอาร์เรย์หรือพารามิเตอร์ของฟังก์ชัน ค่าเริ่มต้นอาจเป็นนิพจน์หรือการเรียกฟังก์ชันก็ได้ และจะถูกประเมินผลเฉพาะเมื่อไม่มีค่าให้ +เช่นเดียวกับอาร์เรย์ ค่าเริ่มต้นจะเป็นนิพจน์หรือการเรียกฟังก์ชันก็ได้ และจะประเมินผลก็ต่อเมื่อไม่มีค่ามาให้เท่านั้น -ในโค้ดด้านล่าง `prompt` ถามค่าสำหรับ `width` แต่ไม่ถามสำหรับ `title`: +ในตัวอย่างด้านล่าง `prompt` จะถามเฉพาะ `width` ที่หายไป แต่ไม่ถาม `title` ที่มีค่าอยู่แล้ว: ```js run let options = { @@ -323,7 +320,7 @@ alert(title); // Menu alert(width); // (แล้วแต่ค่าที่ได้จาก prompt) ``` -เราใช้ทั้งเครื่องหมายทวิภาคและเครื่องหมายเท่ากับร่วมกันก็ได้: +ใช้ทั้งทวิภาคและเครื่องหมายเท่ากับร่วมกันได้ด้วย: ```js run let options = { @@ -339,7 +336,7 @@ alert(w); // 100 alert(h); // 200 ``` -ถ้ามีออบเจ็กต์ที่ซับซ้อนและมีหลายพร็อพเพอร์ตี้ เราดึงเฉพาะที่ต้องการมาได้: +ถ้าออบเจ็กต์มีพร็อพเพอร์ตี้เยอะ ก็ดึงแค่ที่ต้องการมาได้เลย: ```js run let options = { @@ -356,11 +353,9 @@ alert(title); // Menu ### The rest pattern "..." -แล้วถ้าออบเจ็กต์มีพร็อพเพอร์ตี้มากกว่าจำนวนตัวแปรที่เรามีล่ะ? เราเก็บบางส่วนแล้วกำหนด "ส่วนที่เหลือ" ไว้ที่ไหนได้ไหม? - -เราใช้ rest pattern ได้เหมือนกับที่ทำกับอาร์เรย์ ซึ่งบราวเซอร์เก่าบางตัว (IE) ไม่รองรับ แต่ใช้ Babel แก้ได้ บราวเซอร์สมัยใหม่ใช้ได้ทั้งนั้น +แล้วถ้าออบเจ็กต์มีพร็อพเพอร์ตี้มากกว่าตัวแปรที่ต้องการล่ะ? เก็บบางส่วนแล้วโยน "ส่วนที่เหลือ" ไว้ที่ไหนสักที่ได้ไหม? -ดูตัวอย่าง: +ได้เลย — ใช้ rest pattern เหมือนกับอาร์เรย์ บราวเซอร์สมัยใหม่ใช้ได้ทั้งนั้น (เก่ามากอย่าง IE อาจต้องใช้ Babel ช่วย) ```js run let options = { @@ -381,9 +376,8 @@ alert(rest.width); // 100 ``` ````smart header="ข้อควรระวังถ้าไม่มี `let`" -ในตัวอย่างด้านบน ตัวแปรถูกประกาศไว้ในการกำหนดค่า: `let {…} = {…}` แน่นอนว่าเราใช้ตัวแปรที่มีอยู่แล้วได้ โดยไม่ต้องมี `let` แต่มีข้อระวัง +ตัวอย่างข้างบนประกาศตัวแปรพร้อมกับ destructuring เลย (`let {…} = {…}`) แต่จะใช้ตัวแปรที่ประกาศไว้ก่อนหน้าก็ได้ — แค่ต้องระวังเรื่องนี้: -แบบนี้จะไม่ทำงาน: ```js run let title, width, height; @@ -391,7 +385,7 @@ let title, width, height; {title, width, height} = {title: "Menu", width: 200, height: 100}; ``` -ปัญหาคือ JavaScript ตีความ `{...}` ในโค้ดหลัก (ที่ไม่ได้อยู่ในนิพจน์อื่น) ว่าเป็น code block ซึ่งใช้จัดกลุ่มคำสั่งได้แบบนี้: +ปัญหาคือ JavaScript มองว่า `{...}` ที่ขึ้นต้นบรรทัดคือ code block ไม่ใช่ออบเจ็กต์ เช่นเดียวกับที่เราใช้ block จัดกลุ่มคำสั่งแบบนี้: ```js run { @@ -402,9 +396,7 @@ let title, width, height; } ``` -ดังนั้น JavaScript จึงสันนิษฐานว่าเป็น code block และเกิด error ทั้งๆ ที่เราต้องการ destructuring - -เพื่อบอก JavaScript ว่าไม่ใช่ code block เราครอบนิพจน์ด้วยวงเล็บ `(...)`: +แก้ได้ง่ายๆ ครอบด้วยวงเล็บ `(...)` เพื่อบอก JavaScript ว่านี่คือนิพจน์ ไม่ใช่ block: ```js run let title, width, height; @@ -418,9 +410,9 @@ alert( title ); // Menu ## Nested destructuring -ถ้าออบเจ็กต์หรืออาร์เรย์มีออบเจ็กต์หรืออาร์เรย์ซ้อนกันอยู่ข้างใน เราใช้รูปแบบทางซ้ายมือที่ซับซ้อนขึ้นเพื่อดึงข้อมูลจากส่วนที่ลึกกว่าได้ +ถ้าออบเจ็กต์หรืออาร์เรย์ซ้อนกันอยู่ข้างใน รูปแบบทางซ้ายก็ซ้อนตามได้เช่นกัน เพื่อดึงข้อมูลจากส่วนที่ลึกกว่า -ในโค้ดด้านล่าง `options` มีออบเจ็กต์อีกตัวอยู่ใน `size` และมีอาร์เรย์ใน `items` รูปแบบทางซ้ายมือของการกำหนดค่ามีโครงสร้างเดียวกันเพื่อดึงค่าออกมา: +ในตัวอย่างด้านล่าง `options` มีออบเจ็กต์ซ้อนอยู่ใน `size` และอาร์เรย์ใน `items` รูปแบบทางซ้ายมีโครงสร้างตรงกันเพื่อดึงค่าออกมา: ```js run let options = { @@ -449,19 +441,19 @@ alert(item1); // Cake alert(item2); // Donut ``` -พร็อพเพอร์ตี้ทั้งหมดของออบเจ็กต์ `options` ยกเว้น `extra` ที่ไม่อยู่ทางซ้ายมือ ถูกกำหนดให้กับตัวแปรที่ตรงกัน: +พร็อพเพอร์ตี้ทั้งหมดของ `options` ถูกดึงออกมา ยกเว้น `extra` ที่ไม่ได้ระบุทางซ้าย: ![](destructuring-complex.svg) -สุดท้ายเราได้ตัวแปร `width`, `height`, `item1`, `item2` และ `title` จากค่าเริ่มต้น +ผลลัพธ์คือได้ตัวแปร `width`, `height`, `item1`, `item2` และ `title` (ซึ่งใช้ค่าเริ่มต้น) -สังเกตว่าไม่มีตัวแปรสำหรับ `size` และ `items` เพราะเราดึงเนื้อหาข้างในออกมาแทน +สังเกตว่าไม่มีตัวแปรสำหรับ `size` และ `items` — เราดึงเฉพาะเนื้อหาข้างในออกมานั่นเอง ## Smart function parameters -มีบางครั้งที่ฟังก์ชันมีพารามิเตอร์จำนวนมาก ส่วนใหญ่เป็นพารามิเตอร์ optional ซึ่งพบบ่อยในส่วน user interface ลองนึกภาพฟังก์ชันที่สร้างเมนู อาจมีความกว้าง ความสูง หัวข้อ รายการ และอื่นๆ +ฟังก์ชัน user interface มักมีพารามิเตอร์เยอะมาก และส่วนใหญ่เป็น optional ทั้งนั้น ลองนึกภาพฟังก์ชันสร้างเมนูที่รับ ความกว้าง ความสูง หัวข้อ รายการ และอื่นๆ อีกมากมาย -วิธีเขียนฟังก์ชันแบบนี้ที่ไม่ดีคือ: +วิธีเขียนแบบนี้ไม่ค่อยดีนัก: ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -469,20 +461,18 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` -ปัญหาในทางปฏิบัติคือต้องจำลำดับของอาร์กิวเมนต์ ปกติ IDE จะช่วยเราได้ โดยเฉพาะถ้าโค้ดมี documentation ที่ดี แต่ก็ยังยุ่งยาก อีกปัญหาคือจะเรียกฟังก์ชันยังไงเมื่อพารามิเตอร์ส่วนใหญ่ใช้ค่าเริ่มต้นได้ +ปัญหาคือต้องจำลำดับอาร์กิวเมนต์ แม้ IDE จะช่วยบอกได้บ้าง แต่ก็ยังน่าปวดหัว แล้วถ้าอยากเรียกโดยใช้แค่ค่าเริ่มต้นส่วนใหญ่ต้องเขียนยังไง? -แบบนี้เหรอ? +ก็แบบนี้: ```js // undefined ตรงที่ค่าเริ่มต้นใช้ได้ showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) ``` -ดูน่าเกลียดมาก และยิ่งอ่านไม่รู้เรื่องเลยถ้ามีพารามิเตอร์มากขึ้น - -destructuring มาช่วยได้! +น่าเกลียดมาก และยิ่งอ่านไม่รู้เรื่องถ้าพารามิเตอร์เพิ่มขึ้นอีก -เราส่งพารามิเตอร์เป็นออบเจ็กต์ แล้วให้ฟังก์ชัน destructure เป็นตัวแปรทันที: +destructuring ช่วยได้พอดี! ส่งพารามิเตอร์เป็นออบเจ็กต์ แล้วให้ฟังก์ชัน destructure เป็นตัวแปรตรงนั้นเลย: ```js run // ส่งออบเจ็กต์ไปให้ฟังก์ชัน @@ -491,7 +481,7 @@ let options = { items: ["Item1", "Item2"] }; -// ...แล้วมันแตกออกเป็นตัวแปรทันที +// ...แล้วแตกออกเป็นตัวแปรตรงในพารามิเตอร์เลย function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []}*/!*) { // title, items – มาจาก options // width, height – ใช้ค่าเริ่มต้น @@ -502,7 +492,7 @@ function showMenu(*!*{title = "Untitled", width = 200, height = 100, items = []} showMenu(options); ``` -เราใช้ destructuring ที่ซับซ้อนกว่านี้ได้ด้วย ทั้งออบเจ็กต์ซ้อนและการแมปด้วยทวิภาค: +ใช้ destructuring ที่ซับซ้อนขึ้นได้อีก ทั้งออบเจ็กต์ซ้อนและการแมปชื่อด้วยทวิภาค: ```js run let options = { @@ -526,7 +516,7 @@ function showMenu({ showMenu(options); ``` -ไวยากรณ์แบบเต็มเหมือนกับ destructuring assignment: +ไวยากรณ์แบบเต็มเหมือนกับ destructuring assignment ทั่วไป: ```js function({ incomingProperty: varName = defaultValue @@ -534,9 +524,9 @@ function({ }) ``` -สำหรับออบเจ็กต์ของพารามิเตอร์ จะมีตัวแปร `varName` สำหรับพร็อพเพอร์ตี้ `incomingProperty` โดยมีค่าเริ่มต้นเป็น `defaultValue` +พร็อพเพอร์ตี้ `incomingProperty` จะไปอยู่ในตัวแปร `varName` โดยมีค่าเริ่มต้นเป็น `defaultValue` -พึงระวังว่า destructuring แบบนี้สันนิษฐานว่า `showMenu()` ต้องได้รับอาร์กิวเมนต์ ถ้าต้องการให้ทุกค่าใช้ค่าเริ่มต้น ต้องระบุออบเจ็กต์ว่าง: +อย่างไรก็ตาม ต้องระวังอยู่จุดหนึ่ง — destructuring แบบนี้สันนิษฐานว่า `showMenu()` ต้องได้รับอาร์กิวเมนต์เสมอ ถ้าอยากเรียกโดยไม่ส่งอะไรเลย ต้องส่งออบเจ็กต์ว่าง: ```js showMenu({}); // โอเค ทุกค่าใช้ค่าเริ่มต้น @@ -544,7 +534,7 @@ showMenu({}); // โอเค ทุกค่าใช้ค่าเริ่ showMenu(); // แบบนี้จะ error ``` -แก้ได้โดยกำหนดให้ `{}` เป็นค่าเริ่มต้นของออบเจ็กต์พารามิเตอร์ทั้งหมด: +แก้ได้ง่ายๆ กำหนดให้ `{}` เป็นค่าเริ่มต้นของพารามิเตอร์ออบเจ็กต์ทั้งหมด: ```js run function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { @@ -554,19 +544,17 @@ function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { showMenu(); // Menu 100 200 ``` -ในโค้ดด้านบน ออบเจ็กต์อาร์กิวเมนต์ทั้งหมดจะเป็น `{}` โดยค่าเริ่มต้น จึงมีของให้ destructure เสมอ +ด้วยวิธีนี้ ถ้าไม่ส่งอาร์กิวเมนต์มา ค่าเริ่มต้นจะเป็น `{}` และ destructure ออกมาได้เสมอโดยไม่ error ## Summary -- Destructuring assignment ช่วยให้แมปออบเจ็กต์หรืออาร์เรย์ไปยังตัวแปรหลายตัวได้ทันที +- Destructuring assignment ช่วยแตกออบเจ็กต์หรืออาร์เรย์ออกเป็นตัวแปรหลายตัวในคราวเดียว - ไวยากรณ์แบบเต็มสำหรับออบเจ็กต์: ```js let {prop : varName = defaultValue, ...rest} = object ``` - หมายความว่าพร็อพเพอร์ตี้ `prop` จะไปที่ตัวแปร `varName` และถ้าไม่มีพร็อพเพอร์ตี้นั้น จะใช้ค่า `default` - - พร็อพเพอร์ตี้ที่ไม่มีการแมปจะถูกคัดลอกไปยังออบเจ็กต์ `rest` + พร็อพเพอร์ตี้ `prop` จะไปที่ตัวแปร `varName` ถ้าไม่มีพร็อพเพอร์ตี้นั้นจะใช้ `defaultValue` แทน ส่วนพร็อพเพอร์ตี้ที่ไม่ได้ระบุจะถูกรวบไว้ในออบเจ็กต์ `rest` - ไวยากรณ์แบบเต็มสำหรับอาร์เรย์: @@ -574,6 +562,6 @@ showMenu(); // Menu 100 200 let [item1 = defaultValue, item2, ...rest] = array ``` - สมาชิกแรกไปที่ `item1` ตัวที่สองไปที่ `item2` ที่เหลือทั้งหมดสร้างเป็นอาร์เรย์ `rest` + สมาชิกตัวแรกไปที่ `item1` ตัวที่สองไปที่ `item2` ส่วนที่เหลือทั้งหมดรวบเป็นอาร์เรย์ `rest` -- ดึงข้อมูลจากอาร์เรย์/ออบเจ็กต์ที่ซ้อนกันได้ โดยรูปแบบทางซ้ายมือต้องมีโครงสร้างเดียวกับทางขวามือ +- ดึงข้อมูลจากอาร์เรย์/ออบเจ็กต์ที่ซ้อนกันได้ โดยรูปแบบทางซ้ายต้องมีโครงสร้างตรงกับทางขวา diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index 64f6fa8d2..1df0f6d52 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -1,12 +1,12 @@ # วันที่และเวลา -มาทำความรู้จักกับออบเจ็กต์ built-in ตัวใหม่: [Date](mdn:js/Date) ออบเจ็กต์นี้จัดเก็บวันที่และเวลา และมีเมธอดสำหรับจัดการข้อมูลวันที่/เวลา +มาทำความรู้จักกับออบเจ็กต์ built-in ตัวใหม่: [Date](mdn:js/Date) ออบเจ็กต์นี้เก็บวันที่และเวลา พร้อมมีเมธอดสำหรับจัดการข้อมูลเหล่านั้น -ตัวอย่างเช่น เราสามารถใช้มันเพื่อบันทึกเวลาที่สร้างหรือแก้ไขไฟล์ วัดระยะเวลา หรือแค่แสดงวันที่ปัจจุบันก็ได้ +ใช้งานได้หลายอย่าง เช่น บันทึกเวลาที่สร้างหรือแก้ไขไฟล์ วัดระยะเวลา หรือแค่แสดงวันที่ปัจจุบัน ## การสร้าง -ในการสร้างออบเจ็กต์ `Date` ใหม่ ให้เรียก `new Date()` พร้อมอาร์กิวเมนต์ในรูปแบบใดรูปแบบหนึ่งดังนี้: +สร้างออบเจ็กต์ `Date` ด้วย `new Date()` โดยส่งอาร์กิวเมนต์ในรูปแบบใดรูปแบบหนึ่งดังนี้: `new Date()` : ไม่มีอาร์กิวเมนต์ -- สร้างออบเจ็กต์ `Date` สำหรับวันที่และเวลาปัจจุบัน: @@ -29,9 +29,9 @@ alert( Jan02_1970 ); ``` - ตัวเลขจำนวนเต็มที่แสดงจำนวนมิลลิวินาทีที่ผ่านมาตั้งแต่ต้นปี 1970 เรียกว่า *timestamp* + ตัวเลขจำนวนเต็มที่แทนจำนวนมิลลิวินาทีที่ผ่านมาตั้งแต่ต้นปี 1970 เรียกว่า *timestamp* - มันเป็นการแทนค่าวันที่ในรูปแบบตัวเลขที่เบาและกะทัดรัด เราสามารถสร้างวันที่จาก timestamp ได้ตลอดเวลาด้วย `new Date(timestamp)` และแปลงออบเจ็กต์ `Date` เป็น timestamp ด้วยเมธอด `date.getTime()` (ดูรายละเอียดด้านล่าง) + เป็นการแทนค่าวันที่แบบตัวเลขที่เบาและกะทัดรัด สร้างวันที่จาก timestamp ได้ตลอดเวลาด้วย `new Date(timestamp)` และแปลงออบเจ็กต์ `Date` เป็น timestamp ได้ด้วยเมธอด `date.getTime()` (ดูรายละเอียดด้านล่าง) วันที่ก่อน 01.01.1970 จะมี timestamp เป็นค่าลบ เช่น: ```js run @@ -41,7 +41,7 @@ ``` `new Date(datestring)` -: ถ้ามีอาร์กิวเมนต์เพียงตัวเดียวและเป็นสตริง จะถูก parse อัตโนมัติ โดยใช้อัลกอริทึมเดียวกับที่ `Date.parse` ใช้ ซึ่งจะกล่าวถึงในภายหลัง +: ถ้ามีอาร์กิวเมนต์เพียงตัวเดียวและเป็นสตริง จะถูก parse อัตโนมัติ โดยใช้อัลกอริทึมเดียวกับที่ `Date.parse` ใช้ ซึ่งจะพูดถึงในภายหลัง ```js run let date = new Date("2017-01-26"); @@ -78,7 +78,7 @@ ## การเข้าถึงส่วนประกอบของวันที่ -มีเมธอดสำหรับดึงค่าปี เดือน และข้อมูลอื่นๆ จากออบเจ็กต์ `Date`: +เมธอดที่ใช้ดึงค่าปี เดือน และส่วนต่างๆ จากออบเจ็กต์ `Date` มีดังนี้: [getFullYear()](mdn:js/Date/getFullYear) : ดึงปี (4 หลัก) @@ -90,7 +90,7 @@ : ดึงวันที่ในเดือน ตั้งแต่ 1 ถึง 31 ชื่อเมธอดนี้ดูแปลกๆ นิดหน่อย [getHours()](mdn:js/Date/getHours), [getMinutes()](mdn:js/Date/getMinutes), [getSeconds()](mdn:js/Date/getSeconds), [getMilliseconds()](mdn:js/Date/getMilliseconds) -: ดึงค่าส่วนประกอบของเวลาที่สอดคล้องกัน +: ดึงค่าส่วนประกอบเวลาตามลำดับ ```warn header="ไม่ใช่ `getYear()` แต่ใช้ `getFullYear()`" JavaScript engine หลายตัวมีเมธอดที่ไม่ได้มาตรฐานชื่อ `getYear()` เมธอดนี้ถูก deprecated แล้วและบางครั้งคืนค่าปีแค่ 2 หลัก ขอให้หลีกเลี่ยงอย่างเด็ดขาด ให้ใช้ `getFullYear()` แทน @@ -99,13 +99,13 @@ JavaScript engine หลายตัวมีเมธอดที่ไม่ นอกจากนี้ยังดึงวันในสัปดาห์ได้: [getDay()](mdn:js/Date/getDay) -: ดึงวันในสัปดาห์ ตั้งแต่ `0` (อาทิตย์) ถึง `6` (เสาร์) วันแรกเป็นวันอาทิตย์เสมอ บางประเทศอาจไม่ใช่แบบนี้ แต่ก็ไม่สามารถเปลี่ยนแปลงได้ +: ดึงวันในสัปดาห์ ตั้งแต่ `0` (อาทิตย์) ถึง `6` (เสาร์) วันแรกเป็นวันอาทิตย์เสมอ บางประเทศอาจไม่ใช่แบบนี้ แต่ก็ไม่สามารถเปลี่ยนได้ -**เมธอดทั้งหมดข้างต้นคืนค่าส่วนประกอบตามเขตเวลาท้องถิ่น** +**เมธอดทั้งหมดข้างต้นคืนค่าตามเขตเวลาท้องถิ่น** -ยังมีเวอร์ชัน UTC สำหรับแต่ละเมธอด ซึ่งคืนค่าวัน เดือน ปีและอื่นๆ สำหรับเขตเวลา UTC+0: [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay) แค่เพิ่ม `"UTC"` ต่อจาก `"get"` ก็พอ +แต่ละเมธอดมีเวอร์ชัน UTC ด้วย ซึ่งคืนค่าวัน เดือน ปี ฯลฯ สำหรับเขตเวลา UTC+0 ได้แก่ [getUTCFullYear()](mdn:js/Date/getUTCFullYear), [getUTCMonth()](mdn:js/Date/getUTCMonth), [getUTCDay()](mdn:js/Date/getUTCDay) แค่เพิ่ม `"UTC"` ต่อจาก `"get"` เท่านั้น -ถ้าเขตเวลาท้องถิ่นของคุณต่างจาก UTC โค้ดด้านล่างจะแสดงชั่วโมงที่ต่างกัน: +ถ้าเขตเวลาท้องถิ่นต่างจาก UTC โค้ดด้านล่างจะแสดงชั่วโมงที่ต่างกัน: ```js run // วันที่ปัจจุบัน @@ -118,7 +118,7 @@ alert( date.getHours() ); alert( date.getUTCHours() ); ``` -นอกจากเมธอดที่กล่าวมา ยังมีอีกสองเมธอดพิเศษที่ไม่มีเวอร์ชัน UTC: +ยังมีอีกสองเมธอดพิเศษที่ไม่มีเวอร์ชัน UTC: [getTime()](mdn:js/Date/getTime) : คืนค่า timestamp ของวันที่นั้น -- จำนวนมิลลิวินาทีที่ผ่านมาตั้งแต่วันที่ 1 มกราคม 1970 UTC+0 @@ -135,7 +135,7 @@ alert( date.getUTCHours() ); ## การกำหนดค่าส่วนประกอบของวันที่ -เมธอดต่อไปนี้ใช้สำหรับกำหนดค่าส่วนประกอบของวันที่/เวลา: +เมธอดเหล่านี้ใช้กำหนดค่าส่วนประกอบวันที่/เวลา: - [`setFullYear(year, [month], [date])`](mdn:js/Date/setFullYear) - [`setMonth(month, [date])`](mdn:js/Date/setMonth) @@ -148,7 +148,7 @@ alert( date.getUTCHours() ); ทุกเมธอดยกเว้น `setTime()` มีเวอร์ชัน UTC เช่น `setUTCHours()` -จะเห็นว่าบางเมธอดสามารถกำหนดหลายค่าพร้อมกันได้ เช่น `setHours` ส่วนค่าที่ไม่ได้ระบุจะไม่ถูกเปลี่ยน +จะเห็นว่าบางเมธอดกำหนดหลายค่าพร้อมกันได้ เช่น `setHours` ส่วนค่าที่ไม่ได้ระบุจะไม่ถูกเปลี่ยน ตัวอย่าง: @@ -164,7 +164,7 @@ alert(today); // ยังเป็นวันนี้อยู่ แต่ ## การแก้ไขค่าอัตโนมัติ -*การแก้ไขค่าอัตโนมัติ (autocorrection)* เป็นฟีเจอร์ที่มีประโยชน์มากของออบเจ็กต์ `Date` เราสามารถกำหนดค่าที่เกินขอบเขตได้ และมันจะปรับค่าให้ถูกต้องเอง +*การแก้ไขค่าอัตโนมัติ (autocorrection)* เป็นฟีเจอร์ที่มีประโยชน์มากของออบเจ็กต์ `Date` กำหนดค่าที่เกินขอบเขตได้เลย แล้วมันจะปรับให้ถูกต้องเอง ตัวอย่าง: @@ -175,7 +175,7 @@ alert(date); // ...กลายเป็น 1 ก.พ. 2013! ส่วนประกอบที่เกินขอบเขตจะถูกกระจายออกไปอัตโนมัติ -สมมติว่าต้องการเพิ่ม 2 วันให้กับ "28 ก.พ. 2016" ผลลัพธ์อาจเป็น "2 มี.ค." หรือ "1 มี.ค." ถ้าปีนั้นเป็นปีอธิกสุรทิน เราไม่ต้องคิดเรื่องนี้เลย แค่บวก 2 วัน ออบเจ็กต์ `Date` จัดการให้เอง: +สมมติต้องการเพิ่ม 2 วันให้กับ "28 ก.พ. 2016" ผลลัพธ์อาจเป็น "2 มี.ค." หรือ "1 มี.ค." ถ้าปีนั้นเป็นปีอธิกสุรทิน ไม่ต้องคิดเรื่องนี้เลย แค่บวก 2 วัน ออบเจ็กต์ `Date` จัดการให้เอง: ```js run let date = new Date(2016, 1, 28); @@ -195,7 +195,7 @@ date.setSeconds(date.getSeconds() + 70); alert( date ); // แสดงวันที่ที่ถูกต้อง ``` -นอกจากนี้ยังกำหนดค่าศูนย์หรือค่าลบได้ด้วย เช่น: +กำหนดค่าศูนย์หรือค่าลบก็ได้เช่นกัน: ```js run let date = new Date(2016, 0, 2); // 2 ม.ค. 2016 @@ -209,16 +209,16 @@ alert( date ); // 31 ธ.ค. 2015 ## วันที่เป็นตัวเลข และการลบวันที่ -เมื่อแปลงออบเจ็กต์ `Date` เป็นตัวเลข จะได้ค่า timestamp เหมือนกับ `date.getTime()`: +แปลงออบเจ็กต์ `Date` เป็นตัวเลข จะได้ค่า timestamp เหมือนกับ `date.getTime()`: ```js run let date = new Date(); alert(+date); // จำนวนมิลลิวินาที เหมือนกับ date.getTime() ``` -ผลข้างเคียงที่สำคัญคือ วันที่สามารถนำมาลบกันได้ และได้ผลลัพธ์เป็นความต่างในหน่วย ms +ผลที่น่าสนใจคือ วันที่นำมาลบกันได้ โดยได้ผลต่างในหน่วย ms นั่นเอง -ใช้ประโยชน์ได้ในการวัดเวลา: +เอาไปวัดเวลาได้เลย: ```js run let start = new Date(); // เริ่มจับเวลา @@ -239,9 +239,9 @@ alert( `ลูปใช้เวลา ${end - start} ms` ); มีเมธอดพิเศษ `Date.now()` ที่คืนค่า timestamp ปัจจุบัน -ความหมายเทียบเท่ากับ `new Date().getTime()` แต่ไม่สร้างออบเจ็กต์ `Date` ชั่วคราว จึงเร็วกว่าและไม่กดดันการเก็บขยะ (garbage collection) +ความหมายเหมือนกับ `new Date().getTime()` แต่ไม่สร้างออบเจ็กต์ `Date` ชั่วคราว จึงเร็วกว่าและไม่กดดัน garbage collection -มักใช้เพื่อความสะดวกหรือเมื่อประสิทธิภาพสำคัญ เช่นในเกม JavaScript หรือแอปพลิเคชันเฉพาะทาง +ใช้บ่อยเมื่อต้องการความสะดวก หรือเมื่อประสิทธิภาพสำคัญ เช่น ในเกม JavaScript หรือแอปพลิเคชันเฉพาะทาง วิธีนี้จึงน่าจะดีกว่า: @@ -264,11 +264,9 @@ alert( `ลูปใช้เวลา ${end - start} ms` ); // ลบตัว ## Benchmarking -ถ้าต้องการทำ benchmark ที่เชื่อถือได้สำหรับฟังก์ชันที่ใช้ CPU มาก ต้องระมัดระวัง +ถ้าต้องการ benchmark ฟังก์ชันที่ใช้ CPU หนัก ต้องระมัดระวังหน่อย -ลองวัดสองฟังก์ชันที่คำนวณความต่างระหว่างวันที่สองวัน: ฟังก์ชันไหนเร็วกว่า? - -การวัดประสิทธิภาพแบบนี้มักเรียกว่า "benchmark" +ลองวัดสองฟังก์ชันที่คำนวณความต่างระหว่างวันที่สองวัน ฟังก์ชันไหนเร็วกว่า? การวัดแบบนี้เรียกว่า "benchmark" นั่นเอง ```js // มี date1 และ date2 ฟังก์ชันไหนคืนค่าความต่างในหน่วย ms ได้เร็วกว่า? @@ -282,11 +280,11 @@ function diffGetTime(date1, date2) { } ``` -ทั้งสองทำสิ่งเดียวกันทุกประการ แต่ฟังก์ชันหนึ่งใช้ `date.getTime()` อย่างชัดเจนเพื่อดึงค่าวันที่เป็น ms ส่วนอีกฟังก์ชันอาศัยการแปลงวันที่เป็นตัวเลข ผลลัพธ์เหมือนกันเสมอ +ทั้งสองทำสิ่งเดียวกันทุกประการ ฟังก์ชันหนึ่งใช้ `date.getTime()` อย่างชัดเจน อีกฟังก์ชันอาศัยการแปลงวันที่เป็นตัวเลขแทน ผลลัพธ์เหมือนกันเสมอ แล้วฟังก์ชันไหนเร็วกว่ากัน? -ความคิดแรกอาจเป็นการรันซ้ำหลายๆ ครั้งติดกันแล้ววัดความต่างของเวลา สำหรับกรณีนี้ฟังก์ชันเรียบง่ายมาก จึงต้องรันอย่างน้อย 100,000 ครั้ง +ความคิดแรกคือรันซ้ำหลายๆ ครั้งติดกันแล้ววัดเวลา ฟังก์ชันเรียบง่ายมาก จึงต้องรันอย่างน้อย 100,000 ครั้ง มาวัดกัน: @@ -312,17 +310,15 @@ alert( 'เวลาของ diffSubtract: ' + bench(diffSubtract) + 'ms' ); alert( 'เวลาของ diffGetTime: ' + bench(diffGetTime) + 'ms' ); ``` -ว้าว! การใช้ `getTime()` เร็วกว่ามากเลย! เพราะไม่มีการแปลงประเภท engine จึง optimize ได้ง่ายกว่า - -โอเค เราได้ข้อมูลมาแล้ว แต่นี่ยังไม่ใช่ benchmark ที่ดี +ว้าว! `getTime()` เร็วกว่ามากเลย! เพราะไม่มีการแปลงประเภท engine จึง optimize ได้ง่ายกว่า -ลองนึกภาพว่าตอนที่รัน `bench(diffSubtract)` CPU กำลังทำงานอื่นอยู่พร้อมกันและกินทรัพยากรไป พอถึงเวลารัน `bench(diffGetTime)` งานนั้นเสร็จแล้ว +โอเค เราได้ข้อมูลแล้ว แต่นี่ยังไม่ใช่ benchmark ที่ดีนัก -เป็นสถานการณ์จริงที่พบได้บ่อยในระบบปฏิบัติการแบบ multi-process สมัยใหม่ +ลองนึกภาพว่าตอนที่รัน `bench(diffSubtract)` CPU กำลังทำงานอื่นพร้อมกันและกินทรัพยากรไป แล้วพอถึงเวลารัน `bench(diffGetTime)` งานนั้นเสร็จพอดี สถานการณ์แบบนี้พบได้บ่อยในระบบปฏิบัติการแบบ multi-process สมัยใหม่ -ผลก็คือ benchmark แรกจะได้ทรัพยากร CPU น้อยกว่า benchmark ที่สอง ซึ่งอาจนำไปสู่ผลลัพธ์ที่ผิดพลาดได้ +ผลก็คือ benchmark แรกได้ทรัพยากร CPU น้อยกว่า benchmark ที่สอง ซึ่งอาจทำให้ผลลัพธ์คลาดเคลื่อนได้ -**เพื่อให้ benchmark น่าเชื่อถือกว่า ควรรันชุด benchmark ทั้งหมดหลายๆ รอบ** +**เพื่อให้ benchmark น่าเชื่อถือ ควรรันชุด benchmark ทั้งหมดสลับกันหลายๆ รอบ** ตัวอย่าง: @@ -359,10 +355,10 @@ alert( 'เวลารวมของ diffSubtract: ' + time1 ); alert( 'เวลารวมของ diffGetTime: ' + time2 ); ``` -JavaScript engine สมัยใหม่จะเริ่มใช้การ optimize ขั้นสูงเฉพาะกับ "hot code" ที่ทำงานซ้ำหลายครั้ง (ไม่จำเป็นต้อง optimize สิ่งที่รันไม่บ่อย) ดังนั้นในตัวอย่างข้างต้น การรันครั้งแรกๆ ยังไม่ได้รับการ optimize เราอาจต้องการเพิ่มการ "อุ่นเครื่อง" ก่อน: +JavaScript engine สมัยใหม่จะเริ่ม optimize ขั้นสูงเฉพาะกับ "hot code" ที่รันซ้ำหลายครั้ง ดังนั้นในตัวอย่างข้างต้น การรันครั้งแรกๆ ยังไม่ได้รับการ optimize จึงควรเพิ่มการ "อุ่นเครื่อง" ก่อน: ```js -// เพิ่มเพื่อ "อุ่นเครื่อง" ก่อนเข้าลูปหลัก +// อุ่นเครื่องก่อนเข้าลูปหลัก bench(diffSubtract); bench(diffGetTime); @@ -374,14 +370,14 @@ for (let i = 0; i < 10; i++) { ``` ```warn header="ระวังการทำ microbenchmarking" -JavaScript engine สมัยใหม่ทำการ optimize หลายอย่าง ซึ่งอาจบิดเบือนผลของ "การทดสอบเทียม" เมื่อเทียบกับ "การใช้งานจริง" โดยเฉพาะเมื่อ benchmark สิ่งที่เล็กมาก เช่น วิธีที่ตัวดำเนินการทำงาน หรือฟังก์ชัน built-in ดังนั้นถ้าต้องการเข้าใจประสิทธิภาพอย่างจริงจัง โปรดศึกษาว่า JavaScript engine ทำงานอย่างไร แล้วจะพบว่าแทบไม่จำเป็นต้องใช้ microbenchmark เลย +JavaScript engine สมัยใหม่ทำการ optimize หลายอย่าง ซึ่งอาจบิดเบือนผลของ "การทดสอบเทียม" เมื่อเทียบกับ "การใช้งานจริง" โดยเฉพาะเมื่อ benchmark สิ่งที่เล็กมาก เช่น วิธีที่ตัวดำเนินการทำงาน หรือฟังก์ชัน built-in ดังนั้นถ้าต้องการเข้าใจประสิทธิภาพอย่างจริงจัง ให้ศึกษาว่า JavaScript engine ทำงานอย่างไร แล้วจะพบว่าแทบไม่จำเป็นต้องใช้ microbenchmark เลย -บทความดีๆ เกี่ยวกับ V8 สามารถหาได้ที่ +บทความดีๆ เกี่ยวกับ V8 หาได้ที่ ``` ## Date.parse จากสตริง -เมธอด [Date.parse(str)](mdn:js/Date/parse) สามารถอ่านวันที่จากสตริงได้ +เมธอด [Date.parse(str)](mdn:js/Date/parse) อ่านวันที่จากสตริงได้ รูปแบบสตริงควรเป็น: `YYYY-MM-DDTHH:mm:ss.sssZ` โดยที่: @@ -390,9 +386,9 @@ JavaScript engine สมัยใหม่ทำการ optimize หลาย - `HH:mm:ss.sss` -- คือเวลา: ชั่วโมง นาที วินาที และมิลลิวินาที - ส่วน `'Z'` ที่เป็น optional ระบุเขตเวลาในรูปแบบ `+-hh:mm` ถ้าเป็นตัวอักษร `Z` เพียงตัวเดียว หมายถึง UTC+0 -รูปแบบย่อก็ได้เช่นกัน เช่น `YYYY-MM-DD` หรือ `YYYY-MM` หรือแม้แต่ `YYYY` +ใช้รูปแบบย่อก็ได้ เช่น `YYYY-MM-DD` หรือ `YYYY-MM` หรือแม้แต่ `YYYY` -การเรียก `Date.parse(str)` จะ parse สตริงตามรูปแบบที่กำหนดและคืนค่า timestamp (จำนวนมิลลิวินาทีตั้งแต่ 1 ม.ค. 1970 UTC+0) ถ้ารูปแบบไม่ถูกต้อง จะคืนค่า `NaN` +`Date.parse(str)` จะ parse สตริงตามรูปแบบที่กำหนดแล้วคืนค่า timestamp (จำนวนมิลลิวินาทีตั้งแต่ 1 ม.ค. 1970 UTC+0) ถ้ารูปแบบไม่ถูกต้อง จะคืนค่า `NaN` ตัวอย่าง: @@ -402,7 +398,7 @@ let ms = Date.parse('2012-01-26T13:51:50.417-07:00'); alert(ms); // 1327611110417 (timestamp) ``` -เราสามารถสร้างออบเจ็กต์ `new Date` จาก timestamp ได้ทันที: +สร้างออบเจ็กต์ `new Date` จาก timestamp ได้ทันที: ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); @@ -412,16 +408,16 @@ alert(date); ## สรุป -- วันที่และเวลาใน JavaScript ใช้ออบเจ็กต์ [Date](mdn:js/Date) เราไม่สามารถสร้าง "แค่วันที่" หรือ "แค่เวลา" ได้: ออบเจ็กต์ `Date` มีทั้งสองเสมอ -- เดือนนับจากศูนย์ (ใช่ มกราคมคือเดือนที่ศูนย์) -- วันในสัปดาห์ใน `getDay()` ก็นับจากศูนย์เช่นกัน (นั่นคือวันอาทิตย์) -- `Date` จะแก้ไขค่าตัวเองอัตโนมัติเมื่อกำหนดค่าที่เกินขอบเขต สะดวกสำหรับการบวก/ลบวัน/เดือน/ชั่วโมง -- วันที่สามารถลบกันได้ ได้ผลต่างในหน่วยมิลลิวินาที เพราะออบเจ็กต์ `Date` กลายเป็น timestamp เมื่อแปลงเป็นตัวเลข -- ใช้ `Date.now()` เพื่อดึง timestamp ปัจจุบันได้อย่างรวดเร็ว +- วันที่และเวลาใน JavaScript ใช้ออบเจ็กต์ [Date](mdn:js/Date) ไม่สามารถสร้าง "แค่วันที่" หรือ "แค่เวลา" แยกกันได้ ออบเจ็กต์ `Date` มีทั้งสองเสมอ +- เดือนนับจากศูนย์ (ใช่แล้ว มกราคมคือเดือนที่ศูนย์) +- วันในสัปดาห์ใน `getDay()` ก็นับจากศูนย์เช่นกัน นั่นคือวันอาทิตย์ +- `Date` แก้ไขค่าตัวเองอัตโนมัติเมื่อกำหนดค่าเกินขอบเขต สะดวกสำหรับการบวก/ลบวัน/เดือน/ชั่วโมง +- วันที่ลบกันได้ ได้ผลต่างในหน่วยมิลลิวินาที เพราะออบเจ็กต์ `Date` กลายเป็น timestamp เมื่อแปลงเป็นตัวเลข +- ใช้ `Date.now()` เพื่อดึง timestamp ปัจจุบันได้รวดเร็ว -โปรดทราบว่าต่างจากระบบอื่นๆ หลายระบบ timestamp ใน JavaScript มีหน่วยเป็นมิลลิวินาที ไม่ใช่วินาที +น่าสังเกตว่าต่างจากระบบอื่นๆ หลายระบบ timestamp ใน JavaScript มีหน่วยเป็นมิลลิวินาที ไม่ใช่วินาที -บางครั้งเราต้องการวัดเวลาที่แม่นยำกว่านี้ JavaScript เองไม่มีวิธีวัดเวลาในระดับไมโครวินาที (1 ในล้านของวินาที) แต่สภาพแวดล้อมส่วนใหญ่มีให้ เช่น เบราว์เซอร์มี [performance.now()](mdn:api/Performance/now) ที่คืนค่าจำนวนมิลลิวินาทีตั้งแต่เริ่มโหลดหน้าเว็บพร้อมความแม่นยำระดับไมโครวินาที (3 ตำแหน่งหลังจุดทศนิยม): +บางครั้งต้องการวัดเวลาที่แม่นยำกว่านี้ JavaScript เองไม่มีวิธีวัดในระดับไมโครวินาที (1 ในล้านของวินาที) แต่สภาพแวดล้อมส่วนใหญ่มีให้ เช่น เบราว์เซอร์มี [performance.now()](mdn:api/Performance/now) ที่คืนค่าจำนวนมิลลิวินาทีตั้งแต่เริ่มโหลดหน้าเว็บ พร้อมความแม่นยำระดับไมโครวินาที (3 ตำแหน่งหลังจุดทศนิยม): ```js run alert(`โหลดเริ่มมาแล้ว ${performance.now()}ms`); diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index c5bf5209f..3fff1439d 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -1,10 +1,8 @@ # เมธอด JSON และ toJSON -สมมติว่าเรามีออบเจ็กต์ที่ซับซ้อนและต้องการแปลงเป็นสตริง เพื่อส่งผ่านเครือข่ายหรือเพื่อ log ข้อมูล +สมมติว่ามีออบเจ็กต์ที่ซับซ้อนและอยากแปลงให้เป็นสตริง — เพื่อส่งผ่านเครือข่ายหรือเพื่อ log ข้อมูล โดยสตริงนั้นควรครอบคลุมพร็อพเพอร์ตี้สำคัญทั้งหมดด้วย -แน่นอนว่าสตริงนั้นควรรวมพร็อพเพอร์ตี้ที่สำคัญทั้งหมดไว้ด้วย - -เราอาจทำการแปลงแบบนี้: +วิธีง่ายๆ คือเขียน `toString` เองแบบนี้: ```js run let user = { @@ -21,20 +19,20 @@ let user = { alert(user); // {name: "John", age: 30} ``` -...แต่ระหว่างพัฒนา อาจมีการเพิ่มพร็อพเพอร์ตี้ใหม่ เปลี่ยนชื่อ หรือลบพร็อพเพอร์ตี้เก่าออก การอัปเดต `toString` ทุกครั้งอาจกลายเป็นเรื่องยุ่งยาก เราอาจลองวนลูปผ่านพร็อพเพอร์ตี้ แต่ถ้าออบเจ็กต์ซับซ้อนและมีออบเจ็กต์ซ้อนอยู่ข้างใน ก็ต้องแปลงออบเจ็กต์เหล่านั้นด้วย +...แต่ระหว่างพัฒนา พร็อพเพอร์ตี้อาจถูกเพิ่ม เปลี่ยนชื่อ หรือลบออกได้ตลอด การอัปเดต `toString` ทุกครั้งจึงน่าเบื่อหน่าย แถมถ้าออบเจ็กต์ซับซ้อนและมีออบเจ็กต์ซ้อนอยู่ข้างใน ก็ต้องจัดการออบเจ็กต์ย่อยพวกนั้นด้วย -โชคดีที่ไม่จำเป็นต้องเขียนโค้ดจัดการทั้งหมดนี้เอง เพราะมีวิธีที่แก้ปัญหานี้ไว้แล้ว +โชคดีที่ไม่ต้องเขียนโค้ดพวกนี้เอง — มีวิธีสำเร็จรูปที่ทำให้เราแล้ว ## JSON.stringify -[JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) เป็นรูปแบบทั่วไปสำหรับแสดงค่าและออบเจ็กต์ ถูกกำหนดไว้ในมาตรฐาน [RFC 4627](https://tools.ietf.org/html/rfc4627) เดิมทีสร้างขึ้นสำหรับ JavaScript แต่ภาษาอื่นๆ อีกมากก็มีไลบรารีรองรับ JSON ด้วย ทำให้ใช้ JSON แลกเปลี่ยนข้อมูลได้ง่าย เมื่อฝั่ง client ใช้ JavaScript และ server เขียนด้วย Ruby/PHP/Java หรืออะไรก็ตาม +[JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) คือรูปแบบมาตรฐานสำหรับแทนค่าและออบเจ็กต์ กำหนดไว้ในสเปค [RFC 4627](https://tools.ietf.org/html/rfc4627) เดิมทีออกแบบมาเพื่อ JavaScript แต่ทุกวันนี้ภาษาอื่นๆ ก็มีไลบรารีรองรับ JSON เช่นกัน จึงแลกเปลี่ยนข้อมูลได้สะดวกมาก ไม่ว่า client จะเป็น JavaScript และ server จะเป็น Ruby/PHP/Java หรืออะไรก็ตาม JavaScript มีเมธอดสำหรับจัดการ: - `JSON.stringify` — แปลงออบเจ็กต์เป็น JSON - `JSON.parse` — แปลง JSON กลับเป็นออบเจ็กต์ -ลองดูตัวอย่างการใช้ `JSON.stringify` กับข้อมูลนักเรียน: +ลองดูตัวอย่างกับข้อมูลนักเรียน: ```js run let student = { name: 'John', @@ -64,12 +62,11 @@ alert(json); */!* ``` -เมธอด `JSON.stringify(student)` รับออบเจ็กต์แล้วแปลงเป็นสตริง - -สตริง `json` ที่ได้นี้เรียกว่าออบเจ็กต์ที่ถูก *JSON-encoded* หรือ *serialized* หรือ *stringified* หรือ *marshalled* และพร้อมส่งผ่านเครือข่ายหรือเก็บไว้ใน data store ทั่วไปแล้ว +`JSON.stringify(student)` รับออบเจ็กต์แล้วแปลงเป็นสตริง +สตริง `json` ที่ได้นี้เรียกว่าออบเจ็กต์ที่ *JSON-encoded* หรือ *serialized* หรือ *stringified* หรือ *marshalled* — พร้อมส่งผ่านเครือข่ายหรือเก็บลง data store ได้เลย -สิ่งสำคัญคือ ออบเจ็กต์ที่ถูก JSON-encoded มีความแตกต่างจาก object literal ทั่วไปหลายอย่าง: +ทว่าออบเจ็กต์ที่ JSON-encoded ต่างจาก object literal ตรงๆ อยู่บ้าง: - สตริงใช้เครื่องหมายคำพูดคู่ ใน JSON ไม่มีคำพูดเดี่ยวหรือ backtick ดังนั้น `'John'` จะกลายเป็น `"John"` - ชื่อพร็อพเพอร์ตี้ก็ใส่เครื่องหมายคำพูดคู่ด้วย นั่นเป็นกฎบังคับ ดังนั้น `age:30` จะกลายเป็น `"age":30` @@ -100,7 +97,7 @@ alert( JSON.stringify(true) ); // true alert( JSON.stringify([1, 2, 3]) ); // [1,2,3] ``` -JSON เป็นสเปซิฟิเคชันที่เป็นอิสระจากภาษาโปรแกรมและเก็บได้แต่ข้อมูล ดังนั้น `JSON.stringify` จะข้ามพร็อพเพอร์ตี้บางอย่างที่เป็น JavaScript เฉพาะ +JSON เป็นสเปคที่ไม่ขึ้นกับภาษาโปรแกรมใด เก็บได้แต่ข้อมูลล้วนๆ `JSON.stringify` จึงข้ามพร็อพเพอร์ตี้ที่เป็น JavaScript เฉพาะ โดยเฉพาะ: @@ -120,9 +117,9 @@ let user = { alert( JSON.stringify(user) ); // {} (ออบเจ็กต์ว่างเปล่า) ``` -ส่วนใหญ่แล้วก็ไม่มีปัญหา แต่ถ้าไม่ต้องการแบบนี้ เดี๋ยวเราจะดูวิธีปรับแต่งกระบวนการนี้ +โดยส่วนใหญ่ก็ไม่มีปัญหา แต่ถ้าอยากควบคุมตรงนี้ เดี๋ยวเราจะดูวิธีกัน -สิ่งที่ดีคือออบเจ็กต์ที่ซ้อนกันก็รองรับและแปลงโดยอัตโนมัติ +ข้อดีอีกอย่างคือออบเจ็กต์ที่ซ้อนกันก็รองรับและแปลงให้อัตโนมัติ ตัวอย่าง: @@ -146,7 +143,7 @@ alert( JSON.stringify(meetup) ); */ ``` -ข้อจำกัดสำคัญคือต้องไม่มี circular reference +แต่มีข้อจำกัดสำคัญคือต้องไม่มี circular reference ตัวอย่าง: @@ -168,7 +165,7 @@ JSON.stringify(meetup); // Error: Converting circular structure to JSON */!* ``` -การแปลงล้มเหลวเพราะมี circular reference: `room.occupiedBy` อ้างอิง `meetup` และ `meetup.place` อ้างอิง `room`: +การแปลงล้มเหลวเพราะอ้างอิงกันวนไม่รู้จบ: `room.occupiedBy` ชี้ไปที่ `meetup` และ `meetup.place` ก็ชี้กลับมาที่ `room`: ![](json-meetup.svg) @@ -190,9 +187,9 @@ replacer space : จำนวน space สำหรับการจัดรูปแบบ -ส่วนใหญ่ `JSON.stringify` ใช้กับอาร์กิวเมนต์แรกเท่านั้น แต่ถ้าต้องการปรับแต่งกระบวนการแทนที่ เช่น กรอง circular reference ออก ก็ใช้อาร์กิวเมนต์ที่สองได้ +ปกติแล้ว `JSON.stringify` ใช้แค่อาร์กิวเมนต์แรก แต่ถ้าต้องการกรองหรือแปลงค่าระหว่างทาง เช่น ตัด circular reference ออก ก็ส่งอาร์กิวเมนต์ที่สองเข้าไปได้ -ถ้าส่งอาร์เรย์ของพร็อพเพอร์ตี้เข้าไป จะเข้ารหัสเฉพาะพร็อพเพอร์ตี้เหล่านั้น +ถ้าส่งเป็นอาร์เรย์ของชื่อพร็อพเพอร์ตี้ จะเข้ารหัสเฉพาะพร็อพเพอร์ตี้ที่ระบุเท่านั้น ตัวอย่าง: @@ -213,9 +210,9 @@ alert( JSON.stringify(meetup, *!*['title', 'participants']*/!*) ); // {"title":"Conference","participants":[{},{}]} ``` -ดูเหมือนจะเข้มงวดเกินไปหน่อย เพราะรายการพร็อพเพอร์ตี้นี้ถูกนำไปใช้กับโครงสร้างออบเจ็กต์ทั้งหมด ทำให้ออบเจ็กต์ใน `participants` ว่างเปล่า เนื่องจาก `name` ไม่อยู่ในรายการ +เข้มงวดไปนิดนึง — รายการพร็อพเพอร์ตี้นี้ใช้กับโครงสร้างทั้งหมด เลยทำให้ออบเจ็กต์ใน `participants` ว่างเปล่า เพราะ `name` ไม่ได้อยู่ในรายการ -มาใส่พร็อพเพอร์ตี้ทุกตัวยกเว้น `room.occupiedBy` ที่จะทำให้เกิด circular reference: +ลองใส่พร็อพเพอร์ตี้ทุกตัว ยกเว้น `room.occupiedBy` ที่ทำให้เกิด circular reference: ```js run let room = { @@ -240,13 +237,13 @@ alert( JSON.stringify(meetup, *!*['title', 'participants', 'place', 'name', 'num */ ``` -ตอนนี้ทุกอย่างยกเว้น `occupiedBy` ถูก serialize แล้ว แต่รายการพร็อพเพอร์ตี้ค่อนข้างยาว +ตอนนี้ทุกอย่างยกเว้น `occupiedBy` ก็ serialize ครบแล้ว แต่รายการพร็อพเพอร์ตี้ยาวเกินไปหน่อย -โชคดีที่เราใช้ฟังก์ชันแทนอาร์เรย์เป็น `replacer` ได้ +โชคดีที่ส่งฟังก์ชันแทนอาร์เรย์เป็น `replacer` ได้เช่นกัน -ฟังก์ชันนี้จะถูกเรียกสำหรับทุกคู่ `(key, value)` และควรคืนค่าที่ "แทนที่" ซึ่งจะถูกใช้แทนค่าเดิม หรือคืน `undefined` ถ้าต้องการข้ามค่านั้น +ฟังก์ชัน `replacer` จะถูกเรียกทีละคู่ `(key, value)` และควรคืนค่าที่จะใช้แทน หรือคืน `undefined` ถ้าต้องการข้ามพร็อพเพอร์ตี้นั้น -ในกรณีของเรา เราคืน `value` "ตามเดิม" สำหรับทุกอย่างยกเว้น `occupiedBy` และส่ง `undefined` เพื่อข้ามมัน: +ในตัวอย่างนี้ เราคืน `value` ตามเดิมสำหรับทุกอย่าง ยกเว้น `occupiedBy` ที่คืน `undefined` เพื่อข้ามไป: ```js run let room = { @@ -280,20 +277,20 @@ occupiedBy: [object Object] */ ``` -สังเกตว่าฟังก์ชัน `replacer` ได้รับทุกคู่ key/value รวมถึงออบเจ็กต์ที่ซ้อนกันและรายการในอาร์เรย์ด้วย โดยทำงานแบบ recursive และค่าของ `this` ภายใน `replacer` คือออบเจ็กต์ที่มีพร็อพเพอร์ตี้ปัจจุบันอยู่ +สังเกตว่า `replacer` ได้รับทุกคู่ key/value รวมถึงออบเจ็กต์ซ้อนและรายการในอาร์เรย์ด้วย ทำงานแบบ recursive และ `this` ภายใน `replacer` คือออบเจ็กต์ที่มีพร็อพเพอร์ตี้ปัจจุบัน -การเรียกครั้งแรกพิเศษเป็น เพราะใช้ "wrapper object" พิเศษ: `{"": meetup}` กล่าวอีกอย่างคือ คู่ `(key, value)` แรกมี key ว่างเปล่า และ value คือออบเจ็กต์เป้าหมายทั้งหมด นั่นเป็นเหตุผลที่บรรทัดแรกในตัวอย่างข้างต้นเป็น `":[object Object]"` +การเรียกครั้งแรกนั้นพิเศษ — จะใช้ "wrapper object" แบบนี้: `{"": meetup}` นั่นคือคู่ `(key, value)` แรก มี key เป็นสตริงว่าง และ value คือออบเจ็กต์เป้าหมายทั้งหมด จึงเห็นบรรทัดแรกในตัวอย่างเป็น `":[object Object]"` นั่นเอง -แนวคิดคือเพื่อให้ `replacer` มีพลังมากที่สุดเท่าที่จะเป็นไปได้ โดยมีโอกาสวิเคราะห์และแทนที่หรือข้ามแม้แต่ออบเจ็กต์ทั้งหมดหากจำเป็น +แบบนี้ทำให้ `replacer` ทรงพลังมาก — วิเคราะห์ แทนที่ หรือข้ามทั้งออบเจ็กต์ก็ยังได้ ## การจัดรูปแบบ: space -อาร์กิวเมนต์ที่สามของ `JSON.stringify(value, replacer, space)` คือจำนวน space สำหรับการจัดรูปแบบที่อ่านง่าย +อาร์กิวเมนต์ที่สามของ `JSON.stringify(value, replacer, space)` คือจำนวน space สำหรับจัดรูปแบบให้อ่านง่าย -ก่อนหน้านี้ ออบเจ็กต์ที่ stringify แล้วทั้งหมดไม่มีการเยื้องหรือ space เพิ่มเติม ซึ่งก็เพียงพอถ้าต้องการส่งออบเจ็กต์ผ่านเครือข่าย แต่อาร์กิวเมนต์ `space` ใช้สำหรับแสดงผลให้อ่านง่ายขึ้นเท่านั้น +ผลลัพธ์ของ `JSON.stringify` ปกติไม่มีการเยื้องหรือ space เพิ่มเติมเลย — เพียงพอสำหรับส่งผ่านเครือข่าย แต่ถ้าอยากให้อ่านง่ายขึ้น ใส่ `space` เข้าไปได้ -ตรงนี้ `space = 2` บอกให้ JavaScript แสดงออบเจ็กต์ที่ซ้อนกันบนหลายบรรทัด โดยเยื้อง 2 space ข้างใน: +เช่น `space = 2` บอกให้ JavaScript แสดงออบเจ็กต์ซ้อนบนหลายบรรทัด โดยเยื้อง 2 space: ```js run let user = { @@ -329,13 +326,13 @@ alert(JSON.stringify(user, null, 2)); */ ``` -อาร์กิวเมนต์ที่สามยังเป็นสตริงได้ด้วย ในกรณีนั้นสตริงจะถูกใช้แทนจำนวน space สำหรับการเยื้อง +อาร์กิวเมนต์ที่สามยังเป็นสตริงได้ด้วย ในกรณีนั้นสตริงนั้นจะใช้เป็นตัวเยื้องแทนจำนวน space -พารามิเตอร์ `space` ใช้เพื่อการ log และแสดงผลที่อ่านง่ายเท่านั้น +พารามิเตอร์ `space` มีไว้เพื่อ log และแสดงผลให้อ่านง่ายเท่านั้น ไม่มีผลต่อข้อมูล ## toJSON แบบกำหนดเอง -เหมือนกับ `toString` สำหรับการแปลงเป็นสตริง ออบเจ็กต์อาจมีเมธอด `toJSON` สำหรับการแปลงเป็น JSON ซึ่ง `JSON.stringify` จะเรียกโดยอัตโนมัติถ้ามีอยู่ +เหมือนกับ `toString` ที่ใช้แปลงเป็นสตริง ออบเจ็กต์สามารถมีเมธอด `toJSON` สำหรับแปลงเป็น JSON ได้ `JSON.stringify` จะเรียกเมธอดนี้อัตโนมัติถ้ามีอยู่ ตัวอย่าง: @@ -362,9 +359,9 @@ alert( JSON.stringify(meetup) ); */ ``` -จะเห็นว่า `date` `(1)` กลายเป็นสตริง เพราะออบเจ็กต์ Date ทั้งหมดมีเมธอด `toJSON` ในตัวที่คืนค่าสตริงในรูปแบบนั้น +ตรง `(1)` จะเห็นว่า `date` กลายเป็นสตริง — เพราะออบเจ็กต์ Date มีเมธอด `toJSON` ในตัวที่คืนค่าสตริงในรูปแบบนั้นอยู่แล้ว -ทีนี้มาเพิ่ม `toJSON` แบบกำหนดเองให้ออบเจ็กต์ `room` `(2)`: +ทีนี้ลองเพิ่ม `toJSON` แบบกำหนดเองให้ `room` `(2)` ดูบ้าง: ```js run let room = { @@ -396,12 +393,12 @@ alert( JSON.stringify(meetup) ); */ ``` -จะเห็นว่า `toJSON` ถูกใช้ทั้งกับการเรียกโดยตรง `JSON.stringify(room)` และเมื่อ `room` ซ้อนอยู่ในออบเจ็กต์ที่เข้ารหัสอื่น +`toJSON` ทำงานทั้งเมื่อเรียกตรงๆ อย่าง `JSON.stringify(room)` และเมื่อ `room` ซ้อนอยู่ในออบเจ็กต์อื่น ## JSON.parse -ในการ decode สตริง JSON เราต้องใช้เมธอด [JSON.parse](mdn:js/JSON/parse) +หากต้องการ decode สตริง JSON กลับ ใช้เมธอด [JSON.parse](mdn:js/JSON/parse) ไวยากรณ์: ```js @@ -412,7 +409,7 @@ str : สตริง JSON ที่ต้องการ parse reviver -: ฟังก์ชัน function(key,value) ที่ไม่บังคับ จะถูกเรียกสำหรับทุกคู่ `(key, value)` และสามารถแปลงค่าได้ +: ฟังก์ชัน function(key,value) ไม่บังคับ — เรียกทีละคู่ `(key, value)` เพื่อแปลงค่าระหว่างการ parse ตัวอย่าง: @@ -425,7 +422,7 @@ numbers = JSON.parse(numbers); alert( numbers[1] ); // 1 ``` -หรือสำหรับออบเจ็กต์ที่ซ้อนกัน: +หรือถ้าเป็นออบเจ็กต์ซ้อนกัน: ```js run let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; @@ -435,9 +432,9 @@ let user = JSON.parse(userData); alert( user.friends[1] ); // 1 ``` -JSON อาจซับซ้อนแค่ไหนก็ได้ ออบเจ็กต์และอาร์เรย์สามารถมีออบเจ็กต์และอาร์เรย์อื่นซ้อนอยู่ได้ แต่ต้องเป็นไปตามรูปแบบ JSON เดียวกัน +JSON ซับซ้อนได้แค่ไหนก็ได้ ซ้อนออบเจ็กต์และอาร์เรย์ลึกกี่ชั้นก็ยังได้ แต่ต้องเป็นไปตามรูปแบบ JSON เสมอ -นี่คือข้อผิดพลาดที่พบบ่อยใน JSON ที่เขียนด้วยมือ (บางครั้งต้องเขียนเองเพื่อ debug): +นี่คือข้อผิดพลาดที่เจอบ่อยเมื่อเขียน JSON ด้วยมือ (บางทีต้องเขียนเองตอน debug): ```js let json = `{ @@ -449,26 +446,26 @@ let json = `{ }`; ``` -นอกจากนี้ JSON ไม่รองรับ comment ถ้าใส่ comment ใน JSON จะทำให้ไม่ valid +อีกอย่างคือ JSON ไม่รองรับ comment — ใส่ไปก็จะทำให้ invalid ทันที -มีรูปแบบอื่นชื่อ [JSON5](https://json5.org/) ที่อนุญาต unquoted keys, comment ฯลฯ แต่เป็น library แยกต่างหาก ไม่ได้อยู่ในสเปซิฟิเคชันของภาษา +มีรูปแบบทางเลือกชื่อ [JSON5](https://json5.org/) ที่อนุญาต unquoted keys, comment และอื่นๆ แต่เป็น library แยกต่างหาก ไม่ได้อยู่ในสเปค JSON มาตรฐาน -JSON ที่ใช้กันทั่วไปเข้มงวดขนาดนี้ ไม่ใช่เพราะผู้พัฒนาขี้เกียจ แต่เพื่อให้สามารถสร้างอัลกอริทึม parsing ที่ง่าย เชื่อถือได้ และรวดเร็วมาก +ที่ JSON เข้มงวดขนาดนี้ไม่ใช่เพราะผู้พัฒนาขี้เกียจ แต่เพื่อให้สร้างอัลกอริทึม parse ที่ง่าย เชื่อถือได้ และรวดเร็วได้นั่นเอง ## การใช้ reviver -สมมติว่าเราได้รับออบเจ็กต์ `meetup` ที่ถูก stringify มาจาก server +สมมติว่าได้รับออบเจ็กต์ `meetup` ที่ stringify แล้วมาจาก server -ข้อมูลนั้นมีหน้าตาแบบนี้: +ข้อมูลหน้าตาแบบนี้: ```js // title: (ชื่อการประชุม), date: (วันที่ประชุม) let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; ``` -...และตอนนี้เราต้องการ *deserialize* มัน เพื่อแปลงกลับเป็นออบเจ็กต์ JavaScript +...แล้วเราต้องการ *deserialize* มันกลับเป็นออบเจ็กต์ JavaScript -มาทำด้วยการเรียก `JSON.parse`: +ลองเรียก `JSON.parse` ดู: ```js run let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; @@ -482,9 +479,9 @@ alert( meetup.date.getDate() ); // เกิดข้อผิดพลาด! อุ๊ย! เกิดข้อผิดพลาด! -ค่าของ `meetup.date` เป็นสตริง ไม่ใช่ออบเจ็กต์ `Date` แล้ว `JSON.parse` จะรู้ได้อย่างไรว่าควรแปลงสตริงนั้นเป็น `Date`? +`meetup.date` เป็นสตริง ไม่ใช่ออบเจ็กต์ `Date` — `JSON.parse` ไม่มีทางรู้เองว่าต้องแปลงสตริงนั้นเป็น `Date` -มาส่งฟังก์ชัน reviving ให้กับ `JSON.parse` เป็นอาร์กิวเมนต์ที่สอง โดยคืนค่าทุกอย่าง "ตามเดิม" ยกเว้น `date` จะกลายเป็น `Date`: +แก้ได้โดยส่งฟังก์ชัน reviver เป็นอาร์กิวเมนต์ที่สอง โดยคืนทุกอย่างตามเดิม ยกเว้น `date` ที่แปลงเป็น `Date` ก่อน: ```js run let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; @@ -499,7 +496,7 @@ let meetup = JSON.parse(str, function(key, value) { alert( meetup.date.getDate() ); // ทำงานได้แล้ว! ``` -อนึ่ง วิธีนี้ใช้ได้กับออบเจ็กต์ที่ซ้อนกันด้วย: +วิธีนี้ใช้ได้กับออบเจ็กต์ซ้อนกันด้วยเช่นกัน: ```js run let schedule = `{ @@ -523,8 +520,8 @@ alert( schedule.meetups[1].date.getDate() ); // ทำงานได้! ## สรุป -- JSON เป็นรูปแบบข้อมูลที่มีมาตรฐานเป็นของตัวเองและมีไลบรารีรองรับในภาษาโปรแกรมส่วนใหญ่ +- JSON คือรูปแบบข้อมูลมาตรฐานที่มีไลบรารีรองรับในเกือบทุกภาษา - JSON รองรับออบเจ็กต์ธรรมดา อาร์เรย์ สตริง ตัวเลข บูลีน และ `null` -- JavaScript มีเมธอด [JSON.stringify](mdn:js/JSON/stringify) สำหรับ serialize เป็น JSON และ [JSON.parse](mdn:js/JSON/parse) สำหรับอ่านจาก JSON -- ทั้งสองเมธอดรองรับฟังก์ชัน transformer สำหรับการอ่าน/เขียนอย่างชาญฉลาด -- ถ้าออบเจ็กต์มี `toJSON` จะถูกเรียกโดย `JSON.stringify` +- JavaScript มีเมธอด [JSON.stringify](mdn:js/JSON/stringify) สำหรับ serialize และ [JSON.parse](mdn:js/JSON/parse) สำหรับ deserialize +- ทั้งสองเมธอดรองรับฟังก์ชัน transformer สำหรับอ่าน/เขียนแบบ custom ได้ +- ถ้าออบเจ็กต์มีเมธอด `toJSON` ให้ `JSON.stringify` จะเรียกเมธอดนั้นอัตโนมัติ