JS-102 พื้นฐาน JavaScript ที่..คุณ..อาจไม่เคยรู้

credit: http://boingboing.net/2015/11/18/the-web-is-pretty-great-with-j.html

สวัสดีคร๊าบทุกๆท่าน มาต่อกันกับบทที่ 2 สำหรับพื้นฐาน JavaScript ครับ จริงๆแล้วบทความนี้มันก็สืบเนื่องมาจากโปรเจค Training Challenge ที่ช่วงนี้อยู่ใน Week ที่ต้องสอนน้องมายด์ปรับพื้นฐาน JavaScript ขนานใหญ่ เลยถือโอกาสปั้นบทความนี้ขึ้นมาประกอบการสอนไปด้วยเลย แต่สำหรับใครที่ยังไม่ได้อ่านบทแรกก็ไปอ่านได้ที่ “JS-101 พื้นฐาน JavaScript ที่..ต้องห้าม..พลาด

เนื้อหาในบทความนี้ถูกแปลจากบทความ “Explaining Value vs. Reference in Javascript” และอาจมีการเรียบเรียงคำใหม่หรือเพิ่มเนื้อหาบางส่วนให้เข้าใจง่ายมากขึ้นครับผม ดังนั้นใครอยากอ่านต้นฉบับเต็มๆก็กดที่ลิงก์ด้านบนได้เลยครับ

“ข้อมูลเป็นการอธิบายให้เข้าใจภาพรวมโดยง่ายครับ อาจจะไม่ใช่สิ่งที่เกิดขึ้นเบื้องหลัง 100% (เรื่อง passed by value และ Primitive Type) อ่านเพิ่มเติม

JavaScript Data Types

มาพูดถึงเรื่อง Types ใน JS กันก่อนครับ โดยปกติแล้วเราสามารถแบ่ง Data Type เป็นหมวดหมู่ใหญ่ๆได้ 2 ประเภทคือ Primitive Types และ Object

Primitives

Data Type ที่จัดอยู่ในหมวดนี้จะมีการส่งค่าแบบ “passed by value” ซึ่งมี Data Types อยู่ 5 ตัวที่จัดอยู่ในหมวดหมู่นี้ ได้แก่

  • Boolean
  • String
  • Number
  • null
  • undefined

มาลองดูตัวอย่างง่ายๆของการ “passed by value” กันครับ ก่อนอื่นลองประกาศตัวแปรแบบง่ายๆ ดังนี้

var x = 10;
var y = 'abc';
var z = null;

จากนั้น เรามาลองงดูกันดีกว่าครับว่าถ้าของตัวแปรทั้ง 3 ตัวนี้ถูกจัดเก็บใน Memory แล้วมันจะมีหน้าตาอย่างไรกันนะ ?

credit: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0

ทีนี้ถ้าเราลองเปลี่ยนค่าตัวแปรนึงให้มีค่าเท่ากับตัวแปรอีกตัวนึงดู โดยใช้เครื่องหมาย “=” เพื่อ “copy” ค่าของมัน ดังนี้

var x = 10;
var y = 'abc';
var a = x;
var b = y;
console.log(x, y, a, b); // -> 10, 'abc', 10, 'abc'

ตอนนี้ทั้งตัวแปร a และ x จะมีค่าเท่ากับ 10 และทั้งตัวแปร b และ y จะมีค่าเท่ากับ ‘abc’ เช่นกัน โดยทั้งคู่ a, x และ b, y นั้นจะมีค่าที่ตัวเองเก็บไว้ของใครของมัน และไม่มีการอ้างอิงถึงกัน ซึ่งเราเรียกว่าเป็นการ “copy by value” หรือ “passed by value” นั่นเองครับ

credit: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0

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

var x = 10;
var y = 'abc';
var a = x;
var b = y;
a = 5;
b = 'def';
console.log(x, y, a, b); // -> 10, 'abc', 5, 'def'

Objects (Reference Types)

