φυβλαςのβλογ
บล็อกของ phyblas



javascript เบื้องต้น บทที่ ๙: การใช้งานออบเจ็กต์
เขียนเมื่อ 2019/07/31 23:16

ตั้งแต่ในบทที่ ๓ ได้เริ่มเขียนแนะนำข้อมูลชนิดต่างๆไปแล้ว และได้แนะนำเกี่ยวกับข้อมูลชนิดที่เรียกว่า "ออบเจ็กต์" (object) ไป

ออบเจ็กต์เป็นข้อมูลที่มีความสำคัญมากในภาษาจาวาสคริปต์ ในบทนี้จะมาเจาะลึกเพื่อให้เข้าใจยิ่งขึ้น



การสร้างออบเจ็กต์

ออบเจ็กต์ คือข้อมูลโครงสร้างที่ประกอบไปด้วยค่าต่างๆเก็บไว้ภายใน เรียกว่า "พรอเพอร์ตี" (property)

แต่ละพรอเพอร์ตีจะมีการตั้งชื่อเรียกเพื่อเข้าถึง ชื่อนั้นเรียกว่า "คีย์" (key)

การสร้างทำได้ง่ายๆโดยใช้วงเล็บปีกกา {} แล้วใส่คีย์ ตามด้วยทวิภาค (colon) : ตามด้วยค่าพรอเพอร์ตี โดยพรอเพอร์ตีแต่ละตัวจะคั่นด้วยจุลภาค (comma) ,

ตัวอย่าง
var pokemon1 = {
  chue: "ฟุชิงิดาเนะ",
  sung: 0.7,
  nak: 6.9
};
(ภาพฟุชิงิดาเนะ ที่มา)

แบบนี้ก็จะได้ข้อมูลออบเจ็กต์ที่เป็น pokemon (โปเกมอน) ซึ่งมีพรอเพอร์ตีคือ chue (ชื่อ) ฟุชิงิดาเนะ sung (สูง) 0.7 และ nak (หนัก) 6.9

ปกติเวลาสร้างออบเจ็กต์นั้นบ่อยครั้งที่เขียนแยกพรอเพอร์ตีแต่ละตัวไว้เขียนคนละบรรทัดแบบนี้เพื่อให้เข้าใจง่าย

แต่ที่จริงจะเขียนต่อกันทีเดียวในบรรทัดเดียวก็ทำได้ เช่น
var pokemon1 = {chue: "ฟุชิงิดาเนะ", sung: 0.7, nak: 6.9};

ที่สำคัญคือให้คั่นคีย์กับค่าพรอเพอร์ตีด้วยโคลอน : และคั่นระหว่างแต่ละพรอเพอร์ตีด้วยจุลภาค ,

หลังพรอเพอร์ตีตัวสุดท้ายไม่จำเป็นต้องใส่จุลภาค แต่จะใส่ก็ได้ไม่ผิด
var pokemon1 = {chue: "ฟุชิงิดาเนะ", sung: 0.7, nak: 6.9,};

แม้ว่าจะเขียนยัดใส่บรรทัดเดียวกันทั้งหมดแบบนี้ได้ แต่ปกติถ้าหากมีหลายตัวแล้วถ้าใส่บรรทัดเดียวกันหมดจะยาวเกิน ดังนั้นเพื่อให้ดูเรียบร้อยมักจะเขียนแยกแต่ละพรอเพอร์ตีไว้คนละบรรทัด

หากต้องการสร้างออบเจ็กต์ว่างเปล่าที่ไม่มีพรอเพอร์ตีอะไรเลยก็ทำได้ แบบนั้นก็แค่ใส่วงเล็บปีกกาเปิดปิดไว้เฉยๆ
var wangplao = {};

คีย์ของพรอเพอร์ตีนั้นจะใช้คำหรือข้อความอะไรก็ได้ เพียงแต่ถ้าหากมีพวกสัญลักษณ์พิเศษหรือมีเว้นวรรคจำเป็นจะต้องใช้เครื่องหมายคำพูดคร่อม " " เวลาที่ประกาศสร้างขึ้น

เช่น
var pokemon2 = {
  "chue": "พีคาชู",
  "chue len": "พีคา",
  "suan-sung": 0.4,
  "nam-nak-tua": 6.0
};

ในตัวอย่างนี้ เฉพาะ chue (ชื่อ) เท่านั้นที่ไม่จำเป็นต้องใส่เครื่องหมายคำพูดคร่อมก็ได้ แต่ว่าจะใส่ไว้ก็ได้ แล้วแต่

