เขียน CSS Selectors อย่างไรให้มีประสิทธิภาพ ?

ในการเขียน css selectors นั้น เรารู้ดีว่ามี selectors แบบไหนให้เราเลือกใช้ได้บ้าง และแต่ละแบบนั้นมีวิธีการใช้งานอย่างไร แต่หลายๆ คน อาจไม่รู้ว่าการเขียน selectors ในแต่ละแบบนั้น มันมีผลต่อ performance ด้วย

 Web Browsers แบ่ง CSS Rules ออกเป็นกลุ่มๆ

เรามาทำความเข้าใจถึงหลักการทำงานของ web browsers กันก่อน ไม่ว่าเราจะเขียน css rules อย่างไร web browsers จะแบ่ง rules ของเราออกเป็น 4 กลุ่ม ด้วยกัน ดังนี้

  • IDใช้เวลาในการหาน้อยที่สุด
  • Classใช้เวลาในการหามากกว่า ID เล็กน้อย
  • Tagใช้เวลาในการหามากกว่า Class
  • Universalใช้เวลาในการหามาก

ซึ่งการจะดูว่า rule นี้ถูกจัดให้อยู่ในกลุ่มไหนนั้นจะดูจาก “key selector” หรือ selector ที่อยู่ขวาสุดนั่นเอง

 กลุ่ม ID

rule ที่ถูกจัดให้อยู่ในกลุ่มนี้จะต้องมี id selector เป็น key selector

#latest-news { }
ul#latest-news { }
#latest-news[title=”Latest News”] { }
section > div > ul#latest-news:first-child { }

จากโค้ดด้านบน จะได้ว่า rules ทั้ง 4 แบบ ถูกจัดให้อยู่ในกลุ่ม id ทั้งหมด

 กลุ่ม Class

rule ที่ถูกจัดให้อยู่ในกลุ่มนี้จะต้องมี class selector เป็น key selector

.list-item { }
#latest-news .list-item { }
div ul li.list-item { }
ul > .list-item:hover { }

จากโค้ดด้านบน จะได้ว่า rules ทั้ง 4 แบบ ถูกจัดให้อยู่ในกลุ่ม class ทั้งหมด

 กลุ่ม Tag

ถ้า key selector ของ rule นั้นไม่ใช่ทั้ง id selector และ class selector ให้ดูว่ามีการระบุ tag เอาไว้หรือไม่ ถ้ามี จะถูกจัดให้อยู่ในกลุ่ม tag

a { }
li > a { }
#latest-news div[class*=”post”] { }
ul#latest-news li.list-item a:hover { }

จากโค้ดด้านบน จะได้ว่า rules ทั้ง 4 แบบ ถูกจัดให้อยู่ในกลุ่ม tag ทั้งหมด

 กลุ่ม Universal

หาก rule นั้นไม่ตรงกับ 3 กลุ่มข้างต้นเลย จะถูกจัดให้อยู่ในกลุ่ม universal

* { }
[id*=”post”] { }
.home [class*=”entry”] { }
form > [type=”text”] { }

จากโค้ดด้านบน จะได้ว่า rules ทั้ง 4 แบบ ถูกจัดให้อยู่ในกลุ่ม universal ทั้งหมด

 วิธีการอ่าน CSS Rules ของ Web Browsers

ในการดูว่า CSS Rule นี้ จะถูก apply ให้กับ html elements ใดบ้าง web browsers จะต้องทำการ “match” โดยมันจะอ่าน selectors จากขวาไปซ้าย คือจะเริ่มจาก key selector ซึ่งอยู่ทางด้านขวาสุดก่อน web browsers จะดูว่ามี elements ใดที่ match กับ selector ตัวนี้บ้าง ถ้าไม่มีเลย ก็จะหยุดไป แต่ถ้ามี มันก็จะทำการเช็คต่อโดยการเลื่อนไปยัง selector ทางซ้ายมือทีละอันๆ แล้วดูว่า elements ที่ match มาก่อนหน้านี้นั้น ยังคง match กับ selector ตัวใหม่นี้อีกหรือไม่ ถ้ายังมีอีกก็จะเลื่อนไปยัง selector ตัวต่อไปทางซ้ายอีก มันจะทำเช่นนี้ต่อไปเรื่อยๆ จนกว่าจะครบทุกๆ selectors ใน rule นั้นๆ หรือจนกว่าจะไม่เจอ elements ใดๆ ที่ตรงกับ rule นั้นๆ เลย