มาต่อกันที่หมวดหมู่ Object สำหรับ Data Types ที่จัดอยู่ในหมวดนี้จะมีการส่งค่าแบบ “passed by reference” ซึ่งมี Data Types อยู่ 3 ตัวที่จัดอยู่ในหมวดหมู่นี้ ได้แก่

  • Function
  • Array
  • Object

“ตัวแปรใดๆที่ถูกกำหนดค่า และค่านั้นไม่ใช่ Primitive Type ตัวแปรนั้นๆจะเก็บ reference แทนค่าที่กำหนด ซึ่ง reference นั้นจะชี้ไปที่ memory ที่เก็บข้อมูลนั้นอยู่ หมายความว่าจริงๆแล้วตัวแปรจะไม่เก็บค่าไว้ที่ตัวเองแต่จะเก็บ memory address ที่เก็บค่านั้นๆอยู่อีกทีนึง นี่แหละครับการ ‘passed by reference’”

อาจจะงงๆนิดนึง มาลองดูตัวอย่างกันซักนิด ถ้าเราสร้างตัวแปรแบบ object type ตัว object ก็จะถูกสร้างไว้ ณ จุดหนึ่งใน memory ของเครื่องเรา หมายความว่าเมื่อเราประกาศ var arr = [] หมายความว่าเราได้สร้าง array เก็บไว้ใน memory และสิ่งที่ตัวแปร arr ได้รับคือ address ที่บอกตำแหน่งของ array นั้น

1) var arr = [];
2) arr.push(1);

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

จำเอาไว้ว่าค่า address ที่ตัวแปร arr ถืออยู่มีค่าเป็น static ไม่เปลี่ยนแปลง แต่สิ่งที่เปลี่ยนคือค่า array ใน memory ต่างหาก หมายความว่าสิ่งที่เกิดขึ้นเมื่อเราสั่งให้ push ค่า 1 เข้าไปในตัวแปร arr คือ JavaScript Engine จะวิ่งเข้าไปใน memory ตำแหน่งตาม address ที่ตัวแปร arr ถืออยู่ และจัดการกับข้อมูลที่อยู่ภายในนั้นนั่นเอง

การกำหนดค่าแบบ Reference

ทีนี้มาลอง Copy ค่าจากตัวแปรนึงไปอีกตัวนึงด้วยเครื่องหมาย “=” ตามตัวอย่างเดิมกันดีกว่าครับว่ามันจะให้ผลยังไง

var reference = [1];
var refCopy = reference;

หลายๆคนอาจจะคิดว่ามัน Copy ค่า [1] จาก reference ไปใส่ใน refCopy แต่… ผิดครับ สิ่งที่เกิดขึ้นคือ memory address ที่ reference ถืออยู่จะถูก copy ไปเก็บไว้ใน refCopy ครับผมซึ่ง address นั้นจะชี้ไปที่ memory เดียวกันที่เก็บ [1] อยู่นั่นเอง

credit: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0

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

reference.push(2);
console.log(reference, refCopy); // -> [1, 2], [1, 2]

จากจุดนี้หลายๆคนอาจจะร้อง “อ๋อออออ” คำใหญ่ๆกันมาแล้วใช่มั้ยครับ ผมเชื่อว่ามือเขียน JS หลายๆคนน่าจะเคยเจอเหตการณ์ประเภทนี้ไม่มากก็น้อยแน่นอนครับผม แต่อาจจะยังไม่เข้าใจมันอย่างจริงจัง วันนี้คุณก็ได้รู้แล้วว่ามันเกิดอะไรขึ้นในกอไผ่ ผ่ามม พามม

การกำหนดค่าทับของเดิม

ต่อไปเรามาลองดูตัวอย่างที่มี type เป็น object จริงๆกันบ้างดีกว่าครับ ก่อนอื่นลองประกาศตัวแปร object ง่ายๆกันก่อน

var obj = { first: 'reference' };

และสิ่งที่เกิดขึ้นใน memory ก็คือ

credit: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0

ทีนี้เรามาลองกำหนดค่าทับตัวแปรเดิมดูก็จะได้ว่า

var obj = { first: 'reference' };
obj = { second: 'ref2' }