ส่วน chue len (ชื่อเล่น) มีเว้นวรรค และ suan-sung (ส่วนสูง) และ nam-nak-tua (น้ำหนักตัว) มีขีด - อยู่ดังนั้นต้องใส่เครื่องหมายคำพูด ไม่เช่นนั้นจะเกิดข้อผิดพลาด



การเข้าถึงข้อมูลในออบเจ็กต์

เมื่อประกาศสร้างออบเจ็กต์ขึ้นมาแล้วสามารถเข้าถึงเพื่อใช้หรือดูค่าพรอเพอร์ตีได้โดยพิมพ์ชื่อตัวแปรออบเจ็กต์ตามด้วยจุด . แล้วตามด้วยคีย์

ขอยกตัวอย่างด้วย pokemon2 ที่สร้างขึ้นจากหัวข้อที่แล้ว เช่นเมื่อต้องการดูข้อมูล chue (ชื่อ)
alert(pokemon2.chue); // ได้ พีคาชู

หรืออาจเข้าถึงค่าโดยใส่วงเล็บเหลี่ยม แล้วใส่คีย์ไว้ด้านใน กรณีนี้จะต้องใส่เครื่องหมายคำพูดเสมอ
alert(pokemon2["chue"]); // ได้ พีคาชู

แต่สำหรับพรอเพอร์ตีที่ตั้งคีย์เป็นคำที่มีพวกเครื่องหมายหรือเว้นวรรคจะใช้วิธีแรกในการเข้าถึงไม่ได้ ต้องใช้วงเล็บเหลี่ยมเท่านั้น
alert(pokemon2["chue len"]); // ได้ พีคา
alert(pokemon2["suan-sung"]); // ได้ 0.4

นอกจากนี้ หากคีย์ขึ้นต้นด้วยตัวเลขก็ไม่สามารถเข้าถึงโดยใช้จุดเช่นกัน เพียงแต่สำหรับตัวเลขจะใส่เครื่องหมายคำพูดหรือไม่ก็ได้
var h = {1: "a", 2: "b"};
alert(h[1]); // ได้ a
alert(h["2"]); // ได้ b

คีย์ถือเป็นข้อมูลชนิดสายอักขระเสมอ แม้ต่อให้ตอนที่ประกาศชื่อคีย์จะใส่เป็นตัวเลขไปก็จะได้สายอักขระที่เป็นตัวเลข ดังนั้นในที่นี้คีย์ไม่ใช่ตัวเลข 1 แต่เป็นสายอักขระ "1"

เพียงแต่ถ้าใส่ตัวเลข 1 ไปเฉยๆจะถูกตีความเป็นสายอักขระให้ จึงใช้เข้าถึงพรอเพอร์ตีได้เช่นกัน

ถ้าใส่ชื่อพรอเพอร์ตีที่ไม่มีอยู่จะได้ undefined
var obji = {a: 1};
alert(obji.b); // ได้ undefined

สิ่งที่ใส่ใน [ ] อาจเป็นตัวแปรที่เก็บค่าสายอักขระก็ได้ เช่น
var obja = {b: 5};
var prop = "b";
alert(obja[prop]); // ได้ 5

กรณีนี้ obja[prop] ไม่ได้หมายถึงหาพรอเพอร์ตีที่ชื่อ prop แต่หมายถึงให้หาพรอเพอร์ตีที่ชื่อมีค่าตามที่อยู่ในตัวแปร prop นั่นก็คือ b นั่นเอง

แต่ถ้าใส่เครื่องหมายคำพูดกลายเป็น obja["prop"] แบบนี้ จะมีค่าเท่ากับการเขียน obja.prop นั่นคือให้หาพรอเพอร์ตีที่ชื่อ prop ซึ่งในเมื่อไม่ได้กำหนดไว้ก็จะได้ undefined
alert(obja["prop"]); // ได้ undefined

ดังนั้นจะเห็นว่าการใส่เครื่องหมายคำพูดคร่อมหรือไม่นั้นสำคัญ ต้องระวังสับสนแล้วใช้ผิด



การเพิ่มหรือแก้ข้อมูลในออบเจ็กต์

การเพิ่มพรอเพอร์ตีให้กับออบเจ็กต์สามารถทำได้โดยวิธีการที่คล้ายกับตอนดูค่า นั่นคือใช้จุดหรือวงเล็บเหลี่ยม
var pokemon3 = {
  lek: 111,
  lv: 30
};
pokemon3.chue = "ไซฮอร์น"; // เพิ่มพรอเพอร์ตี chue (ชื่อ)
pokemon3["nak"] = 115; // เพิ่มพรอเพอร์ตี nak (หนัก)
alert(pokemon3["chue"]); // ได้ ไซฮอร์น
alert(pokemon3.nak); // ได้ 115

