From a0a5454cdbf6d7101e41a2ae59297147bf857cef Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Sun, 15 Mar 2026 20:19:34 +0700 Subject: [PATCH] docs: translate prototypes section (4 articles) --- .../1-property-after-delete/solution.md | 6 +- .../1-property-after-delete/task.md | 8 +- .../2-search-algorithm/solution.md | 6 +- .../2-search-algorithm/task.md | 10 +- .../3-proto-and-this/solution.md | 8 +- .../3-proto-and-this/task.md | 6 +- .../4-hamster-proto/solution.md | 36 ++-- .../4-hamster-proto/task.md | 10 +- .../01-prototype-inheritance/article.md | 168 +++++++++--------- .../1-changing-prototype/solution.md | 20 +-- .../1-changing-prototype/task.md | 14 +- .../4-new-object-same-constructor/solution.md | 30 ++-- .../4-new-object-same-constructor/task.md | 8 +- .../02-function-prototype/article.md | 92 +++++----- .../1-defer-to-prototype/solution.md | 2 +- .../1-defer-to-prototype/task.md | 8 +- .../2-defer-to-prototype-extended/solution.md | 8 +- .../2-defer-to-prototype-extended/task.md | 10 +- .../03-native-prototypes/article.md | 126 ++++++------- .../2-dictionary-tostring/solution.md | 20 +-- .../2-dictionary-tostring/task.md | 22 +-- .../3-compare-calls/solution.md | 4 +- .../3-compare-calls/task.md | 6 +- .../04-prototype-methods/article.md | 146 +++++++-------- 1-js/08-prototypes/index.md | 2 +- 25 files changed, 388 insertions(+), 388 deletions(-) diff --git a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md index 6d25a462a..63156d6a5 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md @@ -1,4 +1,4 @@ -1. `true`, taken from `rabbit`. -2. `null`, taken from `animal`. -3. `undefined`, there's no such property any more. +1. `true` — มาจาก `rabbit` +2. `null` — มาจาก `animal` +3. `undefined` — ไม่มีพร็อพเพอร์ตี้นี้แล้ว diff --git a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md index f38fb6f97..efbfed935 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Working with prototype +# ทดลองใช้โปรโตไทป์ -Here's the code that creates a pair of objects, then modifies them. +โค้ดด้านล่างสร้างออบเจ็กต์ขึ้นมาคู่หนึ่ง แล้วแก้ไขมัน -Which values are shown in the process? +ในแต่ละขั้นตอน ค่าที่แสดงจะเป็นอะไร? ```js let animal = { @@ -28,4 +28,4 @@ delete animal.jumps; alert( rabbit.jumps ); // ? (3) ``` -There should be 3 answers. +ต้องตอบให้ได้ 3 ข้อ diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md index a16796f9c..ba8542266 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md @@ -1,5 +1,5 @@ -1. Let's add `__proto__`: +1. เพิ่ม `__proto__` เข้าไป: ```js run let head = { @@ -27,6 +27,6 @@ alert( table.money ); // undefined ``` -2. In modern engines, performance-wise, there's no difference whether we take a property from an object or its prototype. They remember where the property was found and reuse it in the next request. +2. ในเอนจินสมัยใหม่ ด้านประสิทธิภาพแล้วไม่ต่างกัน ไม่ว่าจะดึงพร็อพเพอร์ตี้จากตัวออบเจ็กต์เองหรือจากโปรโตไทป์ เพราะเอนจินจำไว้ว่าเจอพร็อพเพอร์ตี้ที่ไหน แล้วครั้งต่อไปก็ไปหาตรงนั้นเลย - For instance, for `pockets.glasses` they remember where they found `glasses` (in `head`), and next time will search right there. They are also smart enough to update internal caches if something changes, so that optimization is safe. + ยกตัวอย่าง `pockets.glasses` เอนจินจะจำไว้ว่าเจอ `glasses` ที่ `head` แล้วครั้งต่อไปก็ค้นหาตรงนั้นทันที นอกจากนี้ยังฉลาดพอที่จะอัปเดตแคชเมื่อมีการเปลี่ยนแปลง จึงเป็นการ optimize ที่ปลอดภัย diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md index bc2db47fe..e803d1210 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Searching algorithm +# อัลกอริทึมการค้นหา -The task has two parts. +โจทย์นี้มี 2 ส่วน -Given the following objects: +กำหนดออบเจ็กต์ต่อไปนี้: ```js let head = { @@ -27,5 +27,5 @@ let pockets = { }; ``` -1. Use `__proto__` to assign prototypes in a way that any property lookup will follow the path: `pockets` -> `bed` -> `table` -> `head`. For instance, `pockets.pen` should be `3` (found in `table`), and `bed.glasses` should be `1` (found in `head`). -2. Answer the question: is it faster to get `glasses` as `pockets.glasses` or `head.glasses`? Benchmark if needed. +1. ใช้ `__proto__` กำหนดโปรโตไทป์ให้การค้นหาพร็อพเพอร์ตี้ไล่ตามลำดับ: `pockets` -> `bed` -> `table` -> `head` เช่น `pockets.pen` ควรเป็น `3` (หาเจอใน `table`) และ `bed.glasses` ควรเป็น `1` (หาเจอใน `head`) +2. ตอบคำถาม: ระหว่าง `pockets.glasses` กับ `head.glasses` อันไหนเร็วกว่ากัน? ลอง benchmark ดูถ้าจำเป็น diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md index 4d6ea2653..1ab06c989 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md @@ -1,7 +1,7 @@ -**The answer: `rabbit`.** +**คำตอบ: `rabbit`** -That's because `this` is an object before the dot, so `rabbit.eat()` modifies `rabbit`. +เพราะ `this` คือออบเจ็กต์ที่อยู่หน้าจุด ดังนั้น `rabbit.eat()` จึงแก้ไข `rabbit` -Property lookup and execution are two different things. +การค้นหาพร็อพเพอร์ตี้กับการเรียกใช้เป็นคนละขั้นตอนกัน -The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit`. +เมธอด `rabbit.eat` ถูกค้นพบในโปรโตไทป์ก่อน จากนั้นจึงรันด้วย `this=rabbit` diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md index ed8482c07..a99b7aa47 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Where does it write? +# เขียนค่าลงที่ไหน? -We have `rabbit` inheriting from `animal`. +เรามี `rabbit` สืบทอดจาก `animal` -If we call `rabbit.eat()`, which object receives the `full` property: `animal` or `rabbit`? +ถ้าเรียก `rabbit.eat()` ออบเจ็กต์ไหนจะได้รับพร็อพเพอร์ตี้ `full`: `animal` หรือ `rabbit`? ```js let animal = { diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md index c141b2ecd..ee8a7d8f3 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md @@ -1,18 +1,18 @@ -Let's look carefully at what's going on in the call `speedy.eat("apple")`. +มาดูกันให้ละเอียดว่าเกิดอะไรขึ้นตอนเรียก `speedy.eat("apple")` -1. The method `speedy.eat` is found in the prototype (`=hamster`), then executed with `this=speedy` (the object before the dot). +1. เมธอด `speedy.eat` ถูกค้นพบในโปรโตไทป์ (`=hamster`) แล้วรันด้วย `this=speedy` (ออบเจ็กต์ที่อยู่หน้าจุด) -2. Then `this.stomach.push()` needs to find `stomach` property and call `push` on it. It looks for `stomach` in `this` (`=speedy`), but nothing found. +2. จากนั้น `this.stomach.push()` ต้องหาพร็อพเพอร์ตี้ `stomach` เพื่อเรียก `push` โดยไปหาใน `this` (`=speedy`) ก่อน แต่ไม่เจอ -3. Then it follows the prototype chain and finds `stomach` in `hamster`. +3. จึงไล่ตามห่วงโซ่โปรโตไทป์ขึ้นไป แล้วเจอ `stomach` ใน `hamster` -4. Then it calls `push` on it, adding the food into *the stomach of the prototype*. +4. จากนั้นเรียก `push` บนมัน ซึ่งก็คือเพิ่มอาหารเข้าไปใน *stomach ของโปรโตไทป์* -So all hamsters share a single stomach! +แฮมสเตอร์ทุกตัวจึงใช้ stomach เดียวกัน! -Both for `lazy.stomach.push(...)` and `speedy.stomach.push()`, the property `stomach` is found in the prototype (as it's not in the object itself), then the new data is pushed into it. +ไม่ว่าจะเป็น `lazy.stomach.push(...)` หรือ `speedy.stomach.push()` พร็อพเพอร์ตี้ `stomach` ล้วนถูกค้นพบในโปรโตไทป์ (เพราะไม่ได้อยู่ในตัวออบเจ็กต์เอง) แล้วข้อมูลใหม่ก็ถูก push เข้าไปในนั้น -Please note that such thing doesn't happen in case of a simple assignment `this.stomach=`: +สังเกตว่าปัญหานี้จะไม่เกิดขึ้น ถ้าใช้การ assign ตรงๆ แบบ `this.stomach=`: ```js run let hamster = { @@ -20,7 +20,7 @@ let hamster = { eat(food) { *!* - // assign to this.stomach instead of this.stomach.push + // ใช้ assign แทน this.stomach.push this.stomach = [food]; */!* } @@ -34,17 +34,17 @@ let lazy = { __proto__: hamster }; -// Speedy one found the food +// ตัวเร็วหาอาหารเจอ speedy.eat("apple"); alert( speedy.stomach ); // apple -// Lazy one's stomach is empty -alert( lazy.stomach ); // +// ตัวขี้เกียจท้องยังว่าง +alert( lazy.stomach ); // <ไม่มีอะไร> ``` -Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object. +ตอนนี้ทำงานถูกต้องแล้ว เพราะ `this.stomach=` ไม่ได้ไปค้นหา `stomach` จากโปรโตไทป์ แต่เขียนค่าลงในตัว `this` โดยตรง -Also we can totally avoid the problem by making sure that each hamster has their own stomach: +อีกวิธีหนึ่งคือกำหนดให้แฮมสเตอร์แต่ละตัวมี stomach ของตัวเอง: ```js run let hamster = { @@ -69,12 +69,12 @@ let lazy = { */!* }; -// Speedy one found the food +// ตัวเร็วหาอาหารเจอ speedy.eat("apple"); alert( speedy.stomach ); // apple -// Lazy one's stomach is empty -alert( lazy.stomach ); // +// ตัวขี้เกียจท้องยังว่าง +alert( lazy.stomach ); // <ไม่มีอะไร> ``` -As a common solution, all properties that describe the state of a particular object, like `stomach` above, should be written into that object. That prevents such problems. +โดยทั่วไป พร็อพเพอร์ตี้ที่เก็บสถานะเฉพาะของแต่ละออบเจ็กต์ เช่น `stomach` ข้างต้น ควรประกาศไว้ในตัวออบเจ็กต์นั้นเลย จะได้ไม่เกิดปัญหาแบบนี้ diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md index 50171123d..7288f964e 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Why are both hamsters full? +# ทำไมแฮมสเตอร์ทั้งสองตัวถึงอิ่ม? -We have two hamsters: `speedy` and `lazy` inheriting from the general `hamster` object. +เรามีแฮมสเตอร์สองตัว: `speedy` และ `lazy` สืบทอดจากออบเจ็กต์ `hamster` -When we feed one of them, the other one is also full. Why? How can we fix it? +เวลาให้อาหารตัวหนึ่ง อีกตัวก็อิ่มด้วย ทำไมถึงเป็นแบบนั้น? แก้ยังไงดี? ```js run let hamster = { @@ -25,11 +25,11 @@ let lazy = { __proto__: hamster }; -// This one found the food +// ตัวนี้หาอาหารเจอ speedy.eat("apple"); alert( speedy.stomach ); // apple -// This one also has it, why? fix please. +// ตัวนี้ก็มีด้วย ทำไม? แก้ด้วยนะ alert( lazy.stomach ); // apple ``` diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index ef6c7ffeb..3e9ebe30d 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -1,22 +1,22 @@ -# Prototypal inheritance +# การสืบทอดแบบโปรโตไทป์ (Prototypal inheritance) -In programming, we often want to take something and extend it. +ในการเขียนโปรแกรม เรามักต้องการนำสิ่งที่มีอยู่แล้วมาต่อยอด -For instance, we have a `user` object with its properties and methods, and want to make `admin` and `guest` as slightly modified variants of it. We'd like to reuse what we have in `user`, not copy/reimplement its methods, just build a new object on top of it. +ยกตัวอย่างเช่น เรามีออบเจ็กต์ `user` ที่มีพร็อพเพอร์ตี้และเมธอดต่างๆ แล้วอยากจะสร้าง `admin` กับ `guest` ที่ปรับเปลี่ยนจาก `user` เล็กน้อย เราอยากนำสิ่งที่มีอยู่ใน `user` มาใช้ซ้ำได้เลย ไม่ต้องก๊อปปี้หรือเขียนเมธอดใหม่ แค่สร้างออบเจ็กต์ใหม่ต่อยอดจากมัน -*Prototypal inheritance* is a language feature that helps in that. +*การสืบทอดแบบโปรโตไทป์ (Prototypal inheritance)* เป็นฟีเจอร์ของภาษาที่ช่วยเรื่องนี้ได้พอดี ## [[Prototype]] -In JavaScript, objects have a special hidden property `[[Prototype]]` (as named in the specification), that is either `null` or references another object. That object is called "a prototype": +ออบเจ็กต์ใน JavaScript มีพร็อพเพอร์ตี้พิเศษที่ซ่อนอยู่ชื่อ `[[Prototype]]` (ตามชื่อในสเปค) ซึ่งมีค่าเป็น `null` หรือเป็นการอ้างอิงไปยังออบเจ็กต์อีกตัวหนึ่ง ออบเจ็กต์ตัวนั้นเราเรียกว่า "โปรโตไทป์" (prototype): ![prototype](object-prototype-empty.svg) -When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. +เวลาอ่านพร็อพเพอร์ตี้จาก `object` แล้วหาไม่เจอ JavaScript จะไปหาจากโปรโตไทป์ให้โดยอัตโนมัติ ในการเขียนโปรแกรมเราเรียกกลไกนี้ว่า "การสืบทอดแบบโปรโตไทป์" (prototypal inheritance) ต่อจากนี้เราจะได้เห็นตัวอย่างมากมาย รวมถึงฟีเจอร์เจ๋งๆ ที่สร้างอยู่บนพื้นฐานของกลไกนี้ -The property `[[Prototype]]` is internal and hidden, but there are many ways to set it. +`[[Prototype]]` เป็นพร็อพเพอร์ตี้ภายในที่ซ่อนอยู่ แต่มีหลายวิธีในการกำหนดค่าให้มัน -One of them is to use the special name `__proto__`, like this: +วิธีหนึ่งคือใช้ชื่อพิเศษ `__proto__` แบบนี้: ```js run let animal = { @@ -27,13 +27,13 @@ let rabbit = { }; *!* -rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal +rabbit.__proto__ = animal; // กำหนดให้ rabbit.[[Prototype]] = animal */!* ``` -Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`. +ทีนี้ถ้าเราอ่านพร็อพเพอร์ตี้จาก `rabbit` แล้วหาไม่เจอ JavaScript จะไปดึงจาก `animal` ให้เอง -For instance: +ลองดูตัวอย่าง: ```js let animal = { @@ -47,24 +47,24 @@ let rabbit = { rabbit.__proto__ = animal; // (*) */!* -// we can find both properties in rabbit now: +// ตอนนี้เราหาพร็อพเพอร์ตี้ทั้งสองตัวได้จาก rabbit: *!* alert( rabbit.eats ); // true (**) */!* alert( rabbit.jumps ); // true ``` -Here the line `(*)` sets `animal` to be the prototype of `rabbit`. +บรรทัด `(*)` กำหนดให้ `animal` เป็นโปรโตไทป์ของ `rabbit` -Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up): +จากนั้นเมื่อ `alert` พยายามอ่านพร็อพเพอร์ตี้ `rabbit.eats` `(**)` ซึ่งไม่มีอยู่ใน `rabbit` JavaScript จะไล่ตาม `[[Prototype]]` ขึ้นไปจนเจอใน `animal` (ดูจากล่างขึ้นบน): ![](proto-animal-rabbit.svg) -Here we can say that "`animal` is the prototype of `rabbit`" or "`rabbit` prototypically inherits from `animal`". +ตรงนี้เราพูดได้ว่า "`animal` เป็นโปรโตไทป์ของ `rabbit`" หรือ "`rabbit` สืบทอดมาจาก `animal` ผ่านโปรโตไทป์" -So if `animal` has a lot of useful properties and methods, then they become automatically available in `rabbit`. Such properties are called "inherited". +ดังนั้นถ้า `animal` มีพร็อพเพอร์ตี้และเมธอดที่มีประโยชน์อยู่เยอะ สิ่งเหล่านั้นจะใช้ได้จาก `rabbit` โดยอัตโนมัติ พร็อพเพอร์ตี้แบบนี้เรียกว่า "สืบทอดมา" (inherited) -If we have a method in `animal`, it can be called on `rabbit`: +ถ้า `animal` มีเมธอดอยู่ เราก็เรียกใช้จาก `rabbit` ได้เลย: ```js run let animal = { @@ -81,17 +81,17 @@ let rabbit = { __proto__: animal }; -// walk is taken from the prototype +// walk ถูกดึงมาจากโปรโตไทป์ *!* rabbit.walk(); // Animal walk */!* ``` -The method is automatically taken from the prototype, like this: +เมธอดถูกดึงมาจากโปรโตไทป์โดยอัตโนมัติ ตามภาพนี้: ![](proto-animal-rabbit-walk.svg) -The prototype chain can be longer: +ห่วงโซ่โปรโตไทป์ (prototype chain) ยาวกว่านี้ก็ได้: ```js run let animal = { @@ -115,47 +115,47 @@ let longEar = { */!* }; -// walk is taken from the prototype chain +// walk ถูกดึงมาจากห่วงโซ่โปรโตไทป์ longEar.walk(); // Animal walk -alert(longEar.jumps); // true (from rabbit) +alert(longEar.jumps); // true (มาจาก rabbit) ``` ![](proto-animal-rabbit-chain.svg) -Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`. +ตอนนี้ถ้าเราอ่านอะไรจาก `longEar` แล้วหาไม่เจอ JavaScript จะไปหาใน `rabbit` ก่อน แล้วค่อยไปหาใน `animal` ต่อ -There are only two limitations: +มีข้อจำกัดอยู่ 2 ข้อ: -1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle. -2. The value of `__proto__` can be either an object or `null`. Other types are ignored. +1. การอ้างอิงจะวนเป็นวงกลมไม่ได้ ถ้าพยายามกำหนด `__proto__` ให้เป็นวง JavaScript จะฟ้อง error +2. ค่าของ `__proto__` ต้องเป็นออบเจ็กต์หรือ `null` เท่านั้น ชนิดอื่นจะถูกเพิกเฉย -Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others. +อีกอย่างที่ค่อนข้างชัดอยู่แล้ว แต่อยากบอกไว้: ออบเจ็กต์หนึ่งตัวมี `[[Prototype]]` ได้แค่ตัวเดียว จะสืบทอดจากสองออบเจ็กต์พร้อมกันไม่ได้ -```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" -It's a common mistake of novice developers not to know the difference between these two. +```smart header="`__proto__` เป็น getter/setter เก่าแก่ของ `[[Prototype]]`" +ข้อผิดพลาดที่พบบ่อยสำหรับนักพัฒนามือใหม่คือสับสนระหว่างสองสิ่งนี้ -Please note that `__proto__` is *not the same* as the internal `[[Prototype]]` property. It's a getter/setter for `[[Prototype]]`. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language. +ควรรู้ว่า `__proto__` *ไม่ใช่สิ่งเดียวกัน*กับพร็อพเพอร์ตี้ภายใน `[[Prototype]]` แต่เป็น getter/setter ของ `[[Prototype]]` ต่างหาก ต่อไปเราจะเจอสถานการณ์ที่ความแตกต่างนี้สำคัญ ตอนนี้แค่จำไว้ก่อนนะ -The `__proto__` property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use `Object.getPrototypeOf/Object.setPrototypeOf` functions instead that get/set the prototype. We'll also cover these functions later. +`__proto__` ถือว่าเก่าไปแล้ว มีอยู่ด้วยเหตุผลทางประวัติศาสตร์ JavaScript สมัยใหม่แนะนำให้ใช้ `Object.getPrototypeOf/Object.setPrototypeOf` แทน ซึ่งเราจะพูดถึงภายหลัง -By the specification, `__proto__` must only be supported by browsers. In fact though, all environments including server-side support `__proto__`, so we're quite safe using it. +ตามสเปค `__proto__` ต้องซัพพอร์ตในเบราว์เซอร์เท่านั้น แต่ในทางปฏิบัติทุกสภาพแวดล้อมรวมถึงฝั่งเซิร์ฟเวอร์ก็ซัพพอร์ต `__proto__` ด้วย จึงใช้ได้อย่างปลอดภัย -As the `__proto__` notation is a bit more intuitively obvious, we use it in the examples. +เนื่องจาก `__proto__` อ่านเข้าใจง่ายกว่า เราจึงใช้ในตัวอย่างต่างๆ ``` -## Writing doesn't use prototype +## การเขียนค่าไม่ผ่านโปรโตไทป์ -The prototype is only used for reading properties. +โปรโตไทป์ถูกใช้เฉพาะตอน*อ่าน*พร็อพเพอร์ตี้เท่านั้น -Write/delete operations work directly with the object. +การเขียนหรือลบจะทำกับตัวออบเจ็กต์โดยตรง -In the example below, we assign its own `walk` method to `rabbit`: +ในตัวอย่างด้านล่าง เรากำหนดเมธอด `walk` ของ `rabbit` เอง: ```js run let animal = { eats: true, walk() { - /* this method won't be used by rabbit */ + /* rabbit จะไม่ใช้เมธอดนี้ */ } }; @@ -172,13 +172,13 @@ rabbit.walk = function() { rabbit.walk(); // Rabbit! Bounce-bounce! ``` -From now on, `rabbit.walk()` call finds the method immediately in the object and executes it, without using the prototype: +จากนี้ไป การเรียก `rabbit.walk()` จะเจอเมธอดในตัวออบเจ็กต์ทันทีและรันเลย โดยไม่ต้องไปหาจากโปรโตไทป์: ![](proto-animal-rabbit-walk-2.svg) -Accessor properties are an exception, as assignment is handled by a setter function. So writing to such a property is actually the same as calling a function. +แต่มีข้อยกเว้นสำหรับ accessor property เนื่องจากการกำหนดค่าจะถูกจัดการโดยฟังก์ชัน setter ดังนั้นการเขียนค่าให้พร็อพเพอร์ตี้แบบนี้ก็เหมือนกับการเรียกฟังก์ชันนั่นเอง -For that reason `admin.fullName` works correctly in the code below: +ด้วยเหตุนี้ `admin.fullName` จึงทำงานได้ถูกต้องในโค้ดด้านล่าง: ```js run let user = { @@ -201,33 +201,33 @@ let admin = { alert(admin.fullName); // John Smith (*) -// setter triggers! +// setter ทำงาน! admin.fullName = "Alice Cooper"; // (**) -alert(admin.fullName); // Alice Cooper, state of admin modified -alert(user.fullName); // John Smith, state of user protected +alert(admin.fullName); // Alice Cooper, สถานะของ admin เปลี่ยน +alert(user.fullName); // John Smith, สถานะของ user ไม่ถูกแตะต้อง ``` -Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called. +ในบรรทัด `(*)` พร็อพเพอร์ตี้ `admin.fullName` มี getter อยู่ในโปรโตไทป์ `user` จึงเรียก getter ตัวนั้น ส่วนบรรทัด `(**)` มี setter อยู่ในโปรโตไทป์ จึงเรียก setter แทน -## The value of "this" +## ค่าของ "this" -An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where are the properties `this.name` and `this.surname` written: into `user` or `admin`? +คำถามที่น่าสนใจจากตัวอย่างข้างบนคือ: ค่าของ `this` ใน `set fullName(value)` คืออะไร? พร็อพเพอร์ตี้ `this.name` กับ `this.surname` ถูกเขียนลงใน `user` หรือ `admin` กันแน่? -The answer is simple: `this` is not affected by prototypes at all. +คำตอบง่ายมาก: โปรโตไทป์ไม่ส่งผลต่อ `this` เลย -**No matter where the method is found: in an object or its prototype. In a method call, `this` is always the object before the dot.** +**ไม่ว่าจะเจอเมธอดที่ไหน จะอยู่ในตัวออบเจ็กต์หรือโปรโตไทป์ก็ตาม เวลาเรียกเมธอด `this` จะเป็นออบเจ็กต์ที่อยู่หน้าจุดเสมอ** -So, the setter call `admin.fullName=` uses `admin` as `this`, not `user`. +ดังนั้นเมื่อเรียก setter ด้วย `admin.fullName=` ค่า `this` จะเป็น `admin` ไม่ใช่ `user` -That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object. +เรื่องนี้สำคัญมากๆ เพราะเราอาจมีออบเจ็กต์ใหญ่ที่มีเมธอดเยอะ แล้วมีออบเจ็กต์อื่นสืบทอดมาจากมัน เวลาออบเจ็กต์ลูกเรียกใช้เมธอดที่สืบทอดมา จะแก้ไขแค่สถานะของตัวเองเท่านั้น ไม่กระทบออบเจ็กต์ต้นทาง -For instance, here `animal` represents a "method storage", and `rabbit` makes use of it. +ยกตัวอย่าง ที่นี่ `animal` ทำหน้าที่เป็น "คลังเก็บเมธอด" แล้ว `rabbit` ก็มาใช้เมธอดเหล่านั้น -The call `rabbit.sleep()` sets `this.isSleeping` on the `rabbit` object: +การเรียก `rabbit.sleep()` จะกำหนดค่า `this.isSleeping` บนออบเจ็กต์ `rabbit`: ```js run -// animal has methods +// animal มีเมธอดต่างๆ let animal = { walk() { if (!this.isSleeping) { @@ -244,26 +244,26 @@ let rabbit = { __proto__: animal }; -// modifies rabbit.isSleeping +// แก้ไขค่า rabbit.isSleeping rabbit.sleep(); alert(rabbit.isSleeping); // true -alert(animal.isSleeping); // undefined (no such property in the prototype) +alert(animal.isSleeping); // undefined (ไม่มีพร็อพเพอร์ตี้นี้ในโปรโตไทป์) ``` -The resulting picture: +ผลลัพธ์เป็นภาพแบบนี้: ![](proto-animal-rabbit-walk-3.svg) -If we had other objects, like `bird`, `snake`, etc., inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method call would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects. +ถ้ามีออบเจ็กต์อื่นอย่าง `bird`, `snake` ฯลฯ สืบทอดมาจาก `animal` พวกมันก็จะเข้าถึงเมธอดของ `animal` ได้เหมือนกัน แต่ `this` ในแต่ละการเรียกเมธอดจะเป็นออบเจ็กต์ตัวที่เรียก (ตัวหน้าจุด) ไม่ใช่ `animal` ดังนั้นเวลาเขียนข้อมูลลง `this` ข้อมูลจะถูกเก็บในออบเจ็กต์แต่ละตัว -As a result, methods are shared, but the object state is not. +สรุปก็คือ เมธอดแชร์กันได้ แต่สถานะของแต่ละออบเจ็กต์แยกกัน -## for..in loop +## ลูป for..in -The `for..in` loop iterates over inherited properties too. +ลูป `for..in` จะวนรวมพร็อพเพอร์ตี้ที่สืบทอดมาด้วย -For instance: +ลองดูตัวอย่าง: ```js run let animal = { @@ -276,19 +276,19 @@ let rabbit = { }; *!* -// Object.keys only returns own keys +// Object.keys คืนเฉพาะ key ของตัวเอง alert(Object.keys(rabbit)); // jumps */!* *!* -// for..in loops over both own and inherited keys -for(let prop in rabbit) alert(prop); // jumps, then eats +// for..in วนทั้ง key ของตัวเองและ key ที่สืบทอดมา +for(let prop in rabbit) alert(prop); // jumps, แล้วก็ eats */!* ``` -If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. +ถ้าไม่ต้องการแบบนั้น และอยากตัดพร็อพเพอร์ตี้ที่สืบทอดมาออก มีเมธอดสำเร็จรูป [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty) ซึ่งจะคืนค่า `true` ถ้า `obj` มีพร็อพเพอร์ตี้ชื่อ `key` เป็นของตัวเอง (ไม่ได้สืบทอดมา) -So we can filter out inherited properties (or do something else with them): +เราจึงกรองพร็อพเพอร์ตี้ที่สืบทอดมาออกได้ (หรือจะทำอะไรอื่นกับมันก็ได้): ```js run let animal = { @@ -304,35 +304,35 @@ for(let prop in rabbit) { let isOwn = rabbit.hasOwnProperty(prop); if (isOwn) { - alert(`Our: ${prop}`); // Our: jumps + alert(`ของเรา: ${prop}`); // ของเรา: jumps } else { - alert(`Inherited: ${prop}`); // Inherited: eats + alert(`สืบทอดมา: ${prop}`); // สืบทอดมา: eats } } ``` -Here we have the following inheritance chain: `rabbit` inherits from `animal`, that inherits from `Object.prototype` (because `animal` is a literal object `{...}`, so it's by default), and then `null` above it: +ห่วงโซ่การสืบทอดในที่นี้เป็นแบบนี้: `rabbit` สืบทอดจาก `animal` ซึ่งสืบทอดจาก `Object.prototype` (เพราะ `animal` เป็นออบเจ็กต์ลิเทอรัล `{...}` จึงเป็นค่าเริ่มต้น) แล้วถัดขึ้นไปก็เป็น `null`: ![](rabbit-animal-object.svg) -Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? We did not define it. Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited. +ลองสังเกตสิ่งที่น่าสนใจอย่างหนึ่ง เมธอด `rabbit.hasOwnProperty` มาจากไหน? เราไม่ได้ประกาศมันเอง ไล่ดูตามห่วงโซ่ก็จะเห็นว่าเมธอดนี้มาจาก `Object.prototype.hasOwnProperty` พูดง่ายๆ ก็คือสืบทอดมานั่นเอง -...But why does `hasOwnProperty` not appear in the `for..in` loop like `eats` and `jumps` do, if `for..in` lists inherited properties? +...แต่ทำไม `hasOwnProperty` ไม่โผล่ในลูป `for..in` เหมือน `eats` กับ `jumps` ล่ะ ในเมื่อ `for..in` วนรวมพร็อพเพอร์ตี้ที่สืบทอดมาด้วย? -The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. And `for..in` only lists enumerable properties. That's why it and the rest of the `Object.prototype` properties are not listed. +คำตอบง่ายมาก: เพราะมันไม่ใช่ enumerable เหมือนพร็อพเพอร์ตี้อื่นๆ ทั้งหมดของ `Object.prototype` มันมี flag `enumerable:false` อยู่ และ `for..in` จะวนเฉพาะพร็อพเพอร์ตี้ที่เป็น enumerable เท่านั้น จึงไม่แสดง `hasOwnProperty` และพร็อพเพอร์ตี้อื่นๆ ของ `Object.prototype` -```smart header="Almost all other key/value-getting methods ignore inherited properties" -Almost all other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties. +```smart header="เมธอดดึง key/value อื่นๆ เกือบทั้งหมดจะข้ามพร็อพเพอร์ตี้ที่สืบทอดมา" +เมธอดดึง key/value อื่นๆ เกือบทั้งหมด เช่น `Object.keys`, `Object.values` ฯลฯ จะข้ามพร็อพเพอร์ตี้ที่สืบทอดมา -They only operate on the object itself. Properties from the prototype are *not* taken into account. +เมธอดเหล่านี้ทำงานกับตัวออบเจ็กต์เองเท่านั้น พร็อพเพอร์ตี้จากโปรโตไทป์*ไม่ถูกนับรวม* ``` -## Summary +## สรุป -- In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`. -- We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon). -- The object referenced by `[[Prototype]]` is called a "prototype". -- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype. -- Write/delete operations act directly on the object, they don't use the prototype (assuming it's a data property, not a setter). -- If we call `obj.method()`, and the `method` is taken from the prototype, `this` still references `obj`. So methods always work with the current object even if they are inherited. -- The `for..in` loop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself. +- ออบเจ็กต์ทุกตัวใน JavaScript มีพร็อพเพอร์ตี้ซ่อน `[[Prototype]]` ซึ่งมีค่าเป็นออบเจ็กต์อีกตัวหรือ `null` +- เราเข้าถึงได้ผ่าน `obj.__proto__` (เป็น getter/setter เก่าแก่ ยังมีวิธีอื่นที่จะพูดถึงเร็วๆ นี้) +- ออบเจ็กต์ที่ `[[Prototype]]` อ้างอิงถึงเรียกว่า "โปรโตไทป์" +- ถ้าเราอ่านพร็อพเพอร์ตี้ของ `obj` หรือเรียกเมธอดแล้วหาไม่เจอ JavaScript จะไปหาจากโปรโตไทป์ให้ +- การเขียนหรือลบจะทำกับตัวออบเจ็กต์โดยตรง ไม่ผ่านโปรโตไทป์ (ยกเว้นกรณีที่เป็น setter) +- ถ้าเรียก `obj.method()` แล้วเมธอดมาจากโปรโตไทป์ `this` ก็ยังอ้างถึง `obj` อยู่ดี ดังนั้นเมธอดจะทำงานกับออบเจ็กต์ปัจจุบันเสมอ แม้จะเป็นเมธอดที่สืบทอดมา +- ลูป `for..in` จะวนทั้งพร็อพเพอร์ตี้ของตัวเองและที่สืบทอดมา ส่วนเมธอดดึง key/value อื่นๆ จะทำงานกับตัวออบเจ็กต์เองเท่านั้น diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md index ebbdf3a7c..d96502668 100644 --- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md +++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md @@ -1,20 +1,20 @@ -Answers: +คำตอบ: -1. `true`. +1. `true` - The assignment to `Rabbit.prototype` sets up `[[Prototype]]` for new objects, but it does not affect the existing ones. + การกำหนดค่าใหม่ให้ `Rabbit.prototype` จะมีผลกับออบเจ็กต์ที่สร้างใหม่หลังจากนี้เท่านั้น ออบเจ็กต์ที่สร้างไปแล้วยังคงอ้างอิงไปยัง `[[Prototype]]` เดิม -2. `false`. +2. `false` - Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`. + ออบเจ็กต์ถูกกำหนดค่าโดยการอ้างอิง (by reference) ออบเจ็กต์จาก `Rabbit.prototype` ไม่ได้ถูกคัดลอก แต่เป็นออบเจ็กต์ตัวเดียวกันที่ถูกอ้างอิงทั้งจาก `Rabbit.prototype` และจาก `[[Prototype]]` ของ `rabbit` - So when we change its content through one reference, it is visible through the other one. + ดังนั้นเมื่อเราเปลี่ยนเนื้อหาผ่านทางการอ้างอิงข้างหนึ่ง อีกข้างก็จะเห็นการเปลี่ยนแปลงด้วย -3. `true`. +3. `true` - All `delete` operations are applied directly to the object. Here `delete rabbit.eats` tries to remove `eats` property from `rabbit`, but it doesn't have it. So the operation won't have any effect. + คำสั่ง `delete` ทำงานโดยตรงกับตัวออบเจ็กต์นั้นๆ เอง `delete rabbit.eats` พยายามลบพร็อพเพอร์ตี้ `eats` จาก `rabbit` แต่ `rabbit` เองไม่มีพร็อพเพอร์ตี้นี้ (มันมาจาก prototype) จึงไม่มีผลอะไรเกิดขึ้น -4. `undefined`. +4. `undefined` - The property `eats` is deleted from the prototype, it doesn't exist any more. + พร็อพเพอร์ตี้ `eats` ถูกลบออกจาก prototype ไปแล้ว จึงไม่มีอยู่อีกต่อไป diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md index 2838c125a..3dedb5795 100644 --- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md +++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Changing "prototype" +# เปลี่ยน "prototype" -In the code below we create `new Rabbit`, and then try to modify its prototype. +ในโค้ดด้านล่าง เราสร้าง `new Rabbit` แล้วลองเปลี่ยน prototype ของมัน -In the start, we have this code: +เริ่มต้นเรามีโค้ดนี้: ```js run function Rabbit() {} @@ -20,7 +20,7 @@ alert( rabbit.eats ); // true ``` -1. We added one more string (emphasized). What will `alert` show now? +1. เราเพิ่มบรรทัดใหม่เข้าไป (ส่วนที่เน้นสี) คราวนี้ `alert` จะแสดงอะไร? ```js function Rabbit() {} @@ -37,7 +37,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -2. ...And if the code is like this (replaced one line)? +2. ...แล้วถ้าโค้ดเป็นแบบนี้ล่ะ (เปลี่ยนบรรทัดเดียว)? ```js function Rabbit() {} @@ -54,7 +54,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -3. And like this (replaced one line)? +3. แล้วถ้าเป็นแบบนี้ (เปลี่ยนบรรทัดเดียว)? ```js function Rabbit() {} @@ -71,7 +71,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -4. The last variant: +4. แบบสุดท้าย: ```js function Rabbit() {} diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md index 372d50dd6..ae7d728b4 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md @@ -1,6 +1,6 @@ -We can use such approach if we are sure that `"constructor"` property has the correct value. +วิธีนี้ใช้ได้ก็ต่อเมื่อเรามั่นใจว่าพร็อพเพอร์ตี้ `"constructor"` ชี้ไปยังค่าที่ถูกต้อง -For instance, if we don't touch the default `"prototype"`, then this code works for sure: +ยกตัวอย่าง ถ้าเราไม่ไปแตะ `"prototype"` เริ่มต้น โค้ดนี้ก็จะทำงานได้: ```js run function User(name) { @@ -10,14 +10,14 @@ function User(name) { let user = new User('John'); let user2 = new user.constructor('Pete'); -alert( user2.name ); // Pete (worked!) +alert( user2.name ); // Pete (ทำงานได้!) ``` -It worked, because `User.prototype.constructor == User`. +ทำงานได้เพราะ `User.prototype.constructor == User` -..But if someone, so to speak, overwrites `User.prototype` and forgets to recreate `constructor` to reference `User`, then it would fail. +..แต่ถ้ามีใครไปเขียนทับ `User.prototype` แล้วลืมกำหนด `constructor` ให้ชี้กลับไปที่ `User` โค้ดก็จะพังทันที -For instance: +ยกตัวอย่าง: ```js run function User(name) { @@ -33,17 +33,17 @@ let user2 = new user.constructor('Pete'); alert( user2.name ); // undefined ``` -Why `user2.name` is `undefined`? +ทำไม `user2.name` ถึงเป็น `undefined`? -Here's how `new user.constructor('Pete')` works: +มาดูกันว่า `new user.constructor('Pete')` ทำงานอย่างไร: -1. First, it looks for `constructor` in `user`. Nothing. -2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!). -3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`. -4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used. +1. แรกสุดมันหา `constructor` ใน `user` ก่อน -- ไม่เจอ +2. จากนั้นก็ไล่ขึ้นไปตาม prototype chain โปรโตไทป์ของ `user` คือ `User.prototype` ซึ่งก็ไม่มี `constructor` เช่นกัน (เพราะเรา "ลืม" กำหนดไว้) +3. ไล่ขึ้นไปอีก `User.prototype` เป็นออบเจ็กต์ธรรมดา โปรโตไทป์ของมันคือ `Object.prototype` ที่มีมาในตัว +4. สุดท้ายก็ไปเจอ `Object.prototype.constructor == Object` จึงใช้ตัวนี้แทน -Finally, at the end, we have `let user2 = new Object('Pete')`. +สรุปแล้วสิ่งที่เกิดขึ้นคือ `let user2 = new Object('Pete')` -Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`. +ซึ่งไม่ใช่สิ่งที่เราต้องการ เราอยากสร้าง `new User` ไม่ใช่ `new Object` ทั้งหมดนี้เกิดจากการที่ `constructor` หายไป -(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all). \ No newline at end of file +(เกร็ดเล็กๆ: `new Object(...)` จะแปลงอาร์กิวเมนต์ให้เป็นออบเจ็กต์ แต่เป็นแค่ความรู้ทางทฤษฎี ในทางปฏิบัติไม่มีใครเรียก `new Object` โดยส่งค่าเข้าไป และโดยทั่วไปเราก็ไม่ใช้ `new Object` ในการสร้างออบเจ็กต์อยู่แล้ว) diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md index 934f3470b..c341ddb2a 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md @@ -2,14 +2,14 @@ importance: 5 --- -# Create an object with the same constructor +# สร้างออบเจ็กต์ด้วยคอนสตรักเตอร์ตัวเดิม -Imagine, we have an arbitrary object `obj`, created by a constructor function -- we don't know which one, but we'd like to create a new object using it. +สมมติว่าเรามีออบเจ็กต์ `obj` ตัวหนึ่ง ที่ถูกสร้างจากคอนสตรักเตอร์ฟังก์ชัน แต่เราไม่รู้ว่าตัวไหน แล้วเราต้องการสร้างออบเจ็กต์ใหม่จากคอนสตรักเตอร์เดียวกัน -Can we do it like that? +เราเขียนแบบนี้ได้ไหม? ```js let obj2 = new obj.constructor(); ``` -Give an example of a constructor function for `obj` which lets such code work right. And an example that makes it work wrong. +ลองยกตัวอย่างคอนสตรักเตอร์ฟังก์ชันของ `obj` ที่ทำให้โค้ดข้างบนทำงานได้ถูกต้อง และอีกตัวอย่างที่ทำให้ทำงานผิดพลาด diff --git a/1-js/08-prototypes/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md index b1ef51826..08ccbddc1 100644 --- a/1-js/08-prototypes/02-function-prototype/article.md +++ b/1-js/08-prototypes/02-function-prototype/article.md @@ -1,18 +1,18 @@ # F.prototype -Remember, new objects can be created with a constructor function, like `new F()`. +จำได้ไหมว่าเราสามารถสร้างออบเจ็กต์ใหม่ด้วยคอนสตรักเตอร์ฟังก์ชัน เช่น `new F()` -If `F.prototype` is an object, then the `new` operator uses it to set `[[Prototype]]` for the new object. +ถ้า `F.prototype` เป็นออบเจ็กต์ ตัวดำเนินการ `new` จะนำมันไปตั้งค่าเป็น `[[Prototype]]` ให้กับออบเจ็กต์ใหม่ ```smart -JavaScript had prototypal inheritance from the beginning. It was one of the core features of the language. +JavaScript มีการสืบทอดแบบโปรโตไทป์ตั้งแต่แรกเริ่มเลย ถือเป็นหนึ่งในฟีเจอร์หลักของภาษา -But in the old times, there was no direct access to it. The only thing that worked reliably was a `"prototype"` property of the constructor function, described in this chapter. So there are many scripts that still use it. +แต่ในสมัยก่อนยังเข้าถึงมันโดยตรงไม่ได้ วิธีเดียวที่ใช้ได้อย่างน่าเชื่อถือคือพร็อพเพอร์ตี้ `"prototype"` ของคอนสตรักเตอร์ฟังก์ชัน ซึ่งจะอธิบายในบทนี้ จึงยังมีโค้ดจำนวนมากที่ใช้วิธีนี้อยู่ ``` -Please note that `F.prototype` here means a regular property named `"prototype"` on `F`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name. +สิ่งที่ควรรู้คือ `F.prototype` ในที่นี้หมายถึงพร็อพเพอร์ตี้ธรรมดาที่ชื่อ `"prototype"` บน `F` ฟังดูคล้ายกับคำว่า "โปรโตไทป์" ในเชิงแนวคิด แต่จริงๆ แล้วเป็นแค่พร็อพเพอร์ตี้ปกติที่ชื่อนี้เท่านั้น -Here's the example: +ลองดูตัวอย่าง: ```js run let animal = { @@ -32,65 +32,65 @@ let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal alert( rabbit.eats ); // true ``` -Setting `Rabbit.prototype = animal` literally states the following: "When a `new Rabbit` is created, assign its `[[Prototype]]` to `animal`". +การเขียน `Rabbit.prototype = animal` มีความหมายว่า "เมื่อสร้าง `new Rabbit` ขึ้นมา ให้กำหนด `[[Prototype]]` ของมันเป็น `animal`" -That's the resulting picture: +ภาพรวมจะเป็นแบบนี้: ![](proto-constructor-animal-rabbit.svg) -On the picture, `"prototype"` is a horizontal arrow, meaning a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`. +ในรูป `"prototype"` คือลูกศรแนวนอน หมายถึงพร็อพเพอร์ตี้ธรรมดา ส่วน `[[Prototype]]` คือลูกศรแนวตั้ง หมายถึงการที่ `rabbit` สืบทอดมาจาก `animal` -```smart header="`F.prototype` only used at `new F` time" -`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. +```smart header="`F.prototype` ใช้แค่ตอน `new F` เท่านั้น" +พร็อพเพอร์ตี้ `F.prototype` จะถูกใช้เฉพาะตอนเรียก `new F` เพื่อกำหนด `[[Prototype]]` ให้กับออบเจ็กต์ใหม่ -If, after the creation, `F.prototype` property changes (`F.prototype = `), then new objects created by `new F` will have another object as `[[Prototype]]`, but already existing objects keep the old one. +ถ้าหลังจากสร้างออบเจ็กต์ไปแล้ว เราเปลี่ยน `F.prototype` (`F.prototype = <ออบเจ็กต์อื่น>`) ออบเจ็กต์ใหม่ที่สร้างจาก `new F` จะได้ `[[Prototype]]` เป็นออบเจ็กต์ตัวใหม่ แต่ออบเจ็กต์เดิมที่สร้างไปก่อนหน้ายังคงอ้างอิงไปยังตัวเก่าอยู่ ``` -## Default F.prototype, constructor property +## Default F.prototype กับ constructor property -Every function has the `"prototype"` property even if we don't supply it. +ทุกฟังก์ชันจะมีพร็อพเพอร์ตี้ `"prototype"` อยู่แล้ว แม้เราจะไม่ได้กำหนดเอง -The default `"prototype"` is an object with the only property `constructor` that points back to the function itself. +ค่าเริ่มต้นของ `"prototype"` เป็นออบเจ็กต์ที่มีพร็อพเพอร์ตี้ `constructor` ชี้กลับไปยังตัวฟังก์ชันเอง -Like this: +แบบนี้: ```js function Rabbit() {} -/* default prototype +/* prototype เริ่มต้น Rabbit.prototype = { constructor: Rabbit }; */ ``` ![](function-prototype-constructor.svg) -We can check it: +ลองตรวจสอบดู: ```js run function Rabbit() {} -// by default: +// ค่าเริ่มต้น: // Rabbit.prototype = { constructor: Rabbit } alert( Rabbit.prototype.constructor == Rabbit ); // true ``` -Naturally, if we do nothing, the `constructor` property is available to all rabbits through `[[Prototype]]`: +ถ้าเราไม่ไปแก้ไขอะไร พร็อพเพอร์ตี้ `constructor` จะถูกส่งต่อไปยัง rabbit ทุกตัวผ่าน `[[Prototype]]`: ```js run function Rabbit() {} -// by default: +// ค่าเริ่มต้น: // Rabbit.prototype = { constructor: Rabbit } -let rabbit = new Rabbit(); // inherits from {constructor: Rabbit} +let rabbit = new Rabbit(); // สืบทอดจาก {constructor: Rabbit} -alert(rabbit.constructor == Rabbit); // true (from prototype) +alert(rabbit.constructor == Rabbit); // true (มาจาก prototype) ``` ![](rabbit-prototype-constructor.svg) -We can use `constructor` property to create a new object using the same constructor as the existing one. +เราสามารถใช้พร็อพเพอร์ตี้ `constructor` สร้างออบเจ็กต์ใหม่โดยใช้คอนสตรักเตอร์เดียวกับที่สร้างออบเจ็กต์เดิมได้ -Like here: +ลองดูตัวอย่าง: ```js run function Rabbit(name) { @@ -105,17 +105,17 @@ let rabbit2 = new rabbit.constructor("Black Rabbit"); */!* ``` -That's handy when we have an object, don't know which constructor was used for it (e.g. it comes from a 3rd party library), and we need to create another one of the same kind. +วิธีนี้มีประโยชน์มากเวลาที่เรามีออบเจ็กต์อยู่แล้ว แต่ไม่รู้ว่ามันถูกสร้างด้วยคอนสตรักเตอร์ตัวไหน (เช่น มาจาก library ภายนอก) แล้วต้องการสร้างออบเจ็กต์อีกตัวที่เป็นชนิดเดียวกัน -But probably the most important thing about `"constructor"` is that... +แต่สิ่งสำคัญที่สุดเกี่ยวกับ `"constructor"` ก็คือ... -**...JavaScript itself does not ensure the right `"constructor"` value.** +**...JavaScript ไม่ได้รับประกันว่าค่าของ `"constructor"` จะถูกต้องเสมอไป** -Yes, it exists in the default `"prototype"` for functions, but that's all. What happens with it later -- is totally on us. +ใช่ มันมีอยู่ใน `"prototype"` เริ่มต้นของฟังก์ชัน แต่แค่นั้นเอง หลังจากนั้นจะเกิดอะไรขึ้นก็ขึ้นอยู่กับเราทั้งหมด -In particular, if we replace the default prototype as a whole, then there will be no `"constructor"` in it. +โดยเฉพาะถ้าเราเขียนทับ prototype เริ่มต้นทั้งก้อน `constructor` ก็จะหายไปด้วย -For instance: +ยกตัวอย่าง: ```js run function Rabbit() {} @@ -129,18 +129,18 @@ alert(rabbit.constructor === Rabbit); // false */!* ``` -So, to keep the right `"constructor"` we can choose to add/remove properties to the default `"prototype"` instead of overwriting it as a whole: +ดังนั้น ถ้าต้องการรักษา `"constructor"` ไว้ เราควรเพิ่ม/ลบพร็อพเพอร์ตี้ใน `"prototype"` ที่มีอยู่แล้ว แทนที่จะเขียนทับทั้งก้อน: ```js function Rabbit() {} -// Not overwrite Rabbit.prototype totally -// just add to it +// ไม่ได้เขียนทับ Rabbit.prototype ทั้งหมด +// แค่เพิ่มพร็อพเพอร์ตี้เข้าไป Rabbit.prototype.jumps = true -// the default Rabbit.prototype.constructor is preserved +// Rabbit.prototype.constructor เริ่มต้นยังคงอยู่ ``` -Or, alternatively, recreate the `constructor` property manually: +หรืออีกวิธีหนึ่งคือสร้าง `constructor` กลับมาเองด้วยมือ: ```js Rabbit.prototype = { @@ -150,26 +150,26 @@ Rabbit.prototype = { */!* }; -// now constructor is also correct, because we added it +// ตอนนี้ constructor ก็ถูกต้องแล้ว เพราะเราเพิ่มมันกลับเข้าไปเอง ``` -## Summary +## สรุป -In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. Later we'll see more advanced programming patterns that rely on it. +ในบทนี้เราได้อธิบายวิธีการกำหนด `[[Prototype]]` ให้กับออบเจ็กต์ที่สร้างผ่านคอนสตรักเตอร์ฟังก์ชันแบบคร่าวๆ ในบทต่อไปเราจะได้เห็นรูปแบบการเขียนโปรแกรมขั้นสูงที่ใช้หลักการนี้ -Everything is quite simple, just a few notes to make things clear: +เนื้อหาทั้งหมดค่อนข้างตรงไปตรงมา แค่มีจุดที่ควรจำไว้: -- The `F.prototype` property (don't mistake it for `[[Prototype]]`) sets `[[Prototype]]` of new objects when `new F()` is called. -- The value of `F.prototype` should be either an object or `null`: other values won't work. -- The `"prototype"` property only has such a special effect when set on a constructor function, and invoked with `new`. +- พร็อพเพอร์ตี้ `F.prototype` (อย่าสับสนกับ `[[Prototype]]`) จะกำหนด `[[Prototype]]` ให้กับออบเจ็กต์ใหม่เมื่อเรียก `new F()` +- ค่าของ `F.prototype` ต้องเป็นออบเจ็กต์หรือ `null` เท่านั้น ค่าอื่นจะไม่ทำงาน +- พร็อพเพอร์ตี้ `"prototype"` จะมีผลพิเศษแบบนี้เฉพาะเมื่อตั้งค่าไว้บนคอนสตรักเตอร์ฟังก์ชัน และเรียกใช้ด้วย `new` เท่านั้น -On regular objects the `prototype` is nothing special: +ถ้าเป็นออบเจ็กต์ธรรมดา `prototype` ก็แค่พร็อพเพอร์ตี้ปกติ ไม่มีอะไรพิเศษ: ```js let user = { name: "John", - prototype: "Bla-bla" // no magic at all + prototype: "Bla-bla" // ไม่มีอะไรวิเศษเลย }; ``` -By default all functions have `F.prototype = { constructor: F }`, so we can get the constructor of an object by accessing its `"constructor"` property. +ฟังก์ชันทุกตัวจะมี `F.prototype = { constructor: F }` เป็นค่าเริ่มต้น ดังนั้นเราจึงสามารถเข้าถึงคอนสตรักเตอร์ของออบเจ็กต์ได้ผ่านพร็อพเพอร์ตี้ `"constructor"` ของมัน diff --git a/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/solution.md b/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/solution.md index ebd2f44e9..00d9e6e0c 100644 --- a/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/solution.md +++ b/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/solution.md @@ -9,5 +9,5 @@ function f() { alert("Hello!"); } -f.defer(1000); // shows "Hello!" after 1 sec +f.defer(1000); // แสดง "Hello!" หลังจาก 1 วินาที ``` diff --git a/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md b/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md index d3b3a51c2..cb01fab74 100644 --- a/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md +++ b/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md @@ -2,16 +2,16 @@ importance: 5 --- -# Add method "f.defer(ms)" to functions +# เพิ่มเมธอด "f.defer(ms)" ให้ฟังก์ชัน -Add to the prototype of all functions the method `defer(ms)`, that runs the function after `ms` milliseconds. +ให้เพิ่มเมธอด `defer(ms)` เข้าไปในโปรโตไทป์ของฟังก์ชันทั้งหมด โดยเมธอดนี้จะเรียกฟังก์ชันหลังจากผ่านไป `ms` มิลลิวินาที -After you do it, such code should work: +เมื่อทำเสร็จแล้ว โค้ดนี้ควรทำงานได้: ```js function f() { alert("Hello!"); } -f.defer(1000); // shows "Hello!" after 1 second +f.defer(1000); // แสดง "Hello!" หลังจาก 1 วินาที ``` diff --git a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md index 99c358c9b..bed119cb9 100644 --- a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md +++ b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md @@ -8,17 +8,17 @@ Function.prototype.defer = function(ms) { } }; -// check it +// ทดสอบ function f(a, b) { alert( a + b ); } -f.defer(1000)(1, 2); // shows 3 after 1 sec +f.defer(1000)(1, 2); // แสดง 3 หลังจาก 1 วินาที ``` -Please note: we use `this` in `f.apply` to make our decoration work for object methods. +สังเกตว่าเราใช้ `this` ใน `f.apply` เพื่อให้ decorator ทำงานได้ถูกต้องกับเมธอดของออบเจ็กต์ด้วย -So if the wrapper function is called as an object method, then `this` is passed to the original method `f`. +ถ้าฟังก์ชัน wrapper ถูกเรียกในฐานะเมธอดของออบเจ็กต์ `this` จะถูกส่งต่อไปยังฟังก์ชันตัวเดิม `f` ด้วย ```js run Function.prototype.defer = function(ms) { diff --git a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md index 4d3823bb8..3d7c36798 100644 --- a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md +++ b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md @@ -2,18 +2,18 @@ importance: 4 --- -# Add the decorating "defer()" to functions +# เพิ่ม "defer()" แบบ decorator ให้ฟังก์ชัน -Add to the prototype of all functions the method `defer(ms)`, that returns a wrapper, delaying the call by `ms` milliseconds. +ให้เพิ่มเมธอด `defer(ms)` เข้าไปในโปรโตไทป์ของฟังก์ชันทั้งหมด โดยเมธอดนี้จะคืนค่า wrapper ที่หน่วงเวลาการเรียกฟังก์ชันออกไป `ms` มิลลิวินาที -Here's an example of how it should work: +ตัวอย่างการใช้งาน: ```js function f(a, b) { alert( a + b ); } -f.defer(1000)(1, 2); // shows 3 after 1 second +f.defer(1000)(1, 2); // แสดง 3 หลังจาก 1 วินาที ``` -Please note that the arguments should be passed to the original function. +อย่าลืมว่าอาร์กิวเมนต์ต้องถูกส่งต่อไปยังฟังก์ชันตัวเดิมด้วย diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index bdfc86dd8..5d732282d 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -1,33 +1,33 @@ -# Native prototypes +# โปรโตไทป์ของออบเจ็กต์มาตรฐาน (Native prototypes) -The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it. +พร็อพเพอร์ตี้ `"prototype"` ถูกใช้งานอย่างกว้างขวางใน JavaScript เอง โดยคอนสตรักเตอร์มาตรฐานทุกตัวล้วนใช้มัน -First we'll look at the details, and then how to use it for adding new capabilities to built-in objects. +เรามาดูรายละเอียดกันก่อน แล้วค่อยดูว่าจะนำไปใช้เพิ่มความสามารถให้กับออบเจ็กต์มาตรฐานได้อย่างไร ## Object.prototype -Let's say we output an empty object: +ลองมาแสดงผลออบเจ็กต์เปล่าดู: ```js run let obj = {}; alert( obj ); // "[object Object]" ? ``` -Where's the code that generates the string `"[object Object]"`? That's a built-in `toString` method, but where is it? The `obj` is empty! +โค้ดที่สร้างสตริง `"[object Object]"` อยู่ที่ไหนกัน? นั่นคือเมธอด `toString` ที่มีอยู่แล้วในตัว แต่มันอยู่ตรงไหนล่ะ? ก็ `obj` ว่างเปล่านี่นา! -...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` is a built-in object constructor function, with its own `prototype` referencing a huge object with `toString` and other methods. +...แต่จริงๆ แล้ว `obj = {}` ก็เหมือนกับ `obj = new Object()` นั่นเอง โดย `Object` คือคอนสตรักเตอร์มาตรฐาน ที่มี `prototype` ชี้ไปยังออบเจ็กต์ขนาดใหญ่ซึ่งมีเมธอด `toString` และเมธอดอื่นๆ อยู่ -Here's what's going on: +หน้าตาเป็นแบบนี้: ![](object-prototype.svg) -When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` according to the rule that we discussed in the previous chapter: +เมื่อเรียก `new Object()` (หรือสร้างออบเจ็กต์แบบย่อ `{...}`) ค่า `[[Prototype]]` ของมันจะถูกตั้งเป็น `Object.prototype` ตามกฎที่เราพูดถึงในบทก่อนหน้า: ![](object-prototype-1.svg) -So then when `obj.toString()` is called the method is taken from `Object.prototype`. +ดังนั้นเมื่อเรียก `obj.toString()` เมธอดนี้จึงถูกหยิบมาจาก `Object.prototype` นั่นเอง -We can check it like this: +ลองพิสูจน์ได้แบบนี้: ```js run let obj = {}; @@ -38,80 +38,80 @@ alert(obj.toString === obj.__proto__.toString); //true alert(obj.toString === Object.prototype.toString); //true ``` -Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`: +สังเกตว่าเหนือ `Object.prototype` ขึ้นไปไม่มี `[[Prototype]]` อีกแล้ว: ```js run alert(Object.prototype.__proto__); // null ``` -## Other built-in prototypes +## โปรโตไทป์ของออบเจ็กต์มาตรฐานตัวอื่นๆ -Other built-in objects such as `Array`, `Date`, `Function` and others also keep methods in prototypes. +ออบเจ็กต์มาตรฐานอื่นๆ อย่าง `Array`, `Date`, `Function` ก็เก็บเมธอดไว้ในโปรโตไทป์เช่นกัน -For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient. +ยกตัวอย่าง เวลาสร้างอาร์เรย์ `[1, 2, 3]` ภายในจะใช้คอนสตรักเตอร์ `new Array()` เป็นค่าเริ่มต้น ทำให้ `Array.prototype` กลายเป็นโปรโตไทป์ของมันและเตรียมเมธอดต่างๆ ไว้ให้ วิธีนี้ช่วยประหยัดหน่วยความจำได้มาก -By specification, all of the built-in prototypes have `Object.prototype` on the top. That's why some people say that "everything inherits from objects". +ตามสเปก โปรโตไทป์มาตรฐานทั้งหมดจะมี `Object.prototype` อยู่บนสุดของสาย เพราะเหตุนี้บางคนจึงบอกว่า "ทุกอย่างสืบทอดมาจากออบเจ็กต์" -Here's the overall picture (for 3 built-ins to fit): +ภาพรวมเป็นแบบนี้ (แสดง 3 ตัวมาตรฐานให้พอดี): ![](native-prototypes-classes.svg) -Let's check the prototypes manually: +ลองตรวจสอบโปรโตไทป์ด้วยตัวเอง: ```js run let arr = [1, 2, 3]; -// it inherits from Array.prototype? +// สืบทอดมาจาก Array.prototype? alert( arr.__proto__ === Array.prototype ); // true -// then from Object.prototype? +// แล้วต่อไปจาก Object.prototype? alert( arr.__proto__.__proto__ === Object.prototype ); // true -// and null on the top. +// บนสุดคือ null alert( arr.__proto__.__proto__.__proto__ ); // null ``` -Some methods in prototypes may overlap, for instance, `Array.prototype` has its own `toString` that lists comma-delimited elements: +เมธอดในโปรโตไทป์อาจซ้ำกันได้ เช่น `Array.prototype` มี `toString` ของตัวเองที่แสดงสมาชิกคั่นด้วยจุลภาค: ```js run let arr = [1, 2, 3] -alert(arr); // 1,2,3 <-- the result of Array.prototype.toString +alert(arr); // 1,2,3 <-- ผลลัพธ์จาก Array.prototype.toString ``` -As we've seen before, `Object.prototype` has `toString` as well, but `Array.prototype` is closer in the chain, so the array variant is used. +อย่างที่เราเห็น `Object.prototype` ก็มี `toString` เหมือนกัน แต่เนื่องจาก `Array.prototype` อยู่ใกล้กว่าในสายโปรโตไทป์ จึงใช้เวอร์ชันของอาร์เรย์แทน ![](native-prototypes-array-tostring.svg) -In-browser tools like Chrome developer console also show inheritance (`console.dir` may need to be used for built-in objects): +เครื่องมือสำหรับนักพัฒนาในเบราว์เซอร์อย่าง Chrome developer console ก็แสดงการสืบทอดนี้ได้เช่นกัน (อาจต้องใช้ `console.dir` สำหรับออบเจ็กต์มาตรฐาน): ![](console_dir_array.png) -Other built-in objects also work the same way. Even functions -- they are objects of a built-in `Function` constructor, and their methods (`call`/`apply` and others) are taken from `Function.prototype`. Functions have their own `toString` too. +ออบเจ็กต์มาตรฐานตัวอื่นก็ทำงานในลักษณะเดียวกัน แม้แต่ฟังก์ชัน -- ฟังก์ชันก็เป็นออบเจ็กต์ของคอนสตรักเตอร์ `Function` มาตรฐาน เมธอดต่างๆ (`call`/`apply` และอื่นๆ) จึงอยู่ใน `Function.prototype` นอกจากนี้ฟังก์ชันยังมี `toString` ของตัวเองอีกด้วย ```js run function f() {} alert(f.__proto__ == Function.prototype); // true -alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects +alert(f.__proto__.__proto__ == Object.prototype); // true, สืบทอดมาจากออบเจ็กต์ ``` -## Primitives +## ค่าพื้นฐาน (Primitives) -The most intricate thing happens with strings, numbers and booleans. +เรื่องที่ซับซ้อนที่สุดเกิดขึ้นกับสตริง ตัวเลข และบูลีน -As we remember, they are not objects. But if we try to access their properties, temporary wrapper objects are created using built-in constructors `String`, `Number` and `Boolean`. They provide the methods and disappear. +อย่างที่ทราบ ค่าเหล่านี้ไม่ใช่ออบเจ็กต์ แต่ถ้าเราลองเข้าถึงพร็อพเพอร์ตี้ของมัน JavaScript จะสร้างออบเจ็กต์ห่อหุ้ม (wrapper object) ชั่วคราวขึ้นมาโดยใช้คอนสตรักเตอร์ `String`, `Number` และ `Boolean` เพื่อเตรียมเมธอดให้ใช้ แล้วก็หายไป -These objects are created invisibly to us and most engines optimize them out, but the specification describes it exactly this way. Methods of these objects also reside in prototypes, available as `String.prototype`, `Number.prototype` and `Boolean.prototype`. +ออบเจ็กต์เหล่านี้ถูกสร้างขึ้นโดยที่เราไม่เห็น และเอนจินส่วนใหญ่จะปรับแต่ง (optimize) จนไม่ต้องสร้างจริงๆ แต่ในสเปกอธิบายไว้อย่างนี้ เมธอดของออบเจ็กต์เหล่านี้ก็อยู่ในโปรโตไทป์เช่นกัน ใช้งานได้ผ่าน `String.prototype`, `Number.prototype` และ `Boolean.prototype` -```warn header="Values `null` and `undefined` have no object wrappers" -Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes either. +```warn header="ค่า `null` และ `undefined` ไม่มีออบเจ็กต์ห่อหุ้ม" +ค่าพิเศษอย่าง `null` และ `undefined` แตกต่างออกไป เพราะไม่มีออบเจ็กต์ห่อหุ้ม จึงไม่มีเมธอดหรือพร็อพเพอร์ตี้ให้ใช้ และไม่มีโปรโตไทป์ที่เกี่ยวข้องด้วย ``` -## Changing native prototypes [#native-prototype-change] +## การแก้ไขโปรโตไทป์มาตรฐาน [#native-prototype-change] -Native prototypes can be modified. For instance, if we add a method to `String.prototype`, it becomes available to all strings: +โปรโตไทป์มาตรฐานสามารถแก้ไขได้ เช่น ถ้าเราเพิ่มเมธอดลงใน `String.prototype` สตริงทุกตัวก็จะใช้เมธอดนั้นได้: ```js run String.prototype.show = function() { @@ -121,32 +121,32 @@ String.prototype.show = function() { "BOOM!".show(); // BOOM! ``` -During the process of development, we may have ideas for new built-in methods we'd like to have, and we may be tempted to add them to native prototypes. But that is generally a bad idea. +ระหว่างพัฒนา เราอาจนึกอยากได้เมธอดใหม่ๆ แล้วอยากเพิ่มเข้าไปในโปรโตไทป์มาตรฐาน แต่โดยทั่วไปแล้ว นี่ไม่ใช่ความคิดที่ดี ```warn -Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them will be overwriting the method of the other. +โปรโตไทป์เป็นของส่วนกลาง จึงเกิดการชนกันได้ง่าย ถ้าไลบรารีสองตัวเพิ่มเมธอด `String.prototype.show` ทั้งคู่ ตัวหนึ่งจะเขียนทับเมธอดของอีกตัว -So, generally, modifying a native prototype is considered a bad idea. +ดังนั้นโดยทั่วไป การแก้ไขโปรโตไทป์มาตรฐานจึงถือว่าไม่ดี ``` -**In modern programming, there is only one case where modifying native prototypes is approved. That's polyfilling.** +**ในการเขียนโปรแกรมยุคใหม่ มีกรณีเดียวที่ยอมรับให้แก้ไขโปรโตไทป์มาตรฐานได้ นั่นคือ polyfilling** -Polyfilling is a term for making a substitute for a method that exists in the JavaScript specification, but is not yet supported by a particular JavaScript engine. +Polyfilling คือการสร้างเมธอดทดแทนสำหรับเมธอดที่มีอยู่ในสเปกของ JavaScript แต่เอนจินบางตัวยังไม่รองรับ -We may then implement it manually and populate the built-in prototype with it. +เราจึงเขียนมันเองแล้วเพิ่มเข้าไปในโปรโตไทป์มาตรฐานได้ -For instance: +ตัวอย่างเช่น: ```js run -if (!String.prototype.repeat) { // if there's no such method - // add it to the prototype +if (!String.prototype.repeat) { // ถ้ายังไม่มีเมธอดนี้ + // เพิ่มเข้าไปในโปรโตไทป์ String.prototype.repeat = function(n) { - // repeat the string n times + // ทำซ้ำสตริง n ครั้ง - // actually, the code should be a little bit more complex than that - // (the full algorithm is in the specification) - // but even an imperfect polyfill is often considered good enough + // จริงๆ แล้วโค้ดควรจะซับซ้อนกว่านี้อีกนิด + // (อัลกอริทึมเต็มอยู่ในสเปก) + // แต่ polyfill ที่ไม่สมบูรณ์ก็มักจะใช้งานได้ดีพอ return new Array(n + 1).join(this); }; } @@ -155,17 +155,17 @@ alert( "La".repeat(3) ); // LaLaLa ``` -## Borrowing from prototypes +## การยืมเมธอดจากโปรโตไทป์ -In the chapter we talked about method borrowing. +ในบท เราได้พูดถึงการยืมเมธอด (method borrowing) -That's when we take a method from one object and copy it into another. +คือการหยิบเมธอดจากออบเจ็กต์หนึ่งไปใช้กับอีกออบเจ็กต์หนึ่ง -Some methods of native prototypes are often borrowed. +เมธอดจากโปรโตไทป์มาตรฐานมักถูกยืมไปใช้บ่อย -For instance, if we're making an array-like object, we may want to copy some `Array` methods to it. +ยกตัวอย่าง ถ้าเรากำลังสร้างออบเจ็กต์ที่คล้ายอาร์เรย์ (array-like) อาจอยากคัดลอกเมธอดบางตัวของ `Array` มาใช้ -E.g. +เช่น ```js run let obj = { @@ -181,18 +181,18 @@ obj.join = Array.prototype.join; alert( obj.join(',') ); // Hello,world! ``` -It works because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property. It doesn't check if the object is indeed an array. Many built-in methods are like that. +วิธีนี้ใช้ได้เพราะอัลกอริทึมภายในของเมธอด `join` สนใจแค่ index ที่ถูกต้องกับพร็อพเพอร์ตี้ `length` เท่านั้น ไม่ได้ตรวจว่าเป็นอาร์เรย์จริงหรือเปล่า เมธอดมาตรฐานหลายตัวก็ทำงานในลักษณะนี้ -Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, so all `Array` methods are automatically available in `obj`. +อีกทางเลือกหนึ่งคือตั้ง `obj.__proto__` ให้ชี้ไปที่ `Array.prototype` เพื่อให้เมธอดของ `Array` ทั้งหมดพร้อมใช้ใน `obj` โดยอัตโนมัติ -But that's impossible if `obj` already inherits from another object. Remember, we only can inherit from one object at a time. +แต่วิธีนี้ทำไม่ได้ถ้า `obj` สืบทอดจากออบเจ็กต์อื่นอยู่แล้ว อย่าลืมว่าเราสืบทอดได้จากออบเจ็กต์เดียวเท่านั้น -Borrowing methods is flexible, it allows to mix functionalities from different objects if needed. +การยืมเมธอดนั้นยืดหยุ่นกว่า เพราะสามารถผสมความสามารถจากหลายออบเจ็กต์เข้าด้วยกันได้ตามต้องการ -## Summary +## สรุป -- All built-in objects follow the same pattern: - - The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype`, etc.) - - The object itself stores only the data (array items, object properties, the date) -- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype` and `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects -- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. The only allowable case is probably when we add-in a new standard, but it's not yet supported by the JavaScript engine +- ออบเจ็กต์มาตรฐานทั้งหมดใช้รูปแบบเดียวกัน: + - เมธอดต่างๆ ถูกเก็บไว้ในโปรโตไทป์ (`Array.prototype`, `Object.prototype`, `Date.prototype` เป็นต้น) + - ตัวออบเจ็กต์เองเก็บแค่ข้อมูล (สมาชิกของอาร์เรย์ พร็อพเพอร์ตี้ของออบเจ็กต์ วันที่) +- ค่าพื้นฐาน (primitives) ก็เก็บเมธอดไว้ในโปรโตไทป์ของออบเจ็กต์ห่อหุ้มเช่นกัน: `Number.prototype`, `String.prototype` และ `Boolean.prototype` มีเพียง `undefined` กับ `null` เท่านั้นที่ไม่มีออบเจ็กต์ห่อหุ้ม +- โปรโตไทป์มาตรฐานสามารถแก้ไขหรือเพิ่มเมธอดใหม่ได้ แต่ไม่แนะนำให้ทำ กรณีเดียวที่ยอมรับได้คือเมื่อต้องการเพิ่ม polyfill สำหรับเมธอดที่อยู่ในมาตรฐานแล้วแต่เอนจินยังไม่รองรับ diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md index f3c9cf0e5..21bd6dfa6 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md @@ -1,13 +1,13 @@ -The method can take all enumerable keys using `Object.keys` and output their list. +เมธอดนี้สามารถดึง key ที่เป็น enumerable ทั้งหมดด้วย `Object.keys` แล้วแสดงผลเป็นรายการ -To make `toString` non-enumerable, let's define it using a property descriptor. The syntax of `Object.create` allows us to provide an object with property descriptors as the second argument. +เพื่อให้ `toString` ไม่เป็น enumerable ก็กำหนดด้วย property descriptor ซึ่ง `Object.create` รับ descriptor เป็นอาร์กิวเมนต์ตัวที่สองได้เลย ```js run *!* let dictionary = Object.create(null, { - toString: { // define toString property - value() { // the value is a function + toString: { // กำหนดพร็อพเพอร์ตี้ toString + value() { // ค่าเป็นฟังก์ชัน return Object.keys(this).join(); } } @@ -17,15 +17,15 @@ let dictionary = Object.create(null, { dictionary.apple = "Apple"; dictionary.__proto__ = "test"; -// apple and __proto__ is in the loop +// apple กับ __proto__ อยู่ในลูป for(let key in dictionary) { - alert(key); // "apple", then "__proto__" -} + alert(key); // "apple" แล้วก็ "__proto__" +} -// comma-separated list of properties by toString +// รายการพร็อพเพอร์ตี้คั่นด้วยจุลภาค จาก toString alert(dictionary); // "apple,__proto__" ``` -When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable. +เมื่อสร้างพร็อพเพอร์ตี้ด้วย descriptor แฟล็กทั้งหมดจะเป็น `false` โดยปริยาย ดังนั้นในโค้ดด้านบน `dictionary.toString` จึงไม่เป็น enumerable -See the chapter [](info:property-descriptors) for review. +ทบทวนเพิ่มเติมได้ในบท [](info:property-descriptors) diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md index 0d831f2cc..db67f11e5 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md @@ -2,30 +2,30 @@ importance: 5 --- -# Add toString to the dictionary +# เพิ่ม toString ให้ dictionary -There's an object `dictionary`, created as `Object.create(null)`, to store any `key/value` pairs. +มีออบเจ็กต์ `dictionary` ที่สร้างด้วย `Object.create(null)` สำหรับเก็บคู่ `key/value` อะไรก็ได้ -Add method `dictionary.toString()` into it, that should return a comma-delimited list of keys. Your `toString` should not show up in `for..in` over the object. +ให้เพิ่มเมธอด `dictionary.toString()` ที่คืนค่ารายการ key คั่นด้วยจุลภาค โดย `toString` ต้องไม่โผล่ขึ้นมาเวลาวน `for..in` บนออบเจ็กต์ -Here's how it should work: +ตัวอย่างการทำงาน: ```js let dictionary = Object.create(null); *!* -// your code to add dictionary.toString method +// โค้ดของคุณที่เพิ่มเมธอด dictionary.toString */!* -// add some data +// เพิ่มข้อมูล dictionary.apple = "Apple"; -dictionary.__proto__ = "test"; // __proto__ is a regular property key here +dictionary.__proto__ = "test"; // __proto__ เป็น key ธรรมดาในที่นี้ -// only apple and __proto__ are in the loop +// มีแค่ apple กับ __proto__ ในลูป for(let key in dictionary) { - alert(key); // "apple", then "__proto__" -} + alert(key); // "apple" แล้วก็ "__proto__" +} -// your toString in action +// toString ที่เราเขียนทำงาน alert(dictionary); // "apple,__proto__" ``` diff --git a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md index 90d3118bf..2471c14c9 100644 --- a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md @@ -1,7 +1,7 @@ -The first call has `this == rabbit`, the other ones have `this` equal to `Rabbit.prototype`, because it's actually the object before the dot. +การเรียกครั้งแรก `this == rabbit` แต่การเรียกที่เหลือทั้งหมด `this` จะเท่ากับ `Rabbit.prototype` เพราะเป็นออบเจ็กต์ที่อยู่หน้าจุด -So only the first call shows `Rabbit`, other ones show `undefined`: +ดังนั้นเฉพาะการเรียกครั้งแรกเท่านั้นที่แสดง `Rabbit` ส่วนที่เหลือแสดง `undefined`: ```js run function Rabbit(name) { diff --git a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md index 09bb7f1ed..864a65be9 100644 --- a/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md +++ b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# The difference between calls +# ความแตกต่างระหว่างการเรียก -Let's create a new `rabbit` object: +สร้างออบเจ็กต์ `rabbit` ขึ้นมาใหม่: ```js function Rabbit(name) { @@ -17,7 +17,7 @@ Rabbit.prototype.sayHi = function() { let rabbit = new Rabbit("Rabbit"); ``` -These calls do the same thing or not? +การเรียกเหล่านี้ให้ผลลัพธ์เหมือนกันหรือไม่? ```js rabbit.sayHi(); diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index 9c5f1eb3d..e3e82f7f1 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -1,31 +1,31 @@ -# Prototype methods, objects without __proto__ +# เมธอดของโปรโตไทป์ และออบเจ็กต์ที่ไม่มี __proto__ -In the first chapter of this section, we mentioned that there are modern methods to setup a prototype. +ในบทแรกของหมวดนี้ เราได้กล่าวไปว่ามีเมธอดสมัยใหม่สำหรับกำหนดโปรโตไทป์ -Setting or reading the prototype with `obj.__proto__` is considered outdated and somewhat deprecated (moved to the so-called "Annex B" of the JavaScript standard, meant for browsers only). +การกำหนดหรืออ่านค่าโปรโตไทป์ด้วย `obj.__proto__` ถือว่าล้าสมัยไปแล้ว และอยู่ในสถานะค่อนข้างจะ deprecated (ถูกย้ายไปอยู่ใน "Annex B" ของมาตรฐาน JavaScript ซึ่งมีไว้สำหรับเบราว์เซอร์เท่านั้น) -The modern methods to get/set a prototype are: +เมธอดสมัยใหม่สำหรับอ่าน/กำหนดโปรโตไทป์มีดังนี้: -- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`. -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`. +- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- คืนค่า `[[Prototype]]` ของ `obj` +- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- กำหนด `[[Prototype]]` ของ `obj` ให้เป็น `proto` -The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`. +การใช้ `__proto__` ที่ยังถือว่ายอมรับได้ มีเพียงกรณีเดียว คือใช้เป็นพร็อพเพอร์ตี้ตอนสร้างออบเจ็กต์ใหม่: `{ __proto__: ... }` -Although, there's a special method for this too: +แม้กระนั้น ก็ยังมีเมธอดเฉพาะสำหรับเรื่องนี้เช่นกัน: -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- สร้างออบเจ็กต์ว่างโดยมี `proto` เป็น `[[Prototype]]` พร้อมทั้งกำหนด property descriptor เพิ่มเติมได้ -For instance: +ตัวอย่างเช่น: ```js run let animal = { eats: true }; -// create a new object with animal as a prototype +// สร้างออบเจ็กต์ใหม่โดยมี animal เป็นโปรโตไทป์ *!* -let rabbit = Object.create(animal); // same as {__proto__: animal} +let rabbit = Object.create(animal); // เหมือนกับ {__proto__: animal} */!* alert(rabbit.eats); // true @@ -35,13 +35,13 @@ alert(Object.getPrototypeOf(rabbit) === animal); // true */!* *!* -Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {} +Object.setPrototypeOf(rabbit, {}); // เปลี่ยนโปรโตไทป์ของ rabbit เป็น {} */!* ``` -The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors. +`Object.create` มีความสามารถมากกว่านั้น เพราะรับอาร์กิวเมนต์ตัวที่สองได้ด้วย คือ property descriptor -We can provide additional properties to the new object there, like this: +เราสามารถกำหนดพร็อพเพอร์ตี้เพิ่มเติมให้ออบเจ็กต์ใหม่ได้แบบนี้: ```js run let animal = { @@ -57,9 +57,9 @@ let rabbit = Object.create(animal, { alert(rabbit.jumps); // true ``` -The descriptors are in the same format as described in the chapter . +รูปแบบของ descriptor เหมือนกับที่อธิบายไว้ในบท -We can use `Object.create` to perform an object cloning more powerful than copying properties in `for..in`: +นอกจากนี้ `Object.create` ยังใช้โคลนออบเจ็กต์ได้ละเอียดกว่าการคัดลอกพร็อพเพอร์ตี้ด้วย `for..in`: ```js let clone = Object.create( @@ -67,43 +67,43 @@ let clone = Object.create( ); ``` -This call makes a truly exact copy of `obj`, including all properties: enumerable and non-enumerable, data properties and setters/getters -- everything, and with the right `[[Prototype]]`. +การเรียกแบบนี้จะสร้างสำเนาที่เหมือน `obj` ทุกประการ ไม่ว่าจะเป็นพร็อพเพอร์ตี้แบบ enumerable หรือ non-enumerable, data property หรือ getter/setter ทุกอย่างรวมถึง `[[Prototype]]` ที่ถูกต้องด้วย -## Brief history +## ประวัติย่อ -There're so many ways to manage `[[Prototype]]`. How did that happen? Why? +มีวิธีจัดการ `[[Prototype]]` มากมายหลายแบบ เป็นอย่างนี้ได้อย่างไร? ทำไมถึงซับซ้อนขนาดนี้? -That's for historical reasons. +คำตอบก็คือเรื่องของประวัติศาสตร์ -The prototypal inheritance was in the language since its dawn, but the ways to manage it evolved over time. +การสืบทอดแบบโปรโตไทป์มีอยู่ในภาษาตั้งแต่แรกเริ่ม แต่วิธีจัดการมันค่อยๆ เปลี่ยนไปตามกาลเวลา: -- The `prototype` property of a constructor function has worked since very ancient times. It's the oldest way to create objects with a given prototype. -- Later, in the year 2012, `Object.create` appeared in the standard. It gave the ability to create objects with a given prototype, but did not provide the ability to get/set it. Some browsers implemented the non-standard `__proto__` accessor that allowed the user to get/set a prototype at any time, to give more flexibility to developers. -- Later, in the year 2015, `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, to perform the same functionality as `__proto__`. As `__proto__` was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is: optional for non-browser environments. -- Later, in the year 2022, it was officially allowed to use `__proto__` in object literals `{...}` (moved out of Annex B), but not as a getter/setter `obj.__proto__` (still in Annex B). +- พร็อพเพอร์ตี้ `prototype` ของคอนสตรักเตอร์ฟังก์ชันใช้งานได้มาตั้งแต่ยุคแรกๆ เลย เป็นวิธีดั้งเดิมที่สุดในการสร้างออบเจ็กต์พร้อมกำหนดโปรโตไทป์ +- ต่อมาในปี 2012 `Object.create` ถูกเพิ่มเข้ามาในมาตรฐาน ทำให้สร้างออบเจ็กต์พร้อมกำหนดโปรโตไทป์ได้ แต่ยังอ่าน/เปลี่ยนโปรโตไทป์ไม่ได้ เบราว์เซอร์บางตัวจึงทำ accessor `__proto__` ที่ไม่เป็นมาตรฐานขึ้นมา เพื่อให้อ่าน/เปลี่ยนโปรโตไทป์ได้ทุกเมื่อ +- ต่อมาในปี 2015 `Object.setPrototypeOf` และ `Object.getPrototypeOf` ถูกเพิ่มเข้ามาในมาตรฐาน ให้ทำงานเดียวกับ `__proto__` ได้ เนื่องจาก `__proto__` ถูกใช้ไปทั่วแล้ว จึงถูกทำเป็น deprecated และถูกย้ายไปอยู่ใน Annex B ของมาตรฐาน ซึ่งเป็นตัวเลือกสำหรับสภาพแวดล้อมที่ไม่ใช่เบราว์เซอร์ +- ต่อมาในปี 2022 `__proto__` ได้รับอนุญาตให้ใช้ใน object literal `{...}` อย่างเป็นทางการ (ย้ายออกจาก Annex B) แต่ห้ามใช้ในรูปแบบ getter/setter อย่าง `obj.__proto__` (ยังอยู่ใน Annex B) -Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`? +ทำไม `__proto__` ถึงถูกแทนที่ด้วยฟังก์ชัน `getPrototypeOf/setPrototypeOf`? -Why was `__proto__` partially rehabilitated and its usage allowed in `{...}`, but not as a getter/setter? +ทำไม `__proto__` ถึงถูก "ฟื้นฟู" เพียงบางส่วน โดยอนุญาตให้ใช้ใน `{...}` ได้ แต่ห้ามใช้เป็น getter/setter? -That's an interesting question, requiring us to understand why `__proto__` is bad. +คำถามนี้น่าสนใจทีเดียว ก่อนจะตอบได้ เราต้องเข้าใจก่อนว่า `__proto__` มีปัญหาอะไร -And soon we'll get the answer. +คำตอบจะมาในหัวข้อถัดไปเลย -```warn header="Don't change `[[Prototype]]` on existing objects if speed matters" -Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time and don't modify it anymore: `rabbit` inherits from `animal`, and that is not going to change. +```warn header="อย่าเปลี่ยน `[[Prototype]]` ของออบเจ็กต์ที่มีอยู่แล้ว ถ้าความเร็วเป็นเรื่องสำคัญ" +ในทางเทคนิค เราอ่าน/กำหนด `[[Prototype]]` ได้ทุกเมื่อ แต่โดยปกติเรากำหนดแค่ครั้งเดียวตอนสร้างออบเจ็กต์ แล้วไม่แก้ไขอีก เช่น `rabbit` สืบทอดจาก `animal` แล้วก็จะเป็นอย่างนั้นตลอดไป -And JavaScript engines are highly optimized for this. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation as it breaks internal optimizations for object property access operations. So avoid it unless you know what you're doing, or JavaScript speed totally doesn't matter for you. +JavaScript engine ถูกปรับแต่งให้ทำงานได้ดีสำหรับกรณีนี้ การเปลี่ยนโปรโตไทป์ "ระหว่างทาง" ด้วย `Object.setPrototypeOf` หรือ `obj.__proto__=` เป็นการดำเนินการที่ช้ามาก เพราะทำให้การ optimize ภายในของ engine สำหรับการเข้าถึงพร็อพเพอร์ตี้เสียหาย ดังนั้นควรหลีกเลี่ยง ยกเว้นจะรู้ว่ากำลังทำอะไรอยู่ หรือความเร็วไม่ใช่เรื่องสำคัญสำหรับงานนั้น ``` -## "Very plain" objects [#very-plain] +## ออบเจ็กต์ "ว่างเปล่าจริงๆ" [#very-plain] -As we know, objects can be used as associative arrays to store key/value pairs. +อย่างที่เราทราบ ออบเจ็กต์สามารถใช้เป็น associative array สำหรับเก็บคู่ key/value ได้ -...But if we try to store *user-provided* keys in it (for instance, a user-entered dictionary), we can see an interesting glitch: all keys work fine except `"__proto__"`. +...แต่ถ้าลองเก็บ key ที่*ผู้ใช้ป้อนเข้ามา* (เช่น พจนานุกรมที่ผู้ใช้พิมพ์เอง) จะพบปัญหาแปลกๆ อย่างหนึ่ง: key ทุกตัวใช้ได้ปกติ ยกเว้น `"__proto__"` -Check out the example: +ลองดูตัวอย่างนี้: ```js run let obj = {}; @@ -111,24 +111,24 @@ let obj = {}; let key = prompt("What's the key?", "__proto__"); obj[key] = "some value"; -alert(obj[key]); // [object Object], not "some value"! +alert(obj[key]); // [object Object] ไม่ใช่ "some value"! ``` -Here, if the user types in `__proto__`, the assignment in line 4 is ignored! +ถ้าผู้ใช้พิมพ์ `__proto__` การกำหนดค่าในบรรทัดที่ 4 จะถูกเพิกเฉย! -That could surely be surprising for a non-developer, but pretty understandable for us. The `__proto__` property is special: it must be either an object or `null`. A string can not become a prototype. That's why assigning a string to `__proto__` is ignored. +คนทั่วไปอาจแปลกใจ แต่สำหรับเราเข้าใจได้ไม่ยาก เพราะพร็อพเพอร์ตี้ `__proto__` มีลักษณะพิเศษ — ค่าของมันต้องเป็นออบเจ็กต์หรือ `null` เท่านั้น สตริงจะเป็นโปรโตไทป์ไม่ได้ การกำหนดค่าสตริงให้ `__proto__` จึงถูกเพิกเฉย -But we didn't *intend* to implement such behavior, right? We want to store key/value pairs, and the key named `"__proto__"` was not properly saved. So that's a bug! +แต่เราไม่ได้*ตั้งใจ*จะทำแบบนี้ ใช่ไหม? เราแค่อยากเก็บคู่ key/value แต่ key ชื่อ `"__proto__"` กลับเก็บไม่ได้ นี่คือบั๊กชัดๆ! -Here the consequences are not terrible. But in other cases we may be storing objects instead of strings in `obj`, and then the prototype will indeed be changed. As a result, the execution will go wrong in totally unexpected ways. +ในตัวอย่างนี้ผลกระทบอาจยังไม่ร้ายแรง แต่ในกรณีอื่นเราอาจเก็บออบเจ็กต์แทนที่จะเป็นสตริง ซึ่งจะทำให้โปรโตไทป์ถูกเปลี่ยนจริงๆ และโปรแกรมจะทำงานผิดพลาดอย่างคาดไม่ถึง -What's worse -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side. +ที่แย่กว่านั้นคือ นักพัฒนาส่วนใหญ่ไม่คิดถึงความเป็นไปได้นี้เลย ทำให้บั๊กแบบนี้สังเกตได้ยาก และอาจกลายเป็นช่องโหว่ด้านความปลอดภัยได้ โดยเฉพาะเมื่อใช้ JavaScript ฝั่งเซิร์ฟเวอร์ -Unexpected things also may happen when assigning to `obj.toString`, as it's a built-in object method. +ปัญหาเดียวกันอาจเกิดขึ้นเมื่อกำหนดค่าให้ `obj.toString` เพราะเป็นเมธอดที่มีอยู่แล้วในออบเจ็กต์ -How can we avoid this problem? +แล้วจะหลีกเลี่ยงปัญหานี้ได้อย่างไร? -First, we can just switch to using `Map` for storage instead of plain objects, then everything's fine: +วิธีแรก เปลี่ยนไปใช้ `Map` แทนออบเจ็กต์ธรรมดา แค่นี้ก็หมดปัญหา: ```js run let map = new Map(); @@ -136,27 +136,27 @@ let map = new Map(); let key = prompt("What's the key?", "__proto__"); map.set(key, "some value"); -alert(map.get(key)); // "some value" (as intended) +alert(map.get(key)); // "some value" (ตามที่ตั้งใจไว้) ``` -...But `Object` syntax is often more appealing, as it's more concise. +...แต่ไวยากรณ์ของ `Object` ก็ดึงดูดใจกว่า เพราะเขียนได้กระชับ -Fortunately, we *can* use objects, because language creators gave thought to that problem long ago. +โชคดีที่เรา*ยังใช้*ออบเจ็กต์ได้ เพราะผู้สร้างภาษาได้คิดเรื่องนี้ไว้นานแล้ว -As we know, `__proto__` is not a property of an object, but an accessor property of `Object.prototype`: +อย่างที่ทราบ `__proto__` ไม่ได้เป็นพร็อพเพอร์ตี้ของออบเจ็กต์โดยตรง แต่เป็น accessor property ของ `Object.prototype`: ![](object-prototype-2.svg) -So, if `obj.__proto__` is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`. +ดังนั้นเวลาอ่านหรือเขียน `obj.__proto__` จริงๆ แล้วมันไปเรียก getter/setter จากโปรโตไทป์ ซึ่งจะอ่าน/กำหนดค่า `[[Prototype]]` -As it was said in the beginning of this tutorial section: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself. +อย่างที่กล่าวไว้ตอนต้นของหมวดนี้: `__proto__` เป็นแค่ทางเข้าถึง `[[Prototype]]` ไม่ใช่ตัว `[[Prototype]]` เอง -Now, if we intend to use an object as an associative array and be free of such problems, we can do it with a little trick: +ทีนี้ ถ้าเราต้องการใช้ออบเจ็กต์เป็น associative array โดยไม่มีปัญหาเหล่านี้ ก็ทำได้ด้วยเทคนิคเล็กๆ: ```js run *!* let obj = Object.create(null); -// or: obj = { __proto__: null } +// หรือ: obj = { __proto__: null } */!* let key = prompt("What's the key?", "__proto__"); @@ -165,27 +165,27 @@ obj[key] = "some value"; alert(obj[key]); // "some value" ``` -`Object.create(null)` creates an empty object without a prototype (`[[Prototype]]` is `null`): +`Object.create(null)` สร้างออบเจ็กต์ว่างที่ไม่มีโปรโตไทป์ (`[[Prototype]]` เป็น `null`): ![](object-prototype-null.svg) -So, there is no inherited getter/setter for `__proto__`. Now it is processed as a regular data property, so the example above works right. +เมื่อไม่มี getter/setter ที่สืบทอดมาสำหรับ `__proto__` มันจะถูกจัดการเป็น data property ธรรมดา ตัวอย่างข้างต้นจึงทำงานได้ถูกต้อง -We can call such objects "very plain" or "pure dictionary" objects, because they are even simpler than the regular plain object `{...}`. +ออบเจ็กต์แบบนี้เรียกว่า "very plain" หรือ "pure dictionary" เพราะมันเรียบง่ายกว่าออบเจ็กต์ธรรมดา `{...}` เสียอีก -A downside is that such objects lack any built-in object methods, e.g. `toString`: +ข้อเสียคือ ออบเจ็กต์แบบนี้ไม่มีเมธอดมาตรฐานของออบเจ็กต์เลย เช่น `toString`: ```js run *!* let obj = Object.create(null); */!* -alert(obj); // Error (no toString) +alert(obj); // Error (ไม่มี toString) ``` -...But that's usually fine for associative arrays. +...แต่สำหรับ associative array แล้วก็ไม่เป็นปัญหาอะไร -Note that most object-related methods are `Object.something(...)`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects: +สังเกตว่าเมธอดส่วนใหญ่ที่เกี่ยวกับออบเจ็กต์จะอยู่ในรูป `Object.something(...)` เช่น `Object.keys(obj)` ซึ่งไม่ได้อยู่ในโปรโตไทป์ จึงยังใช้กับออบเจ็กต์แบบนี้ได้ปกติ: ```js run @@ -196,28 +196,28 @@ chineseDictionary.bye = "再见"; alert(Object.keys(chineseDictionary)); // hello,bye ``` -## Summary +## สรุป -- To create an object with the given prototype, use: +- สร้างออบเจ็กต์พร้อมกำหนดโปรโตไทป์ได้ 2 วิธี: - - literal syntax: `{ __proto__: ... }`, allows to specify multiple properties - - or [Object.create(proto[, descriptors])](mdn:js/Object/create), allows to specify property descriptors. + - ใช้ literal syntax: `{ __proto__: ... }` กำหนดพร็อพเพอร์ตี้หลายตัวพร้อมกันได้ + - หรือใช้ [Object.create(proto[, descriptors])](mdn:js/Object/create) กำหนด property descriptor ได้ - The `Object.create` provides an easy way to shallow-copy an object with all descriptors: + `Object.create` ยังช่วยให้ shallow-copy ออบเจ็กต์พร้อม descriptor ทั้งหมดได้ง่ายๆ: ```js let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` -- Modern methods to get/set the prototype are: +- เมธอดสมัยใหม่สำหรับอ่าน/กำหนดโปรโตไทป์: - - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter). - - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter). + - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- คืนค่า `[[Prototype]]` ของ `obj` (ทำงานเหมือน `__proto__` getter) + - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- กำหนด `[[Prototype]]` ของ `obj` เป็น `proto` (ทำงานเหมือน `__proto__` setter) -- Getting/setting the prototype using the built-in `__proto__` getter/setter isn't recommended, it's now in the Annex B of the specification. +- ไม่แนะนำให้ใช้ `__proto__` getter/setter ที่ built-in มาให้ ตอนนี้มันอยู่ใน Annex B ของมาตรฐานแล้ว -- We also covered prototype-less objects, created with `Object.create(null)` or `{__proto__: null}`. +- เรายังได้เรียนรู้ออบเจ็กต์ที่ไม่มีโปรโตไทป์ สร้างด้วย `Object.create(null)` หรือ `{__proto__: null}` - These objects are used as dictionaries, to store any (possibly user-generated) keys. + ออบเจ็กต์เหล่านี้ใช้เป็น dictionary สำหรับเก็บ key อะไรก็ได้ (รวมถึง key ที่ผู้ใช้ป้อนเข้ามา) - Normally, objects inherit built-in methods and `__proto__` getter/setter from `Object.prototype`, making corresponding keys "occupied" and potentially causing side effects. With `null` prototype, objects are truly empty. + ปกติแล้วออบเจ็กต์จะสืบทอดเมธอด built-in และ `__proto__` getter/setter มาจาก `Object.prototype` ทำให้ key เหล่านั้นถูก "จอง" ไว้แล้ว และอาจทำให้เกิดผลข้างเคียงที่ไม่ต้องการ แต่ถ้าโปรโตไทป์เป็น `null` ออบเจ็กต์จะว่างเปล่าจริงๆ diff --git a/1-js/08-prototypes/index.md b/1-js/08-prototypes/index.md index 8554a0e30..10b1d5505 100644 --- a/1-js/08-prototypes/index.md +++ b/1-js/08-prototypes/index.md @@ -1 +1 @@ -# Prototypes, inheritance +# โปรโตไทป์และการสืบทอด