ใครรู้บ้างครับว่าเกิดอะไรขึ้นกับคำสั่งนี้ ???

credit: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0

สิ่งที่เกิดขึ้นก็คือ memory address เดิมที่เก็บอยู่ในตัวแปร obj ถูกแทนที่ด้วย address ใหม่นั่นเองครับ และเมื่อไม่มีตัวแปรใดๆ reference ไปถึง object หรือค่านั้นๆใน memory แล้วก็จะเป็นหน้าที่ของ js garbage collector ในการจัดการมันนั่นเองครับผมจากตัวอย่างนี้ garbage collector ก็จะทำการกวาด object { first: ‘reference’ } ออกไปจาก memory นั่นเองครับ

เทคนิคและข้อควรรู้

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

การเปรียบเทียบ Reference Types ด้วย == และ ===

var arrRef = [’Hi!’];
var arrRef2 = arrRef;
console.log(arrRef === arrRef2); // -> true

จาก code ตัวอย่างด้านบนหลายๆคนคงเฉยๆ เออแล้วยังไง ?? ทีนี้แล้วมาลองดูอันนี้

var arr1 = ['Hi!'];
var arr2 = ['Hi!'];
console.log(arr1 === arr2); // -> false

อ่าววว เห้ย !!! ไม่เหมือนที่คุยกันไว้นี่หว่า… ถ้าคนที่อ่านและเข้าใจที่ผมอธิบายทั้งหมดมาอย่างละเอียดจะไม่งงกับตัวอย่าง code ด้านบนเลย

555555+ ไม่เป็นไรครับเรามาลองทวนกันอีกซักรอบ การประกาศตัวแปรประเภท Object หรือ Reference Type นั้นตัวแปรจะไม่ได้เก็บค่าตรงๆ แต่จะเก็บ address ที่เก็บค่านั้นๆอยู่ นั่นหมายความว่า code ชุดแรกมีการเก็บ address เดียวกันเมื่อเปรียบเทียบด้วย === แล้วผลจึงออกมาเป็น true

สำหรับ code ชุดที่สองนั้นก็เหตุผลเดียวกันเลยครับ คือตัวแปร arr1 และ arr2 เก็บ address คนละตัวกันแม้ว่าค่าใน address ทั้งสองตัวนั้นจะเหมือนกัน แต่ตอนเปรียบเทียบเราเทียบค่าที่เก็บในตัวแปร ไม่ใช่ค่าใน memory ดังนั้นผลจึงออกมาเป็น false

แล้วถ้าเราจะเทียบค่า array โดยต้องการเทียบค่าใน memory ท่าง่ายๆควรทำอย่างไร เอ้าาา ก็แถมให้แล้วกันครับผม

var arr1str = JSON.stringify(arr1);
var arr2str = JSON.stringify(arr2);
console.log(arr1str === arr2str); // true

การส่งค่าผ่าน Function

ไม่มีอะไรมากคิดง่ายๆว่าเวลาเราผ่านค่าตัวแปรประเภท Primitive Type เข้าฟังก์ชั่นๆนั้นจะทำการ Copy ค่าที่เข้ามาทันทีซึ่งมันจะมีผลเหมือนการใช้เครื่องหมาย “=” อย่างที่เราเขียนกันในตอนต้นนั่นเองครับ

var hundred = 100;
var two = 2;
function multiply(x, y) {
// PAUSE
return x * y;
}
var twoHundred = multiply(hundred, two);

มาลองดูภาพใน memory อีกครั้งเพื่อตอกย้ำความเข้าใจของเรากันครับ

credit: https://codeburst.io/explaining-value-vs-reference-in-javascript-647a975e12a0

จากภาพนี้ถ้าใครยังงงอยู่ ผมจะอธิบายอีกครั้ง (ถ้ายังงงอยู่ไปครับไปเลื่อนกลับไปอ่านใหม่ซะ !!!!!) ตัวแปร hundred และ two จัดเก็บตัวแปรโดยตรงเพราะค่านั้นเป็น Primitive Type ส่วน multiply เก็บค่า memory address เพราะมีค่าเป็น object เป็น object นั่นเองส่วนค่า x และ y จะเกิดขึ้นเมื่อฟังก์ชั่นถูกเรียกใช้งาน ค่าที่ส่งผ่านเข้ามาก็จะถูก copy มาเก็บเอาไว้ตามภาพครับผม