ถ้าหากเป็นพรอเพอร์ตีที่มีอยู่แล้วจะเป็นการแก้ค่านั้น ไม่ได้สร้างพรอเพอร์ตีใหม่ขึ้น
pokemon.lv = 34; // แก้ lv



ออบเจ็กต์ในออบเจ็กต์

สิ่งที่ใส่เป็นพรอเพอร์ตีในออบเจ็กต์จะเป็นข้อมูลชนิดไหนก็ได้ รวมถึงชนิดออบเจ็กต์ นั่นหมายความว่าอาจเกิดโครงสร้างแบบที่ว่าออบเจ็กต์เก็บอยู่ภายในออบเจ็กต์

ตัวอย่าง
phulen = {
  chue: "ซาโตชิ",
  pk1: {
    chue: "พีคาชู",
    lv: 98
  },
  pk2: {
    chue: "ลิซาร์ดอน",
    lv: 40
  }
};

โครงสร้างเวลาสร้างอาจดูซับซ้อนขึ้นสักหน่อย แต่ว่าโดยปกติแล้วจะอาศัยการเคาะวรรคให้ร่นเข้าไปอีกขั้นเพื่อให้เข้าใจว่านี่เป็นโครงสร้างที่อยู่ลึกเข้าไปด้านในอีก ช่วยให้เข้าใจได้ง่ายขึ้น ลดการสับสน

เวลาสร้างออบเจ็กต์ซ้อนในออบเจ็กต์แบบนี้ เพื่อให้ดูง่ายขึ้นอาจเตรียมค่าเอาไว้ในตัวแปรนึงก่อนแล้วค่อยเอาตัวแปรนั้นมาใส่ลงในออบเจ็กต์หลัก

เช่นตัวอย่างข้างต้นอาจเขียนแบบนี้ได้ จะได้ผลเหมือนเดิม
chue = "ซาโตชิ";
pk1 = {
  chue: "พีคาชู",
  lv: 98
};
pk2 = {
  chue: "ลิซาร์ดอน",
  lv: 40
};
phulen = {
  chue: chue,
  pk1: pk1,
  pk2: pk2
};

นอกจากนี้ อาจสร้างออบเจ็กต์เปล่าเตรียมไว้แล้วค่อยๆป้อนพรอเพอร์ตีให้ก็ได้
phulen = {};
phulen.chue = "ซาโตชิ";
phulen.pk1 = {
  chue: "พีคาชู",
  lv: 98
};
phulen.pk2 = {
  chue: "ลิซาร์ดอน",
  lv: 40
};

จะเห็นวิธีการสร้างมีอยู่หลากหลาย สามารถเลือกใช้ตามที่สะดวกได้

ส่วนการจะเข้าถึงข้อมูลที่อยู่ในออบเจ็กต์ด้านในก็อาจทำได้โดยการเขียนจุดต่อกันไปเลย หรือจะใช้วงเล็บเหลี่ยมต่อกัน หรือปนกันก็ได้
alert(phulen.pk1.chue); // ได้ พีคาชู
alert(phulen["pk2"]["chue"]); // ได้ ลิซาร์ดอน
alert(phulen["pk1"].lv); // ได้ 98
alert(phulen.pk2["lv"]); // ได้ 40
alert(phulen.chue); // ได้ ซาโตชิ

จะเอาออบเจ็กต์ข้างในมาเก็บในตัวแปรอีกตัวก่อนแล้วค่อยเข้าถึงพรอเพอร์ตีข้างในอีกทีก็ได้
var pk = phulen.pk1;
alert(pk.chue); // ได้ พีคาชู



ออบเจ็กต์และตัวแปรที่เก็บออบเจ็กต์

เมื่อพูดถึงออบเจ็กต์ มีสิ่งที่จำเป็นจะต้องทำความเข้าใจให้ดี เพื่อไม่ให้ทำอะไรบางอย่างผิดพลาดโดยไม่ตั้งใจ นั่นคือ ความจริงแล้วเมื่อเราสร้างออบเจ็กต์แล้วใส่ไว้ในตัวแปรนึง สิ่งที่ตัวแปรนั้นเก็บไม่ใช่ตัวข้อมูลที่อยู่ภายในออบเจ็กต์นั้น แต่เป็นที่อยู่ที่ชี้ไปยังที่เก็บข้อมูล

