From cf236833b9e6ad1cb0e0478ce16fcfddc07bab3c Mon Sep 17 00:00:00 2001 From: Prasit Tongpradit Date: Mon, 23 Mar 2026 14:39:14 +0700 Subject: [PATCH] docs: translate classes section (7 articles) --- .../01-class/1-rewrite-to-class/task.md | 6 +- 1-js/09-classes/01-class/article.md | 215 ++++++----- .../1-class-constructor-error/solution.md | 8 +- .../1-class-constructor-error/task.md | 8 +- .../source.view/index.html | 2 +- .../2-clock-class-extended/task.md | 10 +- .../02-class-inheritance/article.md | 350 +++++++++--------- .../3-class-extend-object/solution.md | 42 +-- .../3-class-extend-object/task.md | 12 +- .../03-static-properties-methods/article.md | 106 +++--- .../article.md | 191 +++++----- 1-js/09-classes/05-extend-natives/article.md | 50 +-- .../1-strange-instanceof/solution.md | 8 +- .../1-strange-instanceof/task.md | 4 +- 1-js/09-classes/06-instanceof/article.md | 132 +++---- 1-js/09-classes/07-mixins/article.md | 108 +++--- 1-js/09-classes/07-mixins/head.html | 10 +- 1-js/09-classes/index.md | 2 +- 18 files changed, 631 insertions(+), 633 deletions(-) diff --git a/1-js/09-classes/01-class/1-rewrite-to-class/task.md b/1-js/09-classes/01-class/1-rewrite-to-class/task.md index 4477de679..6acfdc6bd 100644 --- a/1-js/09-classes/01-class/1-rewrite-to-class/task.md +++ b/1-js/09-classes/01-class/1-rewrite-to-class/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# Rewrite to class +# เขียนใหม่ด้วยคลาส -The `Clock` class (see the sandbox) is written in functional style. Rewrite it in the "class" syntax. +คลาส `Clock` (ดูใน sandbox) เขียนไว้แบบ functional style ให้เขียนใหม่โดยใช้ไวยากรณ์ "class" -P.S. The clock ticks in the console, open it to see. +P.S. นาฬิกาจะเดินอยู่ใน console ลองเปิดดู diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 135d24929..e353d33e0 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -1,22 +1,21 @@ - -# Class basic syntax +# ไวยากรณ์พื้นฐานของคลาส ```quote author="Wikipedia" -In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods). +ในแนวคิดการเขียนโปรแกรมเชิงวัตถุ (OOP) *คลาส* คือแม่แบบของโค้ดสำหรับสร้างออบเจ็กต์ โดยกำหนดค่าเริ่มต้นของสถานะ (ตัวแปรสมาชิก) และพฤติกรรม (ฟังก์ชันสมาชิกหรือเมธอด) ``` -In practice, we often need to create many objects of the same kind, like users, or goods or whatever. +ในทางปฏิบัติ เรามักจะต้องสร้างออบเจ็กต์จำนวนมากที่มีโครงสร้างเหมือนกัน ไม่ว่าจะเป็นผู้ใช้ สินค้า หรืออะไรก็ตาม -As we already know from the chapter , `new function` can help with that. +อย่างที่เรารู้จากบท แล้วว่า `new function` ช่วยจัดการเรื่องนี้ได้ -But in the modern JavaScript, there's a more advanced "class" construct, that introduces great new features which are useful for object-oriented programming. +แต่ใน JavaScript ยุคใหม่ มีไวยากรณ์ "class" ที่ทำได้มากกว่า และเพิ่มฟีเจอร์ใหม่ๆ ที่มีประโยชน์สำหรับการเขียนโปรแกรมเชิงวัตถุ -## The "class" syntax +## ไวยากรณ์ "class" -The basic syntax is: +โครงสร้างพื้นฐานเป็นแบบนี้: ```js class MyClass { - // class methods + // เมธอดของคลาส constructor() { ... } method1() { ... } method2() { ... } @@ -25,11 +24,11 @@ class MyClass { } ``` -Then use `new MyClass()` to create a new object with all the listed methods. +จากนั้นใช้ `new MyClass()` เพื่อสร้างออบเจ็กต์ใหม่ที่มีเมธอดทั้งหมดตามที่ระบุไว้ -The `constructor()` method is called automatically by `new`, so we can initialize the object there. +เมธอด `constructor()` จะถูกเรียกโดยอัตโนมัติเมื่อใช้ `new` ทำให้เรากำหนดค่าเริ่มต้นให้ออบเจ็กต์ได้ตรงนี้ -For example: +ยกตัวอย่าง: ```js run class User { @@ -44,33 +43,33 @@ class User { } -// Usage: +// วิธีใช้งาน: let user = new User("John"); user.sayHi(); ``` -When `new User("John")` is called: -1. A new object is created. -2. The `constructor` runs with the given argument and assigns it to `this.name`. +เมื่อเรียก `new User("John")` สิ่งที่เกิดขึ้นคือ: +1. สร้างออบเจ็กต์ใหม่ขึ้นมา +2. `constructor` ทำงานโดยรับอาร์กิวเมนต์ที่ส่งมา แล้วกำหนดให้ `this.name` -...Then we can call object methods, such as `user.sayHi()`. +...หลังจากนั้นก็เรียกเมธอดของออบเจ็กต์ได้เลย เช่น `user.sayHi()` -```warn header="No comma between class methods" -A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error. +```warn header="ห้ามใส่จุลภาคระหว่างเมธอดของคลาส" +ข้อผิดพลาดที่มักพบบ่อยสำหรับนักพัฒนามือใหม่คือการใส่จุลภาค (comma) ระหว่างเมธอดของคลาส ซึ่งจะทำให้เกิด syntax error -The notation here is not to be confused with object literals. Within the class, no commas are required. +ไวยากรณ์ตรงนี้ต่างจาก object literal นะ — ภายในคลาสไม่ต้องมีจุลภาคคั่นระหว่างเมธอด ``` -## What is a class? +## คลาสคืออะไรกันแน่? -So, what exactly is a `class`? That's not an entirely new language-level entity, as one might think. +แล้ว `class` จริงๆ คืออะไร? มันไม่ได้เป็นสิ่งใหม่ในระดับภาษาอย่างที่หลายคนอาจเข้าใจ -Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects. +มาเปิดเผยเบื้องหลังกันว่าคลาสจริงๆ แล้วเป็นอะไร เข้าใจตรงนี้แล้วจะช่วยให้เข้าใจเรื่องซับซ้อนอื่นๆ ได้ง่ายขึ้น -In JavaScript, a class is a kind of function. +ใน JavaScript คลาสก็คือฟังก์ชันชนิดหนึ่งนั่นเอง -Here, take a look: +ลองดู: ```js run class User { @@ -78,24 +77,24 @@ class User { sayHi() { alert(this.name); } } -// proof: User is a function +// พิสูจน์: User เป็นฟังก์ชัน *!* alert(typeof User); // function */!* ``` -What `class User {...}` construct really does is: +สิ่งที่ `class User {...}` ทำจริงๆ มีดังนี้: -1. Creates a function named `User`, that becomes the result of the class declaration. The function code is taken from the `constructor` method (assumed empty if we don't write such method). -2. Stores class methods, such as `sayHi`, in `User.prototype`. +1. สร้างฟังก์ชันชื่อ `User` ซึ่งเป็นผลลัพธ์ของการประกาศคลาส โดยโค้ดภายในฟังก์ชันมาจากเมธอด `constructor` (ถ้าไม่ได้เขียน `constructor` ไว้ ก็จะเป็นฟังก์ชันว่างๆ) +2. เก็บเมธอดทั้งหมด เช่น `sayHi` ไว้ใน `User.prototype` -After `new User` object is created, when we call its method, it's taken from the prototype, just as described in the chapter . So the object has access to class methods. +เมื่อสร้างออบเจ็กต์ด้วย `new User` แล้วเรียกเมธอด เมธอดนั้นจะถูกดึงมาจากโปรโตไทป์ ตามหลักการที่อธิบายไว้ในบท ทำให้ออบเจ็กต์เข้าถึงเมธอดของคลาสได้ -We can illustrate the result of `class User` declaration as: +ผลลัพธ์ของการประกาศ `class User` แสดงเป็นภาพได้ดังนี้: ![](class-user.svg) -Here's the code to introspect it: +ลองตรวจสอบด้วยโค้ดกัน: ```js run class User { @@ -103,50 +102,50 @@ class User { sayHi() { alert(this.name); } } -// class is a function +// คลาสก็คือฟังก์ชัน alert(typeof User); // function -// ...or, more precisely, the constructor method +// ...ถ้าจะพูดให้ชัดกว่านั้น ก็คือเมธอด constructor นั่นเอง alert(User === User.prototype.constructor); // true -// The methods are in User.prototype, e.g: -alert(User.prototype.sayHi); // the code of the sayHi method +// เมธอดต่างๆ อยู่ใน User.prototype เช่น: +alert(User.prototype.sayHi); // โค้ดของเมธอด sayHi -// there are exactly two methods in the prototype +// มีเมธอดอยู่ 2 ตัวใน prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi ``` -## Not just a syntactic sugar +## ไม่ใช่แค่น้ำตาลทางไวยากรณ์ -Sometimes people say that `class` is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same thing without using the `class` keyword at all: +บางคนบอกว่า `class` เป็นแค่ "น้ำตาลทางไวยากรณ์ (syntactic sugar)" (ไวยากรณ์ที่ออกแบบมาเพื่อให้อ่านง่ายขึ้น แต่ไม่ได้เพิ่มความสามารถใหม่) เพราะเราสามารถทำสิ่งเดียวกันได้โดยไม่ต้องใช้คีย์เวิร์ด `class` เลย: ```js run -// rewriting class User in pure functions +// เขียน class User ใหม่ด้วยฟังก์ชันล้วนๆ -// 1. Create constructor function +// 1. สร้าง constructor function function User(name) { this.name = name; } -// a function prototype has "constructor" property by default, -// so we don't need to create it +// function prototype มีพร็อพเพอร์ตี้ "constructor" อยู่แล้วโดยค่าเริ่มต้น +// จึงไม่ต้องสร้างเพิ่ม -// 2. Add the method to prototype +// 2. เพิ่มเมธอดเข้าไปที่ prototype User.prototype.sayHi = function() { alert(this.name); }; -// Usage: +// วิธีใช้งาน: let user = new User("John"); user.sayHi(); ``` -The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntactic sugar to define a constructor together with its prototype methods. +ผลลัพธ์ที่ได้จากการเขียนแบบนี้ก็แทบจะเหมือนกัน จึงมีเหตุผลที่จะมองว่า `class` เป็นแค่น้ำตาลทางไวยากรณ์สำหรับการนิยาม constructor พร้อมกับเมธอดบน prototype -Still, there are important differences. +แต่จริงๆ แล้วมีความแตกต่างที่สำคัญอยู่ -1. First, a function created by `class` is labelled by a special internal property `[[IsClassConstructor]]: true`. So it's not entirely the same as creating it manually. +1. ประการแรก ฟังก์ชันที่สร้างจาก `class` จะถูกติดป้ายด้วยพร็อพเพอร์ตี้ภายในพิเศษ `[[IsClassConstructor]]: true` จึงไม่เหมือนกับการสร้างฟังก์ชันเองทั้งหมด - The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with `new`: + JavaScript ตรวจสอบพร็อพเพอร์ตี้นี้ในหลายจุด ยกตัวอย่างเช่น ต่างจากฟังก์ชันปกติตรงที่ต้องเรียกด้วย `new` เสมอ: ```js run class User { @@ -157,7 +156,7 @@ Still, there are important differences. User(); // Error: Class constructor User cannot be invoked without 'new' ``` - Also, a string representation of a class constructor in most JavaScript engines starts with the "class..." + นอกจากนี้ เมื่อแปลง class constructor เป็นสตริง ใน JavaScript engine ส่วนใหญ่จะขึ้นต้นด้วยคำว่า "class..." ```js run class User { @@ -166,23 +165,23 @@ Still, there are important differences. alert(User); // class User { ... } ``` - There are other differences, we'll see them soon. + ยังมีความแตกต่างอื่นๆ อีก ซึ่งเราจะได้เห็นในไม่ช้า -2. Class methods are non-enumerable. - A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. +2. เมธอดของคลาสจะ enumerate ไม่ได้ + การประกาศคลาสจะตั้งค่า flag `enumerable` เป็น `false` ให้กับเมธอดทุกตัวใน `"prototype"` - That's good, because if we `for..in` over an object, we usually don't want its class methods. + ซึ่งเป็นเรื่องดี เพราะถ้าใช้ `for..in` วนลูปออบเจ็กต์ เราก็ไม่อยากให้เมธอดของคลาสโผล่มาด้วย -3. Classes always `use strict`. - All code inside the class construct is automatically in strict mode. +3. คลาสจะใช้ `use strict` เสมอ + โค้ดทั้งหมดภายในคลาสจะอยู่ใน strict mode โดยอัตโนมัติ -Besides, `class` syntax brings many other features that we'll explore later. +นอกจากนี้ ไวยากรณ์ `class` ยังมีฟีเจอร์อื่นๆ อีกมากที่เราจะศึกษาในบทถัดๆ ไป ## Class Expression -Just like functions, classes can be defined inside another expression, passed around, returned, assigned, etc. +เช่นเดียวกับฟังก์ชัน คลาสก็สามารถนิยามไว้ภายในนิพจน์ ส่งต่อไปเป็นค่า คืนค่าออกมา หรือกำหนดให้ตัวแปรได้ -Here's an example of a class expression: +ลองดูตัวอย่าง class expression: ```js let User = class { @@ -192,29 +191,29 @@ let User = class { }; ``` -Similar to Named Function Expressions, class expressions may have a name. +คล้ายกับ Named Function Expression ตรงที่ class expression ก็สามารถมีชื่อได้เช่นกัน -If a class expression has a name, it's visible inside the class only: +ถ้า class expression มีชื่อ ชื่อนั้นจะมองเห็นได้แค่ภายในคลาสเท่านั้น: ```js run // "Named Class Expression" -// (no such term in the spec, but that's similar to Named Function Expression) +// (ไม่ได้มีคำนี้ใน spec แต่คล้ายกับ Named Function Expression) let User = class *!*MyClass*/!* { sayHi() { - alert(MyClass); // MyClass name is visible only inside the class + alert(MyClass); // ชื่อ MyClass มองเห็นได้แค่ภายในคลาส } }; -new User().sayHi(); // works, shows MyClass definition +new User().sayHi(); // ทำงานได้ แสดงนิยามของ MyClass -alert(MyClass); // error, MyClass name isn't visible outside of the class +alert(MyClass); // error, ชื่อ MyClass มองไม่เห็นจากภายนอกคลาส ``` -We can even make classes dynamically "on-demand", like this: +เรายังสร้างคลาสแบบไดนามิก "ตามต้องการ" ได้ด้วย: ```js run function makeClass(phrase) { - // declare a class and return it + // ประกาศคลาสแล้วคืนค่าออกไป return class { sayHi() { alert(phrase); @@ -222,24 +221,24 @@ function makeClass(phrase) { }; } -// Create a new class +// สร้างคลาสใหม่ let User = makeClass("Hello"); new User().sayHi(); // Hello ``` -## Getters/setters +## Getter/Setter -Just like literal objects, classes may include getters/setters, computed properties etc. +เช่นเดียวกับ object literal คลาสก็สามารถมี getter/setter และ computed property ได้ -Here's an example for `user.name` implemented using `get/set`: +ลองดูตัวอย่างการใช้ `get/set` กับ `user.name`: ```js run class User { constructor(name) { - // invokes the setter + // เรียกใช้ setter this.name = name; } @@ -253,7 +252,7 @@ class User { set name(value) { */!* if (value.length < 4) { - alert("Name is too short."); + alert("ชื่อสั้นเกินไป"); return; } this._name = value; @@ -264,14 +263,14 @@ class User { let user = new User("John"); alert(user.name); // John -user = new User(""); // Name is too short. +user = new User(""); // ชื่อสั้นเกินไป ``` -Technically, such class declaration works by creating getters and setters in `User.prototype`. +ในทางเทคนิค การประกาศคลาสแบบนี้ทำงานโดยสร้าง getter และ setter ไว้ใน `User.prototype` -## Computed names [...] +## Computed Name [...] -Here's an example with a computed method name using brackets `[...]`: +ลองดูตัวอย่างการใช้ชื่อเมธอดแบบ computed ด้วยวงเล็บเหลี่ยม `[...]`: ```js run class User { @@ -287,19 +286,19 @@ class User { new User().sayHi(); ``` -Such features are easy to remember, as they resemble that of literal objects. +ฟีเจอร์นี้จำง่าย เพราะคล้ายกับ object literal เลย -## Class fields +## Class Field -```warn header="Old browsers may need a polyfill" -Class fields are a recent addition to the language. +```warn header="เบราว์เซอร์เก่าอาจต้องใช้ polyfill" +Class field เป็นฟีเจอร์ที่เพิ่มเข้ามาไม่นาน ``` -Previously, our classes only had methods. +ก่อนหน้านี้คลาสของเรามีแต่เมธอด -"Class fields" is a syntax that allows to add any properties. +"Class field" คือไวยากรณ์สำหรับเพิ่มพร็อพเพอร์ตี้ใดๆ เข้าไปในคลาสได้ -For instance, let's add `name` property to `class User`: +ยกตัวอย่าง ลองเพิ่มพร็อพเพอร์ตี้ `name` ใน `class User`: ```js run class User { @@ -315,9 +314,9 @@ class User { new User().sayHi(); // Hello, John! ``` -So, we just write " = " in the declaration, and that's it. +เขียนง่ายมาก แค่ใส่ " = " ตามด้วยค่าในการประกาศ -The important difference of class fields is that they are set on individual objects, not `User.prototype`: +สิ่งสำคัญที่ต่างจากเมธอดคือ class field จะถูกกำหนดในแต่ละออบเจ็กต์โดยตรง ไม่ได้อยู่ใน `User.prototype`: ```js run class User { @@ -331,7 +330,7 @@ alert(user.name); // John alert(User.prototype.name); // undefined ``` -We can also assign values using more complex expressions and function calls: +เรายังสามารถกำหนดค่าโดยใช้นิพจน์ที่ซับซ้อนหรือเรียกฟังก์ชันได้ด้วย: ```js run class User { @@ -345,13 +344,13 @@ alert(user.name); // John ``` -### Making bound methods with class fields +### สร้าง bound method ด้วย class field -As demonstrated in the chapter functions in JavaScript have a dynamic `this`. It depends on the context of the call. +อย่างที่เราเห็นจากบท ฟังก์ชันใน JavaScript มี `this` ที่เปลี่ยนไปตามบริบทของการเรียกใช้ -So if an object method is passed around and called in another context, `this` won't be a reference to its object any more. +ดังนั้น ถ้านำเมธอดของออบเจ็กต์ไปใช้ในบริบทอื่น `this` จะไม่ชี้กลับไปที่ออบเจ็กต์เดิมอีกต่อไป -For instance, this code will show `undefined`: +ยกตัวอย่าง โค้ดนี้จะแสดง `undefined`: ```js run class Button { @@ -371,14 +370,14 @@ setTimeout(button.click, 1000); // undefined */!* ``` -The problem is called "losing `this`". +ปัญหานี้เรียกว่า "การสูญเสีย `this`" -There are two approaches to fixing it, as discussed in the chapter : +มี 2 วิธีแก้ไข ตามที่อธิบายไว้ในบท : -1. Pass a wrapper-function, such as `setTimeout(() => button.click(), 1000)`. -2. Bind the method to object, e.g. in the constructor. +1. ส่งฟังก์ชันห่อหุ้ม (wrapper function) เช่น `setTimeout(() => button.click(), 1000)` +2. ผูกเมธอดกับออบเจ็กต์ เช่น ทำใน constructor -Class fields provide another, quite elegant syntax: +class field มีอีกวิธีที่กระชับดี: ```js run class Button { @@ -397,32 +396,32 @@ let button = new Button("hello"); setTimeout(button.click, 1000); // hello ``` -The class field `click = () => {...}` is created on a per-object basis, there's a separate function for each `Button` object, with `this` inside it referencing that object. We can pass `button.click` around anywhere, and the value of `this` will always be correct. +class field `click = () => {...}` จะถูกสร้างขึ้นในแต่ละออบเจ็กต์ แยกฟังก์ชันกันสำหรับ `Button` แต่ละตัว โดย `this` ภายในจะชี้ไปที่ออบเจ็กต์นั้นเสมอ เราจึงส่ง `button.click` ไปที่ไหนก็ได้ และ `this` จะถูกต้องเสมอ -That's especially useful in browser environment, for event listeners. +ฟีเจอร์นี้มีประโยชน์มากโดยเฉพาะในเบราว์เซอร์ สำหรับจัดการ event listener -## Summary +## สรุป -The basic class syntax looks like this: +ไวยากรณ์พื้นฐานของคลาสเป็นดังนี้: ```js class MyClass { - prop = value; // property + prop = value; // พร็อพเพอร์ตี้ - constructor(...) { // constructor + constructor(...) { // คอนสตรักเตอร์ // ... } - method(...) {} // method + method(...) {} // เมธอด - get something(...) {} // getter method - set something(...) {} // setter method + get something(...) {} // getter + set something(...) {} // setter - [Symbol.iterator]() {} // method with computed name (symbol here) + [Symbol.iterator]() {} // เมธอดที่ใช้ computed name (ในที่นี้เป็น symbol) // ... } ``` -`MyClass` is technically a function (the one that we provide as `constructor`), while methods, getters and setters are written to `MyClass.prototype`. +`MyClass` ในทางเทคนิคแล้วก็คือฟังก์ชัน (ตัวที่เราเขียนใน `constructor`) ส่วนเมธอด getter และ setter จะถูกเขียนไว้ใน `MyClass.prototype` -In the next chapters we'll learn more about classes, including inheritance and other features. +ในบทถัดๆ ไป เราจะเรียนรู้เพิ่มเติมเกี่ยวกับคลาส รวมถึงการสืบทอดและฟีเจอร์อื่นๆ diff --git a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md index 4711e4827..1fb627096 100644 --- a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md +++ b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md @@ -1,6 +1,6 @@ -That's because the child constructor must call `super()`. +เป็นเพราะคอนสตรักเตอร์ของคลาสลูกต้องเรียก `super()` ก่อน -Here's the corrected code: +นี่คือโค้ดที่แก้แล้ว: ```js run class Animal { @@ -12,7 +12,7 @@ class Animal { } class Rabbit extends Animal { - constructor(name) { + constructor(name) { *!* super(name); */!* @@ -21,7 +21,7 @@ class Rabbit extends Animal { } *!* -let rabbit = new Rabbit("White Rabbit"); // ok now +let rabbit = new Rabbit("White Rabbit"); // ตอนนี้ใช้ได้แล้ว */!* alert(rabbit.name); // White Rabbit ``` diff --git a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md index 380a4720b..c2006dd18 100644 --- a/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md +++ b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Error creating an instance +# Error ตอนสร้างอินสแตนซ์ -Here's the code with `Rabbit` extending `Animal`. +โค้ดด้านล่างมี `Rabbit` สืบทอดจาก `Animal` -Unfortunately, `Rabbit` objects can't be created. What's wrong? Fix it. +แต่น่าเสียดาย ออบเจ็กต์ `Rabbit` สร้างไม่ได้ อะไรผิดพลาด? ลองแก้ดู ```js run class Animal { @@ -17,7 +17,7 @@ class Animal { } class Rabbit extends Animal { - constructor(name) { + constructor(name) { this.name = name; this.created = Date.now(); } diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html index c0609858b..de4045a86 100644 --- a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html @@ -7,7 +7,7 @@ clock.start(); - /* Your class should work like this: */ + /* คลาสของคุณควรทำงานได้แบบนี้: */ /* diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md index bbc2c6a43..50d3f589f 100644 --- a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md @@ -2,14 +2,14 @@ importance: 5 --- -# Extended clock +# นาฬิกาแบบขยาย (Extended clock) -We've got a `Clock` class. As of now, it prints the time every second. +เรามีคลาส `Clock` อยู่แล้ว ซึ่งแสดงเวลาทุกวินาที [js src="source.view/clock.js"] -Create a new class `ExtendedClock` that inherits from `Clock` and adds the parameter `precision` -- the number of `ms` between "ticks". Should be `1000` (1 second) by default. +ให้สร้างคลาส `ExtendedClock` ที่สืบทอดจาก `Clock` แล้วเพิ่มพารามิเตอร์ `precision` ซึ่งเป็นจำนวนมิลลิวินาทีระหว่าง "ติ๊ก" แต่ละครั้ง ค่าเริ่มต้นคือ `1000` (1 วินาที) -- Your code should be in the file `extended-clock.js` -- Don't modify the original `clock.js`. Extend it. +- เขียนโค้ดในไฟล์ `extended-clock.js` +- อย่าแก้ไขไฟล์ `clock.js` เดิม ให้ขยายจากมันแทน diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 464042d82..bba495161 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -1,13 +1,13 @@ -# Class inheritance +# การสืบทอดคลาส (Class Inheritance) -Class inheritance is a way for one class to extend another class. +การสืบทอดคลาสเป็นวิธีที่ทำให้คลาสหนึ่งสามารถขยายความสามารถจากอีกคลาสหนึ่งได้ -So we can create new functionality on top of the existing. +เราจึงสร้างฟีเจอร์ใหม่ต่อยอดจากสิ่งที่มีอยู่แล้วได้ -## The "extends" keyword +## คีย์เวิร์ด "extends" -Let's say we have class `Animal`: +สมมติว่าเรามีคลาส `Animal` แบบนี้: ```js class Animal { @@ -17,61 +17,61 @@ class Animal { } run(speed) { this.speed = speed; - alert(`${this.name} runs with speed ${this.speed}.`); + alert(`${this.name} วิ่งด้วยความเร็ว ${this.speed}.`); } stop() { this.speed = 0; - alert(`${this.name} stands still.`); + alert(`${this.name} หยุดนิ่ง.`); } } let animal = new Animal("My animal"); ``` -Here's how we can represent `animal` object and `Animal` class graphically: +ถ้าวาดเป็นรูป ออบเจ็กต์ `animal` กับคลาส `Animal` จะหน้าตาแบบนี้: ![](rabbit-animal-independent-animal.svg) -...And we would like to create another `class Rabbit`. +...แล้วเราอยากสร้างอีกคลาสหนึ่งชื่อ `class Rabbit` -As rabbits are animals, `Rabbit` class should be based on `Animal`, have access to animal methods, so that rabbits can do what "generic" animals can do. +เนื่องจากกระต่ายก็เป็นสัตว์ชนิดหนึ่ง คลาส `Rabbit` จึงควรสร้างต่อยอดจาก `Animal` โดยเข้าถึงเมธอดของ Animal ได้ เพื่อให้กระต่ายทำทุกอย่างที่สัตว์ทั่วไปทำได้ -The syntax to extend another class is: `class Child extends Parent`. +ไวยากรณ์สำหรับขยายจากอีกคลาสหนึ่งคือ: `class Child extends Parent` -Let's create `class Rabbit` that inherits from `Animal`: +มาสร้าง `class Rabbit` ที่สืบทอดจาก `Animal` กัน: ```js *!* class Rabbit extends Animal { */!* hide() { - alert(`${this.name} hides!`); + alert(`${this.name} ซ่อนตัว!`); } } let rabbit = new Rabbit("White Rabbit"); -rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.hide(); // White Rabbit hides! +rabbit.run(5); // White Rabbit วิ่งด้วยความเร็ว 5. +rabbit.hide(); // White Rabbit ซ่อนตัว! ``` -Object of `Rabbit` class have access both to `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. +ออบเจ็กต์ของคลาส `Rabbit` สามารถเรียกใช้ได้ทั้งเมธอดของ `Rabbit` เอง เช่น `rabbit.hide()` และเมธอดของ `Animal` เช่น `rabbit.run()` ด้วย -Internally, `extends` keyword works using the good old prototype mechanics. It sets `Rabbit.prototype.[[Prototype]]` to `Animal.prototype`. So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. +เบื้องหลังการทำงาน คีย์เวิร์ด `extends` ใช้กลไกโปรโตไทป์ที่เรารู้จักดีอยู่แล้ว มันตั้งค่า `Rabbit.prototype.[[Prototype]]` ให้ชี้ไปที่ `Animal.prototype` ดังนั้นถ้าหาเมธอดใน `Rabbit.prototype` ไม่เจอ JavaScript ก็จะไปหาต่อใน `Animal.prototype` ![](animal-rabbit-extends.svg) -For instance, to find `rabbit.run` method, the engine checks (bottom-up on the picture): -1. The `rabbit` object (has no `run`). -2. Its prototype, that is `Rabbit.prototype` (has `hide`, but not `run`). -3. Its prototype, that is (due to `extends`) `Animal.prototype`, that finally has the `run` method. +ยกตัวอย่างเช่น การค้นหาเมธอด `rabbit.run` เอนจินจะตรวจสอบตามลำดับ (จากล่างขึ้นบนในรูป): +1. ออบเจ็กต์ `rabbit` (ไม่มี `run`) +2. โปรโตไทป์ของมัน คือ `Rabbit.prototype` (มี `hide` แต่ไม่มี `run`) +3. โปรโตไทป์ถัดขึ้นไป คือ `Animal.prototype` (ซึ่งเป็นผลจาก `extends`) ในที่สุดก็เจอเมธอด `run` ที่นี่ -As we can recall from the chapter , JavaScript itself uses prototypal inheritance for built-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`. That's why dates have access to generic object methods. +อย่างที่เราเรียนรู้มาจากบท ตัว JavaScript เองก็ใช้ prototypal inheritance กับออบเจ็กต์ที่มีมาให้เช่นกัน เช่น `Date.prototype.[[Prototype]]` คือ `Object.prototype` ทำให้ Date เข้าถึงเมธอดทั่วไปของออบเจ็กต์ได้ -````smart header="Any expression is allowed after `extends`" -Class syntax allows to specify not just a class, but any expression after `extends`. +````smart header="หลัง `extends` ใส่ expression อะไรก็ได้" +ไวยากรณ์ของคลาสอนุญาตให้ระบุไม่เฉพาะแค่ชื่อคลาส แต่ใส่ expression อะไรก็ได้หลัง `extends` -For instance, a function call that generates the parent class: +ยกตัวอย่าง การเรียกฟังก์ชันที่สร้างคลาสแม่ขึ้นมา: ```js run function f(phrase) { @@ -86,34 +86,34 @@ class User extends f("Hello") {} new User().sayHi(); // Hello ``` -Here `class User` inherits from the result of `f("Hello")`. +ในที่นี้ `class User` สืบทอดจากผลลัพธ์ของ `f("Hello")` -That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them. +เทคนิคนี้มีประโยชน์สำหรับรูปแบบการเขียนโปรแกรมขั้นสูง เมื่อเราต้องการใช้ฟังก์ชันสร้างคลาสตามเงื่อนไขต่างๆ แล้วค่อยสืบทอดจากคลาสเหล่านั้น ```` -## Overriding a method +## การ Override เมธอด -Now let's move forward and override a method. By default, all methods that are not specified in `class Rabbit` are taken directly "as is" from `class Animal`. +ทีนี้มาดูเรื่องการ override เมธอดกัน โดยปกติแล้ว เมธอดใดที่ไม่ได้กำหนดไว้ใน `class Rabbit` จะถูกนำมาจาก `class Animal` ตรงๆ เลย -But if we specify our own method in `Rabbit`, such as `stop()` then it will be used instead: +แต่ถ้าเรากำหนดเมธอดชื่อเดียวกันไว้ใน `Rabbit` เช่น `stop()` ตัวนี้จะถูกใช้แทน: ```js class Rabbit extends Animal { stop() { - // ...now this will be used for rabbit.stop() - // instead of stop() from class Animal + // ...ตอนนี้เมธอดนี้จะถูกเรียกเมื่อใช้ rabbit.stop() + // แทนที่ stop() จากคลาส Animal } } ``` -Usually, however, we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. +แต่โดยทั่วไปแล้ว เรามักไม่ต้องการแทนที่เมธอดจากคลาสแม่ทั้งหมด แต่อยากต่อยอดจากเมธอดเดิม ปรับแต่งหรือเพิ่มความสามารถเข้าไป เราอาจทำอะไรบางอย่างในเมธอดของเรา แล้วเรียกเมธอดของคลาสแม่ก่อน/หลัง หรือระหว่างการทำงาน -Classes provide `"super"` keyword for that. +คลาสมีคีย์เวิร์ด `"super"` ไว้ใช้สำหรับกรณีนี้ -- `super.method(...)` to call a parent method. -- `super(...)` to call a parent constructor (inside our constructor only). +- `super.method(...)` เรียกเมธอดของคลาสแม่ +- `super(...)` เรียกคอนสตรักเตอร์ของคลาสแม่ (ใช้ได้เฉพาะภายในคอนสตรักเตอร์เท่านั้น) -For instance, let our rabbit autohide when stopped: +ยกตัวอย่าง ให้กระต่ายซ่อนตัวอัตโนมัติเมื่อหยุด: ```js run class Animal { @@ -125,51 +125,51 @@ class Animal { run(speed) { this.speed = speed; - alert(`${this.name} runs with speed ${this.speed}.`); + alert(`${this.name} วิ่งด้วยความเร็ว ${this.speed}.`); } stop() { this.speed = 0; - alert(`${this.name} stands still.`); + alert(`${this.name} หยุดนิ่ง.`); } } class Rabbit extends Animal { hide() { - alert(`${this.name} hides!`); + alert(`${this.name} ซ่อนตัว!`); } *!* stop() { - super.stop(); // call parent stop - this.hide(); // and then hide + super.stop(); // เรียก stop ของคลาสแม่ + this.hide(); // แล้วค่อยซ่อนตัว } */!* } let rabbit = new Rabbit("White Rabbit"); -rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stands still. White Rabbit hides! +rabbit.run(5); // White Rabbit วิ่งด้วยความเร็ว 5. +rabbit.stop(); // White Rabbit หยุดนิ่ง. White Rabbit ซ่อนตัว! ``` -Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process. +ตอนนี้ `Rabbit` มีเมธอด `stop` ที่เรียกเมธอด `super.stop()` ของคลาสแม่ระหว่างทำงาน -````smart header="Arrow functions have no `super`" -As was mentioned in the chapter , arrow functions do not have `super`. +````smart header="Arrow function ไม่มี `super`" +ดังที่กล่าวไว้ในบท arrow function ไม่มี `super` เป็นของตัวเอง -If accessed, it's taken from the outer function. For instance: +ถ้ามีการเข้าถึง `super` ภายใน arrow function จะไปหยิบมาจากฟังก์ชันภายนอก เช่น: ```js class Rabbit extends Animal { stop() { - setTimeout(() => super.stop(), 1000); // call parent stop after 1sec + setTimeout(() => super.stop(), 1000); // เรียก stop ของคลาสแม่หลังผ่านไป 1 วินาที } } ``` -The `super` in the arrow function is the same as in `stop()`, so it works as intended. If we specified a "regular" function here, there would be an error: +`super` ใน arrow function จึงเป็นตัวเดียวกับใน `stop()` ทำให้ทำงานได้ถูกต้อง แต่ถ้าใช้ฟังก์ชันปกติจะเกิด error: ```js // Unexpected super @@ -177,17 +177,17 @@ setTimeout(function() { super.stop() }, 1000); ``` ```` -## Overriding constructor +## การ Override คอนสตรักเตอร์ -With constructors it gets a little bit tricky. +เรื่องคอนสตรักเตอร์นี้ค่อนข้างมีรายละเอียดหน่อย -Until now, `Rabbit` did not have its own `constructor`. +จนถึงตอนนี้ `Rabbit` ยังไม่มีคอนสตรักเตอร์เป็นของตัวเอง -According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated: +ตาม [สเปค](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation) ถ้าคลาสสืบทอดจากคลาสอื่นแล้วไม่มี `constructor` จะมีคอนสตรักเตอร์ "เปล่าๆ" ถูกสร้างขึ้นให้อัตโนมัติดังนี้: ```js class Rabbit extends Animal { - // generated for extending classes without own constructors + // สร้างให้อัตโนมัติสำหรับคลาสลูกที่ไม่มีคอนสตรักเตอร์ของตัวเอง *!* constructor(...args) { super(...args); @@ -196,9 +196,9 @@ class Rabbit extends Animal { } ``` -As we can see, it basically calls the parent `constructor` passing it all the arguments. That happens if we don't write a constructor of our own. +จะเห็นว่าคอนสตรักเตอร์นี้แค่เรียกคอนสตรักเตอร์ของคลาสแม่แล้วส่ง argument ทั้งหมดต่อให้ ซึ่งจะเกิดขึ้นเมื่อเราไม่ได้เขียนคอนสตรักเตอร์เอง -Now let's add a custom constructor to `Rabbit`. It will specify the `earLength` in addition to `name`: +ทีนี้มาลองเพิ่มคอนสตรักเตอร์ของเราเองให้ `Rabbit` โดยกำหนด `earLength` เพิ่มจาก `name`: ```js run class Animal { @@ -223,31 +223,31 @@ class Rabbit extends Animal { } *!* -// Doesn't work! +// ใช้ไม่ได้! let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. */!* ``` -Whoops! We've got an error. Now we can't create rabbits. What went wrong? +โอ้โห! เกิด error ขึ้นมา ตอนนี้สร้างกระต่ายไม่ได้แล้ว เกิดอะไรขึ้น? -The short answer is: +คำตอบสั้นๆ คือ: -- **Constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`.** +- **คอนสตรักเตอร์ของคลาสลูกต้องเรียก `super(...)` และ (!) ต้องเรียก *ก่อน* ที่จะใช้ `this`** -...But why? What's going on here? Indeed, the requirement seems strange. +...แต่ทำไมล่ะ? ที่เป็นแบบนี้เพราะอะไร? ข้อกำหนดนี้ฟังดูแปลกๆ ใช่ไหม? -Of course, there's an explanation. Let's get into details, so you'll really understand what's going on. +แน่นอนว่ามีคำอธิบาย มาลงรายละเอียดกัน เพื่อจะได้เข้าใจจริงๆ ว่าเกิดอะไรขึ้น -In JavaScript, there's a distinction between a constructor function of an inheriting class (so-called "derived constructor") and other functions. A derived constructor has a special internal property `[[ConstructorKind]]:"derived"`. That's a special internal label. +ใน JavaScript มีความแตกต่างระหว่างคอนสตรักเตอร์ของคลาสลูก (เรียกว่า "derived constructor") กับคอนสตรักเตอร์ทั่วไป โดยคอนสตรักเตอร์ของคลาสลูกจะมีพร็อพเพอร์ตี้ภายในพิเศษ `[[ConstructorKind]]:"derived"` ที่ทำให้พฤติกรรมแตกต่างออกไป -That label affects its behavior with `new`. +พร็อพเพอร์ตี้นี้ส่งผลต่อพฤติกรรมเมื่อใช้กับ `new` -- When a regular function is executed with `new`, it creates an empty object and assigns it to `this`. -- But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job. +- เมื่อฟังก์ชันปกติถูกเรียกด้วย `new` จะสร้างออบเจ็กต์เปล่าขึ้นมาแล้วกำหนดให้ `this` +- แต่เมื่อ derived constructor ทำงาน จะ *ไม่ได้* สร้างออบเจ็กต์เอง แต่คาดหวังให้คอนสตรักเตอร์ของคลาสแม่เป็นคนสร้างให้ -So a derived constructor must call `super` in order to execute its parent (base) constructor, otherwise the object for `this` won't be created. And we'll get an error. +ดังนั้น derived constructor จึงต้องเรียก `super` เพื่อให้คอนสตรักเตอร์ของคลาสแม่ (base) ทำงาน ไม่เช่นนั้นออบเจ็กต์สำหรับ `this` จะไม่ถูกสร้างขึ้น แล้วก็จะเกิด error -For the `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here: +เพื่อให้คอนสตรักเตอร์ของ `Rabbit` ทำงานได้ ต้องเรียก `super()` ก่อนใช้ `this` แบบนี้: ```js run class Animal { @@ -273,28 +273,28 @@ class Rabbit extends Animal { } *!* -// now fine +// ตอนนี้ใช้ได้แล้ว let rabbit = new Rabbit("White Rabbit", 10); alert(rabbit.name); // White Rabbit alert(rabbit.earLength); // 10 */!* ``` -### Overriding class fields: a tricky note +### การ Override ฟิลด์ของคลาส: จุดที่ต้องระวัง -```warn header="Advanced note" -This note assumes you have a certain experience with classes, maybe in other programming languages. +```warn header="หมายเหตุขั้นสูง" +หมายเหตุนี้เหมาะสำหรับผู้ที่มีประสบการณ์ใช้คลาสมาบ้างแล้ว อาจเป็นจากภาษาอื่นก็ได้ -It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often). +เนื้อหาส่วนนี้จะช่วยให้เข้าใจภาษาลึกขึ้น และอธิบายพฤติกรรมที่อาจเป็นแหล่งที่มาของ bug (แม้จะไม่บ่อยนัก) -If you find it difficult to understand, just go on, continue reading, then return to it some time later. +ถ้ารู้สึกว่ายากเกินไป ข้ามไปก่อนได้เลย แล้วค่อยกลับมาอ่านทีหลัง ``` -We can override not only methods, but also class fields. +เรา override ได้ไม่เฉพาะเมธอด แต่ override ฟิลด์ของคลาสได้ด้วย -Although, there's a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages. +แต่มีพฤติกรรมที่ค่อนข้างแปลก เมื่อเข้าถึงฟิลด์ที่ถูก override ภายในคอนสตรักเตอร์ของคลาสแม่ ซึ่งต่างจากภาษาโปรแกรมอื่นๆ มาก -Consider this example: +ลองดูตัวอย่างนี้: ```js run class Animal { @@ -315,28 +315,28 @@ new Rabbit(); // animal */!* ``` -Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value. +ในที่นี้ คลาส `Rabbit` สืบทอดจาก `Animal` แล้ว override ฟิลด์ `name` ด้วยค่าของตัวเอง -There's no own constructor in `Rabbit`, so `Animal` constructor is called. +`Rabbit` ไม่มีคอนสตรักเตอร์ของตัวเอง จึงเรียกคอนสตรักเตอร์ของ `Animal` แทน -What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`. +สิ่งที่น่าสนใจคือ ทั้ง `new Animal()` และ `new Rabbit()` ต่าง `alert` ในบรรทัด `(*)` แสดงผลเป็น `animal` ทั้งคู่ -**In other words, the parent constructor always uses its own field value, not the overridden one.** +**พูดอีกอย่างก็คือ คอนสตรักเตอร์ของคลาสแม่จะใช้ค่าฟิลด์ของตัวเองเสมอ ไม่ใช่ค่าที่ถูก override** -What's odd about it? +แปลกไหม? -If it's not clear yet, please compare with methods. +ถ้ายังไม่ชัด ลองเปรียบเทียบกับเมธอดดู -Here's the same code, but instead of `this.name` field we call `this.showName()` method: +โค้ดด้านล่างเหมือนกัน แต่เปลี่ยนจากฟิลด์ `this.name` เป็นการเรียกเมธอด `this.showName()` แทน: ```js run class Animal { - showName() { // instead of this.name = 'animal' + showName() { // แทน this.name = 'animal' alert('animal'); } constructor() { - this.showName(); // instead of alert(this.name); + this.showName(); // แทน alert(this.name); } } @@ -352,55 +352,55 @@ new Rabbit(); // rabbit */!* ``` -Please note: now the output is different. +สังเกตว่าผลลัพธ์ต่างกันแล้ว -And that's what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method. +และนี่คือสิ่งที่เราคาดหวัง เมื่อคอนสตรักเตอร์ของคลาสแม่ถูกเรียกในคลาสลูก จะใช้เมธอดที่ถูก override แล้ว -...But for class fields it's not so. As said, the parent constructor always uses the parent field. +...แต่กับฟิลด์กลับไม่เป็นเช่นนั้น ดังที่กล่าวไป คอนสตรักเตอร์ของคลาสแม่จะใช้ฟิลด์ของคลาสแม่เสมอ -Why is there a difference? +ทำไมถึงแตกต่างกัน? -Well, the reason is the field initialization order. The class field is initialized: -- Before constructor for the base class (that doesn't extend anything), -- Immediately after `super()` for the derived class. +เหตุผลก็คือลำดับการ initialize ฟิลด์ต่างกัน โดยฟิลด์ของคลาสจะถูก initialize ดังนี้: +- *ก่อน* คอนสตรักเตอร์ สำหรับคลาสฐาน (base class) ที่ไม่ได้สืบทอดจากใคร +- *ทันทีหลัง* `super()` สำหรับคลาสลูก (derived class) -In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`. +ในกรณีของเรา `Rabbit` เป็นคลาสลูก ไม่มี `constructor()` ของตัวเอง ซึ่งก็เหมือนกับมีคอนสตรักเตอร์เปล่าๆ ที่มีแค่ `super(...args)` อยู่ข้างใน -So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used. +ดังนั้นเมื่อ `new Rabbit()` เรียก `super()` ก็จะเข้าสู่คอนสตรักเตอร์ของคลาสแม่ และ (ตามกฎของคลาสลูก) ฟิลด์ของ `Rabbit` จะถูก initialize หลังจากนั้น ขณะที่คอนสตรักเตอร์ของคลาสแม่ทำงาน ฟิลด์ของ `Rabbit` จึงยังไม่มี จึงต้องใช้ฟิลด์ของ `Animal` แทน -This subtle difference between fields and methods is specific to JavaScript. +ความแตกต่างอันละเอียดอ่อนระหว่างฟิลด์กับเมธอดนี้ เป็นพฤติกรรมเฉพาะของ JavaScript -Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here. +โชคดีที่พฤติกรรมนี้จะเป็นปัญหาก็ต่อเมื่อใช้ฟิลด์ที่ถูก override ภายในคอนสตรักเตอร์ของคลาสแม่เท่านั้น ซึ่งอาจทำให้สับสนได้ จึงอธิบายไว้ตรงนี้ -If it becomes a problem, one can fix it by using methods or getters/setters instead of fields. +ถ้าเจอปัญหานี้ แก้ได้โดยใช้เมธอดหรือ getter/setter แทนฟิลด์ -## Super: internals, [[HomeObject]] +## Super: เบื้องลึก, [[HomeObject]] -```warn header="Advanced information" -If you're reading the tutorial for the first time - this section may be skipped. +```warn header="ข้อมูลขั้นสูง" +ถ้าอ่าน tutorial นี้เป็นครั้งแรก อาจข้ามส่วนนี้ไปก่อนได้ -It's about the internal mechanisms behind inheritance and `super`. +เนื้อหาส่วนนี้เจาะลึกเรื่องกลไกภายในของการสืบทอดและ `super` ``` -Let's get a little deeper under the hood of `super`. We'll see some interesting things along the way. +มาเจาะลึกเบื้องหลังการทำงานของ `super` กัน จะได้เห็นสิ่งน่าสนใจระหว่างทาง -First to say, from all that we've learned till now, it's impossible for `super` to work at all! +อันดับแรกต้องบอกว่า จากทุกอย่างที่เราเรียนมา `super` ไม่น่าจะทำงานได้เลย! -Yeah, indeed, let's ask ourselves, how it should technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, the engine needs to get the `method` from the prototype of the current object. But how? +จริงๆ นะ ลองคิดดูว่ามันควรทำงานอย่างไร เมื่อเมธอดของออบเจ็กต์ทำงาน จะได้ออบเจ็กต์ปัจจุบันเป็น `this` ถ้าเราเรียก `super.method()` เอนจินก็ต้องหา `method` จากโปรโตไทป์ของออบเจ็กต์ปัจจุบัน แต่ทำยังไงล่ะ? -The task may seem simple, but it isn't. The engine knows the current object `this`, so it could get the parent `method` as `this.__proto__.method`. Unfortunately, such a "naive" solution won't work. +ดูเหมือนง่าย แต่ไม่ง่ายเลย เอนจินรู้จักออบเจ็กต์ปัจจุบัน `this` ก็จริง จึงน่าจะหาเมธอดจากคลาสแม่ได้ด้วย `this.__proto__.method` แต่น่าเสียดาย วิธี "ซื่อๆ" แบบนี้ใช้ไม่ได้ -Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity. +มาดูตัวอย่างปัญหากัน ใช้ออบเจ็กต์ธรรมดาแทนคลาสเพื่อให้เข้าใจง่ายขึ้น -You may skip this part and go below to the `[[HomeObject]]` subsection if you don't want to know the details. That won't harm. Or read on if you're interested in understanding things in-depth. +ถ้าไม่อยากรู้รายละเอียด ข้ามไปที่หัวข้อย่อย `[[HomeObject]]` ด้านล่างได้เลย จะไม่มีผลอะไร หรือถ้าสนใจเจาะลึกก็อ่านต่อได้ -In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`: +ในตัวอย่างด้านล่าง `rabbit.__proto__ = animal` ทีนี้ลองมาดูว่า ถ้าใน `rabbit.eat()` เราเรียก `animal.eat()` ผ่าน `this.__proto__` จะเป็นอย่างไร: ```js run let animal = { name: "Animal", eat() { - alert(`${this.name} eats.`); + alert(`${this.name} กินอาหาร.`); } }; @@ -409,33 +409,33 @@ let rabbit = { name: "Rabbit", eat() { *!* - // that's how super.eat() could presumably work + // super.eat() น่าจะทำงานแบบนี้ this.__proto__.eat.call(this); // (*) */!* } }; -rabbit.eat(); // Rabbit eats. +rabbit.eat(); // Rabbit กินอาหาร. ``` -At the line `(*)` we take `eat` from the prototype (`animal`) and call it in the context of the current object. Please note that `.call(this)` is important here, because a simple `this.__proto__.eat()` would execute parent `eat` in the context of the prototype, not the current object. +ที่บรรทัด `(*)` เราหยิบ `eat` จากโปรโตไทป์ (`animal`) แล้วเรียกในบริบทของออบเจ็กต์ปัจจุบัน สังเกตว่า `.call(this)` สำคัญมาก เพราะถ้าเรียกแค่ `this.__proto__.eat()` จะรันเมธอด `eat` ในบริบทของโปรโตไทป์ ไม่ใช่ออบเจ็กต์ปัจจุบัน -And in the code above it actually works as intended: we have the correct `alert`. +และในโค้ดข้างต้นก็ทำงานได้ถูกต้อง `alert` แสดงผลตามที่ต้องการ -Now let's add one more object to the chain. We'll see how things break: +ทีนี้ลองเพิ่มออบเจ็กต์อีกตัวเข้าไปในสาย chain แล้วจะเห็นว่ามีปัญหา: ```js run let animal = { name: "Animal", eat() { - alert(`${this.name} eats.`); + alert(`${this.name} กินอาหาร.`); } }; let rabbit = { __proto__: animal, eat() { - // ...bounce around rabbit-style and call parent (animal) method + // ...ทำอะไรบางอย่างแบบกระต่าย แล้วเรียกเมธอดของคลาสแม่ (animal) this.__proto__.eat.call(this); // (*) } }; @@ -443,7 +443,7 @@ let rabbit = { let longEar = { __proto__: rabbit, eat() { - // ...do something with long ears and call parent (rabbit) method + // ...ทำอะไรบางอย่างกับหูยาว แล้วเรียกเมธอดของคลาสแม่ (rabbit) this.__proto__.eat.call(this); // (**) } }; @@ -453,55 +453,55 @@ longEar.eat(); // Error: Maximum call stack size exceeded */!* ``` -The code doesn't work anymore! We can see the error trying to call `longEar.eat()`. +โค้ดใช้ไม่ได้แล้ว! เกิด error เมื่อเรียก `longEar.eat()` -It may be not that obvious, but if we trace `longEar.eat()` call, then we can see why. In both lines `(*)` and `(**)` the value of `this` is the current object (`longEar`). That's essential: all object methods get the current object as `this`, not a prototype or something. +อาจไม่เห็นชัดนัก แต่ถ้าไล่การทำงานของ `longEar.eat()` จะเข้าใจว่าทำไม ทั้งบรรทัด `(*)` และ `(**)` ค่าของ `this` คือออบเจ็กต์ปัจจุบัน (`longEar`) ทั้งคู่ จุดนี้สำคัญมาก เพราะเมธอดของออบเจ็กต์ทุกตัวจะได้ออบเจ็กต์ปัจจุบันเป็น `this` ไม่ใช่โปรโตไทป์ -So, in both lines `(*)` and `(**)` the value of `this.__proto__` is exactly the same: `rabbit`. They both call `rabbit.eat` without going up the chain in the endless loop. +ดังนั้น ทั้งบรรทัด `(*)` และ `(**)` ค่าของ `this.__proto__` จึงเป็น `rabbit` เหมือนกัน ทำให้ทั้งคู่เรียก `rabbit.eat` โดยไม่เคยไปถึงขั้นที่สูงกว่าใน chain วนลูปไม่รู้จบ -Here's the picture of what happens: +นี่คือรูปแสดงสิ่งที่เกิดขึ้น: ![](this-super-loop.svg) -1. Inside `longEar.eat()`, the line `(**)` calls `rabbit.eat` providing it with `this=longEar`. +1. ภายใน `longEar.eat()` บรรทัด `(**)` เรียก `rabbit.eat` โดยส่ง `this=longEar` ```js - // inside longEar.eat() we have this = longEar + // ภายใน longEar.eat() เรามี this = longEar this.__proto__.eat.call(this) // (**) - // becomes + // กลายเป็น longEar.__proto__.eat.call(this) - // that is + // ซึ่งก็คือ rabbit.eat.call(this); ``` -2. Then in the line `(*)` of `rabbit.eat`, we'd like to pass the call even higher in the chain, but `this=longEar`, so `this.__proto__.eat` is again `rabbit.eat`! +2. จากนั้นในบรรทัด `(*)` ของ `rabbit.eat` เราต้องการส่งการเรียกขึ้นไปอีกขั้น แต่ `this=longEar` ทำให้ `this.__proto__.eat` ก็เป็น `rabbit.eat` อีก! ```js - // inside rabbit.eat() we also have this = longEar + // ภายใน rabbit.eat() เรามี this = longEar เช่นกัน this.__proto__.eat.call(this) // (*) - // becomes + // กลายเป็น longEar.__proto__.eat.call(this) - // or (again) + // หรือ (อีกครั้ง) rabbit.eat.call(this); ``` -3. ...So `rabbit.eat` calls itself in the endless loop, because it can't ascend any further. +3. ...`rabbit.eat` จึงเรียกตัวเองซ้ำไปเรื่อยๆ เพราะไม่สามารถขึ้นไปอีกขั้นได้ -The problem can't be solved by using `this` alone. +ปัญหานี้แก้ไม่ได้ด้วย `this` เพียงอย่างเดียว ### `[[HomeObject]]` -To provide the solution, JavaScript adds one more special internal property for functions: `[[HomeObject]]`. +เพื่อแก้ปัญหานี้ JavaScript จึงเพิ่มพร็อพเพอร์ตี้ภายในพิเศษอีกตัวหนึ่งให้กับฟังก์ชัน ชื่อว่า `[[HomeObject]]` -When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object. +เมื่อฟังก์ชันถูกกำหนดให้เป็นเมธอดของคลาสหรือออบเจ็กต์ พร็อพเพอร์ตี้ `[[HomeObject]]` จะชี้ไปที่ออบเจ็กต์นั้น -Then `super` uses it to resolve the parent prototype and its methods. +จากนั้น `super` จะใช้ `[[HomeObject]]` เพื่อค้นหาโปรโตไทป์ของคลาสแม่และเมธอดที่ต้องการ -Let's see how it works, first with plain objects: +มาดูวิธีการทำงานกัน เริ่มจากออบเจ็กต์ธรรมดา: ```js run let animal = { name: "Animal", eat() { // animal.eat.[[HomeObject]] == animal - alert(`${this.name} eats.`); + alert(`${this.name} กินอาหาร.`); } }; @@ -522,31 +522,31 @@ let longEar = { }; *!* -// works correctly -longEar.eat(); // Long Ear eats. +// ทำงานได้ถูกต้อง +longEar.eat(); // Long Ear กินอาหาร. */!* ``` -It works as intended, due to `[[HomeObject]]` mechanics. A method, such as `longEar.eat`, knows its `[[HomeObject]]` and takes the parent method from its prototype. Without any use of `this`. +ทำงานได้ถูกต้อง เพราะกลไก `[[HomeObject]]` แต่ละเมธอด เช่น `longEar.eat` จะรู้จัก `[[HomeObject]]` ของตัวเอง แล้วหยิบเมธอดจากคลาสแม่ผ่านโปรโตไทป์ โดยไม่ต้องใช้ `this` เลย -### Methods are not "free" +### เมธอดไม่ได้ "อิสระ" -As we've known before, generally functions are "free", not bound to objects in JavaScript. So they can be copied between objects and called with another `this`. +ก่อนหน้านี้เราเรียนรู้มาว่า ฟังก์ชันใน JavaScript โดยปกติแล้ว "อิสระ" ไม่ได้ผูกกับออบเจ็กต์ใด จึงก็อปปี้ไปมาระหว่างออบเจ็กต์และเรียกด้วย `this` ตัวอื่นได้ -The very existence of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever. +แต่การมีอยู่ของ `[[HomeObject]]` ทำลายหลักการนั้น เพราะเมธอดจำออบเจ็กต์ของตัวเองไว้ `[[HomeObject]]` เปลี่ยนไม่ได้ การผูกนี้จึงเป็นตลอดไป -The only place in the language where `[[HomeObject]]` is used -- is `super`. So, if a method does not use `super`, then we can still consider it free and copy between objects. But with `super` things may go wrong. +ที่เดียวในภาษาที่ใช้ `[[HomeObject]]` ก็คือ `super` ดังนั้นถ้าเมธอดไม่ได้ใช้ `super` ก็ยังถือว่าอิสระและก็อปปี้ไปมาได้ตามปกติ แต่ถ้ามี `super` อาจมีปัญหาได้ -Here's the demo of a wrong `super` result after copying: +ลองดูตัวอย่างที่ `super` ทำงานผิดหลังจากก็อปปี้เมธอด: ```js run let animal = { sayHi() { - alert(`I'm an animal`); + alert(`ฉันเป็นสัตว์`); } }; -// rabbit inherits from animal +// rabbit สืบทอดจาก animal let rabbit = { __proto__: animal, sayHi() { @@ -556,11 +556,11 @@ let rabbit = { let plant = { sayHi() { - alert("I'm a plant"); + alert("ฉันเป็นพืช"); } }; -// tree inherits from plant +// tree สืบทอดจาก plant let tree = { __proto__: plant, *!* @@ -569,32 +569,32 @@ let tree = { }; *!* -tree.sayHi(); // I'm an animal (?!?) +tree.sayHi(); // ฉันเป็นสัตว์ (?!?) */!* ``` -A call to `tree.sayHi()` shows "I'm an animal". Definitely wrong. +เมื่อเรียก `tree.sayHi()` ได้ผลว่า "ฉันเป็นสัตว์" ซึ่งผิดอย่างแน่นอน -The reason is simple: -- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication? -- Its `[[HomeObject]]` is `rabbit`, as it was created in `rabbit`. There's no way to change `[[HomeObject]]`. -- The code of `tree.sayHi()` has `super.sayHi()` inside. It goes up from `rabbit` and takes the method from `animal`. +เหตุผลก็ง่ายๆ: +- ที่บรรทัด `(*)` เมธอด `tree.sayHi` ถูกก็อปปี้มาจาก `rabbit` อาจจะแค่ต้องการลดโค้ดซ้ำ? +- `[[HomeObject]]` ของเมธอดนี้คือ `rabbit` เพราะถูกสร้างไว้ใน `rabbit` และ `[[HomeObject]]` เปลี่ยนไม่ได้ +- โค้ดของ `tree.sayHi()` มี `super.sayHi()` อยู่ข้างใน ซึ่งไล่ขึ้นไปจาก `rabbit` จึงหยิบเมธอดจาก `animal` มา -Here's the diagram of what happens: +นี่คือไดอะแกรมแสดงสิ่งที่เกิดขึ้น: ![](super-homeobject-wrong.svg) -### Methods, not function properties +### ต้องเป็นเมธอด ไม่ใช่ function property -`[[HomeObject]]` is defined for methods both in classes and in plain objects. But for objects, methods must be specified exactly as `method()`, not as `"method: function()"`. +`[[HomeObject]]` ถูกกำหนดให้กับเมธอดทั้งในคลาสและออบเจ็กต์ธรรมดา แต่สำหรับออบเจ็กต์ต้องเขียนในรูปแบบ `method()` ไม่ใช่ `"method: function()"` -The difference may be non-essential for us, but it's important for JavaScript. +ความแตกต่างนี้อาจไม่สำคัญสำหรับเรา แต่สำคัญสำหรับ JavaScript -In the example below a non-method syntax is used for comparison. `[[HomeObject]]` property is not set and the inheritance doesn't work: +ในตัวอย่างด้านล่างใช้ไวยากรณ์แบบ non-method เพื่อเปรียบเทียบ พร็อพเพอร์ตี้ `[[HomeObject]]` จะไม่ถูกกำหนด ทำให้การสืบทอดไม่ทำงาน: ```js run let animal = { - eat: function() { // intentionally writing like this instead of eat() {... + eat: function() { // ตั้งใจเขียนแบบนี้แทน eat() {... // ... } }; @@ -607,21 +607,21 @@ let rabbit = { }; *!* -rabbit.eat(); // Error calling super (because there's no [[HomeObject]]) +rabbit.eat(); // Error calling super (เพราะไม่มี [[HomeObject]]) */!* ``` -## Summary +## สรุป -1. To extend a class: `class Child extends Parent`: - - That means `Child.prototype.__proto__` will be `Parent.prototype`, so methods are inherited. -2. When overriding a constructor: - - We must call parent constructor as `super()` in `Child` constructor before using `this`. -3. When overriding another method: - - We can use `super.method()` in a `Child` method to call `Parent` method. -4. Internals: - - Methods remember their class/object in the internal `[[HomeObject]]` property. That's how `super` resolves parent methods. - - So it's not safe to copy a method with `super` from one object to another. +1. การขยายคลาส: `class Child extends Parent`: + - หมายความว่า `Child.prototype.__proto__` จะเป็น `Parent.prototype` ทำให้เมธอดถูกสืบทอดลงมา +2. เมื่อ override คอนสตรักเตอร์: + - ต้องเรียกคอนสตรักเตอร์ของคลาสแม่ด้วย `super()` ภายในคอนสตรักเตอร์ของ `Child` ก่อนที่จะใช้ `this` +3. เมื่อ override เมธอดอื่น: + - ใช้ `super.method()` ในเมธอดของ `Child` เพื่อเรียกเมธอดของ `Parent` +4. เบื้องหลัง: + - เมธอดจำคลาส/ออบเจ็กต์ของตัวเองไว้ในพร็อพเพอร์ตี้ภายใน `[[HomeObject]]` นี่คือวิธีที่ `super` ค้นหาเมธอดของคลาสแม่ + - ดังนั้นการก็อปปี้เมธอดที่มี `super` ไปยังออบเจ็กต์อื่นจึงไม่ปลอดภัย -Also: -- Arrow functions don't have their own `this` or `super`, so they transparently fit into the surrounding context. +นอกจากนี้: +- Arrow function ไม่มี `this` หรือ `super` ของตัวเอง จึงกลมกลืนไปกับบริบทรอบข้างได้อย่างเป็นธรรมชาติ diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md index cb9829ce0..2071dc8ac 100644 --- a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md @@ -1,14 +1,14 @@ -First, let's see why the latter code doesn't work. +มาดูกันก่อนว่าทำไมโค้ดด้านบนถึงใช้ไม่ได้ -The reason becomes obvious if we try to run it. An inheriting class constructor must call `super()`. Otherwise `"this"` won't be "defined". +สาเหตุจะเห็นชัดเมื่อลองรันดู คลาสลูกที่สืบทอดมาจำเป็นต้องเรียก `super()` ในคอนสตรักเตอร์ ไม่อย่างนั้น `"this"` จะยังไม่ถูกกำหนดค่า -So here's the fix: +แก้ไขได้ดังนี้: ```js run class Rabbit extends Object { constructor(name) { *!* - super(); // need to call the parent constructor when inheriting + super(); // ต้องเรียกคอนสตรักเตอร์ของคลาสแม่เมื่อมีการสืบทอด */!* this.name = name; } @@ -19,16 +19,16 @@ let rabbit = new Rabbit("Rab"); alert( rabbit.hasOwnProperty('name') ); // true ``` -But that's not all yet. +แต่ยังไม่จบแค่นี้ -Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`. +แม้แก้ไขแล้ว ยังมีความแตกต่างสำคัญระหว่าง `"class Rabbit extends Object"` กับ `class Rabbit` ธรรมดา -As we know, the "extends" syntax sets up two prototypes: +อย่างที่เราทราบ ไวยากรณ์ "extends" สร้างการเชื่อมโยงโปรโตไทป์ 2 จุด: -1. Between `"prototype"` of the constructor functions (for methods). -2. Between the constructor functions themselves (for static methods). +1. ระหว่าง `"prototype"` ของคอนสตรักเตอร์ (สำหรับเมธอดปกติ) +2. ระหว่างตัวคอนสตรักเตอร์เอง (สำหรับเมธอด static) -In the case of `class Rabbit extends Object` it means: +ในกรณีของ `class Rabbit extends Object` จะเป็นแบบนี้: ```js run class Rabbit extends Object {} @@ -37,45 +37,45 @@ alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true ``` -So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this: +ดังนั้น `Rabbit` จึงเข้าถึงเมธอด static ของ `Object` ผ่านตัว `Rabbit` ได้เลย เช่น: ```js run class Rabbit extends Object {} *!* -// normally we call Object.getOwnPropertyNames +// ปกติเราเรียก Object.getOwnPropertyNames alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b */!* ``` -But if we don't have `extends Object`, then `Rabbit.__proto__` is not set to `Object`. +แต่ถ้าไม่มี `extends Object` ค่า `Rabbit.__proto__` จะไม่ชี้ไปที่ `Object` -Here's the demo: +ลองดูตัวอย่าง: ```js run class Rabbit {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) -alert( Rabbit.__proto__ === Function.prototype ); // as any function by default +alert( Rabbit.__proto__ === Function.prototype ); // เป็นค่าเริ่มต้นของทุกฟังก์ชัน *!* -// error, no such function in Rabbit +// ไม่มีฟังก์ชันนี้ใน Rabbit alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error */!* ``` -So `Rabbit` doesn't provide access to static methods of `Object` in that case. +ในกรณีนี้ `Rabbit` จึงเข้าถึงเมธอด static ของ `Object` ไม่ได้ -By the way, `Function.prototype` also has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`. +อีกอย่าง `Function.prototype` ก็มีเมธอดทั่วไปของฟังก์ชัน เช่น `call`, `bind` เป็นต้น ซึ่งใช้ได้ทั้งสองกรณี เพราะคอนสตรักเตอร์ `Object` ในตัวก็มี `Object.__proto__ === Function.prototype` เช่นกัน -Here's the picture: +ภาพประกอบ: ![](rabbit-extends-object.svg) -So, to put it short, there are two differences: +สรุปสั้นๆ ความแตกต่างมีสองข้อ: | class Rabbit | class Rabbit extends Object | |--------------|------------------------------| -| -- | needs to call `super()` in constructor | +| -- | ต้องเรียก `super()` ในคอนสตรักเตอร์ | | `Rabbit.__proto__ === Function.prototype` | `Rabbit.__proto__ === Object` | diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md index 1d0f98a74..3d2d7318d 100644 --- a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md @@ -4,9 +4,9 @@ importance: 3 # Class extends Object? -As we know, all objects normally inherit from `Object.prototype` and get access to "generic" object methods like `hasOwnProperty` etc. +อย่างที่เราทราบ ออบเจ็กต์ทุกตัวจะสืบทอดจาก `Object.prototype` โดยปกติ ทำให้เข้าถึงเมธอดทั่วไปของออบเจ็กต์ได้ เช่น `hasOwnProperty` เป็นต้น -For instance: +ตัวอย่าง: ```js run class Rabbit { @@ -18,16 +18,16 @@ class Rabbit { let rabbit = new Rabbit("Rab"); *!* -// hasOwnProperty method is from Object.prototype +// เมธอด hasOwnProperty มาจาก Object.prototype alert( rabbit.hasOwnProperty('name') ); // true */!* ``` -But if we spell it out explicitly like `"class Rabbit extends Object"`, then the result would be different from a simple `"class Rabbit"`? +แต่ถ้าเราเขียนแบบระบุชัดเจนว่า `"class Rabbit extends Object"` ผลลัพธ์จะต่างจาก `"class Rabbit"` ธรรมดาไหม? -What's the difference? +ต่างกันตรงไหน? -Here's an example of such code (it doesn't work -- why? fix it?): +ลองดูตัวอย่างโค้ดนี้ (ซึ่งใช้ไม่ได้ -- ทำไม? แก้ยังไง?): ```js class Rabbit extends Object { diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index 4b493a5e8..b692e9c7b 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -1,9 +1,9 @@ -# Static properties and methods +# พร็อพเพอร์ตี้และเมธอดแบบ Static -We can also assign a method to the class as a whole. Such methods are called *static*. +นอกจากเมธอดปกติแล้ว เรายังกำหนดเมธอดให้กับตัวคลาสโดยตรงได้ด้วย เมธอดแบบนี้เรียกว่า *static* -In a class declaration, they are prepended by `static` keyword, like this: +วิธีประกาศก็แค่เติมคีย์เวิร์ด `static` ไว้หน้าเมธอดในคลาส แบบนี้: ```js run class User { @@ -17,7 +17,7 @@ class User { User.staticMethod(); // true ``` -That actually does the same as assigning it as a property directly: +ซึ่งให้ผลเหมือนกับการกำหนดเมธอดเป็นพร็อพเพอร์ตี้ของคลาสโดยตรง: ```js run class User { } @@ -29,13 +29,13 @@ User.staticMethod = function() { User.staticMethod(); // true ``` -The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule). +ค่า `this` ในการเรียก `User.staticMethod()` คือตัวคอนสตรักเตอร์ `User` เอง (ตามกฎ "ออบเจ็กต์ก่อนจุด") -Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it. +โดยทั่วไปแล้ว เมธอดแบบ static ใช้สำหรับฟังก์ชันที่เกี่ยวข้องกับ "คลาสโดยรวม" ไม่ได้เกี่ยวกับออบเจ็กต์ตัวใดตัวหนึ่ง -For instance, we have `Article` objects and need a function to compare them. +สมมติเรามีออบเจ็กต์ `Article` หลายตัว แล้วต้องการฟังก์ชันเปรียบเทียบ -A natural solution would be to add `Article.compare` static method: +วิธีที่เป็นธรรมชาติคือสร้างเมธอด static ชื่อ `Article.compare`: ```js run class Article { @@ -51,7 +51,7 @@ class Article { */!* } -// usage +// การใช้งาน let articles = [ new Article("HTML", new Date(2019, 1, 1)), new Article("CSS", new Date(2019, 0, 1)), @@ -65,19 +65,19 @@ articles.sort(Article.compare); alert( articles[0].title ); // CSS ``` -Here `Article.compare` method stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class. +ตรงนี้เมธอด `Article.compare` ทำหน้าที่เปรียบเทียบบทความจาก "ระดับคลาส" ไม่ใช่เมธอดของบทความตัวใดตัวหนึ่ง แต่เป็นเมธอดของคลาสทั้งคลาส -Another example would be a so-called "factory" method. +อีกตัวอย่างหนึ่งคือสิ่งที่เรียกว่า "factory method" -Let's say, we need multiple ways to create an article: +สมมติว่าเราต้องการสร้าง article ได้หลายวิธี: -1. Create by given parameters (`title`, `date` etc). -2. Create an empty article with today's date. -3. ...or else somehow. +1. สร้างจากพารามิเตอร์ที่กำหนด (`title`, `date` ฯลฯ) +2. สร้าง article เปล่าที่มีวันที่วันนี้ +3. ...หรือวิธีอื่นๆ -The first way can be implemented by the constructor. And for the second one we can make a static method of the class. +วิธีแรกทำได้ผ่านคอนสตรักเตอร์ ส่วนวิธีที่สองทำได้ด้วยเมธอด static ของคลาส -Such as `Article.createTodays()` here: +เช่น `Article.createTodays()` ในตัวอย่างนี้: ```js run class Article { @@ -88,7 +88,7 @@ class Article { *!* static createTodays() { - // remember, this = Article + // จำไว้ว่า this = Article return new this("Today's digest", new Date()); } */!* @@ -99,20 +99,20 @@ let article = Article.createTodays(); alert( article.title ); // Today's digest ``` -Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class. +ทีนี้ทุกครั้งที่ต้องการสร้างบทความวันนี้ แค่เรียก `Article.createTodays()` ได้เลย อีกครั้ง นี่ไม่ใช่เมธอดของ article ตัวใดตัวหนึ่ง แต่เป็นเมธอดของคลาสทั้งคลาส -Static methods are also used in database-related classes to search/save/remove entries from the database, like this: +เมธอด static ยังใช้กับคลาสที่ทำงานกับฐานข้อมูลด้วย เช่น ค้นหา/บันทึก/ลบข้อมูล: ```js -// assuming Article is a special class for managing articles -// static method to remove the article by id: +// สมมติว่า Article เป็นคลาสสำหรับจัดการบทความ +// เมธอด static สำหรับลบบทความตาม id: Article.remove({id: 12345}); ``` -````warn header="Static methods aren't available for individual objects" -Static methods are callable on classes, not on individual objects. +````warn header="เมธอด static ใช้กับออบเจ็กต์แต่ละตัวไม่ได้" +เมธอด static เรียกได้จากคลาสเท่านั้น ไม่ใช่จากออบเจ็กต์แต่ละตัว -E.g. such code won't work: +เช่น โค้ดนี้จะใช้ไม่ได้: ```js // ... @@ -120,11 +120,11 @@ article.createTodays(); /// Error: article.createTodays is not a function ``` ```` -## Static properties +## พร็อพเพอร์ตี้แบบ Static [recent browser=Chrome] -Static properties are also possible, they look like regular class properties, but prepended by `static`: +นอกจากเมธอดแล้ว พร็อพเพอร์ตี้ก็ประกาศเป็น static ได้เช่นกัน หน้าตาเหมือนพร็อพเพอร์ตี้ปกติ แค่เติม `static` ไว้ข้างหน้า: ```js run class Article { @@ -134,17 +134,17 @@ class Article { alert( Article.publisher ); // Ilya Kantor ``` -That is the same as a direct assignment to `Article`: +ซึ่งให้ผลเหมือนกับการกำหนดค่าให้ `Article` โดยตรง: ```js Article.publisher = "Ilya Kantor"; ``` -## Inheritance of static properties and methods [#statics-and-inheritance] +## การสืบทอดพร็อพเพอร์ตี้และเมธอดแบบ Static [#statics-and-inheritance] -Static properties and methods are inherited. +พร็อพเพอร์ตี้และเมธอดแบบ static สืบทอดได้ด้วย -For instance, `Animal.compare` and `Animal.planet` in the code below are inherited and accessible as `Rabbit.compare` and `Rabbit.planet`: +ตัวอย่างเช่น `Animal.compare` และ `Animal.planet` ในโค้ดด้านล่าง สืบทอดไปยังคลาสลูกและเข้าถึงได้ผ่าน `Rabbit.compare` และ `Rabbit.planet`: ```js run class Animal { @@ -157,7 +157,7 @@ class Animal { run(speed = 0) { this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); + alert(`${this.name} วิ่งด้วยความเร็ว ${this.speed}.`); } *!* @@ -168,10 +168,10 @@ class Animal { } -// Inherit from Animal +// สืบทอดจาก Animal class Rabbit extends Animal { hide() { - alert(`${this.name} hides!`); + alert(`${this.name} ซ่อนตัว!`); } } @@ -184,48 +184,48 @@ let rabbits = [ rabbits.sort(Rabbit.compare); */!* -rabbits[0].run(); // Black Rabbit runs with speed 5. +rabbits[0].run(); // Black Rabbit วิ่งด้วยความเร็ว 5. alert(Rabbit.planet); // Earth ``` -Now when we call `Rabbit.compare`, the inherited `Animal.compare` will be called. +ตอนที่เราเรียก `Rabbit.compare` จะไปเรียก `Animal.compare` ที่สืบทอดมา -How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`. +แล้วมันทำงานอย่างไร? คำตอบคือโปรโตไทป์นั่นเอง อย่างที่คาดเดาได้ คีย์เวิร์ด `extends` ทำให้ `Rabbit` มีการอ้างอิง `[[Prototype]]` ไปยัง `Animal` ![](animal-rabbit-static.svg) -So, `Rabbit extends Animal` creates two `[[Prototype]]` references: +ดังนั้น `Rabbit extends Animal` สร้างการเชื่อมโยง `[[Prototype]]` ถึง 2 จุดด้วยกัน: -1. `Rabbit` function prototypally inherits from `Animal` function. -2. `Rabbit.prototype` prototypally inherits from `Animal.prototype`. +1. ฟังก์ชัน `Rabbit` สืบทอดจากฟังก์ชัน `Animal` ผ่านโปรโตไทป์ +2. `Rabbit.prototype` สืบทอดจาก `Animal.prototype` ผ่านโปรโตไทป์ -As a result, inheritance works both for regular and static methods. +ผลลัพธ์คือการสืบทอดทำงานได้ทั้งเมธอดปกติและเมธอด static -Here, let's check that by code: +มาลองพิสูจน์ด้วยโค้ดกัน: ```js run class Animal {} class Rabbit extends Animal {} -// for statics +// สำหรับ static alert(Rabbit.__proto__ === Animal); // true -// for regular methods +// สำหรับเมธอดปกติ alert(Rabbit.prototype.__proto__ === Animal.prototype); // true ``` -## Summary +## สรุป -Static methods are used for the functionality that belongs to the class "as a whole". It doesn't relate to a concrete class instance. +เมธอด static ใช้สำหรับฟังก์ชันที่เกี่ยวข้องกับ "คลาสโดยรวม" ไม่ได้เกี่ยวกับอินสแตนซ์ใดอินสแตนซ์หนึ่ง -For example, a method for comparison `Article.compare(article1, article2)` or a factory method `Article.createTodays()`. +ตัวอย่างเช่น เมธอดเปรียบเทียบ `Article.compare(article1, article2)` หรือ factory method อย่าง `Article.createTodays()` -They are labeled by the word `static` in class declaration. +ประกาศได้โดยใส่คีย์เวิร์ด `static` ในคลาส -Static properties are used when we'd like to store class-level data, also not bound to an instance. +พร็อพเพอร์ตี้แบบ static ใช้เมื่อต้องการเก็บข้อมูลระดับคลาส ซึ่งไม่ได้ผูกกับอินสแตนซ์ใดเช่นกัน -The syntax is: +ไวยากรณ์มีดังนี้: ```js class MyClass { @@ -237,13 +237,13 @@ class MyClass { } ``` -Technically, static declaration is the same as assigning to the class itself: +ในทางเทคนิค การประกาศ static เหมือนกับการกำหนดค่าให้คลาสโดยตรง: ```js MyClass.property = ... MyClass.method = ... ``` -Static properties and methods are inherited. +พร็อพเพอร์ตี้และเมธอดแบบ static สืบทอดได้ -For `class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`. +เมื่อเขียน `class B extends A` โปรโตไทป์ของคลาส `B` จะชี้ไปยัง `A` นั่นคือ `B.[[Prototype]] = A` ดังนั้นถ้าหาฟิลด์ไม่พบใน `B` ก็จะไปค้นหาต่อใน `A` diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 91efb89ee..a06a14789 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -1,95 +1,94 @@ +# พร็อพเพอร์ตี้และเมธอดแบบ Private กับ Protected -# Private and protected properties and methods +หลักการสำคัญอย่างหนึ่งของ OOP คือ การแยก internal interface ออกจาก external interface ให้ชัดเจน -One of the most important principles of object oriented programming -- delimiting internal interface from the external one. +เรื่องนี้ "ต้องทำ" เลยถ้าจะพัฒนาอะไรที่ซับซ้อนกว่าแอป "hello world" -That is "a must" practice in developing anything more complex than a "hello world" app. +เพื่อให้เข้าใจเรื่องนี้ ลองหยุดคิดเรื่องโค้ดสักครู่ แล้วมองไปที่สิ่งของในชีวิตจริงกัน -To understand this, let's break away from development and turn our eyes into the real world. +อุปกรณ์ที่เราใช้ทุกวันนั้นซับซ้อนมาก แต่เพราะ internal interface ถูกแยกออกจาก external interface ไว้อย่างดี เราจึงใช้งานได้โดยไม่ต้องปวดหัว -Usually, devices that we're using are quite complex. But delimiting the internal interface from the external one allows to use them without problems. +## ตัวอย่างในชีวิตจริง -## A real-life example - -For instance, a coffee machine. Simple from outside: a button, a display, a few holes...And, surely, the result -- great coffee! :) +ลองนึกถึงเครื่องชงกาแฟ ภายนอกดูเรียบง่าย: มีปุ่มกด หน้าจอแสดงผล ช่องใส่น้ำ... แล้วก็ผลลัพธ์ — กาแฟแก้วเยี่ยม! :) ![](coffee.jpg) -But inside... (a picture from the repair manual) +แต่ภายใน... (ภาพจากคู่มือซ่อม) ![](coffee-inside.jpg) -A lot of details. But we can use it without knowing anything. +มีชิ้นส่วนเต็มไปหมด แต่เราก็ใช้งานได้โดยไม่ต้องรู้อะไรเลย -Coffee machines are quite reliable, aren't they? We can use one for years, and only if something goes wrong -- bring it for repairs. +เครื่องชงกาแฟนี่เชื่อถือได้มากเลยนะ ใช้กันเป็นปีๆ ไม่มีปัญหา พอเสียค่อยส่งซ่อม -The secret of reliability and simplicity of a coffee machine -- all details are well-tuned and *hidden* inside. +เคล็ดลับของความน่าเชื่อถือและความเรียบง่ายก็คือ — ชิ้นส่วนทุกอย่างถูกจัดเรียงอย่างดีและ*ซ่อน*อยู่ภายใน -If we remove the protective cover from the coffee machine, then using it will be much more complex (where to press?), and dangerous (it can electrocute). +ลองคิดดูว่าถ้าเราเปิดฝาครอบออก การใช้งานจะยุ่งยากขึ้นมาก (ต้องกดตรงไหน?) แถมยังอันตรายอีกด้วย (อาจโดนไฟดูด) -As we'll see, in programming objects are like coffee machines. +ในการเขียนโปรแกรมก็เหมือนกัน ออบเจ็กต์ก็เปรียบได้กับเครื่องชงกาแฟ -But in order to hide inner details, we'll use not a protective cover, but rather special syntax of the language and conventions. +แต่แทนที่จะใช้ฝาครอบป้องกัน เราจะใช้ไวยากรณ์พิเศษของภาษาและข้อตกลงร่วมกันของนักพัฒนาแทน -## Internal and external interface +## Internal interface กับ External interface -In object-oriented programming, properties and methods are split into two groups: +ในโลกของ OOP พร็อพเพอร์ตี้และเมธอดจะแบ่งออกเป็น 2 กลุ่ม: -- *Internal interface* -- methods and properties, accessible from other methods of the class, but not from the outside. -- *External interface* -- methods and properties, accessible also from outside the class. +- *Internal interface* — เมธอดและพร็อพเพอร์ตี้ที่เข้าถึงได้จากเมธอดอื่นภายในคลาสเท่านั้น ภายนอกเข้าถึงไม่ได้ +- *External interface* — เมธอดและพร็อพเพอร์ตี้ที่เข้าถึงได้จากภายนอกคลาสด้วย -If we continue the analogy with the coffee machine -- what's hidden inside: a boiler tube, heating element, and so on -- is its internal interface. +กลับมาเปรียบกับเครื่องชงกาแฟอีกครั้ง — สิ่งที่ซ่อนอยู่ข้างใน เช่น ท่อน้ำร้อน ตัวทำความร้อน ฯลฯ คือ internal interface -An internal interface is used for the object to work, its details use each other. For instance, a boiler tube is attached to the heating element. +internal interface มีไว้ให้ออบเจ็กต์ทำงานภายใน ชิ้นส่วนต่างๆ ใช้งานร่วมกันเอง เช่น ท่อน้ำร้อนต่อเข้ากับตัวทำความร้อน -But from the outside a coffee machine is closed by the protective cover, so that no one can reach those. Details are hidden and inaccessible. We can use its features via the external interface. +แต่จากภายนอก เครื่องชงกาแฟมีฝาครอบปิดไว้ ไม่มีใครเข้าถึงข้างในได้ ชิ้นส่วนถูกซ่อนไว้หมด เราใช้งานได้แค่ผ่าน external interface เท่านั้น -So, all we need to use an object is to know its external interface. We may be completely unaware how it works inside, and that's great. +ดังนั้น สิ่งที่ต้องรู้ในการใช้ออบเจ็กต์ก็มีแค่ external interface ไม่ต้องรู้เลยว่าข้างในทำงานยังไง — แค่นี้ก็สบายแล้ว -That was a general introduction. +นี่เป็นแค่บทนำทั่วไป -In JavaScript, there are two types of object fields (properties and methods): +สำหรับ JavaScript พร็อพเพอร์ตี้และเมธอดของออบเจ็กต์แบ่งได้เป็น 2 แบบ: -- Public: accessible from anywhere. They comprise the external interface. Until now we were only using public properties and methods. -- Private: accessible only from inside the class. These are for the internal interface. +- Public: เข้าถึงได้จากทุกที่ ซึ่งก็คือ external interface ที่ผ่านมาเราใช้แบบ public มาตลอด +- Private: เข้าถึงได้แค่จากภายในคลาสเท่านั้น สำหรับเป็น internal interface -In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it (like private, but plus access from inheriting classes). They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them. +ในหลายๆ ภาษามีฟิลด์แบบ "protected" ด้วย ซึ่งเข้าถึงได้จากภายในคลาสและคลาสที่สืบทอดมา (คล้าย private แต่คลาสลูกก็เข้าถึงได้ด้วย) ฟิลด์ protected ก็มีประโยชน์สำหรับ internal interface เช่นกัน ว่ากันจริงๆ แล้วใช้บ่อยกว่า private เสียอีก เพราะปกติแล้วเราอยากให้คลาสลูกเข้าถึงได้ -Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated. +JavaScript ไม่มี protected ในระดับภาษา แต่ในทางปฏิบัติมันมีประโยชน์มาก จึงมีการเลียนแบบกันอยู่ -Now we'll make a coffee machine in JavaScript with all these types of properties. A coffee machine has a lot of details, we won't model them to stay simple (though we could). +ทีนี้เรามาลองสร้างเครื่องชงกาแฟใน JavaScript กัน โดยใช้พร็อพเพอร์ตี้ทุกแบบที่กล่าวมา เครื่องชงกาแฟจริงมีชิ้นส่วนเยอะมาก แต่เราจะทำแบบง่ายๆ ก่อน (แม้จะจำลองให้ซับซ้อนได้ก็ตาม) -## Protecting "waterAmount" +## การป้องกัน "waterAmount" -Let's make a simple coffee machine class first: +มาสร้างคลาสเครื่องชงกาแฟอย่างง่ายกันก่อน: ```js run class CoffeeMachine { - waterAmount = 0; // the amount of water inside + waterAmount = 0; // ปริมาณน้ำในเครื่อง constructor(power) { this.power = power; - alert( `Created a coffee-machine, power: ${power}` ); + alert( `สร้างเครื่องชงกาแฟ กำลังไฟ: ${power}` ); } } -// create the coffee machine +// สร้างเครื่องชงกาแฟ let coffeeMachine = new CoffeeMachine(100); -// add water +// เติมน้ำ coffeeMachine.waterAmount = 200; ``` -Right now the properties `waterAmount` and `power` are public. We can easily get/set them from the outside to any value. +ตอนนี้พร็อพเพอร์ตี้ `waterAmount` และ `power` เป็น public อยู่ เราอ่านและเขียนค่าจากภายนอกได้เลย จะใส่ค่าอะไรก็ได้ -Let's change `waterAmount` property to protected to have more control over it. For instance, we don't want anyone to set it below zero. +ทีนี้มาเปลี่ยน `waterAmount` ให้เป็น protected กัน เพื่อให้ควบคุมค่าได้มากขึ้น เช่น ไม่ให้ตั้งค่าต่ำกว่าศูนย์ -**Protected properties are usually prefixed with an underscore `_`.** +**พร็อพเพอร์ตี้แบบ protected มักจะนำหน้าด้วยขีดล่าง `_`** -That is not enforced on the language level, but there's a well-known convention between programmers that such properties and methods should not be accessed from the outside. +ตัวภาษาเองไม่ได้บังคับ แต่เป็นข้อตกลงร่วมกันของนักพัฒนาว่า พร็อพเพอร์ตี้และเมธอดที่ขึ้นต้นด้วย `_` ไม่ควรเข้าถึงจากภายนอก -So our property will be called `_waterAmount`: +พร็อพเพอร์ตี้ของเราจึงมีชื่อว่า `_waterAmount`: ```js run class CoffeeMachine { @@ -112,22 +111,22 @@ class CoffeeMachine { } -// create the coffee machine +// สร้างเครื่องชงกาแฟ let coffeeMachine = new CoffeeMachine(100); -// add water -coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10 +// เติมน้ำ +coffeeMachine.waterAmount = -10; // _waterAmount จะเป็น 0 ไม่ใช่ -10 ``` -Now the access is under control, so setting the water amount below zero becomes impossible. +ตอนนี้การเข้าถึงถูกควบคุมแล้ว จะตั้งค่าน้ำให้ต่ำกว่าศูนย์ไม่ได้อีกต่อไป -## Read-only "power" +## "power" แบบอ่านได้อย่างเดียว -For `power` property, let's make it read-only. It sometimes happens that a property must be set at creation time only, and then never modified. +สำหรับพร็อพเพอร์ตี้ `power` เรามาทำให้อ่านได้อย่างเดียว บางทีพร็อพเพอร์ตี้ควรกำหนดค่าได้แค่ตอนสร้างออบเจ็กต์ แล้วหลังจากนั้นก็แก้ไขไม่ได้อีก -That's exactly the case for a coffee machine: power never changes. +เครื่องชงกาแฟก็เป็นแบบนี้พอดี กำลังไฟตั้งค่าครั้งเดียวแล้วไม่เปลี่ยน -To do so, we only need to make getter, but not the setter: +วิธีทำก็แค่สร้าง getter โดยไม่สร้าง setter: ```js run class CoffeeMachine { @@ -143,18 +142,18 @@ class CoffeeMachine { } -// create the coffee machine +// สร้างเครื่องชงกาแฟ let coffeeMachine = new CoffeeMachine(100); -alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W +alert(`กำลังไฟ: ${coffeeMachine.power}W`); // กำลังไฟ: 100W -coffeeMachine.power = 25; // Error (no setter) +coffeeMachine.power = 25; // Error (ไม่มี setter) ``` -````smart header="Getter/setter functions" -Here we used getter/setter syntax. +````smart header="ฟังก์ชัน Getter/setter" +ตรงนี้เราใช้ไวยากรณ์ getter/setter -But most of the time `get.../set...` functions are preferred, like this: +แต่ส่วนใหญ่นิยมใช้ฟังก์ชัน `get.../set...` มากกว่า แบบนี้: ```js class CoffeeMachine { @@ -173,26 +172,26 @@ class CoffeeMachine { new CoffeeMachine().setWaterAmount(100); ``` -That looks a bit longer, but functions are more flexible. They can accept multiple arguments (even if we don't need them right now). +ดูยาวกว่าหน่อย แต่ฟังก์ชันยืดหยุ่นกว่า เพราะรับพารามิเตอร์ได้หลายตัว (แม้ตอนนี้ยังไม่จำเป็น) -On the other hand, get/set syntax is shorter, so ultimately there's no strict rule, it's up to you to decide. +อีกมุมหนึ่ง ไวยากรณ์ get/set ก็สั้นกว่า สุดท้ายแล้วไม่มีกฎตายตัว ตัดสินใจเอาเองได้เลย ```` -```smart header="Protected fields are inherited" -If we inherit `class MegaMachine extends CoffeeMachine`, then nothing prevents us from accessing `this._waterAmount` or `this._power` from the methods of the new class. +```smart header="ฟิลด์ protected สืบทอดได้" +ถ้าเราสร้าง `class MegaMachine extends CoffeeMachine` ก็ไม่มีอะไรห้ามไม่ให้เข้าถึง `this._waterAmount` หรือ `this._power` จากเมธอดของคลาสใหม่ -So protected fields are naturally inheritable. Unlike private ones that we'll see below. +ดังนั้น ฟิลด์ protected จึงสืบทอดได้ตามธรรมชาติ ต่างจากฟิลด์ private ที่จะพูดถึงต่อไป ``` ## Private "#waterLimit" [recent browser=none] -There's a finished JavaScript proposal, almost in the standard, that provides language-level support for private properties and methods. +มีข้อเสนอ (proposal) ของ JavaScript ที่เสร็จสมบูรณ์แล้วและใกล้เป็นมาตรฐาน ซึ่งรองรับพร็อพเพอร์ตี้และเมธอดแบบ private ในระดับภาษา -Privates should start with `#`. They are only accessible from inside the class. +ฟิลด์ private ต้องขึ้นต้นด้วย `#` และเข้าถึงได้จากภายในคลาสเท่านั้น -For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`: +ลองดูตัวอย่าง พร็อพเพอร์ตี้ private `#waterLimit` และเมธอด private `#fixWaterAmount` สำหรับตรวจสอบปริมาณน้ำ: ```js run class CoffeeMachine { @@ -216,17 +215,17 @@ class CoffeeMachine { let coffeeMachine = new CoffeeMachine(); *!* -// can't access privates from outside of the class +// เข้าถึง private จากภายนอกคลาสไม่ได้ coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error */!* ``` -On the language level, `#` is a special sign that the field is private. We can't access it from outside or from inheriting classes. +ในระดับภาษา `#` เป็นเครื่องหมายพิเศษที่บอกว่าฟิลด์นี้เป็น private เข้าถึงจากภายนอกหรือจากคลาสที่สืบทอดมาไม่ได้ -Private fields do not conflict with public ones. We can have both private `#waterAmount` and public `waterAmount` fields at the same time. +ฟิลด์ private ไม่ขัดแย้งกับฟิลด์ public เราสามารถมีทั้ง `#waterAmount` (private) และ `waterAmount` (public) พร้อมกันได้ -For instance, let's make `waterAmount` an accessor for `#waterAmount`: +ลองดูตัวอย่าง ทำให้ `waterAmount` เป็น accessor สำหรับ `#waterAmount`: ```js run class CoffeeMachine { @@ -249,26 +248,26 @@ machine.waterAmount = 100; alert(machine.#waterAmount); // Error ``` -Unlike protected ones, private fields are enforced by the language itself. That's a good thing. +ต่างจาก protected ตรงที่ฟิลด์ private ถูกบังคับโดยตัวภาษาเอง ซึ่งเป็นข้อดี -But if we inherit from `CoffeeMachine`, then we'll have no direct access to `#waterAmount`. We'll need to rely on `waterAmount` getter/setter: +แต่ถ้าเราสืบทอดจาก `CoffeeMachine` จะเข้าถึง `#waterAmount` โดยตรงไม่ได้ ต้องใช้ getter/setter `waterAmount` แทน: ```js class MegaCoffeeMachine extends CoffeeMachine { method() { *!* - alert( this.#waterAmount ); // Error: can only access from CoffeeMachine + alert( this.#waterAmount ); // Error: เข้าถึงได้จาก CoffeeMachine เท่านั้น */!* } } ``` -In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reasons to access its internals. That's why protected fields are used more often, even though they are not supported by the language syntax. +ในหลายกรณี ข้อจำกัดนี้เข้มงวดเกินไป ถ้าเราต้อง extend `CoffeeMachine` อาจมีเหตุผลที่สมควรที่ต้องเข้าถึงข้อมูลภายใน นี่จึงเป็นเหตุผลที่ฟิลด์ protected ถูกใช้บ่อยกว่า แม้ว่าตัวภาษาจะไม่รองรับก็ตาม -````warn header="Private fields are not available as this[name]" -Private fields are special. +````warn header="ฟิลด์ private ไม่สามารถเข้าถึงผ่าน this[name] ได้" +ฟิลด์ private มีความพิเศษ -As we know, usually we can access fields using `this[name]`: +ปกติเราเข้าถึงฟิลด์ได้โดยใช้ `this[name]`: ```js class User { @@ -280,43 +279,43 @@ class User { } ``` -With private fields that's impossible: `this['#name']` doesn't work. That's a syntax limitation to ensure privacy. +แต่กับฟิลด์ private ทำแบบนี้ไม่ได้ `this['#name']` ใช้งานไม่ได้ เป็นข้อจำกัดทางไวยากรณ์เพื่อรักษาความเป็น private ```` -## Summary +## สรุป -In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)). +ในแง่ของ OOP การแยก internal interface ออกจาก external interface เรียกว่า [การห่อหุ้ม (encapsulation)](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)) -It gives the following benefits: +ประโยชน์ที่ได้มีดังนี้: -Protection for users, so that they don't shoot themselves in the foot -: Imagine, there's a team of developers using a coffee machine. It was made by the "Best CoffeeMachine" company, and works fine, but a protective cover was removed. So the internal interface is exposed. +ป้องกันผู้ใช้ไม่ให้ทำพลาด +: ลองนึกภาพว่ามีทีมนักพัฒนาใช้เครื่องชงกาแฟอยู่ เครื่องนี้ผลิตโดยบริษัท "Best CoffeeMachine" ทำงานได้ดี แต่ฝาครอบถูกถอดออกไป ทำให้เห็น internal interface หมด - All developers are civilized -- they use the coffee machine as intended. But one of them, John, decided that he's the smartest one, and made some tweaks in the coffee machine internals. So the coffee machine failed two days later. + นักพัฒนาทุกคนมีมารยาท ใช้เครื่องตามปกติ แต่มีคนหนึ่งชื่อ John คิดว่าตัวเองเก่ง เลยเข้าไปปรับแต่งข้างใน ผลก็คือเครื่องพังภายในสองวัน - That's surely not John's fault, but rather the person who removed the protective cover and let John do his manipulations. + ที่จริงมันไม่ใช่ความผิดของ John แต่เป็นความผิดของคนที่ถอดฝาครอบออกและปล่อยให้ John เข้าไปแก้ไขต่างหาก - The same in programming. If a user of a class will change things not intended to be changed from the outside -- the consequences are unpredictable. + ในการเขียนโปรแกรมก็เหมือนกัน ถ้าผู้ใช้คลาสไปเปลี่ยนสิ่งที่ไม่ได้ออกแบบมาให้เปลี่ยนจากภายนอก ผลที่ตามมาจะคาดเดาไม่ได้ -Supportable -: The situation in programming is more complex than with a real-life coffee machine, because we don't just buy it once. The code constantly undergoes development and improvement. +ดูแลรักษาง่ายขึ้น +: สถานการณ์ในการเขียนโปรแกรมซับซ้อนกว่าเครื่องชงกาแฟจริง เพราะเราไม่ได้แค่ซื้อมาใช้ครั้งเดียว แต่โค้ดต้องถูกพัฒนาและปรับปรุงอยู่ตลอด - **If we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users.** + **ถ้าเราแยก internal interface ไว้อย่างชัดเจน นักพัฒนาคลาสจะสามารถเปลี่ยนพร็อพเพอร์ตี้และเมธอดภายในได้อย่างอิสระ โดยไม่ต้องแจ้งผู้ใช้เลย** - If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them. + ถ้าเราเป็นนักพัฒนาคลาส ก็สบายใจได้ว่าเมธอด private สามารถเปลี่ยนชื่อ เปลี่ยนพารามิเตอร์ หรือแม้แต่ลบทิ้งได้อย่างปลอดภัย เพราะไม่มีโค้ดภายนอกมาพึ่งพา - For users, when a new version comes out, it may be a total overhaul internally, but still simple to upgrade if the external interface is the same. + สำหรับผู้ใช้ เวลาเวอร์ชันใหม่ออกมา ข้างในอาจเปลี่ยนหมดเลย แต่ถ้า external interface ยังเหมือนเดิม ก็อัปเกรดได้ง่ายๆ -Hiding complexity -: People adore using things that are simple. At least from outside. What's inside is a different thing. +ซ่อนความซับซ้อน +: คนเราชอบอะไรที่ใช้ง่าย อย่างน้อยก็ภายนอก ข้างในจะซับซ้อนแค่ไหนก็ไม่เป็นไร - Programmers are not an exception. + นักพัฒนาก็ไม่ต่างกัน - **It's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.** + **สะดวกที่สุดเมื่อรายละเอียดการทำงานภายในถูกซ่อนไว้ และมี external interface ที่เรียบง่ายพร้อมเอกสารอธิบายให้ใช้งาน** -To hide an internal interface we use either protected or private properties: +ในการซ่อน internal interface เราใช้ได้ทั้งพร็อพเพอร์ตี้แบบ protected และ private: -- Protected fields start with `_`. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with `_` from its class and classes inheriting from it. -- Private fields start with `#`. JavaScript makes sure we can only access those from inside the class. +- ฟิลด์ protected ขึ้นต้นด้วย `_` ซึ่งเป็นข้อตกลงร่วมกัน ไม่ได้บังคับในระดับภาษา ตามหลักแล้วควรเข้าถึงฟิลด์ที่ขึ้นต้นด้วย `_` เฉพาะจากคลาสนั้นและคลาสที่สืบทอดมาเท่านั้น +- ฟิลด์ private ขึ้นต้นด้วย `#` ตัว JavaScript จะดูแลให้ว่าเข้าถึงได้แค่จากภายในคลาสเท่านั้น -Right now, private fields are not well-supported among browsers, but can be polyfilled. +ณ ปัจจุบัน ฟิลด์ private ยังไม่ได้รับการรองรับในทุกเบราว์เซอร์ แต่สามารถใช้ polyfill ได้ diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md index 28b4c6eb6..8965178b5 100644 --- a/1-js/09-classes/05-extend-natives/article.md +++ b/1-js/09-classes/05-extend-natives/article.md @@ -1,12 +1,12 @@ -# Extending built-in classes +# การสืบทอดคลาสที่มีอยู่แล้วในภาษา -Built-in classes like Array, Map and others are extendable also. +คลาสที่มีอยู่แล้วในตัว (built-in) อย่าง Array, Map และอื่นๆ สามารถถูกสืบทอดได้เช่นกัน -For instance, here `PowerArray` inherits from the native `Array`: +ยกตัวอย่างเช่น `PowerArray` ที่สืบทอดจาก `Array`: ```js run -// add one more method to it (can do more) +// เพิ่มเมธอดเข้าไป (จะเพิ่มอีกกี่ตัวก็ได้) class PowerArray extends Array { isEmpty() { return this.length === 0; @@ -21,20 +21,20 @@ alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); // false ``` -Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type `PowerArray`. Their internal implementation uses the object's `constructor` property for that. +สังเกตจุดที่น่าสนใจมากตรงนี้ เมธอดของ built-in อย่าง `filter`, `map` และอื่นๆ จะคืนค่าเป็นออบเจ็กต์ของคลาสที่สืบทอดมา นั่นก็คือ `PowerArray` นั่นเอง เบื้องหลังการทำงานนั้นใช้พร็อพเพอร์ตี้ `constructor` ของออบเจ็กต์เป็นตัวกำหนด -In the example above, +จากตัวอย่างข้างบน ```js arr.constructor === PowerArray ``` -When `arr.filter()` is called, it internally creates the new array of results using exactly `arr.constructor`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result. +เมื่อเรียก `arr.filter()` ตัว JavaScript จะสร้างอาร์เรย์ผลลัพธ์ใหม่โดยใช้ `arr.constructor` ไม่ใช่ `Array` ธรรมดา ข้อดีก็คือเราสามารถใช้เมธอดของ `PowerArray` ต่อกับผลลัพธ์ได้เลย -Even more, we can customize that behavior. +ยิ่งไปกว่านั้น เรายังปรับแต่งพฤติกรรมนี้ได้อีกด้วย -We can add a special static getter `Symbol.species` to the class. If it exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on. +วิธีการคือเพิ่ม static getter ชื่อ `Symbol.species` เข้าไปในคลาส ถ้ามี getter ตัวนี้อยู่ JavaScript จะใช้คอนสตรักเตอร์ที่มันคืนค่ามาในการสร้างออบเจ็กต์ใหม่ใน `map`, `filter` และเมธอดอื่นๆ -If we'd like built-in methods like `map` or `filter` to return regular arrays, we can return `Array` in `Symbol.species`, like here: +ถ้าต้องการให้เมธอดอย่าง `map` หรือ `filter` คืนค่าเป็น `Array` ธรรมดา ก็แค่ return `Array` ใน `Symbol.species` แบบนี้: ```js run class PowerArray extends Array { @@ -43,7 +43,7 @@ class PowerArray extends Array { } *!* - // built-in methods will use this as the constructor + // เมธอด built-in จะใช้ตัวนี้เป็นคอนสตรักเตอร์ static get [Symbol.species]() { return Array; } @@ -53,37 +53,37 @@ class PowerArray extends Array { let arr = new PowerArray(1, 2, 5, 10, 50); alert(arr.isEmpty()); // false -// filter creates new array using arr.constructor[Symbol.species] as constructor +// filter สร้างอาร์เรย์ใหม่โดยใช้ arr.constructor[Symbol.species] เป็นคอนสตรักเตอร์ let filteredArr = arr.filter(item => item >= 10); *!* -// filteredArr is not PowerArray, but Array +// filteredArr ไม่ใช่ PowerArray แต่เป็น Array ธรรมดา */!* alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function ``` -As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further. +จะเห็นว่าตอนนี้ `.filter` คืนค่าเป็น `Array` ธรรมดาแล้ว ฟังก์ชันเสริมที่เราเพิ่มไว้จึงไม่ถูกส่งต่อไปด้วย -```smart header="Other collections work similarly" -Other collections, such as `Map` and `Set`, work alike. They also use `Symbol.species`. +```smart header="คอลเลกชันอื่นๆ ก็ทำงานในลักษณะเดียวกัน" +คอลเลกชันอื่นๆ เช่น `Map` และ `Set` ก็ใช้ `Symbol.species` ในแบบเดียวกัน ``` -## No static inheritance in built-ins +## Static method ไม่ถูกสืบทอดใน built-in -Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc. +ออบเจ็กต์ built-in มี static method เป็นของตัวเอง เช่น `Object.keys`, `Array.isArray` เป็นต้น -As we already know, native classes extend each other. For instance, `Array` extends `Object`. +อย่างที่เราทราบกันแล้ว คลาส built-in ก็สืบทอดกันเป็นลำดับ เช่น `Array` สืบทอดจาก `Object` -Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the article [](info:static-properties-methods#statics-and-inheritance). +ปกติแล้วเมื่อคลาสหนึ่งสืบทอดจากอีกคลาส ทั้ง static method และ non-static method จะถูกสืบทอดไปด้วย ซึ่งอธิบายไว้แล้วในบทความ [](info:static-properties-methods#statics-and-inheritance) -But built-in classes are an exception. They don't inherit statics from each other. +แต่คลาส built-in เป็นข้อยกเว้น เพราะ static method จะไม่ถูกสืบทอดระหว่างกัน -For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no, for instance, `Array.keys()` (or `Date.keys()`) static method. +ยกตัวอย่างเช่น ทั้ง `Array` และ `Date` ต่างก็สืบทอดจาก `Object` ดังนั้นอินสแตนซ์ของทั้งสองจึงมีเมธอดจาก `Object.prototype` ให้ใช้ได้ แต่ `Array.[[Prototype]]` ไม่ได้อ้างอิงไปยัง `Object` จึงไม่มี static method อย่าง `Array.keys()` (หรือ `Date.keys()`) -Here's the picture structure for `Date` and `Object`: +ลองดูแผนภาพโครงสร้างของ `Date` กับ `Object`: ![](object-date-inheritance.svg) -As you can see, there's no link between `Date` and `Object`. They are independent, only `Date.prototype` inherits from `Object.prototype`. +จะเห็นว่า `Date` กับ `Object` ไม่ได้เชื่อมกัน ทั้งสองเป็นอิสระจากกัน มีแค่ `Date.prototype` เท่านั้นที่สืบทอดจาก `Object.prototype` -That's an important difference of inheritance between built-in objects compared to what we get with `extends`. +นี่คือความแตกต่างสำคัญของการสืบทอดในออบเจ็กต์ built-in เมื่อเทียบกับการสืบทอดผ่าน `extends` ที่เราใช้กันปกติ diff --git a/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md index d41d90edf..9b8769e85 100644 --- a/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md +++ b/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md @@ -1,7 +1,7 @@ -Yeah, looks strange indeed. +ใช่ ดูแปลกจริงๆ -But `instanceof` does not care about the function, but rather about its `prototype`, that it matches against the prototype chain. +แต่ `instanceof` ไม่ได้สนใจตัวฟังก์ชัน สิ่งที่มันตรวจสอบคือพร็อพเพอร์ตี้ `prototype` ว่าตรงกับโปรโตไทป์ตัวไหนใน prototype chain หรือไม่ -And here `a.__proto__ == B.prototype`, so `instanceof` returns `true`. +ในกรณีนี้ `a.__proto__ == B.prototype` จึงทำให้ `instanceof` คืนค่า `true` -So, by the logic of `instanceof`, the `prototype` actually defines the type, not the constructor function. +สรุปก็คือ ตามตรรกะของ `instanceof` แล้ว สิ่งที่กำหนด "ชนิด" คือ `prototype` ไม่ใช่คอนสตรักเตอร์ฟังก์ชัน diff --git a/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md index 5b8dc7de3..83a930250 100644 --- a/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md +++ b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Strange instanceof +# instanceof ที่ดูแปลกๆ -In the code below, why does `instanceof` return `true`? We can easily see that `a` is not created by `B()`. +ในโค้ดด้านล่าง ทำไม `instanceof` ถึงคืนค่า `true`? ทั้งๆ ที่ `a` ไม่ได้ถูกสร้างจาก `B()` เลย ```js run function A() {} diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index 00aee3376..619be4fbf 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -1,42 +1,42 @@ -# Class checking: "instanceof" +# การตรวจสอบคลาส: "instanceof" -The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account. +ตัวดำเนินการ `instanceof` ใช้ตรวจสอบว่าออบเจ็กต์นั้นเป็นอินสแตนซ์ของคลาสใดคลาสหนึ่งหรือไม่ โดยตรวจสอบรวมถึงการสืบทอดด้วย -Such a check may be necessary in many cases. For example, it can be used for building a *polymorphic* function, the one that treats arguments differently depending on their type. +การตรวจสอบแบบนี้จำเป็นในหลายสถานการณ์ ยกตัวอย่างเช่น ใช้สร้างฟังก์ชันแบบ *polymorphic* ที่จัดการอาร์กิวเมนต์ต่างกันตามชนิดของข้อมูล -## The instanceof operator [#ref-instanceof] +## ตัวดำเนินการ instanceof [#ref-instanceof] -The syntax is: +รูปแบบการเขียนเป็นดังนี้: ```js obj instanceof Class ``` -It returns `true` if `obj` belongs to the `Class` or a class inheriting from it. +จะคืนค่า `true` ถ้า `obj` เป็นอินสแตนซ์ของ `Class` หรือคลาสที่สืบทอดมาจากคลาสนั้น -For instance: +ตัวอย่างเช่น: ```js run class Rabbit {} let rabbit = new Rabbit(); -// is it an object of Rabbit class? +// rabbit เป็นออบเจ็กต์ของคลาส Rabbit ไหม? *!* alert( rabbit instanceof Rabbit ); // true */!* ``` -It also works with constructor functions: +ใช้ได้กับคอนสตรักเตอร์ฟังก์ชันเช่นกัน: ```js run *!* -// instead of class +// ใช้ฟังก์ชันแทนคลาส function Rabbit() {} */!* alert( new Rabbit() instanceof Rabbit ); // true ``` -...And with built-in classes like `Array`: +...รวมถึงคลาสมาตรฐานอย่าง `Array` ด้วย: ```js run let arr = [1, 2, 3]; @@ -44,19 +44,19 @@ alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true ``` -Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypically inherits from `Object`. +สังเกตว่า `arr` จัดอยู่ในคลาส `Object` ด้วยเช่นกัน เพราะ `Array` สืบทอดมาจาก `Object` ผ่านทางโปรโตไทป์นั่นเอง -Normally, `instanceof` examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`. +ปกติแล้ว `instanceof` จะตรวจสอบโดยไล่ดูตาม prototype chain แต่เราสามารถกำหนดตรรกะเองได้ผ่าน static method `Symbol.hasInstance` -The algorithm of `obj instanceof Class` works roughly as follows: +อัลกอริทึมของ `obj instanceof Class` ทำงานคร่าวๆ ดังนี้: -1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`, and we're done. That's how we can customize the behavior of `instanceof`. +1. ถ้ามี static method `Symbol.hasInstance` อยู่ ก็เรียกใช้เลย: `Class[Symbol.hasInstance](obj)` ซึ่งจะคืนค่า `true` หรือ `false` แค่นี้ก็จบ นี่คือวิธีที่เราปรับแต่งพฤติกรรมของ `instanceof` ได้ - For example: + ตัวอย่างเช่น: ```js run - // set up instanceof check that assumes that - // anything with canEat property is an animal + // กำหนดให้ instanceof ถือว่า + // ถ้ามีพร็อพเพอร์ตี้ canEat แสดงว่าเป็นสัตว์ class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; @@ -65,24 +65,24 @@ The algorithm of `obj instanceof Class` works roughly as follows: let obj = { canEat: true }; - alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called + alert(obj instanceof Animal); // true: เรียก Animal[Symbol.hasInstance](obj) ``` -2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceof Class` checks whether `Class.prototype` is equal to one of the prototypes in the `obj` prototype chain. +2. คลาสส่วนใหญ่ไม่มี `Symbol.hasInstance` กรณีนี้จะใช้ตรรกะปกติ คือ `obj instanceof Class` จะตรวจว่า `Class.prototype` ตรงกับโปรโตไทป์ตัวใดตัวหนึ่งใน prototype chain ของ `obj` หรือไม่ - In other words, compare one after another: + พูดง่ายๆ ก็คือเปรียบเทียบทีละตัวตามลำดับ: ```js obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... - // if any answer is true, return true - // otherwise, if we reached the end of the chain, return false + // ถ้าตรงกับตัวไหนก็คืนค่า true + // แต่ถ้าไล่จนสุดสายแล้วไม่ตรงสักตัว ก็คืนค่า false ``` - In the example above `rabbit.__proto__ === Rabbit.prototype`, so that gives the answer immediately. + จากตัวอย่างข้างต้น `rabbit.__proto__ === Rabbit.prototype` ตรงกันเลยตั้งแต่ขั้นแรก จึงได้คำตอบทันที - In the case of an inheritance, the match will be at the second step: + แต่ถ้ามีการสืบทอด จะตรงกันที่ขั้นที่สอง: ```js run class Animal {} @@ -93,76 +93,76 @@ The algorithm of `obj instanceof Class` works roughly as follows: alert(rabbit instanceof Animal); // true */!* - // rabbit.__proto__ === Animal.prototype (no match) + // rabbit.__proto__ === Animal.prototype (ไม่ตรง) *!* - // rabbit.__proto__.__proto__ === Animal.prototype (match!) + // rabbit.__proto__.__proto__ === Animal.prototype (ตรง!) */!* ``` -Here's the illustration of what `rabbit instanceof Animal` compares with `Animal.prototype`: +นี่คือภาพแสดงสิ่งที่ `rabbit instanceof Animal` เปรียบเทียบกับ `Animal.prototype`: ![](instanceof.svg) -By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), that returns `true` if `objA` is somewhere in the chain of prototypes for `objB`. So the test of `obj instanceof Class` can be rephrased as `Class.prototype.isPrototypeOf(obj)`. +นอกจากนี้ยังมีเมธอด [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf) ที่คืนค่า `true` ถ้า `objA` อยู่ใน prototype chain ของ `objB` ดังนั้น `obj instanceof Class` จึงเขียนอีกแบบได้เป็น `Class.prototype.isPrototypeOf(obj)` -It's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters. +ที่น่าสนใจคือ ตัวคอนสตรักเตอร์ `Class` เองไม่ได้มีส่วนร่วมในการตรวจสอบเลย! สิ่งที่มีผลคือ prototype chain และ `Class.prototype` เท่านั้น -That can lead to interesting consequences when a `prototype` property is changed after the object is created. +เรื่องนี้อาจทำให้เกิดผลลัพธ์ที่น่าแปลกใจ เมื่อพร็อพเพอร์ตี้ `prototype` ถูกเปลี่ยนหลังจากสร้างออบเจ็กต์ไปแล้ว -Like here: +ลองดูตัวอย่างนี้: ```js run function Rabbit() {} let rabbit = new Rabbit(); -// changed the prototype +// เปลี่ยน prototype Rabbit.prototype = {}; -// ...not a rabbit any more! +// ...ไม่ใช่กระต่ายอีกต่อไปแล้ว! *!* alert( rabbit instanceof Rabbit ); // false */!* ``` -## Bonus: Object.prototype.toString for the type +## โบนัส: Object.prototype.toString สำหรับตรวจชนิดข้อมูล -We already know that plain objects are converted to string as `[object Object]`: +เรารู้แล้วว่าออบเจ็กต์ธรรมดาเมื่อแปลงเป็นสตริงจะได้ `[object Object]`: ```js run let obj = {}; alert(obj); // [object Object] -alert(obj.toString()); // the same +alert(obj.toString()); // เหมือนกัน ``` -That's their implementation of `toString`. But there's a hidden feature that makes `toString` actually much more powerful than that. We can use it as an extended `typeof` and an alternative for `instanceof`. +นี่คือการทำงานของ `toString` ของออบเจ็กต์ แต่จริงๆ แล้วมีความสามารถซ่อนอยู่ที่ทำให้ `toString` ทรงพลังกว่าที่คิดมาก เราสามารถใช้มันเป็น `typeof` เวอร์ชันอัปเกรด และเป็นตัวเลือกแทน `instanceof` ได้เลย -Sounds strange? Indeed. Let's demystify. +ฟังดูแปลกใช่ไหม? มาดูกันว่าทำได้อย่างไร -By [specification](https://tc39.github.io/ecma262/#sec-object.prototype.tostring), the built-in `toString` can be extracted from the object and executed in the context of any other value. And its result depends on that value. +ตาม[สเปก](https://tc39.github.io/ecma262/#sec-object.prototype.tostring) เราสามารถดึงเมธอด `toString` มาตรฐานออกมาจากออบเจ็กต์ แล้วเรียกใช้กับค่าอะไรก็ได้ ผลลัพธ์ที่ได้จะขึ้นอยู่กับค่าที่ส่งเข้าไป -- For a number, it will be `[object Number]` -- For a boolean, it will be `[object Boolean]` -- For `null`: `[object Null]` -- For `undefined`: `[object Undefined]` -- For arrays: `[object Array]` -- ...etc (customizable). +- ถ้าเป็นตัวเลข จะได้ `[object Number]` +- ถ้าเป็นบูลีน จะได้ `[object Boolean]` +- ถ้าเป็น `null`: `[object Null]` +- ถ้าเป็น `undefined`: `[object Undefined]` +- ถ้าเป็นอาร์เรย์: `[object Array]` +- ...และอื่นๆ (ปรับแต่งได้) -Let's demonstrate: +ลองดูตัวอย่าง: ```js run -// copy toString method into a variable for convenience +// คัดลอกเมธอด toString มาเก็บไว้ในตัวแปรเพื่อความสะดวก let objectToString = Object.prototype.toString; -// what type is this? +// ข้อมูลนี้เป็นชนิดอะไร? let arr = []; alert( objectToString.call(arr) ); // [object *!*Array*/!*] ``` -Here we used [call](mdn:js/function/call) as described in the chapter [](info:call-apply-decorators) to execute the function `objectToString` in the context `this=arr`. +ตรงนี้เราใช้ [call](mdn:js/function/call) ตามที่อธิบายไว้ในบท [](info:call-apply-decorators) เพื่อเรียกฟังก์ชัน `objectToString` โดยกำหนดให้ `this=arr` -Internally, the `toString` algorithm examines `this` and returns the corresponding result. More examples: +ภายในอัลกอริทึม `toString` จะตรวจสอบค่า `this` แล้วคืนผลลัพธ์ที่สอดคล้องกัน ลองดูตัวอย่างเพิ่มเติม: ```js run let s = Object.prototype.toString; @@ -174,9 +174,9 @@ alert( s.call(alert) ); // [object Function] ### Symbol.toStringTag -The behavior of Object `toString` can be customized using a special object property `Symbol.toStringTag`. +พฤติกรรมของ `toString` ของ Object สามารถปรับแต่งได้ผ่านพร็อพเพอร์ตี้พิเศษ `Symbol.toStringTag` -For instance: +ตัวอย่างเช่น: ```js run let user = { @@ -186,10 +186,10 @@ let user = { alert( {}.toString.call(user) ); // [object User] ``` -For most environment-specific objects, there is such a property. Here are some browser specific examples: +ออบเจ็กต์เฉพาะของแต่ละสภาพแวดล้อมก็มีพร็อพเพอร์ตี้นี้เช่นกัน ตัวอย่างจากเบราว์เซอร์: ```js run -// toStringTag for the environment-specific object and class: +// toStringTag ของออบเจ็กต์และคลาสเฉพาะสภาพแวดล้อม: alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest @@ -197,22 +197,22 @@ alert( {}.toString.call(window) ); // [object Window] alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest] ``` -As you can see, the result is exactly `Symbol.toStringTag` (if exists), wrapped into `[object ...]`. +จะเห็นว่าผลลัพธ์คือค่าของ `Symbol.toStringTag` (ถ้ามี) ครอบด้วย `[object ...]` -At the end we have "typeof on steroids" that not only works for primitive data types, but also for built-in objects and even can be customized. +สรุปแล้วเรามี "typeof เวอร์ชันอัปเกรด" ที่ใช้ได้ไม่ใช่แค่กับชนิดข้อมูลพื้นฐาน แต่ยังใช้กับออบเจ็กต์มาตรฐาน และปรับแต่งเองได้ด้วย -We can use `{}.toString.call` instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check. +เราสามารถใช้ `{}.toString.call` แทน `instanceof` สำหรับออบเจ็กต์มาตรฐาน เมื่อต้องการได้ชนิดข้อมูลเป็นสตริง แทนที่จะแค่ตรวจสอบว่าจริงหรือเท็จ -## Summary +## สรุป -Let's summarize the type-checking methods that we know: +มาสรุปวิธีตรวจสอบชนิดข้อมูลที่เรารู้จักกัน: -| | works for | returns | +| | ใช้กับ | คืนค่า | |---------------|-------------|---------------| -| `typeof` | primitives | string | -| `{}.toString` | primitives, built-in objects, objects with `Symbol.toStringTag` | string | -| `instanceof` | objects | true/false | +| `typeof` | ค่าพื้นฐาน (primitives) | สตริง | +| `{}.toString` | ค่าพื้นฐาน, ออบเจ็กต์มาตรฐาน, ออบเจ็กต์ที่มี `Symbol.toStringTag` | สตริง | +| `instanceof` | ออบเจ็กต์ | true/false | -As we can see, `{}.toString` is technically a "more advanced" `typeof`. +จะเห็นว่า `{}.toString` เป็น `typeof` เวอร์ชันอัปเกรดที่ทำได้มากกว่า -And `instanceof` operator really shines when we are working with a class hierarchy and want to check for the class taking into account inheritance. +ส่วน `instanceof` จะเหมาะมากเมื่อทำงานกับลำดับชั้นของคลาส และต้องการตรวจสอบคลาสโดยนับรวมการสืบทอดด้วย diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 526b832ef..ad23d568e 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -1,22 +1,22 @@ -# Mixins +# Mixin -In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class. +ใน JavaScript เราสืบทอดได้จากออบเจ็กต์เดียวเท่านั้น `[[Prototype]]` ของออบเจ็กต์มีได้แค่ตัวเดียว และคลาสก็ extend ได้แค่คลาสเดียว -But sometimes that feels limiting. For instance, we have a class `StreetSweeper` and a class `Bicycle`, and want to make their mix: a `StreetSweepingBicycle`. +แต่บางครั้งก็รู้สึกว่าไม่พอ เช่น เรามีคลาส `StreetSweeper` กับคลาส `Bicycle` แล้วอยากรวมกันเป็น `StreetSweepingBicycle` -Or we have a class `User` and a class `EventEmitter` that implements event generation, and we'd like to add the functionality of `EventEmitter` to `User`, so that our users can emit events. +หรือมีคลาส `User` กับคลาส `EventEmitter` ที่จัดการเรื่องอีเวนต์ แล้วอยากเอาความสามารถของ `EventEmitter` มาใส่ใน `User` เพื่อให้ผู้ใช้สามารถส่งอีเวนต์ได้ -There's a concept that can help here, called "mixins". +แนวคิดที่ช่วยแก้ปัญหานี้เรียกว่า "mixin" -As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class containing methods that can be used by other classes without a need to inherit from it. +ตามคำนิยามใน Wikipedia [mixin](https://en.wikipedia.org/wiki/Mixin) คือคลาสที่มีเมธอดให้คลาสอื่นเอาไปใช้ได้โดยไม่ต้องสืบทอด -In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes. +พูดง่ายๆ ก็คือ *mixin* เตรียมเมธอดที่เพิ่มพฤติกรรมบางอย่างไว้ให้ แต่เราไม่ได้ใช้มันโดดๆ เราเอามัน "ผสม" เข้าไปในคลาสอื่นต่างหาก -## A mixin example +## ตัวอย่างของ mixin -The simplest way to implement a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class. +วิธีง่ายที่สุดในการทำ mixin ใน JavaScript คือสร้างออบเจ็กต์ที่มีเมธอดที่มีประโยชน์ แล้วค่อย merge เข้าไปในโปรโตไทป์ของคลาสไหนก็ได้ -For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`: +ตัวอย่างเช่น mixin ชื่อ `sayHiMixin` นี้เพิ่มความสามารถ "พูด" ให้กับ `User`: ```js run *!* @@ -32,7 +32,7 @@ let sayHiMixin = { }; *!* -// usage: +// การใช้งาน: */!* class User { constructor(name) { @@ -40,14 +40,14 @@ class User { } } -// copy the methods +// คัดลอกเมธอดเข้ามา Object.assign(User.prototype, sayHiMixin); -// now User can say hi +// ตอนนี้ User พูดได้แล้ว new User("Dude").sayHi(); // Hello Dude! ``` -There's no inheritance, but a simple method copying. So `User` may inherit from another class and also include the mixin to "mix-in" the additional methods, like this: +ไม่มีการสืบทอดเกิดขึ้น เป็นแค่การคัดลอกเมธอดธรรมดาๆ ดังนั้น `User` ยังสามารถ extend คลาสอื่นได้ แล้วค่อยเอา mixin เข้ามา "ผสม" เมธอดเพิ่มเติม แบบนี้: ```js class User extends Person { @@ -57,9 +57,9 @@ class User extends Person { Object.assign(User.prototype, sayHiMixin); ``` -Mixins can make use of inheritance inside themselves. +Mixin เองก็ใช้การสืบทอดระหว่างกันได้ด้วย -For instance, here `sayHiMixin` inherits from `sayMixin`: +ตัวอย่างเช่น `sayHiMixin` สืบทอดจาก `sayMixin`: ```js run let sayMixin = { @@ -69,11 +69,11 @@ let sayMixin = { }; let sayHiMixin = { - __proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here) + __proto__: sayMixin, // (หรือจะใช้ Object.setPrototypeOf เพื่อกำหนดโปรโตไทป์ก็ได้) sayHi() { *!* - // call parent method + // เรียกเมธอดของ parent */!* super.say(`Hello ${this.name}`); // (*) }, @@ -88,43 +88,43 @@ class User { } } -// copy the methods +// คัดลอกเมธอดเข้ามา Object.assign(User.prototype, sayHiMixin); -// now User can say hi +// ตอนนี้ User พูดได้แล้ว new User("Dude").sayHi(); // Hello Dude! ``` -Please note that the call to the parent method `super.say()` from `sayHiMixin` (at lines labelled with `(*)`) looks for the method in the prototype of that mixin, not the class. +สังเกตว่าเมื่อเรียก `super.say()` จาก `sayHiMixin` (บรรทัดที่มี `(*)`) จะไปค้นหาเมธอดจากโปรโตไทป์ของ mixin ไม่ใช่จากคลาส -Here's the diagram (see the right part): +ดูแผนภาพประกอบ (ดูส่วนขวา): ![](mixin-inheritance.svg) -That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above. +ที่เป็นแบบนี้เพราะเมธอด `sayHi` กับ `sayBye` ถูกสร้างขึ้นใน `sayHiMixin` ตั้งแต่แรก ดังนั้นถึงจะคัดลอกไปแล้ว พร็อพเพอร์ตี้ภายใน `[[HomeObject]]` ก็ยังชี้ไปที่ `sayHiMixin` อยู่ดังที่เห็นในภาพ -As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`. +เนื่องจาก `super` ค้นหาเมธอดของ parent จาก `[[HomeObject]].[[Prototype]]` จึงหมายความว่ามันค้นหาจาก `sayHiMixin.[[Prototype]]` นั่นเอง ## EventMixin -Now let's make a mixin for real life. +ทีนี้มาลองทำ mixin ที่ใช้งานจริงกันบ้าง -An important feature of many browser objects (for instance) is that they can generate events. Events are a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows us to easily add event-related functions to any class/object. +ฟีเจอร์สำคัญอย่างหนึ่งของออบเจ็กต์ในเบราว์เซอร์หลายตัว คือความสามารถในการสร้างอีเวนต์ อีเวนต์เป็นวิธีที่ดีในการ "กระจายข้อมูล" ไปยังทุกส่วนที่สนใจ มาลองทำ mixin ที่ช่วยเพิ่มฟังก์ชันจัดการอีเวนต์ให้กับคลาส/ออบเจ็กต์ไหนก็ได้กันเถอะ -- The mixin will provide a method `.trigger(name, [...data])` to "generate an event" when something important happens to it. The `name` argument is a name of the event, optionally followed by additional arguments with event data. -- Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when an event with the given `name` triggers, and get the arguments from the `.trigger` call. -- ...And the method `.off(name, handler)` that removes the `handler` listener. +- mixin นี้จะมีเมธอด `.trigger(name, [...data])` สำหรับ "สร้างอีเวนต์" เมื่อมีเหตุการณ์สำคัญเกิดขึ้น อาร์กิวเมนต์ `name` คือชื่อของอีเวนต์ ตามด้วยอาร์กิวเมนต์เพิ่มเติมที่เป็นข้อมูลของอีเวนต์ +- เมธอด `.on(name, handler)` สำหรับเพิ่มฟังก์ชัน `handler` เป็น listener ของอีเวนต์ที่มีชื่อนั้น เมื่ออีเวนต์ `name` ถูก trigger ขึ้นมา จะเรียก handler พร้อมส่งอาร์กิวเมนต์จาก `.trigger` ให้ +- ...และเมธอด `.off(name, handler)` สำหรับลบ `handler` ออก -After adding the mixin, an object `user` will be able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen for such events to load the calendar for the logged-in person. +หลังจากเพิ่ม mixin นี้เข้าไป ออบเจ็กต์ `user` จะสร้างอีเวนต์ `"login"` ได้เมื่อผู้ใช้ล็อกอิน จากนั้นออบเจ็กต์อื่น เช่น `calendar` ก็สามารถ listen อีเวนต์นี้เพื่อโหลดปฏิทินของผู้ใช้ที่ล็อกอินเข้ามา -Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may assign handlers to react on that event. And so on. +หรือ `menu` จะสร้างอีเวนต์ `"select"` เมื่อเลือกรายการเมนู แล้วออบเจ็กต์อื่นๆ ก็กำหนด handler เพื่อตอบสนองต่ออีเวนต์นั้นได้ เป็นต้น -Here's the code: +นี่คือโค้ด: ```js run let eventMixin = { /** - * Subscribe to event, usage: + * ติดตามอีเวนต์ ตัวอย่างการใช้งาน: * menu.on('select', function(item) { ... } */ on(eventName, handler) { @@ -136,7 +136,7 @@ let eventMixin = { }, /** - * Cancel the subscription, usage: + * ยกเลิกการติดตาม ตัวอย่างการใช้งาน: * menu.off('select', handler) */ off(eventName, handler) { @@ -150,59 +150,59 @@ let eventMixin = { }, /** - * Generate an event with the given name and data + * สร้างอีเวนต์พร้อมชื่อและข้อมูลที่กำหนด * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers?.[eventName]) { - return; // no handlers for that event name + return; // ไม่มี handler สำหรับอีเวนต์นี้ } - // call the handlers + // เรียก handler ทั้งหมด this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; ``` -- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name occurs. Technically, there's an `_eventHandlers` property that stores an array of handlers for each event name, and it just adds it to the list. -- `.off(eventName, handler)` -- removes the function from the handlers list. -- `.trigger(eventName, ...args)` -- generates the event: all handlers from `_eventHandlers[eventName]` are called, with a list of arguments `...args`. +- `.on(eventName, handler)` -- กำหนดฟังก์ชัน `handler` ให้ทำงานเมื่ออีเวนต์นั้นเกิดขึ้น ภายในจะมีพร็อพเพอร์ตี้ `_eventHandlers` เก็บอาร์เรย์ของ handler แยกตามชื่ออีเวนต์ แล้วเพิ่ม handler ใหม่เข้าไปในรายการ +- `.off(eventName, handler)` -- ลบฟังก์ชันออกจากรายการ handler +- `.trigger(eventName, ...args)` -- สร้างอีเวนต์ขึ้นมา โดยเรียก handler ทุกตัวจาก `_eventHandlers[eventName]` พร้อมส่งอาร์กิวเมนต์ `...args` ให้ -Usage: +ตัวอย่างการใช้งาน: ```js run -// Make a class +// สร้างคลาส class Menu { choose(value) { this.trigger("select", value); } } -// Add the mixin with event-related methods +// เพิ่ม mixin ที่จัดการอีเวนต์ Object.assign(Menu.prototype, eventMixin); let menu = new Menu(); -// add a handler, to be called on selection: +// เพิ่ม handler ที่จะทำงานเมื่อเลือกรายการ: *!* -menu.on("select", value => alert(`Value selected: ${value}`)); +menu.on("select", value => alert(`เลือกค่า: ${value}`)); */!* -// triggers the event => the handler above runs and shows: -// Value selected: 123 +// trigger อีเวนต์ => handler ด้านบนทำงาน แสดงผลว่า: +// เลือกค่า: 123 menu.choose("123"); ``` -Now, if we'd like any code to react to a menu selection, we can listen for it with `menu.on(...)`. +ทีนี้ถ้าต้องการให้โค้ดส่วนใดตอบสนองเมื่อมีการเลือกเมนู ก็แค่ listen ด้วย `menu.on(...)` -And `eventMixin` mixin makes it easy to add such behavior to as many classes as we'd like, without interfering with the inheritance chain. +และ mixin `eventMixin` ช่วยให้เราเพิ่มพฤติกรรมนี้ให้กับกี่คลาสก็ได้ โดยไม่กระทบกับห่วงโซ่การสืบทอดเลย -## Summary +## สรุป -*Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes. +*Mixin* -- เป็นคำศัพท์ทั่วไปในการเขียนโปรแกรมเชิงวัตถุ หมายถึงคลาสที่เตรียมเมธอดไว้ให้คลาสอื่นเอาไปใช้ -Some other languages allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype. +บางภาษาอนุญาตให้สืบทอดจากหลายคลาสได้ (multiple inheritance) แต่ JavaScript ไม่รองรับ อย่างไรก็ตาม เราใช้ mixin แทนได้โดยการคัดลอกเมธอดเข้าไปในโปรโตไทป์ -We can use mixins as a way to augment a class by adding multiple behaviors, like event-handling as we have seen above. +เราใช้ mixin เพื่อเพิ่มพฤติกรรมหลายๆ อย่างให้กับคลาสได้ เช่น การจัดการอีเวนต์อย่างที่เห็นข้างต้น -Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that happening. +จุดที่ต้องระวังคือ mixin อาจเกิดปัญหาได้ถ้าเมธอดไปทับเมธอดเดิมของคลาสโดยไม่ได้ตั้งใจ ดังนั้นควรตั้งชื่อเมธอดของ mixin อย่างรอบคอบ เพื่อลดโอกาสที่จะซ้ำกัน diff --git a/1-js/09-classes/07-mixins/head.html b/1-js/09-classes/07-mixins/head.html index 20e3a6354..acb8ab3d4 100644 --- a/1-js/09-classes/07-mixins/head.html +++ b/1-js/09-classes/07-mixins/head.html @@ -2,7 +2,7 @@ let eventMixin = { /** - * Subscribe to event, usage: + * ติดตามอีเวนต์ ตัวอย่างการใช้งาน: * menu.on('select', function(item) { ... } */ on(eventName, handler) { @@ -14,7 +14,7 @@ }, /** - * Cancel the subscription, usage: + * ยกเลิกการติดตาม ตัวอย่างการใช้งาน: * menu.off('select', handler) */ off(eventName, handler) { @@ -28,15 +28,15 @@ }, /** - * Generate the event and attach the data to it + * สร้างอีเวนต์พร้อมแนบข้อมูล * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers || !this._eventHandlers[eventName]) { - return; // no handlers for that event name + return; // ไม่มี handler สำหรับอีเวนต์นี้ } - // call the handlers + // เรียก handler ทั้งหมด this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } }; diff --git a/1-js/09-classes/index.md b/1-js/09-classes/index.md index 87846ef6b..11593edd3 100644 --- a/1-js/09-classes/index.md +++ b/1-js/09-classes/index.md @@ -1 +1 @@ -# Classes +# คลาส