หน้าเว็บของเรานั้น ก็เปรียบเเสมือนห้องเรียนห้องหนึ่ง ที่มี html elements เป็นเหมือนเด็กนักเรียนที่นั่งอยู่ในห้อง ส่วนครูนั้นก็ไม่ใช่ใครที่ไหน มันคือ web browser นั่นเอง ในการเรียนการสอน ครูก็มักจะมีการเรียกเด็กบางคนขึ้นมาตอบคำถามบ้างเป็นธรรมดา คำถามคือ เขาจะมีวิธีการจำแนกเด็กอย่างไร?

การเขียน css rules ก็เหมือนกับการกำหนดวิธีการจำแนกเด็กนักเรียนให้กับครู การใส่ id ให้กับ element เปรียบเหมือนการเขียนป้ายชื่อของเด็กนักเรียนคนนั้นแล้วเอามาห้อยคอ ทีนี้เวลาครูจะหา ด.ช. เอ สิ่งที่ครูต้องทำก็แค่มองหาเด็กที่มีป้ายชื่อห้อยคอก่อนว่ามีใครบ้าง พอเจอแล้วก็ดูต่อว่าป้ายนั้นเขียนไว้ว่า ด.ช. เอ ใช่หรือไม่ การหา element จาก id ของ web browsers นั้นจะใช้เวลาน้อยมาก เนื่องจาก id มีได้แค่ที่เดียวเท่านั้น

การกำหนด class ให้กับ elements ต่างๆ จะเหมือนกับการแบ่งเด็กนักเรียนออกเป็นกลุ่มๆ หากเรากำหนด class ให้กับ elements แล้วล่ะก็ web browsers จะสามารถหาตัว elements เจอได้ง่ายเหมือนกับการที่ครูมองหาเฉพาะเด็กที่แต่งชุดลูกเสือที่มีอยู่ในห้องเรียน แค่มองปราดเดียวก็ดูออกแล้ว

ส่วนการเขียน css rule โดยใช้ tag เป็น key selector นั้นจะเหมือนกับการมองสิ่งที่เด็กนักเรียน “เป็น” วิธีนี้ web browsers จะมองหาตัว elements เหมือนกับการที่ครูมองหาเฉพาะเด็กนักเรียนที่เป็นผู้ชาย มันอาจใช้เวลาเพิ่มขึ้นนิดหน่อย แต่ก็ไม่นานมากนัก

และสุดท้ายคือการเขียน css rule ที่อยู่ในกลุ่ม universal วิธีนี้ก็เหมือนกับการที่ครูต้องการจะหาเด็กมาเข้าร่วมการประกวดร้องเพลงในโรงเรียน เขาไม่สามารถดูภายนอกแล้วรู้ได้ทันทีว่าใครร้องเพลงเพราะบ้าง เขาจำเป็นต้องไล่ดูไปทีละคนๆ เราจะเห็นว่าวิธีนี้นั้นมี candidates เยอะมาก

