6 poin oleh GN⁺ 2024-04-27 | 3 komentar | Bagikan ke WhatsApp
  • Meski menempel pada elemen yang sama, atribut HTML dan properti DOM dapat berperilaku seperti ruang penyimpanan terpisah, sehingga perubahan di satu sisi tidak selalu otomatis tercermin di sisi lain
  • Atribut terlihat dalam serialisasi HTML dan panel Elements di developer tools, tetapi properti adalah state khusus DOM, sehingga tidak muncul dalam hasil seperti outerHTML
  • Nilai atribut selalu berupa string dan namanya tidak peka huruf besar-kecil, sedangkan properti dapat memiliki berbagai tipe seperti objek, dan namanya peka huruf besar-kecil
  • Ada properti yang merefleksikan atribut seperti id, className, htmlFor, ariaLabel, tetapi ada juga pengecualian seperti input.value yang memisahkan state saat ini dan pengaturan default
  • Target yang disetel berbeda-beda menurut framework: Preact dan VueJS umumnya memprioritaskan properti, React umumnya memprioritaskan atribut, sedangkan Lit membedakan keduanya secara eksplisit dengan sintaks .value

Atribut dan properti bisa berbeda meski namanya sama

  • Atribut (attributes) dan properti (properties) dapat berperilaku seperti ruang penyimpanan yang berbeda
    • Pada `` getAttribute('foo') mengembalikan 'bar', tetapi div.foo bisa saja undefined
    • Setelah itu, meski menetapkan div.foo = 'hello world', nilai atribut foo tetap 'bar'
  • Dalam template framework, meski memakai sintaks yang terlihat seperti atribut, seperti ``, secara internal framework dapat menetapkan properti tergantung situasinya
    • Kapan properti ditetapkan berbeda-beda menurut framework
    • Beberapa pengaturan dapat menimbulkan efek samping yang mengubah properti sekaligus atribut

Yang tersisa dalam serialisasi HTML adalah atribut

  • Atribut ada di HTML dan DOM, tetapi properti hanya ada di DOM
  • Meski menjalankan div.setAttribute('foo', 'bar') dan div.hello = 'world', div.outerHTML hanya menampilkan atribut, seperti `

`

  • Panel Elements pada developer tools browser menampilkan atribut elemen, bukan seluruh propertinya

Tipe nilai dan penanganan huruf besar-kecil

  • Nilai atribut harus bekerja dalam bentuk terserialisasi, sehingga selalu berupa string
    • Jika objek dimasukkan dengan setAttribute, objek tersebut disimpan sebagai string "[object Object]"
    • Jika objek yang sama dimasukkan ke properti, tipenya dapat tetap sebagai objek
  • Nama atribut tidak peka huruf besar-kecil
    • HeLlO="world" dapat dinormalisasi menjadi atribut hello di DOM
    • setAttribute('FOO', 'bar') juga muncul sebagai atribut foo
  • Nama properti peka huruf besar-kecil
    • Meski menetapkan div.TeSt = 'value', div.test bisa saja undefined
  • Berbeda dari nama atribut, nilai atribut peka huruf besar-kecil

Properti yang merefleksikan atribut

  • Sebagian properti merefleksikan (reflect) atribut tertentu
    • div.id berperan sebagai getter/setter yang membaca dan menulis atribut id
    • Jika menjalankan div.id = 'bar', atribut id juga berubah menjadi 'bar'
  • Dalam relasi refleksi, atribut adalah sumber data
    • Menetapkan properti akan memperbarui atribut
    • Membaca properti akan membaca nilai atribut
  • Atribut yang didefinisikan dalam spesifikasi sering dibuatkan properti padanannya demi kemudahan
    • Atribut foo sembarang bukan atribut yang didefinisikan dalam spesifikasi, sehingga properti foo yang bersesuaian tidak otomatis muncul
  • Spesifikasi `` mendefinisikan atribut di “Content attributes” dan properti di “DOM interface”
    • Atribut IDL reversed dan type harus merefleksikan atribut konten dengan nama yang sama