ความหมายก็คือ สมมุติว่าเราสร้างออบเจ็กต์ป้อนให้ตัวแปรนึง จากนั้นเราเอาค่าตัวแปรนั้นไปป้อนให้ตัวแปรอื่นอีก
var pokemon3 = {lv: 15};
var pokemonx = pokemon3;

จะได้ว่าตัวแปรทั้ง ๒ ตัวนั้นชี้ไปที่ออบเจ็กต์ตัวเดียวกัน ค่าพรอเพอร์ตีของข้อมูลภายในออบเจ็กต์นั้นจะเข้าถึงผ่านตัวแปรไหนก็ได้เหมือนกัน
alert(pokemon3.lv); // ได้ 15
alert(pokemonx.lv); // ได้ 15

และหากมีการเปลี่ยนแปลงค่าพรอเพอร์ตีในนั้น ความเปลี่ยนแปลงก็จะเกิดขึ้นกับทั้งสองตัวแปร เพราะถือเป็นตัวเดียวกัน
pokemonx.lv = 17; // แก้ค่า lv
alert(pokemonx.lv); // ได้ 17
alert(pokemon3.lv); // ได้ 17

เพียงแต่ระวังสับสน ที่ว่าจะเปลี่ยนแปลงไปพร้อมกันนั้นคือเฉพาะเมื่อมีการแก้พรอเพอร์​ตีเท่านั้น แต่หากมีการป้อนค่าแทนเข้าไปที่ตัวแปรนั้นใหม่โดยตรง ตัวแปรนั้นจะไปเก็บข้อมูลใหม่แทน และไม่มีอะไรเกี่ยวข้องกับออบเจ็กต์ที่เคยอยู่กับตัวแปรนั้นอีก ตัวแปรอีกตัวที่ไม่ได้ถูกแทนก็จะอยู่เหมือนเดิม และต่อให้ทำอะไรอีกก็ไม่เกี่ยวข้องกันแล้ว
pokemonx = {lv: 21}; // เอาออบเจ็กต์ใหม่แทนลงตัวแปรเดิม
alert(pokemonx.lv); // ได้ 21
alert(pokemon3.lv); // ได้ 17
pokemon3.lv = 23;
alert(pokemon3.lv); // ได้ 23
alert(pokemonx.lv); // ได้ 21

ตรงนี้อาจเข้าใจยากและชวนสับสนสักหน่อย อาจต้องใช้เวลาคิดและทำความเข้าใจให้ดี

หากต้องการคัดลอกออบเจ็กต์โดยไม่ได้ต้องการให้เป็นออบเจ็กต์เดียวกัน อาจต้องทำแบบนี้
var pk1 = { chue: "ฮิโตคาเงะ", lv: 14 }; // ออบเจ็กต์ต้นฉบับ
var pk2 = { chue: pk1.chue, lv: pk1.chue }; // ออบเจ็กต์ที่คัดลอกมา

แต่ก็ดูแล้วยุ่งยาก เพราะต้องมาไล่เขียนพรอเพอร์ตีทีละตัวทั้งหมด

แต่ใน ES5 มีฟังก์ชันที่ช่วยทำให้คัดลอกออบเจ็กต์ได้อย่างสะดวก รายละเอียดอ่านในบทที่ ๒๘

หรืออย่างใน ES6 จะมีฟังก์ชัน Object.assign ซึ่งก็เป็นอีกวิธีที่ช่วยทำให้คัดลอกออบเจ็กต์ได้



การตรวจดูว่าออบเจ็กต์มีคีย์ที่ต้องการอยู่หรือไม่

ทำได้โดยใช้ in โดยใส่ชื่อคีย์ ตามด้วย in แล้วตามด้วยออบเจ็กต์นั้น
var obju = {
  a: 5,
  "100": "!!!"
};
alert("a" in obju); // ได้ true
alert("b" in obju); // ได้ false

กรณีที่คีย์เป็นตัวเลข จะใส่ในรูปสายอักขระหรือตัวเลขก็ได้
alert("100" in obju); // ได้ true
alert(100 in obju); // ได้ true



การลบพรอเพอร์ตีออกจากออบเจ็กต์

การลบพรอเพอร์ตีที่มีอยู่ในออบเจ็กต์ออกไปทำได้โดยใช้คำสั่ง delete พอลบไปแล้วก็จะกลายเป็นเหมือนไม่มีตัวตนอยู่อีก

ตัวอย่าง
var obra = {k: 10};
alert(obra.k); // ได้ 10
alert("k" in obra); // ได้ true
delete obra.k; // ลบ
alert(obra.k); // ได้ undefined
alert("k" in obra); // ได้ false



ออบเจ็กต์คือโครงสร้างแถวลำดับแบบจับคู่