ขั้นตอนการหา elements ของ web browsers นี้เองที่จะมีเรื่องของ performance เข้ามาเกี่ยวข้อง ยิ่ง rule นั้นซับซ้อนมากเท่าไร ยิ่งใช้เวลาในการ match มากขึ้นเท่านั้น จากการทดลองของ Steve Souders เขาได้เรียงลำดับประสิทธิภาพของ selectors ในแบบต่างๆ เอาไว้ดังนี้

  1. ID – #header
  2. Class – .post
  3. Tag – div
  4. Sibling – h1 + ph1 ~ p
  5. Child – ul > li
  6. Descendant – ul li
  7. Universal – *
  8. Attribute – [type="text"]
  9. Pseudo-classes, Pseudo-elements – a:hoverp::first-letter

จากการจัดอันดับประสิทธิภาพของ selectors ด้านบน เราจะเห็นว่า selectors ในแต่ละแบบนั้น มีประสิทธิภาพที่ต่างกัน โดย id นั้นจะใช้เวลาในการ match น้อยที่สุด ส่วน pseudo-classes และ pseudo-elements จะใช้เวลาในการ match มากที่สุด และอย่าลืมว่า web browsers นั้นอ่าน selectors จากขวาไปซ้ายทีละตัวๆ ยิ่งเขียน selectors ยาวมากเท่าไร ประสิทธิภาพก็ยิ่งลดลงมากเท่านั้น

 เขียน Selectors อย่างไรให้มีประสิทธิภาพ ?

หลังจากที่เราเข้าใจวิธีการอ่าน css rules ของ web browsers เราจึงได้รู้ว่าการเลือกใช้ selectors นั้นมีความสำคัญ ในการเขียน selectors ให้เรายึดหลักต่อไปนี้

  อย่าเขียน Selectors เกินความจำเป็น

อย่างที่ทุกคนทราบกันดีว่า id นั้นมีได้เพียงที่เดียวในหนึ่งหน้า ฉะนั้นหากเราจะใช้ id selector แล้ว เราไม่ควรใส่อะไรเพิ่มเข้าไปอีก

BAD

div#latest-news { }
.box#latest-news { }
.sidebar #latest-news { }

นี่เป็นตัวอย่างการใช้ id selector ที่แย่ ในตัวอย่างแรก web browsers ทั้งหลายจะมองหา elements ที่มี id ชื่อว่า “latest-news” ก่อน เมื่อเจอแล้วก็จะดูต่อว่า element นั้นเป็น div หรือไม่ ซึ่งจริงๆ แล้ว มันไม่จำเป็นต้องเช็คอีก เพราะ id นั้นมีได้เพียงที่เดียวเท่านั้น เช่นเดียวกับตัวอย่างที่ 2 ที่จะต้องเช็คต่อว่า element นั้นมี class “box” อยู่ด้วยหรือไม่ และในตัวอย่างที่ 3 ที่แย่สุดเลยก็ว่าได้ เนื่องจากมันต้องไปไล่เช็ค ancestors ทุกตัวของ element นี้ ว่ามีตัวที่มี class “sidebar” บ้างหรือไม่

GOOD

#latest-news{ }

ในทำนองเดียวกัน เราก็ไม่จำเป็นต้องใส่อะไรหน้า class อีก เพราะมันจะทำให้เสียเวลาในการ match เพิ่มโดยไม่จำเป็น

BAD

p.red { }

GOOD

.red { }

อย่างไรก็ตาม การใส่อะไรข้างหน้า class บางทีก็มีประโยชน์เหมือนกันในเรื่องของ semantic การใช้ p.red มีข้อดีตรงมันช่วยให้เรารู้ว่า css rule นี้มีไว้เพื่อทำให้ตัวอักษรใน paragraph เป็นสีแดง หรือบางทีเราอาจจะต้องการกำหนดสไตล์สำหรับ class “red” เฉพาะที่ p element เพียงอย่างเดียวเท่านั้น

 ลดการใช้ Descendant Selector

การใช้ descendant selector นั้นทำให้ประสิทธิภาพลดลงอย่างมาก เราจึงควรหลีกเลี่ยง

BAD

ul#latest-news li a {  }      /* แย่มาก */
ul#latest-news > li > a {  }  /* ดีขึ้น แต่ก็ยังแย่อยู่ดี */

