มาสรุปสิ่งที่ได้จาก js21days challenge [week2]
หลังจากจบ week แรกแล้ว มาต่อ day 8 กันต่อเลยดีกว่า
ส่วนของ week แรก อยู่ที่นี่จ้า
การบ้านทั้งหมดจ้า
https://js21days-challenge.firebaseapp.com/
Day8: What is "this"?
this
หรือ context เป็น concept ที่สำคัญมากๆใน javascript เลยแหละ ที่ beginner ไปถึง advance
1) Lexical scope & Dynamic scope
ก่อนอื่นมาทดลองกัน โดยการ print this
ออกมาดูกันก่อนนะ
function printName() {
console.log(this);
}
printName();
ผลที่ได้ this
คือ window
ที่เป็น global object นั่นเอง ต่างจากภาษาอื่นๆคือ วิธีการที่เรียกใช้งาน ในที่นี้คือไม่มีอะไรนำหน้าการใช้งาน function ตัวนี้ เลยใส่ค่าเป็น window object ให้เอง
2) this
คืออะไรและทำงานอย่างไร
- Invoker object : เรียกใช้งาน function เป็น
object.function
const beeber = { name: 'Beeber', printName };
const jane = { name: 'Jane', printName };
beeber.printName();
jane.printName();
ในตัวอย่างนี้ สร้าง object มาสองตัวเนอะ ใน ES6 มันสามารถใส่ค่าของ field ลงไปได้เลยถ้าชื่อมันเหมือนกันงี้ ตามที่เราจดทันอะนะ การเรียกใช้งาน เอา object.function
เพื่อดูว่า this ที่ได้ คืออะไร
สรุป this
ไม่ได้ขึ้นว่าเขียนไว้ตรงไหน แต่ขึ้นว่าเราเรียกใช้มันอย่างไร และมีการเปลี่ยนค่าไปเรื่อยๆตามวิธีใช้
- Global object (window, global)
ประกาศตัวแปรตัวนึงแบบ global เพิ่มให้ print name ออกมา และเรียกใช้งานตามปกติ
function printName() {
console.log(this);
console.log(`My name is ${this.name}`)
}
name = 'Global';
printName();
ผลที่ได้นั้น object สองตัวแรกเหมือนเดิม เพิ่มเติมคือ print ชื่อออกมาด้วย ส่วนตัวแปร name
ที่เราเพิ่งประกาศไปเมื่อกี้ ถูก assign ให้เป็น property ของ global object หรือ window object อัตโนมัติ ดังนั้น
- Constructor function : function ธรรมดาที่สร้าง object จาก function นั้นได้ (ถ้าเราเขียนโปรแกรมกันมา จะพบเห็นได้บ่อย เป็นการ init ค่าของ object ตัวนั้นๆนั่นเอง)
ตั้งชื่อ function ตัวหน้าเป็นตัวใหญ่ เพื่อบอกว่า function ไหน สร้างเป็น object ได้
function Person(name) {
this.name = name;
this.printName = printName;
}
const joylada = new Person('Joylada');
joylada.printName();
this
ตอนนี้จะเป็น object Person
ที่ชื่อ joylada
แล้วนะ สรุปคือ ใช้ในการ set property ของ function ได้
3) set ค่า this
โดย 3 functions call()
, apply()
, and bind()
ก่อนอื่นเรามาประกาศเจ้า Person
ในรอบนี้ใส่ nationality
และ city
ร่วมด้วย แล้วสร้าง object ใหม่ จากนั้นใน printName()
ให้พิมพ์ของเพิ่มมาด้วย
เมื่อใช้ printName()
ใน Person
ซึ่งจริงๆอาจจะเป็น function อื่นๆหรือ 3rd-party library ที่มีอยู่แล้ว และไม่สามารถควบคุมได้ ดังนั้น this
ในที่นี้ น่าจะเป็น global เนอะ
function printName(nationality, city) {
console.log(this);
console.log(`My name is ${this.name}, I'm ${nationality} and am living in ${city}`);
}
function Person(name, nationality, city) {
this.name = name;
this.nationality = nationality;
this.city = city;
printName(this.nationality, this.city);
}
const beeber = new Person('Beeber', 'Thai', 'Bangkok');
ซึ่งผลที่ได้ก็ตรงกับที่เราเดาไว้เลย ส่วน name = 'Global'
ก็คือตัวที่เรา set ก่อนหน้านี้และยังไม่ได้ถูก refresh เลยยังไม่หายไปจ้า
ดังนั้น เราจะมา force ค่า this
ด้วย call
โดยใส่ไปใน Person
เพิ่ม โดย parameter แรกใส่ this
ส่วนสองตัวถัดมาเหมือนเดิม
printName.call(this, this.nationality, this.city);
เราจะได้ object this
เป็นเจ้า Person
ของ object ที่ชื่อว่า beeber
จากนั้นมาลองใช้ apply
ดู โดยวิธีเรียกจะเปลี่ยนไป โดย parameter แรกยังคงเหมือนใน call
แต่ parameter ตัวที่สองให้ใส่เป็น array ซึ่งข้างในคือสิ่งที่เราต้องการนั่นเอง
printName.apply(this, [this.nationality, this.city]);
ผลที่ได้จะเหมือนกับ call
เลย
สุดท้าย bind
การเรียกใช้จะต่างกับ call
และ apply
ตรงที่สองตัวนั้นจะเรียกใช้ function ทันที ส่วน bind
จะทำการ return function อันใหม่มาให้เรา แบบนี้ ซึ่งผลที่ได้ก็จะเหมือนกันนั่นแหละ
const printBeeber = printName.bind(this);
printBeeber(this.nationality, this.city);
Day9: Parallax Scrolling
เลื่อนลงมาแล้วโชว์ effect บางอย่าง เลื่อนให้เจอพระจันทร์และ text ซะ
- สร้าง
run()
ข้างในเพิ่ม event listener ของ document จับตอนที่เรา scroll
document.addEventListener('scroll', onScroll);
- สร้าง
onScroll()
เพื่อ get moon และ wish element และลองพิมพ์ค่าwindow.scrollY
จะพบว่า scroll ไปด้านล่าง ค่าจะเพิ่มขึ้นเรื่อยๆ ส่วนถ้า scroll ขึ้นไป ค่าจะลดลงเรื่อยๆนั่นเองจนเป็น 0 และนำค่านี้ไปคำนวณต่อ - ให้พระจันทร์เลื่อนไปบนขวา
moonElement.style.transform = `translate(${window.scrollY * 0.7}%, ${window.scrollY * -0.7}%`;
- และให้ text เลื่อนขึ้นลงไปอย่างเดียว (ในตัวอย่างคูณ - 1.2 แต่มันไม่สวยง่ะเลยเพิ่มไปหน่อยนึง)
wishElement.style.transform = `translateY(${window.scrollY * -1.5}%)`;
จบจ้า สวยๆ อย่างรวดเร็ว เราสามารถประยุกต์ใช้ได้ โดยตัดพวก background และอื่นๆไปทำแบบนี้ได้นะ
Day10: Kanban Board
บอร์ด todo-list ที่เราใช้ list ว่างานที่ต้องการทำ กำลังทำ และทำเสร็จแล้ว คืออะไร เช่นใน Trello นั่นเอง มี feature drag and drop เหมือนกันเลยด้วยยย
ก่อนอื่น ใน index.html
นั้น จะมี class แม่ที่ชื่อว่า title
และมีลูกหมูสามตัวคือ Todo
, Doing
และ Done
ใน TODO เขาก็จะเตรียม content ไว้ให้เราก่อนเนอะ มี class หลานที่ชื่อว่า drop-zone
คือของข้างในนี้เราสามารถ drag and drop ได้จ้า นั่นคือ task
นั่นเอง และมี attribute ที่ชื่อว่า draggable
มีค่าเป็น true
บอกว่าอันนี้ลากได้นะ
- เราจะดึง element ชื่อว่า
task
ทั้งหมด โดยdocument.querySelectorAll('.task')
แต่จะได้ผลเป็น node list ดังนั้นต้องใส่Array.from(document.querySelectorAll('.task'))
ให้ออกมาเป็น array list เพื่อเอาไปทำforEach
ให้กับtask
แต่ละตัวได้ - และดึง element ที่ชื่อว่า
drop-zone
ทำเหมือนtask
- เพิ่ม event listener ให้กับ
task
แต่ละตัว ให้สามารถลากการ์ดได้
taskElements.forEach((taskElement) => {
taskElement.addEventListener('dragstart', onDragStart);
});
และเจ้า onDragStart
ก็ save element ที่ถูก drag ไป
let dragingElement;
function onDragStart() {
dragingElement = this;
}
- และก็เพิ่ม event listener ให้กับเจ้า
drop-zone
dropZoneElements.forEach((dropZoneElement) => {
dropZoneElement.addEventListener('drop', onDrop);
});
ปัญหาใน HTML5 ก็คือ มันไม่ drop อ่ะ มันจะเกิด event drop ไม่ได้ ถ้าไม่ทำการ prevent default ใน event dragOver
และ dragEnter
dropZoneElements.forEach((dropZoneElement) => {
dropZoneElement.addEventListener('drop', onDrop);
dropZoneElement.addEventListener('dragover', onDragOver);
dropZoneElement.addEventListener('dragenter', onDragEnter);
});
event.preventDefault();
คือเป็นการ cancel behavior ปกติ ของ event นี้ออกไปนั่นเอง เมื่อใส่ไปแล้วทำให้เราสามารถ drop ได้หล่ะ
function onDragOver(event) {
event.preventDefault();
}
function onDragEnter(event) {
event.preventDefault();
}
- สุดท้าย drop แล้วไปไหน? add element ที่เราลากอยู่ ไปยัง column นั้นๆ เมื่อใส่เสร็จแล้ว reset ค่าให้เป็น null ซะ เป็นอันจบจ้าา
function onDrop() {
this.append(dragingElement);
dragingElement = null;
}
สรุป เราได้แค่ตัวลากวาง kanban board ยังเซฟเก็บและเพิ่มใหม่ไม่ได้ เดี๋ยวเราลองไปทำเพิ่มดีกว่าตอนว่างๆ
Day11: Text Reveal
ทำให้เว็บมีสีสันมากขึ้น เมื่อเลื่อนลงมา text จะค่อยๆเลื่อนขึ้นมาจากด้านล่าง
ในโปรเจกจะมี index.html
ที่รูปแล้วก็ text และก็ style.css
ที่ set การ display ต่างๆและก็สี โดย text มี opacity เป็น 0 และสุดท้าย start.js
มาลุยกันเลยดีกว่าาาา
- เพิ่ม event listener ในการ scroll
document.addEventListener('scroll'. onScroll);
- ต่อมาสร้าง
onScroll()
จ้า จะทำคล้ายตอนทำ Kanban Board ตรง เราดึง classsection
ออกมา และแปลงเป็น array เพื่อใช้คำสั่งforEach
ได้ - ใช้คำสั่ง
forEach
เพื่อดึง element ต่างๆภายในsection
ในที่นี้จะมีimg
กับtextElement
เนอะ - สร้างตัวแปร
const scrollPosition = window.pageYOffset;
เพราะเราจะดูว่าเลื่อนจอไปเท่าไหร่ - ตำแหน่งที่แสดง text คือ ค่าสูงสุดของรูปเรา
imageElement.offsetTop
บวกกับความสูงของรูปหารด้วย 10imageElement.offsetHeight / 10
ให้เป็นค่าของตัวแปรrevealPosition
- ใน
style.css
นั้น มี class 2 class ที่น่าสนใจ คือ.text
เป็นตัว default เนอะ ให้opacity
เป็น 0,transform
ให้อยู่ตํ่ากว่าที่ควรเป็น 25px,transition
เหมือน animation ค่อยๆเลื่อนขึ้นมา 0.4 วินาที และ class.reveal
คือให้opacity
เป็น 1 และtransform
ให้กลับมาอยู่ตำแหน่งปัจจุบันที่ควรจะเป็น - ถ้าเรา scroll ไปแล้วมากกว่าหรือเท่ากับ
revealPosition
ก็ให้แสดง text ขึ้นมา โดยให้textElement
เพิ่ม classreveal
เข้าไป เป็นอันจบtextElement.classList.add('reveal');
Day12: Air Quality Visualizer
สร้างแอพได้ดูฝุ่นแบบเรียลทามกัน แต่เข้าใจว่าไม่น่าวัดได้ในกรณีที่ คำว่ารักมันกลายเป็นฝุ่นไปแล้ว แน่นอนนน!!!
ในที่นี้จะใช้ API ของ AirVisual ที่เราใช้วัดค่าฝุ่น PM2.5 ในช่วงก่อนหน้านี้ว่า เอ๊ะ วันนี้ต้องใส่หน้ากากออกจากบ้านไหมนะ ก่อนที่จะ Work From Home อะนะ เอ้ออออ หมดไปกับค่าแมสก์ก็เยอะอยู่จ้า ฮือออออออ สามารถเข้าไปสมัครได้ทางนี้จ้า
แน่นอนว่าเราเลือก Community ซึ่งฟรี และเราคงใช้ไม่เกิน 10,000 call/month เนอะ
จากนั้นก็สร้าง key เนอะ และเอาไปใช้ต่อได้ ซึ่งอายุของ key ที่สร้างมีอายุประมาณ 1 ปีด้วยกันเนอะ
- มีการ set สีที่จะแสดง ใน
style.css
ที่:root
- ก้อป key ที่ได้เมื่อกี้จากการสร้าง key มาสร้างตัวแปร แล้วมาใส่เป็นตัวแปร
const
- สร้างตัวแปร
city
,state
,country
- จากนั้นสร้าง
getAirQuality({city, state, country})
แต่ function นี้พิเศษ ตรงใส่ parameter เป็น object ทำให้เราสามารถดึงค่า parameter มาใช้โดยไม่ต้องคำนึงถึงลำดับได้ ช่วยแก้ปัญหาเวลาเรา pass ค่าไปหลายๆตัวแปร เช่น 3 4 5 ตัว อาจจะทำให้เราเองงงได้ - ยิง API โดยใช้
fetch()
ซึ่ง parameter เป็น path ของ API นั้นๆ และใส่ parameter ต่างๆลงไปด้วย คือcity
,state
,country
และkey
- ใช้เจ้า
async
นำหน้า function และawait
นำหน้าตอนfetch()
เพื่อรอให้เรียก API เสร็จก่อนนั่นเอง response
ที่ได้ เราได้ 200 OK มาเนอะ แต่เรายังไม่ได้ data
- เราจึงต้องแปลงให้เป็น json ก่อน แน่นอนว่าต้องทำหลังจากที่เรียก API เสร็จแล้ว จึงใส่ await เข้าไป
const result = await response.json();
และนี่คือผลที่ได้จ้า
- ใช้ destructuring ใน ES6 เข้ามาช่วยดึงตัวแปรที่เราต้องการออกมา โดย destructuring ชั้นนอกเป็น
data
และทำอีกชั้นนึงเป็นcurrent
เพื่อดึงอาการ และค่าฝุ่นมาอีกรอบนึง และใช้ destructuring อีกชั้นนึง เพื่อดึงpollution
กับweather
- แล้วก็ return ค่า
aqi
,temperature
,humidity
,wind
ออกมา
const {aqi, temperature, humidity, wind} = await getAirQuality({city, state, country});
- จากนั้นเอาค่าต่างๆที่ได้นำไปแสดงในหน้าเว็บที่
displayAirQuality()
จะได้ดังนี้
- แต่ยังไม่จบ เอาค่า
aqi
มาเปลี่ยนสีพื้นหลังหน่อยว่าอากาศตอนนี้เป็นยังไง ตอนนี้ผลต่างกันไม่เยอะอะเนอะ แบบเปลี่ยนสีพื้นหลังงี้
เราเองก็เห็นหน้า document API ของ AirVisual เลยคิดว่า น่าจะทำอะไรได้มากกว่านี้ อาจจะให้ใส่ location ที่ต้องการเอง หรือเอาไปใช้ในแชทบอทแล้วทำ Flex Message สวยๆก็ได้เนอะ
Day13: JavaScript Weird Parts
รวมของแปลกๆจากภาษา JavaScript ที่ผลออกมา ไม่ตรงกับที่เราอยากได้
1) NaN : Not a Number
ตามปกติ ค่าใดค่าหนึ่งจะเท่ากับตัวมันเอง
if (null === null) {
console.log('Equal');
}
NaN
เป็น preventive type เป็นค่าพื้นฐานของ javascript เทียบเท่า undefined เทียบเท่ากับ null แต่ความพิเศษคือ ไม่เท่ากับตัวมันเอง อย่างโค้ดตัวอย่างด้านล่างก็คือจะได้ค่า false
เนอะ
if (NaN === NaN) {
console.log('Equal');
}
แล้วทำไมถึงไม่เท่ากับตัวมันเองอ่ะ? เพราะว่า เพื่อป้องกันการคำนวณที่ผิดพลาด ในเคสต่างๆเหล่านี้
NaN/NaN === 1
NaN * ` === NaN
แล้วเราจะ check ได้อย่างไรว่า ตัวแปรนั้น มีค่าเป็น NaN
หรือเปล่า?
เช่น ให้ result
เป็น 1 / 'hello'
ได้ผลเป็น NaN
เพราะไม่สามารถเอาไปหารแบบนี้ได้เนอะ การ check ให้ใช้ Number.isNaN(result)
const result = 1 / 'hello';
if (Number.isNaN(result)) {
console.log('Equal to Nan');
}
และนี่คือผลที่ได้
2) Type Coercion
Coercion แปลว่า การบังคับ ในที่นี้ก็คือการแปลง type อัตโนมัติโดย javascript นั่นเอง ตัวอย่าง
if (1 < 2 < 3) {
console.log('Inside');
}
อันนี้เราแอบงงๆว่ามัน check condition ซ้อนกันแบบนี้ได้ด้วยหรอ ฮ่าๆ เลยเดาผลไม่ออกเลยว่าเป็นอะไร เมื่อเอาไป run ดูผลว่าเป็น true
อ่ะ แล้วถ้าแบบนี้หล่ะ ผลจะออกมาเหมือนกันไหมนะ?
if (3 > 2 > 1) {
console.log('Inside');
}
สรุปผลออกมาไม่เหมือนกันอ่ะ เป็น false
ทำไมกันนะ?
3 > 2
ผลออกมาเป็น true
และ true > 1
ซึ่ง true === 1
ทำให้ผลออกมาเป็น false
แล้วคิดว่า 2 - '1'
ออกมาแล้วได้ผลอะไร เราเดาว่า 1 เพราะว่า มันแปลง String '1' เป็น 1 ซึ่งผลออกมาก็เป็น 1 จริงๆด้วย ซึ่งเราก็เดาถูกอีกล้าววว 555
แล้ว 2 + '1'
ผลออกมาเป็นอะไร เราเดานะ 21 เพราะมันมองเลข 2 เป็น String ไปแล้ว มันเลยต่อ String ให้เลย ซึ่งเราก็เดาถูกอีกเช่นเคย 555
แล้ว true + true
เดาไปในสองแนวทาง ไม่ออก true ก็พิมพ์ truetrue แล้วผลที่ได้ก็ครือออ 2 ห๊ะ เพราะว่ามันเห็นว่า true ทำการบวกกับอะไรสักอย่างอยู่ เลยแปลงตัวเองเป็น 1 นั่นเอง
สรุปเราต้องเข้าใจมัน + ถ้าไม่แน่ใจก็แปลง type ให้ถูกต้อง เช่น อยากให้บวกแล้วได้ผลเป็น 3 ก็แปลงจาก String เป็นเลขฐาน 10 ก่อน แบบนี้
2 + Number.parseInt('1', 10)
3) Interpreter & Compiler
คิดว่าผลลัพธ์ของสิ่งนี้คืออะไร ทางนี้เดาว่าน่าจะ return ออกมาเป็น object ก้อนนึง มั้งนะ
function getPerson() {
return {
name: 'Kotlin'
};
}
console.log(getPerson());
จริงๆคำตอบคือได้ undefined มาอ่ะ แต่เราได้ object มา งงเลย
กลับไปดูตัวอย่าง พบว่าคุณปีกกาไม่เหมือนกันจ้า
function getPerson() {
return
{
name: 'Kotlin'
};
}
console.log(getPerson());
นั่นแหละได้ผลตามตัวอย่างแล้ว เพราะอะไรกันหล่ะ?
เพราะว่า การที่เรา return
แล้วขึ้นบรรทัดใหม่ มันจะมองเป็น return;
ที่มี ;
เข้ามาให้เราเองเลย ทำให้สิ่งที่อยู่มนปีกกาไม่ถูกทำ เพราะคิดว่าทำจบแล้ว
ดูจาก VS Code พบว่าสีของ attribute name
คนละสีอยู่เนอะ
สรุปอย่าลืมใส่ ;
ในการเขียน javascript ด้วยเนอะ ถึงบางทีมันจะแอบใส่ให้เราก็ตาม เพื่อการทำงานที่ถูกต้องจ้า
ปล. แรกๆที่เรียนมีเผลอไม่ใส่ ด้วยสาเหตุง่ายๆ คือ เราเป็น Android Kotlin Developer หล่ะสิ ;) //ง่ายหรือโบ้ยย ตอบบบ!!!
4) Checking Object Type
สมมุติเราทำการ check type แบบนี้ แน่นอนเนอะว่าผลมันออกมาเป็น object
อยู่แล้ว
const person = {};
if (typeof person === 'object') {
console.log('Yes, object')
}
ถ้าเราเปลี่ยนค่า person
เป็น null
หล่ะ ผลออกมาเป็นอะไร นี่เดาแบบมั่วว่า เป็น object
เนอะ ไม่รู้ทำไมเหมือนกัน 555555 จริงๆหลายๆคนคิดว่ามันน่าจะออกมาเป็น false
สิ เพราะอะไรหล่ะ? มันน่าจะมองเป็น object
ที่เป็นค่า null
ดังนั้นต้อง check null
เพิ่มด้วย ดังนี้
const person = null;
if (typeof person === 'object' && person != null) {
console.log('Yes, object')
}
Day14: Carousel
คลิกเพื่อหมุนรูปไปเรื่อยๆ เป็น image slider
- ใน
index.html
นั้น มี carousel ซึ่งไส้ในมีรูป แล้วก็ปุ่มกดอยู่ด้านนอก - มาเริ่มเขียนโค้ดกันโดยการดึง element รูป, ปุ่ม previous, และปุ่ม start
- สร้างตัวแปรเอาไว้ track ว่าเราเลื่อนดูรูป index ที่เท่าไหร่
- เพิ่ม event ให้กับปุ่มกด
previousButtonElement.addEventListener('click', () => displayImage(imageElements, currentIndex-1));
แล้วผลที่ได้ มัน error ทางเราเดาก่อนว่า ถ้าให้ index แรกเป็น 0 ถ้าขยับซ้ายไป index -1 ก็น่าจะพังแหละจ้า ซึ่งทางเราก็เดาถูกต้องนะฮ๊าบบบ
- เราต้องเพิ่ม condition ว่า ถ้าได้ index มาน้อยกว่า 0 แล้วน้านนน ให้แสดงรูปสุดท้ายในชุดนี้ มันจะมีความมูฟออนเป็นวงกลม ซึ่งถูกแล้วเนอะในที่นี้ แต่เอ้ยย มันมีความกระโดดปนหน่วงๆแหะ และอย่าลืม update
currentImage
ด้วยน้าาา - ส่วนปุ่ม
next
ทำเหมือนกัน แต่ +1 นะจ๊ะ
nextButtonElement.addEventListener('click', () => displayImage(imageElements, currentIndex+1));
แต่จะเจอเรื่อง index เกินจ้า ดังนั้นเพิ่ม condition ให้เขาหน่อย ขอสรุปโค้ดรวบตึงได้ที่นี่เลยแล้วกัน
function displayImage(imageElements, newIndex) {
const lastIndex = imageElements.length - 1;
if (newIndex < 0) {
newIndex = lastIndex;
} else if (newIndex > lastIndex) {
newIndex = 0;
}
const newImage = imageElements[newIndex];
newImage.scrollIntoView({ behavior: 'smooth'});
currentIndex = newIndex;
}
จบไปล้าว 14 วันด้วยกัน ยังเหลือโค้งสุดท้ายอีก 7 วัน บวกมีเซอร์ไพร์สด้วย มาติดตามกันจ้า
7 วันถัดไปจ้า