ในภาษาโปรแกรมต่างๆจะมีข้อมูลกลุ่มประเภทที่เรียกว่า "แถวลำดับแบบจับคู่" (associative array)

เพียงแต่ว่าในแต่ละภาษามีชื่อเรียกต่างกันไป เช่น

- ในภาษาไพธอน เรียกว่า "ดิกชันนารี" (dict)
- ในภาษารูบี เรียกว่า "แฮช" (hash)
- ในภาษา C++ เรียกว่า "โครงสร้างข้อมูล" (structure)

รายละเอียดก็อาจต่างกันออกไป แต่โดยรวมแล้วมีสิ่งที่เหมือนกันคือเป็นข้อมูลชนิดกลุ่ม ซึ่งประกอบด้วยข้อมูลหลายๆอย่างอยู่ด้วยกัน โดยข้อมูลแต่ละตัวจะถูกตั้งชื่อไว้เพื่อใช้อ้างอิงได้เวลาที่ต้องการเข้าถึงข้อมูลในนั้น ชื่อที่ใช้เข้าถึงข้อมูลนั้นเรียกว่า "คีย์" (key)

ปกติเวลาเข้าถึงข้อมูลในแถวลำดับแบบจับคู่จะทำได้โดยเขียน ["คีย์"] แบบนี้ต่อท้าย

และในภาษาที่มีแนวคิดการเขียนโปรแกรมเชิงวัตถุ (OOP) โดยทั่วไปจะมีสิ่งที่เรียกว่าเป็น "ออบเจ็กต์" คือเป็น "วัตถุ" ในออบเจ็กต์จะประกอบไปด้วยสิ่งที่เรียกว่า "พรอเพอร์ตี" (property) หรือในบางภาษาอาจเรียกว่า "แอตทริบิวต์" (attribute) แต่ในจาวาสคริปต์มักใช้คำว่าพรอเพอร์ตี ที่จริงมักจะหมายถึงสิ่งเดียวกันหรือใกล้เคียงกัน

ซึ่งเวลาเข้าถึงข้อมูลในพรอเพอร์ตีจะเขียนในรูป .ชื่อพรอเพอร์ตี แบบนี้

ออบเจ็กต์ในจาวาสคริปต์นั้นจะเห็นว่าการเข้าถึงพรอเพอร์ตีสามารถเขียนในรูป ออบเจ็กต์["คีย์"] แบบนี้ได้ จึงมีคุณสมบัติเหมือนเป็นแถวลำดับแบบจับคู่ไปด้วย

ในขณะเดียวกัน ก็สามารถเข้าถึงข้อมูลได้โดยใช้ ออบเจ็กต์.ชื่อพรอเพอร์ตี ได้เหมือนออบเจ็กต์ในภาษาอื่น จึงมีคุณสมบัติเหมือนเป็นออบเจ็กต์ในภาษาอื่นไปด้วย

ดังนั้น ออบเจ็กต์ในจาวาสคริปต์นั้นเป็นทั้งออบเจ็กต์และเป็นทั้งแถวลำดับแบบจับคู่ไปในตัว

ในขณะที่ในภาษาอื่นเช่นไพธอนหรือรูบีนั้น ออบเจ็กต์ กับ แถวลำดับแบบจับคู่ (ในไพธอนเรียก "ดิกชันนารี" ในรูบีเรียก "แฮช") ถือเป็นคนละสิ่งกัน แม้จะมีส่วนคล้าย แต่อะไรหลายอย่างแตกต่างกันมาก

การรวมเอาออบเจ็กต์กับแถวลำดับแบบจับคู่ไว้เป็นสิ่งเดียวกันแบบนี้จึงอาจถือว่าเป็นลักษณะเฉพาะที่สำคัญอย่างหนึ่งของจาวาสคริปต์




-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
python
-- numpy
-- matplotlib

-- pandas
-- pytorch
maya
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
javascript
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
เรียนภาษาจีน
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

เรียนรู้วิธีการใช้ regular expression (regex)
หลักการเขียนทับศัพท์ภาษาจีนกวางตุ้ง
การใช้ unix shell เบื้องต้น ใน linux และ mac
หลักการเขียนทับศัพท์ภาษาจีนกลาง
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
บันทึกการเที่ยวสวีเดน 1-12 พ.ค. 2014
แนะนำองค์การวิจัยและพัฒนาการสำรวจอวกาศญี่ปุ่น (JAXA)
เล่าประสบการณ์ค่ายอบรมวิชาการทางดาราศาสตร์โดยโซวเคนได 10 - 16 พ.ย. 2013
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?

ไทย

日本語

中文