จากตัวอย่างนี้ web browsers จะมองหา key selector ซึ่งก็คือ a ก่อน แล้วค่อยเลื่อนต่อไปทางซ้ายมือ โดยดูว่า a นั้นเป็น descendant ของ li หรือไม่ ถ้าเป็นให้ดูต่อว่า li นั้นเป็น descendant ของ #latest-news หรือไม่ และถ้ายังเป็นอีกให้ดูว่า #latest-news นั้นเป็น ul หรือไม่ การเขียน selector แบบนี้ส่งผลเสียต่อ performance เป็นอย่างมาก

GOOD

.latest-news-link {  }

วิธีแก้ง่ายๆ คือการเพิ่ม class “latest-news-link” ให้กับ a ทีนี้ web browsers ก็จะสามารถหา elements นั้นๆ เจอได้อย่างรวดเร็ว แต่การเขียนแบบนี้ก็มีข้อเสียตรงที่มันจะทำให้การ maintenance ลำบากขึ้น เพราะเราอาจดูไม่ออกว่า .latest-news-link นี้เป็น link ที่อยู่ใน ul#latest-news

 ลดการใช้ CSS Rules ที่อยู่ในกลุ่ม Universal

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

BAD

[type=”text”] { }           /* แย่มาก */
[class~=”input-text”] { }   /* แย่มากๆ เพราะไม่จำเป็น */

GOOD

input[type=”text”] { }      /* ดีขึ้นมาก */
.input-text { }             /* ดีที่สุด */

จะเห็นว่า css rules 2 แบบแรกนั้น ถูกจัดให้อยู่ในกลุ่ม universal เพราะใน key selector ไม่มี id, class หรือ tag เลย ทำให้ web browsers จะต้องไปไล่เช็คกับ elements ทุกตัวในหน้านั้นๆ การเขียน css rules 2 แบบหลัง เป็นวิธีที่ดีกว่า

 มันจะช้าลงสักแค่ไหนกันเชียว ?

เชื่อเหลือเกินว่าหลายๆ คนคงอยากจะรู้ว่าการเลือกใช้ selectors โดยไม่คำนึงถึง performance เลยนั้น มันจะทำให้เว็บช้าลงขนาดไหน คำตอบคือมันไม่ใช่แค่ช้าลงเพียงไม่กี่วินาที แต่มันช้าลงเพียงแค่ไม่กี่มิลลิวินาที อ่าว ? แล้วจะเอาเรื่องนี้มาเขียนบทความทำไม !!!

เรื่อง performance ของ css selectors นี้อาจจะเห็นผลไม่ค่อยชัดนัก เนื่องจากความเร็วของการประมวลผลในสมัยนี้นั้นเรียกได้ว่าเร็วเอามากๆ สมมติว่าเราเลือกใช้ selectors โดยเน้นไปที่ performance เป็นหลัก เมื่อลอง render ดู เราพบว่ามันใช้เวลาเพียงแค่ 400ms แต่พอเราเลือกใช้ selectors แบบตามใจตัวเอง เมื่อลอง render ดูใหม่เราอาจจะพบว่ามันใช้เวลาเพิ่มเป็น 600ms จะเห็นว่าส่วนต่างขนาด 150% นั้น ฟังดูเยอะก็จริง แต่ผู้ใช้งานนั้น อาจจะสัมผัสความต่างเพียงแค่ 200ms นี้ไม่ได้

อย่างไรก็ตาม การเลือกใช้ selectors โดยคำนึงถึง performance นี้ จะเห็นผลได้ชัดมากขึ้นหากในหน้านั้นๆ มี html elements และ css rules อยู่เป็นจำนวนมาก นอกจากนี้ หากเว็บของเราเป็นเว็บที่เน้นในเรื่องของ performance เป็นหลักแล้วล่ะก็ การคำนึงถึง performance ของ selectors คงจะเป็นเรื่องที่มองข้ามไม่ได้