Relasi refleksi dengan nama yang berbeda

  • Sebagian properti memiliki nama yang berbeda dari atribut yang direfleksikannya
  • Ada kasus nama diubah agar sesuai dengan gaya properti JavaScript
    • el.crossOrigin pada `` merefleksikan atribut crossorigin
    • el.ariaLabel pada semua elemen merefleksikan atribut aria-label
    • Properti refleksi ARIA menjadi lintas-browser pada akhir 2023; sebelumnya hanya atribut yang dapat digunakan
  • Ada juga kasus nama diubah karena dulunya merupakan reserved word JavaScript
    • el.className pada semua elemen merefleksikan atribut class
    • el.htmlFor pada `` merefleksikan atribut for

Validasi, konversi tipe, dan nilai default

  • Properti dapat memiliki validasi dan nilai default, tetapi atribut itu sendiri tidak memiliki perilaku seperti itu
  • input.type menunjukkan perbedaan atribut dan properti dengan baik
    • Meski input.getAttribute('type') adalah null, input.type mengembalikan nilai default 'text'
    • input.type = 'number' terlihat seperti 'number' baik pada atribut maupun properti
    • input.type = 'foo' memasukkan 'foo' ke atribut, tetapi getter properti melihat nilai yang tidak valid dan mengembalikan 'text'
  • Sebagian properti melakukan konversi tipe
    • Pada `` details.getAttribute('open') adalah string kosong dan details.open adalah true
    • details.open = false menghapus atribut open
    • details.open = 'hello' tidak memasukkan string apa adanya ke atribut, melainkan mengonversinya menjadi boolean sehingga atribut open berada dalam keadaan ada
  • Properti seperti img.height mengonversi nilai atribut menjadi angka
    • Setter mengubah nilai yang masuk menjadi angka, dan memperlakukan angka negatif sebagai 0

input.value memisahkan nilai saat ini dan nilai default

  • Pada input ada properti value dan atribut value, tetapi properti value tidak merefleksikan atribut value
  • Properti yang merefleksikan atribut value adalah defaultValue
    • Getter defaultValue mengembalikan atribut value atau string kosong
    • Setter defaultValue menetapkan atribut value sebagai string
  • Properti value awalnya mengikuti defaultValue, lalu setelah ditetapkan lewat JavaScript atau interaksi pengguna, ia menggunakan nilai internal
    • Pada `` awal, value, defaultValue, dan getAttribute('value') semuanya adalah 'default'
    • Jika menjalankan input.defaultValue = 'new default', atribut dan value sama-sama terlihat seperti 'new default'
    • Setelah itu, jika menjalankan input.value = 'hello!', value menjadi 'hello!', tetapi atribut dan defaultValue tetap 'new default'
    • Meski menjalankan setAttribute('value', 'another new default'), value saat ini tetap 'hello!'
  • Saat form di-reset, perilakunya bisa berupa pengaturan ulang state internal value

Atribut lebih alami untuk pengaturan, properti untuk state

  • Pembedaan bahwa atribut digunakan untuk pengaturan (configuration) dan properti untuk state terasa alami
  • `` adalah contoh yang sesuai dengan pembedaan ini, kecuali dari sisi penamaan
    • Atribut value menetapkan nilai default
    • Properti value menyediakan state saat ini
  • Validasi diterapkan saat membaca atau menulis properti, dan tidak diterapkan saat membaca atau menulis atribut; pembedaan ini juga cocok dengan cara pandang tersebut
  • dan merepresentasikan keadaan terbuka dengan atribut open, dan browser secara langsung menambahkan atau menghapus atribut ini sesuai interaksi pengguna
    • Desain ini merusak pembedaan bahwa atribut adalah untuk pengaturan
    • Framework yang memelihara DOM atau kode JavaScript biasa perlu siap menghadapi situasi ketika DOM berubah sendiri
  • Desain alternatif dapat membaginya menjadi atribut pengaturan default seperti ``, properti details.open untuk membaca dan menulis state saat ini, serta pseudo-class CSS untuk menargetkan state
  • Simon Peters menemukan diskusi desain awal terkait hal ini
  • contenteditable juga merusak pembedaan ini, tetapi diperlakukan sebagai opt-in yang menerima banyak kerusakan

