ปี2020, จริงๆ เราไม่ต้องใช้ jQuery แล้วก็ได้นะ
Tags: javascript, jquery, programming, tutorial
Published: 2020-10-18 16:40:48

jQuery เป็นหนึ่งใน JavaScript Library ที่โด่งดังมาก (เมื่อ 10 ปีที่แล้ว) เรียกว่าในยุคนั้นแทบจะทุกเว็บจะต้องมีการติดตั้ง jQuery เอาไว้อย่างแน่นอน

แต่เมื่อยุคสมัยเปลี่ยนไป เบราเซอร์ใหม่ๆ ไม่มีปัญหาการรัน JavaScript ตั้งแต่ ES6 ขึ้นไปแล้ว การใช้งาน jQuery จึงลดน้อยลงเรื่อยๆ 

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

ในบทความนี้ก็เลยลองเอาคำสั่ง jQuery ที่ใช้บ่อยๆ มาลองเทียบดู ว่าถ้าอยากเขียนแบบนี้โดยไม่ใช้ jQuery เราจะต้องทำยังไง

## Ajax

ในยุค jQuery กำลังรุ่งเรือง การเรียกข้อมูลจาก API ด้วย HTTP Request หรือที่ในยุคนั้นนิยมเรียกว่า **Ajax** ถ้าเขียนด้วย JavaScript แบบธรรมดาจะยากมาก

การที่ jQuery มีฟังก์ชันสำหรับเรียกใช้ Ajax ง่ายๆ ด้วยเป็นหนึ่งในเหตุผลที่ทำให้มันได้รับความนิยม ถึงขนาดว่าบางครั้งโหลด jQuery ติดเข้าไปเพราะต้องการใช้ฟังก์ชัน `ajax()` แค่นั้นเลยก็มีนะ

```js
$.ajax({
  method: 'GET',
  url: '/api/data',
  data: {
    page: 1,
  },
  dataType: 'json',
  success: function(data){
    console.log('Success:', data)
  },
  error: function(error){
    console.error('Error:', error)
  },
})
```

แต่ข้อเสียของ `jQuery.ajax()` ก็คือตอนนี้ฝั่ง vanilla มีทั้ง `fetch()` หรือไลบรารี่อย่าง `Axios` ให้ใช้งาน ซึ่งทั้งสองวิธีนี้ถูกสร้างมาด้วย JavaScript ยุคใหม่โดยใช้ **Promise** แล้ว ไม่เหมือนกับ jQuery ที่ยังใช้สไตล์ callback function อยู่เลย

```js
//fetch API
fetch('/api/data', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    page: 1,
  }),
})
.then(response => response.json())
.then(data => {
  console.log('Success:', data)
})
.catch((error) => {
  console.error('Error:', error)
})

//Axios
axios.get('/api/data', {
  params: {
    pafe: 1
  }
})
.then(function (response) {
  console.log('Success:', data)
})
.catch(function (error) {
  console.error('Error:', error)
})
```

## Query Elements

### First Match Element

หา element ตัวแรก

```js
//jQuery
$('.ele').first()
$('.ele:first')

//Vanilla
document.querySelector('.box')
```

### All Elements

หา element ทุกตัวที่ตรงกับ selector

```js
//jQuery
$('.ele')

//Vanilla
document.querySelectorAll('.box')
```

> สำหรับ jQuery แล้วการหา element แค่ 1 ตัวหรือหาทั้งหมด ผลที่ได้ไม่ต่างกันนั้นคืออ็อบเจคของ jQuery แต่สำหรับวนิลาแล้ว `querySelector` จะให้ค่ามาเป็น Element ส่วน `querySelectorAll` จะให้ค่าเป็น **Array of Elements** นะ

### Nested Element

การหา element ภายใน element อีกทีหนึ่ง

```js
//jQuery
let container = $('#container')
container.find('.box')

//Vanilla
let container = document.querySelector('.container')
container.querySelector('.box')
```

### Action on Elements

สำหรับการสั่ง action อะไรบางอย่างกับ element ที่ได้มา ถ้าเลือกออกมาแค่ 1 element อันนี้ไม่ยาก

```js
//jQuery
$(".box").first().doSomething()

//Vanilla
document.querySelector(".box").doSomething()
```

ปัญหาจะเกิดขึ้นเมื่อเรา select all กับ element ทุกตัว ซึ่งเรื่องนี้น่าจะง่ายกว่าถ้าเราใช้ jQuery เพราะการสั่ง action ของ jQuery นั้นจะวนลูปทำให้กับ element ทุกตัวอยู่แล้ว

```js
//jQuery
$(".box").doSomething()

//Vanilla
document.querySelectorAll(".box").forEach(box => { 
    /* doSomething */ 
})
```

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

เช่นสมมุติว่าเราอยาก hide ทุกอย่างที่มีคลาส **box** ถ้าเป็น jQuery ก็สั่งง่ายๆ เลยคือ `.hide()` แต่ถ้าเป็นวนิลาก็วนลูปยาวไป แล้วก็กำหนดที่ style ให้ display=none แทน

