Dethe Elza ( [email protected] ) สถาปนิกด้านเทคนิคอาวุโส Blast Radius
Document Object Model (DOM) เป็นหนึ่งในเครื่องมือที่ใช้บ่อยที่สุดสำหรับจัดการข้อมูล XML และ HTML แต่ศักยภาพของมันก็ไม่ค่อยถูกใช้ประโยชน์อย่างเต็มที่ ด้วยการใช้ประโยชน์จาก DOM และทำให้ใช้งานง่ายขึ้น คุณจะได้รับเครื่องมือที่มีประสิทธิภาพสำหรับแอปพลิเคชัน XML รวมถึงแอปพลิเคชันเว็บแบบไดนามิก
ฉบับนี้ประกอบด้วยคอลัมนิสต์รับเชิญ เพื่อน และเพื่อนร่วมงาน Dethe Elza Dethe มีประสบการณ์มากมายในการพัฒนาเว็บแอปพลิเคชันโดยใช้ XML และฉันอยากจะขอบคุณเขาที่ช่วยฉันแนะนำการเขียนโปรแกรม XML โดยใช้ DOM และ ECMAScript คอยติดตามคอลัมน์นี้เพื่อดูคอลัมน์ของ Dethe เพิ่มเติม
- David Mertz
DOM เป็นหนึ่งใน API มาตรฐานสำหรับการประมวลผล XML และ HTML มักถูกวิพากษ์วิจารณ์ว่าต้องใช้ความจำมาก ช้า และละเอียด ถึงกระนั้น มันก็เป็นตัวเลือกที่ดีที่สุดสำหรับหลาย ๆ แอปพลิเคชัน และง่ายกว่า SAX ซึ่งเป็น API หลักอื่น ๆ ของ XML อย่างแน่นอน DOM จะค่อยๆ ปรากฏในเครื่องมือต่างๆ เช่น เว็บเบราว์เซอร์, เบราว์เซอร์ SVG, OpenOffice และอื่นๆ
DOM นั้นยอดเยี่ยมเพราะเป็นมาตรฐานและมีการนำไปใช้อย่างกว้างขวางและรวมอยู่ในมาตรฐานอื่นๆ ตามมาตรฐานแล้ว การจัดการข้อมูลเป็นแบบไม่เชื่อเรื่องภาษาการเขียนโปรแกรม (ซึ่งอาจเป็นจุดแข็งหรือไม่ใช่ก็ได้ แต่อย่างน้อยก็ทำให้วิธีที่เราจัดการข้อมูลสอดคล้องกัน) ขณะนี้ DOM ไม่เพียงแต่สร้างไว้ในเว็บเบราว์เซอร์เท่านั้น แต่ยังเป็นส่วนหนึ่งของข้อกำหนดเฉพาะที่ใช้ XML หลายประการอีกด้วย ตอนนี้มันเป็นส่วนหนึ่งของคลังแสงของคุณแล้ว และบางทีคุณอาจยังใช้มันบ้างเป็นครั้งคราว ฉันเดาว่าถึงเวลาแล้วที่จะใช้ประโยชน์จากสิ่งที่มันนำมาสู่โต๊ะอย่างเต็มที่
หลังจากทำงานกับ DOM มาสักระยะหนึ่ง คุณจะเห็นรูปแบบพัฒนาขึ้น ซึ่งเป็นสิ่งที่คุณต้องการทำซ้ำแล้วซ้ำอีก ทางลัดช่วยให้คุณทำงานกับ DOM ที่มีความยาวและสร้างโค้ดที่สวยงามและอธิบายได้ในตัว ต่อไปนี้เป็นชุดคำแนะนำและเคล็ดลับที่ฉันใช้บ่อยๆ พร้อมด้วยตัวอย่าง JavaScript
เคล็ดลับแรก
ในการแทรก After และ prependChild
คือ "ไม่มีเคล็ดลับ"DOM มีสองวิธีในการเพิ่มโหนดลูกให้กับโหนดคอนเทนเนอร์ (โดยปกติจะเป็นองค์ประกอบหรือเอกสารหรือส่วนของเอกสาร): appendChild(node) และ insertBefore(node, ReferenceNode) ดูเหมือนมีบางอย่างขาดหายไป จะเกิดอะไรขึ้นหากฉันต้องการแทรกหรือเติมโหนดย่อยไว้ข้างหน้าหลังโหนดอ้างอิง (ทำให้โหนดใหม่เป็นโหนดแรกในรายการ) หลายปีที่ผ่านมา วิธีแก้ปัญหาของฉันคือเขียนฟังก์ชันต่อไปนี้:
การลงรายการ 1 วิธีการแทรกและเพิ่มด้วยไม่ถูกต้อง
ฟังก์ชั่น insertAfter (พาเรนต์, โหนด, ReferenceNode) {
ถ้า (referenceNode.nextSibling) {
parent.insertBefore (โหนด, ReferenceNode.nextSibling);
} อื่น {
parent.appendChild (โหนด);
-
-
ฟังก์ชั่น prependChild (พาเรนต์, โหนด) {
ถ้า (parent.firstChild) {
parent.insertBefore (โหนด parent.firstChild);
} อื่น {
parent.appendChild (โหนด);
-
-
ในความเป็นจริง เช่นเดียวกับรายการ 1 ฟังก์ชัน insertBefore() ถูกกำหนดให้กลับไปที่ appendChild() เมื่อโหนดอ้างอิงว่างเปล่า ดังนั้นแทนที่จะใช้วิธีการข้างต้น คุณสามารถใช้วิธีการในรายการ 2 หรือข้ามไปและใช้ฟังก์ชันในตัว:
รายการ 2 วิธีที่ถูกต้องในการแทรกและเพิ่มจากก่อนหน้า
ฟังก์ชั่น insertAfter (พาเรนต์, โหนด, ReferenceNode) {
parent.insertBefore (โหนด, ReferenceNode.nextSibling);
-
ฟังก์ชั่น prependChild (พาเรนต์, โหนด) {
parent.insertBefore (โหนด parent.firstChild);
-
หากคุณยังใหม่กับการเขียนโปรแกรม DOM สิ่งสำคัญคือต้องชี้ให้เห็นว่า แม้ว่าคุณจะมีพอยน์เตอร์หลายตัวที่ชี้ไปยังโหนดได้ แต่โหนดนั้นมีอยู่ในตำแหน่งเดียวในแผนผัง DOM เท่านั้น ดังนั้นหากคุณต้องการแทรกมันเข้าไปในต้นไม้ ก็ไม่จำเป็นต้องเอามันออกจากต้นไม้ก่อนเพราะมันจะถูกลบออกโดยอัตโนมัติ กลไกนี้สะดวกเมื่อจัดลำดับโหนดใหม่โดยเพียงแค่แทรกโหนดเหล่านั้นลงในตำแหน่งใหม่
ตามกลไกนี้ หากคุณต้องการสลับตำแหน่งของโหนดสองโหนดที่อยู่ติดกัน (เรียกว่า node1 และ node2) คุณสามารถใช้หนึ่งในโซลูชันต่อไปนี้:
node1.parentNode.insertBefore(node2, node1);
หรือ
node1.parentNode.insertBefore(node1.nextSibling, node1);
คุณสามารถทำอะไรกับ DOM ได้อีก?
DOM ถูกใช้กันอย่างแพร่หลายในหน้าเว็บ หากคุณเยี่ยมชมไซต์ bookmarklets (ดูแหล่งข้อมูล) คุณจะพบสคริปต์สั้นเชิงสร้างสรรค์มากมายที่สามารถจัดเรียงหน้าใหม่ แยกลิงก์ ซ่อนรูปภาพหรือโฆษณา Flash และอื่นๆ อีกมากมาย
อย่างไรก็ตาม เนื่องจาก Internet Explorer ไม่ได้กำหนดค่าคงที่ของอินเทอร์เฟซของโหนดที่สามารถใช้เพื่อระบุชนิดของโหนดได้ คุณต้องแน่ใจว่า ถ้าคุณละเว้นค่าคงที่ของอินเทอร์เฟซ คุณจะต้องกำหนดค่าคงที่ของอินเทอร์เฟซในสคริปต์ DOM สำหรับเว็บก่อน
รายการ 3 ตรวจสอบให้แน่ใจว่าได้กำหนดโหนดแล้ว
ถ้า (!window['โหนด']) {
window.Node = วัตถุใหม่ ();
โหนด ELEMENT_NODE = 1;
โหนด ATTRIBUTE_NODE = 2;
โหนด TEXT_NODE = 3;
โหนด CDATA_SECTION_NODE = 4;
โหนดENTITY_REFERENCE_NODE = 5;
โหนดENTITY_NODE = 6;
โหนด PROCESSING_INSTRUCTION_NODE = 7;
โหนด.COMMENT_NODE = 8;
โหนด DOCUMENT_NODE = 9;
โหนด DOCUMENT_TYPE_NODE = 10;
โหนด DOCUMENT_FRAGMENT_NODE = 11;
โหนดNOTATION_NODE = 12;
-
รายการ 4 แสดงวิธีการแยกโหนดข้อความทั้งหมดที่มีอยู่ในโหนด:
รายการ 4 ข้อความภายใน
ฟังก์ชั่นข้อความภายใน (โหนด) {
// นี่คือข้อความหรือโหนด CDATA?
ถ้า (node.nodeType == 3 || node.nodeType == 4) {
กลับ node.data;
-
วาร์ฉัน;
var returnValue = [];
สำหรับ (i = 0; i < node.childNodes.length; i ++) {
returnValue.push(ข้อความภายใน(node.childNodes[i]));
-
กลับ returnValue.join('');
-
ทางลัด
ผู้คนมักบ่นว่า DOM นั้นละเอียดเกินไป และฟังก์ชันง่ายๆ ต้องใช้โค้ดจำนวนมาก ตัวอย่างเช่น หากคุณต้องการสร้างองค์ประกอบ
รายการ 5 "เส้นทางยาว" ในการสร้าง
หากคุณสร้างโหนดด้วยวิธีนี้บ่อยๆ การพิมพ์โค้ดทั้งหมดนี้จะทำให้คุณเบื่ออย่างรวดเร็ว ต้องมีวิธีแก้ปัญหาที่ดีกว่า - และนั่นก็เป็นเช่นนั้น! นี่คือยูทิลิตี้ที่ช่วยคุณสร้างองค์ประกอบ ตั้งค่าคุณสมบัติและสไตล์ขององค์ประกอบ และเพิ่มโหนดข้อความย่อย ยกเว้นพารามิเตอร์ชื่อ พารามิเตอร์อื่นๆ ทั้งหมดเป็นทางเลือก
รายการ 6 ทางลัดองค์ประกอบองค์ประกอบ ()
องค์ประกอบฟังก์ชัน (ชื่อ attrs สไตล์ ข้อความ) {
var e = document.createElement(ชื่อ);
ถ้า (attrs) {
สำหรับ (ป้อน attrs) {
ถ้า (คีย์ == 'คลาส') {
e.className = attrs[คีย์];
} อื่นถ้า (คีย์ == 'id') {
e.id = attrs[คีย์];
} อื่น {
e.setAttribute(คีย์, attrs[คีย์]);
-
-
-
ถ้า (สไตล์) {
สำหรับ (คีย์ในรูปแบบ) {
e.style[คีย์] = สไตล์[คีย์];
-
-
ถ้า (ข้อความ) {
e.appendChild(document.createTextNode(ข้อความ));
-
กลับอี;
-
การใช้ทางลัดนี้ คุณสามารถสร้างองค์ประกอบ
รายการ 7. วิธีง่ายๆ ในการสร้าง
ยูทิลิตี้นี้สามารถช่วยคุณประหยัดเวลาได้มากเมื่อคุณต้องการสร้างวัตถุ DHTML ที่ซับซ้อนจำนวนมากอย่างรวดเร็ว รูปแบบในที่นี้หมายความว่า หากคุณมีโครงสร้าง DOM เฉพาะที่ต้องสร้างบ่อยๆ ให้ใช้ยูทิลิตีเพื่อสร้างโครงสร้างเหล่านั้น สิ่งนี้ไม่เพียงช่วยลดจำนวนโค้ดที่คุณเขียน แต่ยังช่วยลดการตัดและวางโค้ดซ้ำ ๆ (ต้นเหตุของข้อผิดพลาด) และช่วยให้คิดได้ง่ายขึ้นเมื่ออ่านโค้ด
อะไรต่อไป?
DOM มักจะมีช่วงเวลาที่ยากลำบากในการบอกคุณว่าโหนดถัดไปคืออะไรตามลำดับของเอกสาร ต่อไปนี้เป็นยูทิลิตี้บางส่วนที่จะช่วยให้คุณเดินหน้าและถอยหลังระหว่างโหนด:
รายการ 8. nextNode และ prevNode
// ส่งคืนโหนดถัดไปตามลำดับเอกสาร
ฟังก์ชั่น nextNode (โหนด) {
ถ้า (!node) กลับเป็นโมฆะ;
ถ้า (node.firstChild){
กลับโหนดfirstChild;
} อื่น {
กลับ nextWide (โหนด);
-
-
// ฟังก์ชั่นตัวช่วยสำหรับ nextNode()
ฟังก์ชั่น nextWide (โหนด) {
ถ้า (!node) กลับเป็นโมฆะ;
ถ้า (node.nextSibling) {
กลับ node.nextSibling;
} อื่น {
กลับ nextWide (node.parentNode);
-
-
// ส่งคืนโหนดก่อนหน้าตามลำดับเอกสาร
ฟังก์ชั่น prevNode (โหนด) {
ถ้า (!node) กลับเป็นโมฆะ;
ถ้า (node.previousSibling) {
กลับ PreviousDeep (node.previousSibling);
-
กลับโหนด parentNode;
-
// ฟังก์ชั่นตัวช่วยสำหรับ prevNode()
ฟังก์ชั่น PreviousDeep (โหนด) {
ถ้า (!node) กลับเป็นโมฆะ;
ในขณะที่ (node.childNodes.length) {
โหนด = node.lastChild;
-
โหนดส่งคืน;
-
ใช้ DOM ได้อย่างง่ายดาย
บางครั้งคุณอาจต้องการวนซ้ำผ่าน DOM เรียกใช้ฟังก์ชันบนแต่ละโหนด หรือส่งคืนค่าจากแต่ละโหนด เนื่องจากแนวคิดเหล่านี้เป็นเรื่องทั่วไป DOM ระดับ 2 จึงรวมส่วนขยายที่เรียกว่า DOM Traversal and Range ไว้แล้ว (ซึ่งกำหนดอ็อบเจ็กต์และ API สำหรับการวนซ้ำโหนดทั้งหมดใน DOM) ซึ่งใช้เพื่อใช้ฟังก์ชันและเลือกช่วงใน DOM . เนื่องจากฟังก์ชันเหล่านี้ไม่ได้ถูกกำหนดไว้ใน Internet Explorer (อย่างน้อยก็ยังไม่มีการกำหนดไว้) คุณสามารถใช้ nextNode() เพื่อทำสิ่งที่คล้ายกันได้
แนวคิดนี้คือการสร้างเครื่องมือทั่วไปที่เรียบง่าย จากนั้นประกอบเข้าด้วยกันด้วยวิธีต่างๆ เพื่อให้ได้ผลลัพธ์ตามที่ต้องการ หากคุณคุ้นเคยกับ Functional Programming ก็จะดูคุ้นเคย ไลบรารี Beyond JS (ดูแหล่งข้อมูล) นำแนวคิดนี้ไปข้างหน้า
รายการ 9. ยูทิลิตี้ DOM ที่ใช้งานได้
// ส่งคืน Array ของโหนดทั้งหมด เริ่มต้นที่ startNode และ
// ดำเนินการต่อไปยังส่วนที่เหลือของแผนผัง DOM
ฟังก์ชั่น listNodes (startNode) {
รายการ var = อาร์เรย์ใหม่ ();
โหนด var = startNode;
ในขณะที่ (โหนด) {
list.push (โหนด);
โหนด = โหนดถัดไป (โหนด);
-
รายการส่งคืน;
-
// เช่นเดียวกับ listNodes() แต่ทำงานย้อนกลับจาก startNode
// โปรดทราบว่านี่ไม่เหมือนกับการรัน listNodes() และ
//กลับรายการ
ฟังก์ชั่น listNodesReversed (startNode) {
รายการ var = อาร์เรย์ใหม่ ();
โหนด var = startNode;
ในขณะที่ (โหนด) {
list.push (โหนด);
โหนด = ก่อนหน้าโหนด (โหนด);
-
รายการส่งคืน;
-
// ใช้ func กับแต่ละโหนดใน nodeList ส่งคืนรายการผลลัพธ์ใหม่
แผนผังฟังก์ชัน (รายการ func) {
var result_list = อาร์เรย์ใหม่ ();
สำหรับ (var i = 0; i < list.length; i++) {
result_list.push(func(รายการ[i]));
-
กลับ result_list;
-
// ใช้การทดสอบกับแต่ละโหนด ส่งคืนรายการโหนดใหม่ที่ต้องการ
// test(node) คืนค่าเป็นจริง
ตัวกรองฟังก์ชัน (รายการ, ทดสอบ) {
var result_list = อาร์เรย์ใหม่ ();
สำหรับ (var i = 0; i < list.length; i++) {
ถ้า (ทดสอบ (รายการ [i])) result_list.push (รายการ [i]);
-
กลับ result_list;
-
รายการ 9 มีเครื่องมือพื้นฐานสี่อย่าง ฟังก์ชัน listNodes() และ listNodesReversed() สามารถขยายเป็นความยาวเสริมได้ คล้ายกับเมธอด Slice() ของ Array ฉันปล่อยให้สิ่งนี้เป็นแบบฝึกหัดสำหรับคุณ สิ่งที่ควรทราบอีกประการหนึ่งคือฟังก์ชัน map() และ filter() นั้นเป็นฟังก์ชันทั่วไปโดยสมบูรณ์ และสามารถใช้เพื่อทำงานกับรายการใดก็ได้ (ไม่ใช่แค่รายการของโหนด) ตอนนี้ฉันจะแสดงให้คุณเห็นวิธีการต่างๆ ที่สามารถนำมารวมกันได้
รายชื่อ 10 การใช้ยูทิลิตี้ที่ใช้งานได้
// รายการชื่อองค์ประกอบทั้งหมดตามลำดับเอกสาร
ฟังก์ชั่น isElement (โหนด) {
กลับ node.nodeType == Node.ELEMENT_NODE;
-
ฟังก์ชั่น nodeName (โหนด) {
กลับ node.nodeName;
-
var elementNames = map (ตัวกรอง (listNodes (เอกสาร), isElement), nodeName);
// ข้อความทั้งหมดจากเอกสาร (ละเว้น CDATA)
ฟังก์ชั่น isText (โหนด) {
กลับ node.nodeType == Node.TEXT_NODE;
-
ฟังก์ชั่น nodeValue (โหนด) {
กลับ node.nodeValue;
-
var allText = map (ตัวกรอง (listNodes (เอกสาร), isText), nodeValue);
คุณสามารถใช้ยูทิลิตี้เหล่านี้เพื่อแยก ID แก้ไขสไตล์ ค้นหาโหนดบางโหนดและลบออก และอื่นๆ อีกมากมาย เมื่อมีการปรับใช้ DOM Traversal และ Range API อย่างกว้างขวาง คุณจะสามารถใช้เพื่อแก้ไขแผนผัง DOM โดยไม่ต้องสร้างรายการก่อน ไม่เพียงแต่มีประสิทธิภาพเท่านั้น แต่ยังทำงานในลักษณะเดียวกันกับที่ฉันเน้นไว้ข้างต้นอีกด้วย
โซนอันตรายของ DOM
โปรดทราบว่า DOM API หลักไม่ได้ช่วยให้คุณสามารถแยกวิเคราะห์ข้อมูล XML ลงใน DOM หรือทำให้ DOM เป็นอนุกรมลงใน XML ฟังก์ชันเหล่านี้ถูกกำหนดไว้ในส่วนขยาย DOM ระดับ 3 "โหลดและบันทึก" แต่ยังใช้งานไม่เต็มที่ ดังนั้น อย่าเพิ่งคิดถึงฟังก์ชันเหล่านี้ในตอนนี้ แต่ละแพลตฟอร์ม (เบราว์เซอร์หรือแอปพลิเคชัน DOM ระดับมืออาชีพอื่นๆ) มีวิธีการแปลงระหว่าง DOM และ XML ของตัวเอง แต่การแปลงข้ามแพลตฟอร์มอยู่นอกเหนือขอบเขตของบทความนี้
DOM ไม่ใช่เครื่องมือที่ปลอดภัย โดยเฉพาะเมื่อใช้ DOM API เพื่อสร้างแผนผังที่ไม่สามารถซีเรียลไลซ์เป็น XML ได้ อย่าผสม DOM1 non-namespace APIs และ DOM2 namespace-aware APIs (เช่น createElement และ createElementNS) ในโปรแกรมเดียวกัน หากคุณใช้เนมสเปซ ให้พยายามประกาศเนมสเปซทั้งหมดที่ตำแหน่งองค์ประกอบรูท และอย่าแทนที่คำนำหน้าเนมสเปซ ไม่เช่นนั้นสิ่งต่างๆ จะทำให้เกิดความสับสนอย่างมาก โดยทั่วไปแล้ว ตราบใดที่คุณปฏิบัติตามกิจวัตรประจำวัน คุณจะไม่กระตุ้นให้เกิดสถานการณ์สำคัญที่อาจทำให้คุณเดือดร้อนได้
หากคุณใช้ InnerText และ InnerHTML ของ Internet Explorer ในการแยกวิเคราะห์ คุณสามารถลองใช้ฟังก์ชัน elem() ด้วยการสร้างยูทิลิตี้ที่คล้ายกัน คุณจะได้รับความสะดวกมากขึ้นและสืบทอดคุณประโยชน์ของโค้ดข้ามแพลตฟอร์ม การผสมทั้งสองวิธีนี้แย่มาก
อักขระ Unicode บางตัวไม่รวมอยู่ใน XML การใช้งาน DOM ช่วยให้คุณสามารถเพิ่มได้ แต่ผลที่ตามมาคือไม่สามารถทำให้เป็นอนุกรมได้ อักขระเหล่านี้ประกอบด้วยอักขระควบคุมส่วนใหญ่และอักขระแต่ละตัวในคู่ตัวแทน Unicode สิ่งนี้จะเกิดขึ้นเฉพาะเมื่อคุณพยายามรวมข้อมูลไบนารี่ไว้ในเอกสาร แต่นั่นเป็นอีกสถานการณ์หนึ่ง
บทสรุป
ฉันได้กล่าวถึงสิ่งต่างๆ มากมายที่ DOM สามารถทำได้ แต่ยังมีอะไรอีกมากมายที่ DOM (และ JavaScript) สามารถทำได้ ศึกษาและสำรวจตัวอย่างเหล่านี้เพื่อดูว่าสามารถใช้เพื่อแก้ไขปัญหาที่อาจต้องใช้สคริปต์ไคลเอ็นต์ เทมเพลต หรือ API พิเศษได้อย่างไร
DOM มีข้อจำกัดและข้อบกพร่อง แต่ก็มีข้อดีหลายประการเช่นกัน โดยสร้างไว้ในแอปพลิเคชันต่างๆ มากมาย โดยทำงานในลักษณะเดียวกันไม่ว่าคุณจะใช้เทคโนโลยี Java, Python หรือ JavaScript ก็ตาม ซึ่งทั้งเรียบง่ายและมีประสิทธิภาพในการใช้งาน แอปพลิเคชันจำนวนมากขึ้นเริ่มรองรับ DOM รวมถึงแอปพลิเคชันที่ใช้ Mozilla, OpenOffice และ XMetaL ของ Blast Radius ข้อกำหนดจำเพาะเพิ่มมากขึ้นเรื่อยๆ จำเป็นต้องมี DOM และขยายออกไป (เช่น SVG) ดังนั้น DOM จึงอยู่รอบตัวคุณเสมอ คุณควรใช้เครื่องมือที่ใช้งานกันอย่างแพร่หลายนี้