Cara penanganan di tiap framework

  • Preact dan VueJS, kecuali beberapa pengecualian yang telah didefinisikan sebelumnya, menetapkan properti ketika propName in element bernilai benar, dan jika tidak, menetapkan atribut
    • Secara default, mereka lebih memilih properti daripada atribut
    • Metode rendering string bekerja sebaliknya, dan item yang hanya berupa properti diabaikan
    • Implementasi terkait: setProperty di Preact, shouldSetAsProp di VueJS
  • React menetapkan atribut, kecuali beberapa pengecualian yang telah didefinisikan sebelumnya
    • Cara ini membuat logikanya mirip dengan metode rendering string
    • Karena properti custom element tidak ada dalam daftar yang telah didefinisikan React, properti tersebut ditetapkan sebagai atribut, dan API yang hanya berupa properti mungkin tidak berfungsi
    • Pada React 19, untuk custom element, React akan beralih ke model Preact/VueJS
    • React memopulerkan nama properti className dalam sintaks yang terlihat seperti atribut, tetapi secara internal menetapkan atribut class
    • Implementasi terkait: setProp di React
  • lit-html mempertahankan perbedaan antara atribut dan properti lewat sintaks
    • Jika menambahkan . di depan nama seperti ``, yang ditetapkan adalah properti, bukan atribut
    • Dokumentasi terkait: Lit expressions

