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..bb9dbda56 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` ของสตริง ต่างแค่ได้ subarray กลับมาแทน substring -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: +โดยปกติ `concat` จะคัดลอกแค่สมาชิกจากอาร์เรย์ ส่วนออบเจ็กต์อื่นๆ แม้จะดูคล้ายอาร์เรย์ จะถูกเพิ่มเข้าไปทั้งก้อน: ```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` ใช้การเปรียบเทียบแบบ strict `===` ดังนั้นถ้าค้นหา `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` ที่มีอาร์กิวเมนต์เดียว ซึ่งเป็นรูปแบบที่ใช้กันทั่วไป อาร์กิวเมนต์อื่นๆ (`index`, `array`) ใช้น้อยมาก -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` คืนแค่สมาชิกตัวแรกที่ตรงเงื่อนไข -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,35 @@ 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`. - -For instance: +เมธอด [arr.reverse](mdn:js/Array/reverse) กลับลำดับสมาชิกใน `arr` ```js run let arr = [1, 2, 3, 4, 5]; @@ -518,15 +516,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 +532,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 +544,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('')` ด้วยสตริงว่างจะได้อาร์เรย์ของตัวอักษรทีละตัว: ```js run let str = "test"; @@ -556,27 +554,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 +582,20 @@ 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. +อาร์กิวเมนต์: -As the function is applied, the result of the previous function call is passed to the next one as the first argument. +- `accumulator` -- ผลลัพธ์จากการเรียกครั้งก่อน ถ้าเป็นครั้งแรกจะเท่ากับ `initial` (ถ้าระบุไว้) +- `item` -- สมาชิกปัจจุบัน +- `index` -- ตำแหน่งของสมาชิก +- `array` -- อาร์เรย์เอง -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 +605,71 @@ 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` +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. - -The calculation table is the same as above, minus the first row. +ผลลัพธ์เหมือนกัน เพราะถ้าไม่มีค่าเริ่มต้น `reduce` จะใช้สมาชิกตัวแรกเป็นค่าเริ่มต้น แล้วเริ่มวนจากสมาชิกตัวที่สอง -But such use requires an extreme care. If the array is empty, then `reduce` call without initial value gives an error. +ตารางการคำนวณก็เหมือนด้านบน เพียงแต่ขาดแถวแรก -Here's an example: +แต่ต้องระวัง — ถ้าอาร์เรย์ว่างเปล่า การเรียก `reduce` โดยไม่ระบุค่าเริ่มต้นจะเกิดข้อผิดพลาด ```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 +677,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 +714,7 @@ let users = [ ]; *!* -// find users, for who army.canJoin returns true +// หาผู้ใช้ที่ army.canJoin คืนค่า true let soldiers = users.filter(army.canJoin, army); */!* @@ -731,53 +723,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 +779,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..6531b6562 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 ด้วย อย่างสตริงก็เช่นกัน -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: +ด้านล่างคือโค้ดครบถ้วนสำหรับ `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()` +- แต่เมื่อเรียก `range[Symbol.iterator]()` จะสร้างออบเจ็กต์ "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 แค่ตัวเดียวคือตัวออบเจ็กต์นั่นเอง แต่ก็ไม่ค่อยมีใครวนแบบขนานสองตัวพร้อมกันอยู่แล้ว แม้แต่ในสถานการณ์ 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` ที่วนซ้ำ 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. +ในงานจริงบนเบราว์เซอร์หรือสภาพแวดล้อมอื่น เราอาจเจอออบเจ็กต์ที่เป็น 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` คือค่าถัดไป +- `for..of` เรียก `Symbol.iterator` อัตโนมัติ แต่เรียกโดยตรงก็ได้เช่นกัน +- 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 เพราะเป็นแนวคิดที่ abstract กว่า -`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` ช่วยให้ใส่ฟังก์ชัน 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 37f5e48c2..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,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`. +นี่คือหนึ่งในฟีเจอร์ที่โดดเด่นที่สุดของ `Map` — ใช้ออบเจ็กต์เป็น key ได้เลย ซึ่ง `Object` ทำไม่ได้ เพราะ Object ธรรมดาใช้ string เป็น key ได้ แต่ไม่สามารถใช้ Object อื่นเป็น key ได้ -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 ธรรมดา ไม่ว่าจะใส่ `john` หรือ `ben` เป็น key มันจะแปลงให้เป็น 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` ได้ด้วยเมธอดสามแบบ: -- [`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,12 @@ 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` คืนค่าอาร์เรย์ `[ ["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)`. - -There's `Object.fromEntries` method that does the reverse: given an array of `[key, value]` pairs, it creates an object from them: +ถ้า `Object.entries` แปลง object เป็น Map ได้ `Object.fromEntries` ก็ทำสิ่งตรงกันข้าม — รับอาร์เรย์ของคู่ `[key, value]` แล้วสร้าง object กลับออกมา: ```js run let prices = Object.fromEntries([ @@ -194,16 +192,12 @@ 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`. - -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. - -Here we go: +ใช้ `Object.fromEntries` แปลง `Map` กลับเป็น plain object ได้ เช่น เก็บข้อมูลไว้ใน `Map` แต่ต้องส่งให้โค้ดของ third-party ที่รับแค่ plain object — ทำแบบนี้: ```js run let map = new Map(); @@ -212,42 +206,40 @@ 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()` ทุกประการ ## 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. - -Its main methods are: +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) คือคอลเล็กชันชนิดพิเศษ — "ชุดของค่า" (ไม่มี key) ที่ **แต่ละค่าปรากฏได้เพียงครั้งเดียว** -- [`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. +เมธอดหลักที่มี: -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. +- [`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 ทั้งหมด -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.add(value)` ซ้ำด้วยค่าเดิมจะไม่มีผลใดๆ นั่นเองที่ทำให้แต่ละค่าปรากฏใน `Set` ได้เพียงครั้งเดียว -`Set` is just the right thing for that: +สมมติมีผู้เยี่ยมชมเข้ามา และต้องการจำทุกคน — แต่ไม่อยากให้คนเดิมซ้ำกัน ผู้เยี่ยมชมคนหนึ่งควรนับแค่ครั้งเดียว `Set` เหมาะกับงานนี้พอดี: ```js run let set = new Set(); @@ -256,76 +248,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. +อีกทางหนึ่งคือใช้อาร์เรย์แล้วตรวจสอบซ้ำทุกครั้งที่เพิ่มด้วย [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) แต่ประสิทธิภาพจะแย่กว่ามาก เพราะ `arr.find` ต้องวนอ่านอาร์เรย์ทั้งหมดทุกครั้ง ส่วน `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..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,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. +จากที่เรารู้กันในบท ค่าจะอยู่ในหน่วยความจำตราบเท่าที่ยัง "เข้าถึงได้" และอาจถูกนำไปใช้งาน -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` เป็น key ใน `WeakMap` เพียงที่เดียว จะถูกลบออกจาก 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.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` ในโค้ดข้างบน) JavaScript engine จะ 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. +engine เป็นตัวตัดสินใจเอง อาจทำทันทีหรือรอก่อนแล้วค่อยทำทีหลังพร้อมกันหลายๆ ตัว จำนวนสมาชิกใน `WeakMap` ณ ขณะหนึ่งจึงไม่แน่นอน — อาจทำความสะอาดไปแล้ว ยังไม่ได้ทำ หรือทำไปแค่บางส่วน นั่นจึงเป็นเหตุผลที่เมธอดสำหรับเข้าถึง 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 พอผู้ใช้จากไป (ออบเจ็กต์ถูก 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` ไม่สามารถเข้าถึงได้จากที่อื่นนอกจากใน `WeakMap` ก็จะถูกลบออกจากหน่วยความจำพร้อมกับข้อมูลที่ผูกกับ key นั้นโดยอัตโนมัติ -## 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 — เก็บผลลัพธ์จากฟังก์ชันไว้ เพื่อที่การเรียกครั้งต่อๆ ไปด้วยออบเจ็กต์เดิมจะได้นำผลลัพธ์เดิมมาใช้ได้เลยโดยไม่ต้องคำนวณใหม่ -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` แต่เพิ่มได้เฉพาะออบเจ็กต์เท่านั้น (ไม่ใช่ค่า 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()` และไม่รองรับการวนซ้ำ -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` คือไม่รองรับการวนซ้ำ และดึงเนื้อหาทั้งหมดออกมาไม่ได้ แต่นั่นก็ไม่ได้ขัดขวางการทำหน้าที่หลักของมัน — เป็นที่เก็บข้อมูล "เสริม" สำหรับออบเจ็กต์ที่ถูกเก็บและจัดการในที่อื่น -## 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..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,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..47d678208 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-value เป็นหน่วยเดียว +- อาร์เรย์ใช้เก็บข้อมูลเป็นรายการที่มีลำดับ -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* คือไวยากรณ์พิเศษที่ช่วยให้ "แกะ" อาร์เรย์หรือออบเจ็กต์ออกเป็นตัวแปรหลายตัวได้ในคราวเดียว สะดวกกว่าการอ้างอิง index หรือ key ทีละตัวมาก -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,38 @@ 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 +95,8 @@ alert(user.surname); // Smith ```` -````smart header="Looping with .entries()" -In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method. - -We can use it with destructuring to loop over the keys-and-values of an object: +````smart header="วนลูปด้วย .entries()" +จำเมธอด [Object.entries(obj)](mdn:js/Object/entries) จากบทก่อนได้ไหม? นำมาใช้ร่วมกับ destructuring เพื่อวนลูปผ่าน key-value ของออบเจ็กต์ได้เลย: ```js run let user = { @@ -105,15 +104,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` จะง่ายกว่าอีก เพราะ Map เป็น iterable อยู่แล้ว: ```js run let user = new Map(); @@ -121,73 +120,71 @@ 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 มีเทคนิคที่รู้จักกันดีสำหรับสลับค่าตัวแปรสองตัว: ```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` ก็ได้ ขอแค่ใส่จุดสามจุดข้างหน้าและวางไว้เป็นตัวสุดท้ายเท่านั้น: ```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 +195,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`) เท่านั้น — `name` มีค่าอยู่แล้ว จึงไม่ถาม ## Object destructuring -The destructuring assignment also works with objects. +ออบเจ็กต์ก็ destructure ได้เช่นกัน -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 +251,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 +272,7 @@ let options = { }; *!* -// { sourceProperty: targetVariable } +// { พร็อพเพอร์ตี้ต้นทาง: ตัวแปรปลายทาง } let {width: w, height: h, title} = options; */!* @@ -288,9 +285,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 +303,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 +317,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 +336,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 +345,7 @@ let options = { height: 200 }; -// only extract title as a variable +// ดึงเฉพาะ title มาเป็นตัวแปร let { title } = options; alert(title); // Menu @@ -356,11 +353,9 @@ 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. +แล้วถ้าออบเจ็กต์มีพร็อพเพอร์ตี้มากกว่าตัวแปรที่ต้องการล่ะ? เก็บบางส่วนแล้วโยน "ส่วนที่เหลือ" ไว้ที่ไหนสักที่ได้ไหม? -It looks like this: +ได้เลย — ใช้ rest pattern เหมือนกับอาร์เรย์ บราวเซอร์สมัยใหม่ใช้ได้ทั้งนั้น (เก่ามากอย่าง IE อาจต้องใช้ Babel ช่วย) ```js run let options = { @@ -370,46 +365,43 @@ 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`" +ตัวอย่างข้างบนประกาศตัวแปรพร้อมกับ destructuring เลย (`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 ไม่ใช่ออบเจ็กต์ เช่นเดียวกับที่เราใช้ 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. - -To show JavaScript that it's not a code block, we can wrap the expression in parentheses `(...)`: +แก้ได้ง่ายๆ ครอบด้วยวงเล็บ `(...)` เพื่อบอก JavaScript ว่านี่คือนิพจน์ ไม่ใช่ block: ```js run let title, width, height; -// okay now +// โอเคแล้ว *!*(*/!*{title, width, height} = {title: "Menu", width: 200, height: 100}*!*)*/!*; alert( title ); // Menu @@ -418,9 +410,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 +424,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 +441,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. +ฟังก์ชัน user interface มักมีพารามิเตอร์เยอะมาก และส่วนใหญ่เป็น optional ทั้งนั้น ลองนึกภาพฟังก์ชันสร้างเมนูที่รับ ความกว้าง ความสูง หัวข้อ รายการ และอื่นๆ อีกมากมาย -Here's a bad way to write such a function: +วิธีเขียนแบบนี้ไม่ค่อยดีนัก: ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -469,32 +461,30 @@ 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 จะช่วยบอกได้บ้าง แต่ก็ยังน่าปวดหัว แล้วถ้าอยากเรียกโดยใช้แค่ค่าเริ่มต้นส่วนใหญ่ต้องเขียนยังไง? -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! +น่าเกลียดมาก และยิ่งอ่านไม่รู้เรื่องถ้าพารามิเตอร์เพิ่มขึ้นอีก -We can pass parameters as an object, and the function immediately destructurizes them into variables: +destructuring ช่วยได้พอดี! ส่งพารามิเตอร์เป็นออบเจ็กต์ แล้วให้ฟังก์ชัน 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 +492,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 +503,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 +516,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 +524,17 @@ function({ }) ``` -Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. +พร็อพเพอร์ตี้ `incomingProperty` จะไปอยู่ในตัวแปร `varName` โดยมีค่าเริ่มต้นเป็น `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 +544,24 @@ 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 ออกมาได้เสมอโดยไม่ error ## 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. - - Object properties that have no mapping are copied to the `rest` object. + พร็อพเพอร์ตี้ `prop` จะไปที่ตัวแปร `varName` ถ้าไม่มีพร็อพเพอร์ตี้นั้นจะใช้ `defaultValue` แทน ส่วนพร็อพเพอร์ตี้ที่ไม่ได้ระบุจะถูกรวบไว้ในออบเจ็กต์ `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..1df0f6d52 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,110 @@ 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()` อย่างชัดเจน อีกฟังก์ชันอาศัยการแปลงวันที่เป็นตัวเลขแทน ผลลัพธ์เหมือนกันเสมอ -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 +306,21 @@ 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. - -Okay, we have something. But that's not a good benchmark yet. +ว้าว! `getTime()` เร็วกว่ามากเลย! เพราะไม่มีการแปลงประเภท engine จึง optimize ได้ง่ายกว่า -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. +โอเค เราได้ข้อมูลแล้ว แต่นี่ยังไม่ใช่ benchmark ที่ดีนัก -A pretty real scenario for a modern multi-process OS. +ลองนึกภาพว่าตอนที่รัน `bench(diffSubtract)` CPU กำลังทำงานอื่นพร้อมกันและกินทรัพยากรไป แล้วพอถึงเวลารัน `bench(diffGetTime)` งานนั้นเสร็จพอดี สถานการณ์แบบนี้พบได้บ่อยในระบบปฏิบัติการแบบ 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 +344,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 จึงควรเพิ่มการ "อุ่นเครื่อง" ก่อน: ```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 +398,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 +406,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..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 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: +วิธีง่ายๆ คือเขียน `toString` เองแบบนี้: ```js run let user = { @@ -21,20 +19,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 เช่นกัน จึงแลกเปลี่ยนข้อมูลได้สะดวกมาก ไม่ว่า 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: +ลองดูตัวอย่างกับข้อมูลนักเรียน: ```js run let student = { name: 'John', @@ -48,11 +46,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 +62,34 @@ alert(json); */!* ``` -The method `JSON.stringify(student)` takes the object and converts it into a string. - -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.stringify(student)` รับออบเจ็กต์แล้วแปลงเป็นสตริง +สตริง `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 +97,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 +135,7 @@ let meetup = { }; alert( JSON.stringify(meetup) ); -/* The whole structure is stringified: +/* โครงสร้างทั้งหมดถูกแปลงเป็น string: { "title":"Conference", "room":{"number":23,"participants":["john","ann"]}, @@ -146,9 +143,9 @@ alert( JSON.stringify(meetup) ); */ ``` -The important limitation: there must be no circular references. +แต่มีข้อจำกัดสำคัญคือต้องไม่มี circular reference -For instance: +ตัวอย่าง: ```js run let room = { @@ -160,41 +157,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`: +การแปลงล้มเหลวเพราะอ้างอิงกันวนไม่รู้จบ: `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 +201,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 +222,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 +237,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. +ฟังก์ชัน `replacer` จะถูกเรียกทีละคู่ `(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 +253,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 +277,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. +ผลลัพธ์ของ `JSON.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 +303,7 @@ let user = { }; alert(JSON.stringify(user, null, 2)); -/* two-space indents: +/* เยื้องด้วย 2 space: { "name": "John", "age": 25, @@ -317,7 +314,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 +326,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 +359,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. +ตรง `(1)` จะเห็นว่า `date` กลายเป็นสตริง — เพราะออบเจ็กต์ Date มีเมธอด `toJSON` ในตัวที่คืนค่าสตริงในรูปแบบนั้นอยู่แล้ว -Now let's add a custom `toJSON` for our object `room` `(2)`: +ทีนี้ลองเพิ่ม `toJSON` แบบกำหนดเองให้ `room` `(2)` ดูบ้าง: ```js run let room = { @@ -396,28 +393,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)` เพื่อแปลงค่าระหว่างการ parse -For instance: +ตัวอย่าง: ```js run -// stringified array +// อาร์เรย์ที่ถูก stringify let numbers = "[0, 1, 2, 3]"; numbers = JSON.parse(numbers); @@ -425,7 +422,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 +432,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 — ใส่ไปก็จะทำให้ invalid ทันที -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 แยกต่างหาก ไม่ได้อยู่ในสเปค JSON มาตรฐาน -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 เข้มงวดขนาดนี้ไม่ใช่เพราะผู้พัฒนาขี้เกียจ แต่เพื่อให้สร้างอัลกอริทึม parse ที่ง่าย เชื่อถือได้ และรวดเร็วได้นั่นเอง -## 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 +473,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`: +แก้ได้โดยส่งฟังก์ชัน reviver เป็นอาร์กิวเมนต์ที่สอง โดยคืนทุกอย่างตามเดิม ยกเว้น `date` ที่แปลงเป็น `Date` ก่อน: ```js run let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; @@ -496,10 +493,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 +512,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.parse](mdn:js/JSON/parse) สำหรับ deserialize +- ทั้งสองเมธอดรองรับฟังก์ชัน transformer สำหรับอ่าน/เขียนแบบ custom ได้ +- ถ้าออบเจ็กต์มีเมธอด `toJSON` ให้ `JSON.stringify` จะเรียกเมธอดนั้นอัตโนมัติ