ทริคเพิ่มเติม

ถ้าเราต้องการ copy array หรือ object โดยไม่อยากจะให้มัน reference ถึงกัน จะทำยังไงดี ?? จัดไปชุดใหญ่ไฟกระพริบ

var person = { name: 'noomerzx', age: 14 }var newPersonObj = JSON.parse(JSON.stringify(person));

ข้อควรระวังคือหลายๆคนอาจจะใช้คำสั่ง copy object หรือ array เช่น object.assign หรือ […arr] ที่มาใหม่ๆใน ES6 ที่ทำให้ดู cool แต่บางทีอาจจะทำให้หัวระเบิดได้ในภายหลังถ้าไม่เข้าใจว่ามันทำงานยังไง ลองดูตัวอย่าง code กันหน่อย

var person = { name: 'noomerzx', age: 14, parents: ['mom', 'dad'] }var newPersonObj = JSON.parse(JSON.stringify(person));

การ copy ด้วยคำสั่งนี้มันไม่ใช่การ copy ข้อมูลแบบลึกุสด เพราะบางที object หรือ array นั้นๆ (ในที่นี้คือ person) ยังมี object, array เป็น sub data อยู่อีก ซึ่งมันจะยังคง reference ถึงกันอยู่ครับ (parents) ฉะนั้นวิธีที่ง่ายที่สุดที่จะ copy object แบบลึกไปจนสุดทุกๆข้อมูลก็คือใช้ JSON.parse(JSON.stringify(person)) นั่นเองครับผม

มีข้อควรระวังนิดนึงสำหรับวิธีการนี้คือสำหรับ object บางประเภทเช่น date นั้นการ convert กลับมาจะมี type เป็น string แทนที่จะเป็น object date ครับ แล้วเราอาจจะต้องจัดการกับ date อีกทีหนึ่ง (ขอบคุณ คุณ Anurat Eiamphoklarp สำหรับคำแนะนำครับ)

อ่านและดูตัวอย่างเพิ่มเติมเรื่อง deep clone object

โจทย์วัดกึ๋นทดสอบความเข้าใจ

หลังจากอ่านบทความกันมายาวเหยียด ใครอ่านจบก็ขอขอบคุณใจทุกๆท่านจริงๆครับ ผิดพลาดประการใดก็ติชมแนะนำกันได้เสมอครับผม

มาครับ มาลองทำโจทย์เล่นๆกันดู ว่าหลังจากอ่านและทำความเข้าใจกันมาขนาดนี้แล้ว เรามีความเข้าใจใน JavaScript กันเพิ่มมากขึ้นจริงๆหรือเปล่านะ !?

ข้อแรก

function changeAgeAndReference(person) {
person.age = 25;
person = {
name: 'John',
age: 50
};

return person;
}
var personObj1 = {
name: 'Alex',
age: 30
};
var personObj2 = changeAgeAndReference(personObj1);console.log(personObj1); // -> ?
console.log(personObj2); // -> ?

ข้อสอง

var personObj1 = {
name: 'Alex',
age: 30
};
var person = personObj1;
person.age = 25;
person = {
name: 'john',
age: 50
};
var personObj2 = person;console.log(personObj1); // -> ?
console.log(personObj2); // -> ?

ปล.เฉลยอยู่ใน comment นะครับผม

Reference

--

--

--

เรื่องเล่าประสบการณ์จากการคลุกคลานของทีมงานที่อยากจะเติบโตไปด้วยกัน

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
noomerZx

noomerZx

Software Engineer, Blogger, Runner

More from Medium

A beginners guide to JavaScript Callbacks and how to avoid the Callback Hell with Promises

Building UI using JavaScript Classes

Cart quantity increase or decrease using Javascript onclick function

Asynchronous JavaScript — Part 3…