```js
//jQuery
$(".box").hide()

//Vanilla
document.querySelectorAll(".box").forEach(box => { 
    box.style.display = "none" 
})
```


## DOM Traversal

การวิ่งขึ้นๆ ลงๆ ใน DOM (โครงสร้างชั้นของ HTML ที่อยู่ในรูปแบบของ Tree) อันนี้ไม่ต่างกันมากเท่าไหร่ แค่คำสั่งของ jQuery จะสั้น/เขียนกระชับกว่านิดหน่อย

```js
//jQuery
box.next()
box.prev()
box.parent()
box.children()
box.siblings()

//Vanilla
box.nextElementSibling
box.previousElementSibling
box.parentElement
box.children
box.parentNode.children.filter(el => el !== box) 
```

ตัวที่วนิลาไม่มีคือ `siblings()` ซึ่งเป็นคำสั่งเอาไว้เลือก Element ที่อยู่เลเวลเดียวกับเราทั้งหมด (แต่ไม่รวม Element ตัวเอง) ดังนั้นเลยต้องประยุกต์นิดหน่อยโดยวิ่งขึ้นไปที่ parent แล้วเลือก child ทุกตัวที่ไม่ใช่ตัวมันเองแทน

## Event Handling

การกำหนด Event Listener แทบจะไม่ต่างกันแล้ว เพราะตอนหลังวนิลาก็คำสั่ง `addEventListener` ให้ใช้งานแล้ว

```js
//jQuery
ele.click(function(event){})
ele.on('click', function(event){})
ele.on('mouseenter', function(event){})
ele.on('keyup', function(event){})
ele.off('click', function(event){})

//Vanilla
ele.addEventListener('click', (e) => {})
ele.addEventListener('mouseenter', (e) => {})
ele.addEventListener('keyup', (e) => {})
ele.removeEventListener('click', (e) => {})
```

### Delegate

การดีลีเกตคือการกำหนด Event Listener ที่ Element ชั้นนอกแทนที่จะกำหนดที่ตัวมันเองตามปกติ

เช่นหากเราต้องการสร้าง Listener ที่ทำงานเมื่อกด `<li>` ในโครงสร้างแบบนี้

```html
<ul>
    <li></li>
    <li></li>
    <li></li>
</ul>
```

เราก็ไปกำหนดให้ Listener อยู่ที่ `<ul>` แทน

สาเหตุที่ต้องทำแบบนี้มักจะเกิดกับกรณีที่ `<li>` นั้นอาจจะโดนสร้างแบบ dynamic โดย script ส่วนอื่น ทำให้อาจจะมี li เกิดขึ้นมาใหม่ได้เรื่อยๆ การไล่สั่งให้มัน Listener ทีละครั้งๆ ไปเป็นอะไรที่จัดการยากกมา เลยไปสั่งที่ ul แทน (แล้วพอเกิด Event ก็ให้ ul ส่งไปบอก li ภายในอีกที)

สำหรับวนิลาไม่มีคำสั่งให้ทำดีลีเกตได้เหมือน jQuery ดังนั้นก็ต้องประยุกต์เอาอีกแล้ว (ฮา)

```js
//jQuery
container.on('click', '.box', function(event){
    //...
})

//Vanilla
container.addEventListener('click', function(event){
  if(event.target.matches('.box')){
      //...
  }
})
```

### Create Event

อันนี้เป็นการสร้าง Event ขึ้นมาเอง .. ไม่ต่างกันมากนัก
```js
//jQuery
ele.trigger('click')

//Vanilla
ele.dispatchEvent(new Event('click'))
```

## Styling

การเซ็ต CSS ให้กับ Element 

ของ jQuery จะใช้คำสั่ง `.css()` แต่วนิลาสามารถเซ็ตลงไปได้ผ่าน properties ที่ชื่อว่า `.style`
```js
//jQuery
ele.css('color', '#000')
ele.css({
  'color': '#000',
  'background': '#FFF',
})

//Vanilla
ele.style.color = '#000'
ele.style.background = '#FFF'
ele.style.cssText = 'color: #000, background: #FFF'
```
บางครั้ง jQuery ก็มีฟังก์ชันที่จริงๆ เบื้องหลักก็ไปเซ็ตค่า css นั่นแหละแบบนี้ด้วยนะ
```js
//jQuery
box.hide()
box.show()

//Vanilla
box.style.display = 'none'
box.style.display = 'block'
```

## Document Ready

ในการรันสคริป บางทีเราต้องการจะให้หน้าเพจโหลดเสร็จซะก่อน สำหรับ jQuery นั้นเราใช้คำสั่ง `ready()` หรือส่งฟังก์ชันเข้าไปเลยก็ได้ โค้ดส่วนนี้จะทำงานเหมือน document ทั้งหมดโหลดเสร็จแล้ว

สำหรับ vanilla นั้นไม่มีคำสั่งสำเร็จขนาดนั้น แต่ถ้าอยากได้ก็เขียนได้ ... แต่เอาจริงๆ ส่วนใหญ่เราจะใช้วิธีเรียกฟังก์ชันที่ท้ายๆ ของหน้าเพจมากกว่า