การเลือกใช้ selectors โดยคำนึงถึง performance ก็เหมือนกับจรรยาบรรณของ developers แม้สิ่งที่เราทำนั้นผู้ใช้งานส่วนใหญ่อาจจะสัมผัสไม่ได้ก็จริง แต่อย่างน้อยก็มีหนึ่งคนที่รู้คือตัวเรานั่นเอง การปล่อยงานที่ไร้คุณภาพออกไปนั้นถือเป็นการดูถูกตัวเอง นี่เป็นสิ่งที่ไม่ควรทำ

 ความพอดี

การคำนึงถึง performance นั้นไม่ได้แปลว่าห้ามใช้ selectors นอกเหนือจาก id, class และ tag แต่อย่างใด แต่มันหมายถึงการเขียน selectors แบบคุ้มค่า ไม่ฟุ่มเฟือย selectors แบบอื่นๆ นั้นใช้ได้ แต่ควรใช้เมื่อจำเป็นจริงๆ เท่านั้น

บางคนอาจจะสงสัยว่า หากเราต้องการเขียน css rules ให้ render หน้าเว็บได้เร็วสุดๆ เราก็ต้องกำหนด class ให้กับ html elements ทั้งหมดในหน้านั้นเลยงั้นหรือ ? คำตอบคือ “ไม่ใช่”

จริงอยู่ที่การทำอย่างนั้น ดูเหมือนจะทำให้ web browsers หา elements ที่ต้องการได้เร็วขึ้น แต่การใส่ class มาก “เกินไป” นั้นก็มีข้อเสียอยู่เหมือนกัน

หากใส่ class ให้ทุกๆ elements เลย ขนาดของโค้ด html จะต้องใหญ่ขึ้นอย่างแน่นอน ซึ่งส่งผลให้ปริมาณไฟล์ที่เราต้องดาวน์โหลดมานั้นมากขึ้นตามไปด้วย การดาวน์โหลดไฟล์ส่วนต่างที่เพิ่มขึ้นมานี้ อาจใช้เวลามากกว่าเวลาที่ลดลงไปจากผลของ performance ที่ดีขึ้นเสียอีก

นอกจากนี้ การใส่ class ให้กับทุกๆ elements อาจไม่ได้ทำให้หน้าเว็บ render ได้เร็วอย่างที่เราคิด สมมติในห้องเรียนห้องหนึ่ง มีเด็กนักเรียนนั่งอยู่เต็มไปหมด ผู้ปกครองต้องการพบลูกของเขา จึงบอกลักษณะกับครูไปว่าเด็กคนนั้นใช้กระเป๋าหนังยี่ห้อ Jacob สิ่งที่ครูต้องทำคือมองหาเด็กที่ 1. มีกระเป๋าหนัง 2. กระเป๋าหนังนั้นมียี่ห้อ Jacob หากในห้องนั้นมีเด็กเพียงไม่กี่คนที่มีกระเป๋าหนัง ครูก็จะสามารถหาเด็กที่มีกระเป๋าหนังยี่ห้อ Jacob ได้อย่างง่ายดาย ในทางกลับกัน หากเด็กในห้องนั้นมีกระเป๋าหนังกันทุกคน เท่ากับว่าครูต้องไล่ดูยี่ห้อของกระเป๋าหนังไปทีละคนๆ จนครบทั้งห้อง บางคนอาจสงสัยว่าทำไมครูไม่ตะโกนถามเด็กๆ ล่ะว่า ใครมีกระเป๋าหนังยี่ห้อ Jacob บ้าง จะได้ไม่ต้องมาเสียเวลาหา คำตอบคือ html นั้นพูดไม่ได้

 บทสรุปการเลือกใช้ CSS Selectors

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

 

แหล่งอ้างอิง

http://www.siamhtml.com/css-selectors-performance/