ข้ามไปยังเนื้อหา

Properties vs Attributes

เมื่อเขียน <my-card count="5" label="hello"> ใน HTML browser จะเก็บค่าเหล่านั้นเป็น attribute nodes — และทุกค่าของ attribute คือ string เสมอ ไม่ว่าจะพิมพ์อะไรก็ตาม การเรียก element.getAttribute('count') จะคืนค่า string "5" ไม่ใช่ตัวเลข 5

const el = document.querySelector('my-card');
console.log(el.getAttribute('count')); // "5" (string)
console.log(el.getAttribute('label')); // "hello" (string)
console.log(el.getAttribute('missing')); // null (not present)

สิ่งสำคัญที่ต้องจำคือ getAttribute คืนค่าเป็น string หรือ null เท่านั้น — ไม่เคยเป็น boolean, number, array หรือ object

Properties คือ property ของ JavaScript object ปกติบน element instance สามารถเป็น JS type ใดก็ได้ ไม่ว่าจะเป็น boolean, number, array, object หรือ function เข้าถึงด้วย dot notation ไม่ใช่ getAttribute

el.count = 5; // number
el.rows = [{ id: 1 }]; // array
el.config = { theme: 'dark', size: 'lg' }; // object
el.disabled = true; // boolean

ค่า property เหล่านี้จะไม่ถูก serialize กลับเข้า DOM เว้นแต่ component จะทำเองอย่างชัดเจน property ที่กำหนดบน element instance อยู่ใน JavaScript memory เท่านั้น

Boolean attributes ใน HTML ทำงานโดย การมีอยู่หรือไม่มีอยู่ — ถ้า attribute นั้นอยู่บน element แสดงว่าเป็น true ถ้าไม่มีแสดงว่าเป็น false ค่าของ attribute string ไม่มีผลใดๆ

นั่นหมายความว่า <my-btn disabled=""> และ <my-btn disabled="false"> ต่างก็ ถูก disabled เพราะ attribute นั้นมีอยู่ มีเพียง <my-btn> (ไม่มี attribute เลย) เท่านั้นที่หมายถึงไม่ถูก disabled

อย่าเปรียบเทียบ getAttribute('disabled') === 'true' หรือ === true ให้ใช้ hasAttribute หรือตรวจสอบว่าค่าที่คืนมาเป็น null หรือไม่เสมอ

// Boolean attribute — check presence, not value
class MyButton extends HTMLElement {
get disabled() { return this.hasAttribute('disabled'); }
set disabled(v) { v ? this.setAttribute('disabled', '') : this.removeAttribute('disabled'); }
}

สังเกตว่า setter กำหนด attribute เป็น empty string '' เมื่อค่าเป็น truthy — นั่นคือ pattern ที่ถูกต้องตามแบบ HTML getter ไม่สนใจว่า string ที่เก็บอยู่คืออะไร แค่ถามว่า attribute นั้นมีอยู่หรือเปล่า?

เมื่อต้องการส่ง array, object หรือค่าที่ไม่ใช่ string เข้าไปใน component JS property setter คือเครื่องมือที่เหมาะสม การพยายาม serialize object เป็น attribute string (เช่น data="[object Object]") นั้นเปราะบางและสูญเสียข้อมูล

// Rich data — use a property, not an attribute
class DataTable extends HTMLElement {
set rows(data) { // data is an array of objects
this._rows = data;
this._render();
}
get rows() { return this._rows || []; }
_render() { /* ... */ }
}

จากนั้นใช้งานผ่าน JavaScript:

const table = document.querySelector('data-table');
table.rows = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];

setter รับ array จริงๆ — ไม่ต้อง parse เลย เมื่อข้อมูลเปลี่ยน แค่กำหนด property ใหม่และ _render จะทำงาน

element <user-card> ด้านล่างรวมทั้งสองเทคนิคเข้าด้วยกัน:

  • boolean attribute premium — เมื่อมีอยู่จะแสดง crown
  • property setter profile — รับ object { name, role } และ re-render

HTML กำหนด attribute premium ตรงใน markup JavaScript จากนั้นกำหนด el.profile = { name: 'Alice', role: 'Admin' } — เป็น plain object — ซึ่ง trigger property setter และ re-render card

สถานการณ์ใช้
ค่า config ที่เป็น string ทั่วไป (label, type, variant)Attribute
flag ชนิด Boolean (disabled, hidden, selected)Boolean attribute + hasAttribute
Number, object, array หรือ functionProperty setter
ค่าที่กำหนดจาก HTML markup เท่านั้นAttribute
ค่าที่กำหนดจาก JavaScriptได้ทั้งคู่ — แนะนำ property สำหรับค่าที่ไม่ใช่ string
ตัวเลือกBenefitCost
Attributeมองเห็นได้ตรงใน HTML markup และ DevTools ใช้เป็น CSS attribute selector ได้ รองรับ server-rendered HTML โดยไม่ต้องใช้ JavaScriptเก็บได้แค่ string เสมอ ส่งข้อมูลซับซ้อน เช่น array หรือ object เข้าไปตรงๆ ไม่ได้
Propertyรับค่า JS type ใดก็ได้ ไม่ว่าจะเป็น object, array หรือ function เข้าถึงเร็วกว่าผ่าน dot notationไม่ปรากฏใน HTML markup หรือแผง Elements ของ DevTools ทำให้ debug จาก markup อย่างเดียวยากขึ้น
  • เทียบ getAttribute('disabled') === 'true' — boolean attribute ทำงานด้วยการมีอยู่ ไม่ใช่ค่าของ string ต้องใช้ hasAttribute แทนเสมอ
  • ใช้ JSON.stringify() ยัด object เข้า attribute string เช่น data='{"a":1}' — attribute เก็บได้แค่ string เปราะบางและ parse ผิดพลาดง่าย ควรส่งผ่าน property setter โดยตรงแทน
  • คาดหวังว่ากำหนด property แล้ว attribute จะอัปเดตตามอัตโนมัติ — ถ้าไม่ได้เขียน reflection pattern เอง (getter/setter ที่เรียก setAttribute) การกำหนด property เปล่าๆ จะไม่ sync กลับไปที่ attribute ทำให้ state ที่เห็นใน markup กับใน JS ไม่ตรงกัน

💡 ตัวอย่างจากของจริง

Element <input> ดั้งเดิมของ browser คือตัวอย่างที่ชัดเจนที่สุดของความแตกต่างนี้ — attribute value คือค่าเริ่มต้น (default value) ที่ตั้งมาจาก HTML ส่วน property .value คือค่าปัจจุบันที่ผู้ใช้พิมพ์อยู่ตอนนี้ ทั้งสองค่าต่างกันได้ทันทีที่ผู้ใช้เริ่มพิมพ์

GitHub’s Catalyst framework แนะนำให้ custom element เปิด property สำหรับข้อมูลซับซ้อนเสมอ และสงวน attribute ไว้สำหรับค่า config ที่เป็น string ธรรมดาเท่านั้น

`getAttribute` คืนค่าเป็น type อะไรเสมอ?
ควรเช็คว่า boolean attribute อย่าง `disabled` มีอยู่หรือไม่ยังไง?
ตอนไหนที่ใช้ JS property ดีกว่า HTML attribute?
กำหนด `<my-el count="5">` แล้ว `this.getAttribute('count')` มี type เป็นอะไร?