By valentinogagliardi
Translator: front-end wit
Source: github
In recent days, I have no way to send 200 + cash to my public account. The way of participation is as follows:
https://mp.weixin.qq.com/s/PT...
Document object model (DOM)
JS there are many places for us to Tucao, but not so bad. As a script language running in browser, it is very useful for processing web pages. In this article, we'll see how we can interact and modify HTML documents and their elements. But first let's unravel the mystery of the document object model.
Document object model is a basic concept, which is the basis of all our work in browser. But what is that? When we visit a web page, the browser will point out how to interpret each HTML element. This creates a virtual representation of the HTML document and saves it in memory. The HTML page is converted into a tree structure, and each HTML element becomes a leaf that connects to the parent branch. Consider this simple HTML page:
<!DOCTYPE html> <html lang="en"> <head> <title>A super simple title!</title> </head> <body> <h1>A super simple web page!</h1> </body> </html
When the browser scans the html above, it creates a document object model, which is a mirror image of the html structure. At the top of the structure is a document, also known as the root element, which contains another element: html. The html element contains a head, which has a title. Then there's the body with h1. Each html element is represented by a specific type (also known as an interface) and may contain text or other nested elements
document (HTMLDocument) | | --> html (HTMLHtmlElement) | | --> head (HtmlHeadElement) | | | | --> title (HtmlTitleElement) | | --> text: "A super simple title!" | | --> body (HtmlBodyElement) | | | | --> h1 (HTMLHeadingElement) | | --> text: "A super simple web page!"
Each HTML element is from the Element Derived, but a large part of them are further specialized. We can check the prototype to find out what kind of elements belong to. For example, the h1 element is an HTMLHeadingElement
document.quertSelector('h1').__proto__ // Output: HTMLHeadingElement
HTMLHeadingElement
againHTMLElement
"Descendants" ofdocument.querySelector('h1').__proto__.__proto__ // Output: HTMLElement
Element is a very general base class, from which all objects under Document objects inherit. This interface describes the methods and attributes that are common to all elements of the same kind. Some interfaces inherit from element and add some additional functions to describe specific behaviors. For example, the HTML element interface is the basic interface for all HTML elements, while the SVGElement interface is the basis for all SVG elements. Most of the functionality is further developed in the hierarchy interface of this class.
At this point (especially for beginners), there may be some confusion between document and window. Window refers to the browser, while document refers to the current HTML page. Window is a global object that can be accessed directly from any JS code running in the browser. It is not a "native" object of JS, but is exposed by the browser itself. Window has many properties and methods, as shown below:
window.alert('Hello world'); // Shows an alert window.setTimeout(callback, 3000); // Delays execution window.fetch(someUrl); // makes XHR requests window.open(); // Opens a new tab window.location; // Browser location window.history; // Browser history window.navigator; // The actual device window.document; // The current page
Because these attributes are global, you can also omit window:
alert('Hello world'); // Shows an alert setTimeout(callback, 3000); // Delays execution fetch(someUrl); // makes XHR requests open(); // Opens a new tab location; // Browser location history; // Browser history navigator; // The actual device document; // The current page
You should be familiar with some of these methods, such as setTimeout() or window.navigator, which can get the language of the current browser:
if (window.navigator) { var lang = window.navigator.language; if (lang === "en-US") { // show something } if (lang === "it-IT") { // show something else } }
To learn more about methods on window s, see MDN document . In the next section, let's take a closer look at DOM.
Nodes, elements, and DOM operations
The document interface has many practical methods, such as querySelector(), which is used to select any HTML element in the current HTML page:
document.querySelector('h1');
Window represents the browser of the current window. The following instructions are the same as above:
window.document.querySelector('h1');
Anyway, the following syntax is more common, and we will use it extensively in the next section:
document.methodName();
In addition to querySelector() for selecting HTML elements, there are many more useful methods
// Return single element document.getElementById('testimonials'); // Return an HTML collection document.getElementsByTagName('p'); // Returns a list of nodes document.querySelectorAll('p');
We can not only select HTML elements, but also interact and modify their internal state. For example, you want to read or change the internal content of a given element:
// Read or write document.querySelector('h1').innerHtml; // Read document.querySelector('h1').innerHtml = ''; // Write! Ouch!
Every HTML element in DOM is also a "node". In fact, we can check the node type like this:
document.querySelector('h1').nodeType;
The above result returns 1, which represents the identifier of the node of type Element. We can also check the node name:
document.querySelector('h1').nodeName; "H1"
Here, the node name is returned in uppercase. Usually we deal with four types of nodes in DOM
- document: root node (nodeType 9)
- Nodes of type Element: actual HTML tags (nodeType 1), such as < p > and < div >
- Node of type attribute: attribute (attribute) of each HTML element
- Text type node: the actual text content of the element (nodeType 3)
Since the element is a node, the node can have properties (also known as attributes). We can check and operate these properties:
// Return true or false document.querySelector('a').hasAttribute('href'); // Return property text content, or null document.querySelector('a').getAttribute('href'); // Set the given property document.querySelector('a').setAttribute('href', 'someLink');
As we said earlier, DOM is a tree like structure. This feature is also reflected in HTML elements. Each element may have a parent element and a child element. We can check some attributes of the element to see them:
// Return an HTML collection document.children; // Returns a list of nodes document.childNodes; // Return to a node document.querySelector('a').parentNode; // Return HTML elements document.querySelector('a').parentElement;
Learned how to select and query HTML elements. What about creating elements? To create a new node of Element type, the native DOM API provides the createElement method:
var heading = document.createElement('h1');
To create a text node using createTextNode:
var text = document.createTextNode('Hello world');
You can combine the two nodes by attaching text to a new HTML element. Finally, you can attach the heading element to the root document:
var heading = document.createElement('h1'); var text = document.createTextNode('Hello world'); heading.appendChild(text); document.body.appendChild(heading);
You can also use the remove() method to remove nodes from the DOM. When a method is called on an element, the node disappears from the page:
document.querySelector('h1').remove();
These are all we need to know to start using JS to manipulate DOM in browsers. In the next section, we will use DOM flexibly, but first we need to make a detour, because we need to discuss "DOM events".
DOM and events
DOM elements are smart. They can contain not only text and other HTML elements, but also "emit" and respond to "events." Browse any website and open the browser console. Use the following command to select an element:
document.querySelector('p')
Look at this property
document.querySelector('p').onclick
What type is it:
typeof document.querySelector('p').onclick // "object"
"object"! Why is it called "onclick"?
If you are interested, you can view the prototype chain of any HTML Element. It will be found that each Element is also an Element, while the Element is a node, and the node is an EventTarget. You can use instanceof to verify this.
document.querySelector('p') instanceof EventTarget // true
I'd love to call EventTarget the father of all HTML elements, but there's no real inheritance in JS, it's more like any HTML element can see the properties of another connection object. Therefore, any HTML element has the same feature as EventTarget: the ability to publish events.
But what is an event? Take HTML buttons. If you click on it, it's an event. With this. onclick object, we can register the event, as long as the element is clicked, it will run. The function passed to an event is called an event listener or an event handle.
Events and listening
There are three ways to register event listeners in the DOM. The first form is older and should be avoided because it is coupled with logical operations and Tags
<! -- bad way -- > < button onclick = "console. Log ('clicked ')" > like it, just click me < / button >
The second option depends on the object named after the event. For example, we can listen to the click event by registering a function on the object. onclick:
document.querySelector("button").onclick = handleClick; function handleClick() { console.log("Clicked!"); }
This syntax is more concise and is a good alternative to inline handlers. There is another modern form based on addEventListener:
document.querySelector("button").addEventListener("click", handleClick); function handleClick() { console.log("Clicked!"); }
Personally, I prefer this form, but if you want to maximize browser compatibility, please use the. on mode. Now that we have an HTML element and an event listener, let's take a closer look at DOM events.
Event objects, event defaults, and event bubbling
Each function passed as an event handler receives an object named "event" by default
var button = document.querySelector("button"); button.addEventListener("click", handleClick); function handleClick() { console.log(event); }
It can be used directly in the function body, but in my code, I prefer to explicitly declare it as a parameter:
function handleClick(event) { console.log(event); }
The event object is "necessary", because we can control the behavior of the event by calling some methods on the event. Events actually have specific characteristics, especially "default" and "bubbling." Consider an HTML link. Create a new HTML file called click-event.html with the following tags:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Click event</title> </head> <body> <div> <a href="/404.html">click me!</a> </div> </body> <script src="click-event.js"></script> </html>
Run the file in the browser and try clicking the link. It will jump to a 404 interface. The default behavior for a click event on a link is to go to the actual page specified in the href attribute. But what if I told you there was a way to block the default? Type preventDefault(), which can be used for event objects. Use the following code to create a new file called click-event.js:
var button = document.querySelector("a"); button.addEventListener("click", handleClick); function handleClick(event) { event.preventDefault(); }
Refresh the page in the browser and try clicking the link now: it won't jump. Because we blocked the browser's "event default" link is not the only HTML element of the default operation, the form has the same features.
Another interesting feature also occurs when HTML elements are nested within another element. Consider the following HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Nested events</title> </head> <body> <div id="outer"> I am the outer div <div id="inner"> I am the inner div </div> </div> </body> <script src="nested-events.js"></script> </html>
And the following JS code:
// nested-events.js var outer = document.getElementById('inner'); var inner = document.getElementById('outer'); function handleClick(event){ console.log(event); } inner.addEventListener('click', handleClick); outer.addEventListener('click', handleClick);
There are two event listeners, one for the external div and one for the internal Div. Click on the internal div exactly and you will see:
Two event objects are printed. That's what event bubbles are doing. It looks like a bug in browser behavior that can be disabled using the stopPropagation() method, which is also called on the event object
// function handleClick(event) { event.stopPropagation(); console.log(event); } ///
Although it looks like a poor implementation, bubbling can be a real bonus when registering too many event listeners is really bad for performance. Consider the following example:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Event bubbling</title> </head> <body> <ul> <li>one</li> <li>two</li> <li>three</li> <li>four</li> <li>five</li> </ul> </body> <script src="event-bubbling.js"></script> </html>
How many event listeners do you need to register in the list if you want to listen to the click events in the list? The answer is: one. You only need a listener registered on ul to intercept all the clicks on any li:
// event-bubbling.js var ul = document.getElementsByTagName("ul")[0]; function handleClick(event) { console.log(event); } ul.addEventListener("click", handleClick);
As you can see, event bubbling is a practical way to improve performance. In fact, registering event listeners is an expensive operation for browsers, and in the case of a large number of element lists, performance may be lost.
Generating tables with JS
Now let's start coding. Given an array of objects, you want to generate an HTML table dynamically. HTML tables are represented by < Table > elements. Each table can also have a header, represented by the < thead > element. The header can have one or more rows < tr >, each with a cell, represented by a < th > element. As follows:
<table> <thead> <tr> <th>name</th> <th>height</th> <th>place</th> </tr> </thead> <!-- more stuff here! --> </table>
More than that, in most cases, each table has a body defined by < tbody > and < tbody > contains a set of rows < tr >. Each row can have cells that contain actual data. Table cells are defined by < td >. Complete as follows:
<table> <thead> <tr> <th>name</th> <th>height</th> <th>place</th> </tr> </thead> <tbody> <tr> <td>Monte Falco</td> <td>1658</td> <td>Parco Foreste Casentinesi</td> </tr> <tr> <td>Monte Falterona</td> <td>1654</td> <td>Parco Foreste Casentinesi</td> </tr> </tbody> </table>
The task now is to generate the table from the JS object array. First, create a new file named build-table.html. The content is as follows:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Build a table</title> </head> <body> <table> <!-- here goes our data! --> </table> </body> <script src="build-table.js"></script> </html>
Create another file named build-table.js in the same folder and start with the following array:
"use strict"; var mountains = [ { name: "Monte Falco", height: 1658, place: "Parco Foreste Casentinesi" }, { name: "Monte Falterona", height: 1654, place: "Parco Foreste Casentinesi" }, { name: "Poggio Scali", height: 1520, place: "Parco Foreste Casentinesi" }, { name: "Pratomagno", height: 1592, place: "Parco Foreste Casentinesi" }, { name: "Monte Amiata", height: 1738, place: "Siena" } ];
Consider this form. First of all, we need a < thead >:
document.createElement('thead')
It's not wrong, but look carefully Table document for MDN You'll find an interesting detail. < Table > is a HTMLTableElement , which also contains interesting methods. The most useful one is htmltableelement. Createhead (), which can help us create the < thead > we need.
First, write a function to generate the ad tag generateTableHead
function generateTableHead(table) { var thead = table.createTHead(); }
This function takes a selector and creates a < thead > on a given table:
function generateTableHead(table) { var thead = table.createTHead(); } var table = document.querySelector("table"); generateTableHead(table);
Open build table.html in the browser: nothing. However, if you open the browser console, you can see a new < thead > attached to the table.
Then fill in the header content. First, create a row in it. There is another way to help: HTMLTableElement.insertRow(). With this, we can expand our approach:
function generateTableHead (table) { var thead = table,createThead(); var row = thead.insertRow(); }
At this point, we can generate our lines. By looking at the source array, you can see that any object has the information we need:
var mountains = [ { name: "Monte Falco", height: 1658, place: "Parco Foreste Casentinesi" }, { name: "Monte Falterona", height: 1654, place: "Parco Foreste Casentinesi" }, { name: "Poggio Scali", height: 1520, place: "Parco Foreste Casentinesi" }, { name: "Pratomagno", height: 1592, place: "Parco Foreste Casentinesi" }, { name: "Monte Amiata", height: 1738, place: "Siena" } ];
This means that we can pass another parameter to our function: an array traversing to generate header cells:
function generateTableHead(table, data) { var thead = table.createTHead(); var row = thead.insertRow(); for (var i = 0; i < data.length; i++) { var th = document.createElement("th"); var text = document.createTextNode(data[i]); th.appendChild(text); row.appendChild(th); } }
Unfortunately, there is no native way to create cells, so turn to document.createElement("th"). It is also worth noting that document.createTextNode(data[i]) is used to create text nodes and appendChild() is used to add new elements to each tag.
When you create and manipulate elements in this way, we call them "imperative" DOM operations. Modern front-end libraries solve this problem by supporting a "declarative" approach. Instead of ordering the browser step by step, we can declare which HTML elements we need, and the rest is handled by the library.
Back in our code, you can use the first function as follows
var table = document.querySelector("table"); var data = Object.keys(mountains[0]); generateTableHead(table, data);
Now we can further generate the data of the actual table. The next function will implement a logic similar to generateTableHead, but this time we need two nested for loops. In the innermost loop, another native method is used to create a series of td's. The method is HTMLTableRowElement.insertCell(). Add another function named generateTable to the previously created file
function generateTable(table, data) { for (var i = 0; i < data.length; i++) { var row = table.insertRow(); for (var key in data[i]) { var cell = row.insertCell(); var text = document.createTextNode(data[i][key]); cell.appendChild(text); } } }
Call the above function to pass the HTML table and object array as parameters:
generateTable(table, mountains);
Let's delve into the logic of generateTable. Parameter data is an array corresponding to mountains. The outermost for loop traverses the array and creates a row for each element:
function generateTable(table, data) { for (var i = 0; i < data.length; i++) { var row = table.insertRow(); // omitted for brevity } }
The innermost loop traverses each key of any given object and creates a text node containing key values for each object
function generateTable(table, data) { for (var i = 0; i < data.length; i++) { var row = table.insertRow(); for (var key in data[i]) { // inner loop var cell = row.insertCell(); var text = document.createTextNode(data[i][key]); cell.appendChild(text); } } }
Final code:
var mountains = [ { name: "Monte Falco", height: 1658, place: "Parco Foreste Casentinesi" }, { name: "Monte Falterona", height: 1654, place: "Parco Foreste Casentinesi" }, { name: "Poggio Scali", height: 1520, place: "Parco Foreste Casentinesi" }, { name: "Pratomagno", height: 1592, place: "Parco Foreste Casentinesi" }, { name: "Monte Amiata", height: 1738, place: "Siena" } ]; function generateTableHead(table, data) { var thead = table.createTHead(); var row = thead.insertRow(); for (var i = 0; i < data.length; i++) { var th = document.createElement("th"); var text = document.createTextNode(data[i]); th.appendChild(text); row.appendChild(th); } } function generateTable(table, data) { for (var i = 0; i < data.length; i++) { var row = table.insertRow(); for (var key in data[i]) { var cell = row.insertCell(); var text = document.createTextNode(data[i][key]); cell.appendChild(text); } } }
Which calls:
var table = document.querySelector("table"); var data = Object.keys(mountains[0]); generateTable(table, mountains); generateTableHead(table, data);
Execution result:
Of course, our method should be improved. We will introduce it in the next chapter.
summary
DOM is a virtual copy of a web page saved in memory by a web browser. DOM operation refers to the operation of creating, modifying and deleting HTML elements from dom. In the past, we used to rely on jQuery for simpler tasks, but now the native API is mature enough to make jQuery obsolete. On the other hand, jQuery won't disappear soon, but every JS developer must know how to manipulate DOM using native APIs.
There are many reasons to do this. The extra libraries increase the load time and the size of JS applications. Needless to say, DOM operations often appear in interviews.
Each available HTML element in the DOM has an interface that exposes a certain number of attributes and methods. When you have questions about which method to use, Refer to MDN documentation . The most common way to manipulate DOM is to use document.createElement() to create new HTML elements, and document.createTextNode() to create text nodes in DOM. Last but not least. appendchild(), which is used to attach new HTML elements or text nodes to existing elements.
HTML elements can also emit events, also known as DOM events. Notable events are "click", "submit", "drag", "drop", and so on. DOM events have special behaviors, such as "default" and bubbling.
JS developers can take advantage of these properties, especially for event bubbling, which are very useful for accelerating event handling in DOM. While a good understanding of native API s is a good thing, modern front-end libraries offer undoubted benefits. It is feasible to build a large JS application with Angular, React and Vue.
The bugs that may exist after code deployment can't be known in real time. In order to solve these bugs afterwards, a lot of time has been spent on log debugging. Here is a good BUG monitoring tool recommended by the way. Fundebug.
Original text: https://github.com/valentinog...
Communication
Alibaba cloud has been doing activities recently, with a discount of 20%. If you are interested, please take a look at: https://promotion.aliyun.com/...
The articles of dry goods series are summarized as follows. I think it's a good idea to order a Star. Welcome to learn from each other.
https://github.com/qq44924588...
Because of the space limitation, today's sharing is only here. If you want to know more, you can scan the QR code at the bottom of each article, and then pay attention to our wechat public account to learn more information and valuable content.
I don't go to bed until 2 o'clock every time I sort out the articles. It's very painful. I hope I can support you and encourage you.