3 komentar

 
GN⁺ 2024-04-27
Komentar Hacker News
  • Atribut data-* menjadi kompromi yang cukup baik di antara pendekatan yang dibahas dalam artikel
    Atribut ini otomatis diekspos di JavaScript sebagai properti baca/tulis pada dataset, sehingga bisa diakses seperti document.getElementById('myDiv').dataset.payload == "something"
    Hanya saja, atribut HTML memakai kebab-case sedangkan JavaScript memakai camelCase, jadi perlu hati-hati karena data-my-cool-data-attribute menjadi dataset.myCoolDataAttribute

    • Saya benar-benar tidak suka konversi penulisan huruf besar/kecil otomatis
      Di berbagai ekosistem yang memakai camelCase, tidak ada konvensi mapan untuk menangani singkatan berhuruf besar, jadi kasus seperti dataset.myID menjadi serba tanggung
    • Saya memakai metode seperti Element.hasAttribute('data-thing'), Element.getAttribute('data-thing'), Element.setAttribute('data-thing', '...') untuk menghindari konversi antara kebab-case dan camelCase
      Saat memeriksa atau debugging di DOM, kita jadi tidak perlu melewati konversi aneh, sehingga maksudnya jauh lebih jelas
    • Dokumen MDN tentang data attributes membahas sintaks HTML, akses JavaScript, akses CSS, dan hal-hal yang perlu diperhatikan terkait aksesibilitas: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Us...
      Selain itu, https://developer.mozilla.org/en-US/docs/Web/HTML/Global_att... menjelaskan atribut global data-* sebagai “atribut data kustom yang memungkinkan pertukaran informasi proprietary antara HTML dan representasi DOM-nya oleh skrip”
    • Atribut ini sampai tingkat tertentu juga bisa diakses dari CSS
    • Ini juga merupakan cara lain yang menarik untuk berkomunikasi antarkonteks eksekusi JavaScript
      Konteks yang dimaksud di sini tidak harus berarti iframe yang berbeda, tetapi juga kasus seperti ekstensi browser yang membuat konteks eksekusi JavaScript terpisah namun tetap bisa mengakses DOM yang sama
      Dalam istilah protokol developer tools, hal seperti ini juga disebut isolated worlds
  • Hal-hal yang dibahas artikel ini menunjukkan kompleksitas dan perilaku yang tidak banyak membantu dalam kehidupan nyata
    Perilaku seperti ini hanya punya kekurangan tanpa kelebihan, dan spesifikasinya seharusnya cukup mendefinisikan objek DOM dan objek JavaScript sebagai hal yang sama, sehingga atribut dan properti juga menjadi konsep yang sama

    • Kalau pembatasan itu memang “cukup” seperti itu, sebagian besar framework UI modern tidak akan bisa bekerja sama sekali
      Bisa menyimpan nilai non-string pada node DOM adalah kelebihan yang jelas, dan bukan hal yang bisa disepelekan
    • Saya setuju kalau bagian “cukup” itu dihapus
      Ini sama sekali tidak sederhana kecuali semua properti diwajibkan hanya berupa string
      Kita juga harus memikirkan konversi dengan angka, properti yang bisa diisi objek, dan properti yang tidak punya pasangan atribut
      Meski begitu, akan lebih baik jika ada konvensi atau aturan yang 100% tetap, tetapi itu bukan hal yang sederhana
    • Kalau begitu, antarmuka DOM tidak akan bisa memiliki nilai non-string, dan karena metode juga termasuk di dalamnya, sebagian besar fungsi DOM akan hilang
    • Sulit membuat atribut dan properti benar-benar simetris dalam API DOM yang ada
      Metode seperti addEventListener adalah properti prototipe dari elemen DOM individual, tetapi tidak masuk akal jika dipandang sebagai atribut string
      Kalau begitu, metode elemen DOM harus menjadi fungsi bebas seperti HTMLElement.addEventListener(foo, 'some-event', e => console.log(e));; ini soal selera, tetapi enak juga bisa memakai pemanggilan metode objek di JavaScript
    • Saya kurang paham kenapa keduanya harus berbeda
      Elemen DOM punya antarmuka yang sangat spesifik untuk mengubah state lewat setAttribute, dan ketika seseorang hanya menempelkan properti ke objek JavaScript yang dibuat untuk mengirim pesan ke DOM lewat antarmuka itu, kenapa kita harus berharap itu akan otomatis dirutekan?
      Mungkin API seperti itu akan memberi pengalaman yang lebih baik, tetapi ada beberapa tantangan, walaupun rasanya memang akan lebih rapi
      Mungkin karena saya terlalu lama menulis kode backend, ketika melihat antarmuka seperti ini rasanya ada banyak hal aneh terjadi di balik layar, seolah ada pengembang baik hati di masa lalu yang memberi wrapper paling aman yang mungkin
  • Sudut pandang yang membantu untuk memahami ini adalah bahwa atribut dalam arti sempit adalah node DOM, yaitu node Attr
    Node ini bisa punya properti seperti name, value, ownerElement, prefix, dan secara keseluruhan dapat diakses sebagai nilai properti attributes milik elemen pemiliknya dalam bentuk NamedNodeMap, bukan array
    Karena itu, nama getAttribute(name) agak menyesatkan; sebenarnya ia tidak mengembalikan node atribut itu sendiri, melainkan properti value dari node atribut tersebut, jadi lebih dekat ke getAttributeValue(name)
    Referensi: https://developer.mozilla.org/en-US/docs/Web/API/Element/get..., https://developer.mozilla.org/en-US/docs/Web/API/Element/get..., https://developer.mozilla.org/en-US/docs/Web/API/Attr

    • Penjelasan itu benar, tetapi saya tidak tahu apakah ada hal praktis yang hanya bisa dilakukan lewat node atribut atau el.attributes, dan tidak bisa dilakukan lewat setAttribute, getAttribute, getAttributeNames, hasAttribute
      Node atribut juga tidak bisa dipindahkan antar elemen, dan urutan node atribut di dalam elemen pun tidak bisa diubah
  • Jake, lalu bagaimana dengan ini: https://jakearchibald.com/2023/against-self-closing-tags-in-...

    • “Anda akan melihat sintaks ini di blog saya, itu karena Prettier yang melakukannya dan saya sangat menyukai Prettier”
  • Saya paham logika untuk tidak memperbarui properti, tetapi fakta bahwa target bisa dipilih dengan CSS itu berguna

    • Itu sebabnya saya bilang seharusnya juga ada pseudo-class, mirip :checked pada elemen input
    • Bagi saya juga, kasus ini lebih besar daripada semua alasan lainnya
      Jauh lebih mudah untuk debugging dengan langsung memeriksa DOM daripada harus melewati debugger
  • Mungkin perbedaan yang paling penting adalah persistensi, tetapi ini tidak dibahas dalam artikelnya
    Atribut tetap ada selama kita masih bisa mengakses root object yang sama dari pohon DOM tersebut, misalnya selama belum meninggalkan halaman
    Properti objek hanya bertahan sampai objek itu di-garbage collect

    • Objek ada selama node DOM yang memiliki atribut itu masih ada, jadi siklus hidupnya sama
      Jika node DOM dikloning atau melalui serialisasi/deserialisasi, itu menjadi node DOM yang sepenuhnya berbeda dengan atribut yang sama, sehingga tidak lagi memiliki properti yang bukan nilai default dari node lain
    • Di sini, selain perbedaan atribut dan properti yang sudah ada, tidak ada perbedaan perilaku yang nyata
    • Saya penasaran bagaimana tepatnya perbedaan itu muncul dalam praktik
  • Tulisan yang menarik
    Saya selalu melihat elemen DOM di JavaScript seperti proxy
    Itu memungkinkan kita bekerja dengan elemen DOM yang sebenarnya, tetapi pada saat yang sama itu juga hanya objek JavaScript, jadi rasanya kita bisa menetapkan nilai dan melakukan hal yang kita mau selain memakai fungsi-fungsi yang berinteraksi dengan elemen aslinya

  • Penjelasannya enak dibaca karena dipaparkan dengan contoh, dan terutama karena ada beberapa detail yang sering mudah terlewat, ini tampaknya bisa membantu banyak orang

  • Properti DOM juga disebut IDL attributes

    • Benar, tetapi saya sengaja menghindari istilah itu
      Di luar para penulis spesifikasi, jarang ada orang yang menyebutnya IDL attributes, dan kalau keduanya sama-sama disebut attribute itu jadi sangat membingungkan
  • Tulisan yang bagus, dan ini membuat saya penasaran seperti apa bentuk cara representasi yang lebih baik
    Akan menarik jika HTML bisa memiliki atribut yang bukan string, dan nilainya merujuk ke nilai lokal dari tag script yang memiliki scope
    Perspektif yang melihat atribut sebagai konfigurasi default terasa agak aneh
    Misalnya, jika atribut class hanya menampilkan nilai default, saat class di-toggle maka class yang aktif tidak akan terlihat di DOM inspector atau HTML yang diserialkan, atau class yang nonaktif justru akan terlihat, dan itu akan terasa cukup aneh

    • Bukan berarti saya menyebutnya konfigurasi default, melainkan sekadar konfigurasi
      Atribut class bekerja dengan baik dalam sistem ini
      Saya sebagai pemilik dokumen menetapkan atribut class, dan seiring waktu saya bisa mengubah konfigurasi itu, tetapi hanya saya yang melakukannya
      Dalam sistem alternatif, browser akan menambah dan menghapus class sendiri sebagai respons terhadap status hover atau focus, tetapi untungnya itu tidak begitu, dan yang dipakai adalah pseudo-class
 
superwoou 2024-04-27

attribute dan property sama-sama diterjemahkan sebagai "atribut" ya..