```js
//jQuery
$(document).ready(function(){ 
  /*...*/ 
})

$(function(){ 
  /*...*/ 
})

//Vanilla
(function(callback){
  if (document.readyState != "loading") callback()
  else document.addEventListener("DOMContentLoaded", callback)
})(function(){
   /*...*/
})
```

### Class Properties

สำหรับสำหรับจัดการ `class` ซึ่งตอนนี้ฝั่ง vanilla ก็ทำได้ทั้งหมดเหมือน jQuery แล้วผ่าน properties ชื่อ `classList`
```js
//jQuery
box.addClass('active focus')
box.removeClass('active')
box.toggleClass('active')
box.hasClass('active')

//Vanilla
box.classList.add('active', 'focus')
box.classList.remove('active')
box.classList.toggle('active')
box.classList.replace('active', 'blur')
box.classList.contains('active')
```

### Create Virtual Element

Virtual Element เป็นการสร้าง Element HTML ขึ้นมาในฝั่ง JavaScript ซึ่งสามารถเอาไปปะให้แสดงผลใน document ได้ ซึ่งหลายๆ เฟรมเวิร์คตอนนี้ก็ใช้วิธีนี้แหละ ในการควบคุมการทำงานระหว่างสคริปกับ HTML

สำหรับ jQuery การสร้าง Virtual Element แค่เติม `<>` ครอบชื่อแท็กลงไปก็พอแล้ว ซึ่งเราจะได้ element ออกมา สามารถเอาไปใช้งานได้เลย

ทางฝั่ง vanilla ตอนนี้วิธีสร้าง Virtual Element ก็ไม่ยากล่ะ เพราะมีคำสั่ง `createElement` ให้ใช้

```js
//jQuery
let div = $('<div>')
div.text('Hello World!')
div.html('Hello World!')

//Vanilla
let div = document.createElement('div')
div.textContent = 'Hello World!'
div.innerHTML = 'Hello World!'
```

### DOM Manipulate

การจัดการ DOM อันนี้เราคิดว่าคำสั่งทางฝั่ง vanilla อ่านเข้าใจง่ายกว่า

คือแบ่งเป็น 2 เรื่อง การแก้ไข DOM **ภายใน** กับ **ภายนอก (replace)**

```js
//jQuery
el.replaceWith('x')
el.html('x')

//Vanilla
el.outerHTML = 'x'
el.innserHTML = 'x'
```

ส่วนการเพิ่ม element เข้าไป ก็มี 2 คำสั่งคือเพิ่มต่อท้าย กับเพิ่มแทรกเข้าไปเป็นตัวแรก ซึ่งฝั่ง vanilla นั้นไม่มีคำสั่งเพิ่มแบบแทรกเข้าไปเป็นตัวแรก ต้องหา child ตัวแรกให้ได้ก่อน แล้วใช้ `insertBefore` ต่ออีกที
```js
//jQuery
ul.append($('<li>'))
ul.prepend($('<li>'))

//Vanilla
ul.appendChild(document.createElement('li'))
ul.insertBefore(document.createElement('li'), parent.firstChild)
```
เรื่องการลบทิ้ง, การแทรกในตำแหน่งต่างๆ, และการ clone อันนี้ไม่ยากเท่าไหร่
```js
//jQuery
el.remove()

//Vanilla
el.parentNode.removeChild(el)
```


```js
//jQuery
$(target).after(element)
$(target).before(element)

//Vanilla
target.insertAdjacentElement('afterend', element)
target.insertAdjacentElement('beforebegin', element)
```



```js
//jQuery
$(el).clone()

//Vanilla
el.cloneNode(true)
```

### Attribute

เรื่องการจัดการ attribute ฝั่ง jQuery จะพิเศษหน่อยตรงมี `data()` ให้ใช้งานด้วย

```js
//jQuery
el.prop('data-x')
el.attr('data-x')
el.data('x')

//Vanilla
el['data-x']
```

```js
//jQuery
el.removeAttr('data-x')

//Vanilla
el.removeAttribute('data-x')
```

### Effect

สำหรับ jQuery นั้นมีฟังก์ชันที่ทำให้เราโชว์/ซ่อน element แบบมีอนิเมชันได้ด้วย เช่น `fadeOut`, `fadeIn`, `slideUp`, `slideDown` (ซึ่งเบื้องหลังเป็นการสั่งเปลี่ยนค่าพวกความสูงหรือ opacity ด้วยลูปเอานะ)

สำหรับการเขียนแบบใหม่ ตอนนี้เราไม่นิยมให้ JavaScript เป็นคนจัดการอนิเมชันแล้ว แต่โยนไปเป็นหน้าที่ของ css แทน เช่นการใช้ transition 

ฝั่ง JavaScript มีหน้าที่แค่ระบุคลาสของ css เข้าไปเท่านั้นพอ

```js
//jQuery
$(el).fadeOut()

//Vanilla
el.classList.add('hide')
el.classList.remove('show')
```
```css
/* With this CSS */
.show {
  opacity: 1;
}
.hide {
  opacity: 0;
  transition: opacity 400ms;
}
```

---

จริงๆ น่าจะยังมีอีกหลายคำสั่ง