本文介绍使用JavaScript对浏览器和页面编程的基本方法,包括BOM对象、DOM对象,以及计时器函数的应用。
BOM和DOM
通过BOM(浏览器对象模式,Browser Object Model),可以对浏览器窗口、URL地址、导航、浏览历史、显示设备、页面内容等操作。其中,window对象表示浏览器窗口,是BOM中的主对象,其子对象包括:
document对象,表示页面对象,是DOM的主角,也就是说,DOM是BOM的子集。
location对象,处理URL信息。
navigator对象,包含了浏览器信息。
history对象,控制浏览记录,如forward()方法为前进操作、back()方法为后退操作、go()方法指定前进(正数)或后退(负数)的浏览记录数量。
screen对象,包含了用户显示设备的信息。
实际应用中,随意修改浏览器外观和默认行为并不是好的设计方法,现代Web项目开发中几乎不再使用;本文接下来将主要讨论这些对象的常用操作。
页面节点
DOM(文档对象模型,Document Object Model)可以有效地处理HTML、XHTML等文档结构,下面先来看一个简单的HTML文件代码。
HTML |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <div id="article"> <h1 id="title1">标题一</h1> <p id="p1">段落一</p> <p id="p2">段落二</p> </div> </body> </html> |
页面中的HTML元素一目了然,但现在需要了解页面中的“节点(node)”。在</html>标记后面添加如下代码。
JavaScript |
<script> let art = document.getElementById("article"); alert(art.childNodes.length); </script> |
即使还没有介绍代码中的doucment.getElementById()方法和art.childNodes.length属性,也应该可以大概猜出来这两行代码的功能是显示id为article的div元素的子节点数量,显示结果为7。在HTML代码中可以看到,div元素下只有一个h1元素和两个元素,那么7个节点是从哪儿来的呢?请注意,在h1和p元素的前后还有空白处,这里也是节点;这些节点不是元素,那它们是什么呢?
按下来,修改JavaScript代码如下。
JavaScript |
<script> let art = document.getElementById("article"); let node = art.childNodes[0]; alert(node.nodeName); alert(node.nodeValue); alert(node.nodeType); </script> |
代码中,使用art.childNodes[0]获取了div元素下的第一个子节点,然后显示了节点的三个属性,分别是:
nodeType属性,显示节点类型值,1(ELEMENT_NODE)为页面元素,3(TEXT_NODE)为文本节点,本例会显示3。
nodeName属性,显示节点名称,本例显示为"#text"。如果是HTML元素,则会显示元素名的大写形式。
nodeValue属性,显示文本类节点的内容,非文本类节点返回null,本例为空白字符。DOM节点类型中,3(TEXT_NODE)、4(CDATA_NODE)、8(COMMENT_NODE)为文本类型的节点。
将art.childNodes[0]的索引值修改为1,可以看到h1元素的相关属性,会分别显示"H1"、null和1。可以看到,元素节点的nodeType返回1,nodeName属性会返回元素名的大写形式,nodeValue属性返回null值。
从测试中可以看到,代码中定义的页面,从div元素以下的节点结构如下图所示。

#图#页面节点结构
虽然页面文档的结构是由各种节点组成,但在操作时一般会从一个确定的元素节点开始,下面就从元素节点的角度了解更多地操作。
获取和创建元素
获取元素时,可以使用document对象的以下方法和属性:
getElementById()方法,按元素的id属性值获取唯一的元素对象。
getElementsByClassName()方法,按元素的class属性值获取元素集合。
getElementsByName()方法,按元素的name属性值获取元素集合。
getElementsByTagName()方法,按元素类型名称获取元素集合,如所有P元素。
元素集合属性,如images表示页面中所有img元素的集合,links表示页面中所包含href属性的a元素集合,forms表示页面中所有表单(from)元素的集合,embeds表示包含所有embed元素的集合等。
对于一个已获取的节点对象,常用的属性和方法包括:
parentNode属性,获取节点的上级节点。
hasChildNodes()方法,判断节点中是否有子节点,返回true或false。
firstChild属性,获取节点中的第一个子节点,没有子节点时返回undefined。
lastChild属性,获取节点中的最后一个子节点,没有子节点时返回undefined。
childNodes属性,返回节点中所有子节点的集合。
appendChild(node)方法,在节点中添加子节点node。
insertBefore(x,y)方法,在节点的子节点y前插入子节点x。
创建节点时可以使用document对象的如下方法:
createElement(elementName),根据元素名称创建元素节点。
createTextNode(text)方法,根据文本内容创建文本节点。
下面的代码会在“段落二”前添加“段落三”元素节点。
JavaScript |
<script> let p3 = document.createElement("p"); let p3Text = document.createTextNode("段落三"); p3.appendChild(p3Text); let p2 = document.getElementById("p2"); let art = document.getElementById("article"); art.insertBefore(p3, p2); </script> |
运行代码,页面显示效果如下图所示。

#图#动态创建节点
利用p2元素的父节点也可以完成插入操作,如下面的代码。
JavaScript |
<script> let p3 = document.createElement("p"); let p3Text = document.createTextNode("段落三"); p3.appendChild(p3Text); let p2 = document.getElementById("p2"); p2.parentNode.insertBefore(p3, p2); </script> |
执行结果与前例相同。
元素的innerHTML和innerText属性
实际应用中,还可以使用innerHTML和innerText属性修改元素中的HTML代码或文本内容。下面的代码演示了这两个属性的应用和区别。
JavaScript |
<script> let s = "X<sup>2</sup>"; let p1 = document.getElementById("p1"); let p2 = document.getElementById("p2"); p1.innerHTML = s; p2.innerText = s; </script> |
代码中,变量s包含了一些HTML代码,内容为大写字母X和上标格式的数字2。接下来,通过innerHTML属性将s的内容设置到p1元素,通过innerText属性将s的内容设置到p2元素,页面显示效果如下图所示。

#图#元素的innerHTML和innerText属性
可以看到,innerHTML属性内容会解析为HTML代码,而innerText属性内容则解析为文本内容。此外,还可以通过这两个属性读取元素中的HTML代码和文本内容。
读取和设置元素属性
读取元素的属性值时,可以使用元素对象的getAttribute()方法,其参数为属性名,如下面的代码会显示页面中第一个h1元素的id属性名。
JavaScript |
<script> let arr = document.getElementsByTagName("h1"); let e = arr[0]; alert(e.getAttribute("id")); </script> |
执行代码会显示"title1"。
设置元素对象的属性值时可以使用元素对象的setAttribute()方法,参数分别是属性名和属性值,如下面的代码会设置第一个h1元素的style属性以改变其显示的样式。
JavaScript |
<script> let arr = document.getElementsByTagName("h1"); let e = arr[0]; e.setAttribute("style","font-style:italic;color:red;"); </script> |
代码中设置第一个h1元素文本显示为斜体和红色,效果如下图所示。

#图#设置元素的style属性
可以通过元素对象的style属性中的样式属性设置元素样式,如下面的代码。
JavaScript |
<script> let arr = document.getElementsByTagName("h1"); let e = arr[0]; e.style.fontStyle = "italic"; e.style.color = "red"; </script> |
代码执行效果与前例相同。
window对象
首先了解window对象的open()方法与close()方法,其中,open()方法用于打开新的浏览器窗口,其参数包括:
url,设置打开的资源路径。
target,设置资源的打开方式,默认为"_blank",指定在新的窗口或标签中打开;设置为"_self"时指定在当前窗口或标签打开。
features,可以通过一系列参数设置新窗口的外观,一般不需要使用。
replace,是否替换浏览器历史记录,默认为false。
window.open()方法会返回一个窗口对象,而窗体对象的close()方法可以关闭窗口。下面的代码演示了相关应用。
HTML |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <div id="article"> <button type="button" onclick="winOpen();">打开</button> <button type="button" onclick="winClose();">关闭</button> </div> </body> </html> <script> let win = null; // function winOpen() { win = window.open("http://caohuayu.com"); } // function winClose() { if (win === null) alert("没有可关闭的窗口"); else win.close(); } </script> |
页面中定义了两个按钮,其中,“打开”按钮会在新窗口或标签中打开作者的个人网站,回到初始页面,点击“关闭”按钮可以关闭打开的新网站窗口或标签。
此外,调用window对象的close()方法可以关闭当前窗口标签。
window对象的onload事件会在页面元素完全加载后触发,所以,这里是进行页面初始化工作的好地方,下面的代码演示了相关应用。
JavaScript |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <h1 id="title1"></h1> </body> </html> <script> window.onload = function () { document.getElementById("title1").innerText = "页面已加载"; }; </script> |
本例,页面加载后会在h1元素中显示“页面已加载”。
有些时间,特别是在大型Web项目中,一个页面的初始化工作可能不止一次,此时就需要一个通用的机制能够正确地添加多个初始化函数,如下面的代码。
JavaScript |
<script> function addWinLoadFunc(fn) { if (typeof fn !== "function") return; let oldFn = window.onload; window.onload = function () { if (typeof oldFn === "function") oldFn(); fn(); }; } // function pageInit1() { alert("初始化1"); } // addWinLoadFunc(pageInit1); addWinLoadFunc(function () { alert("初始化2"); }); addWinLoadFunc(() => alert("初始化3")); </script> |
代码中首先定义了addWinLoadFunc()函数,参数fn指定为函数类型。addWinLoadFunc()函数中,当参数fn不是函数类型时会退出函数;然后,通过oldFn变量备份window.onload事件的原始代码;接下来定义window.onload事件执行的新函数,其中,当onload原始代码为函数时先调用它,最后调用fn指定的函数,这样就保证了初始化代码的执行顺序。
pageInit1()函数为第一个初始化函数,第一次调用addWinLoadFunc()函数时直接使用函数名作为参数;第二次调用addWinLoadFunc()函数时使用了匿名函数;第三次调用addWinLoadFunc()函数时使用了=>运算符创建的简化函数。打开页面,会依次显示"初始化1"、"初始化2"、"初始化3"。
URL与文本编码
location对象可以处理地址栏显示的URL地址信息,常用的属性包括:
href属性,获取地址栏显示的完整的URL地址。
host和hostname,获取URL中的服务器主机名称。
pathname属性,获取服务器中的资源路径。
port属性,获取URL中的服务器端口。
protocol属性,获取URL中使用的协议,如http:、https:。
search属性,获取问号(?)及以后的查询参数。
hash属性,获取#符号及以后的内容。
下面的代码会显示相关的属性值,请注意,打开测试页面后可以在网址的最后手动添加"?name=Tom&age=25"。
JavaScript |
<script> alert(location.href); // http://localhost:58476/demo/test.html?name=Tom&age=25 alert(location.host); // localhost:58476 alert(location.pathname);// /demo/test.html alert(location.port);// 58476 alert(location.protocol);// http: alert(location.search);// ?name=Tom&age=25 </script> |
处理问号(?)后面的参数时,可删除问号,然后使用&符号分割成数组,数组中的每一个元素就是“参数名=值”的格式,需要再次使用等号(=)分割为参数名和值。下面的代码可以将URL中的查询参数转换为Map对象。
JavaScript |
<script> function getQueryParam() { let result = new Map(); let s = location.search; if (s.length === 0) return result; let arr = s.substring(1).split(/&/g); let kvArr; for (let kv of arr) { kvArr = kv.split("="); result.set(kvArr[0], kvArr[1]); } return result; } // let qryParam = getQueryParam(); qryParam.forEach((v, k) => alert(k + " : " + v)); </script> |
本例,如果访问网址为“http://localhost:58476/demo/test.html?name=Tom&age=25”,则会依次显示"name : Tom"、"age : 25"。代码中,getQueryParam()函数会返回一个Map对象,如果URL中没有查询参数,则返回一个没有元素的空集合。
需要注意的是,URL地址在传递过程中可能会进行编码处理,此时,可以使用以下函数进行处理:
encodeURI()和decodeURI()函数,对URL进行编码和解码操作,不处理的字符包括字母、数字,以及字符# - _ . ! ~ * ' ( ) ; , / ? : @ & = + $。
encodeURIComponent()和decodeURIComponent()函数,对URL进行编码和解码操作,不处理的字符包括字母、数字,以及字符( ) . ! ~ * ' - _。
计时器函数
setTimeount()函数需要指定两个参数,其中,参数一指定执行的函数,参数二指定等待多少毫秒以后开始执行;函数会返回一个计时器标识,可以使用clearTimeout()函数终止指定标识的计时器代码。需要注意的是,setTimeout()指定的函数只会执行一次,如果需要再次执行,需要重复调用setTimeout()函数,下面的代码演示了相关应用。
HTML |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <h1 id="title1"></h1> <button type="button" onclick="clearTimeout(timerFlag);">停止</button> </body> </html> <script> let timerFlag = null; function showTime() { let h1 = document.getElementById("title1"); h1.innerText = new Date().toLocaleTimeString(); timerFlag = setTimeout(showTime, 1000); } // showTime(); </script> |
JavaScript代码中,timerFlag变量会保存最后一次调用setTimeout()函数的计时器标识,showTime()函数中,会将系统当前时间显示到h1元素中,每隔1秒更新一次。点击页面中的“停止”按钮后,会调用clearTimeout()函数停止时间的更新。
setInterval()函数的第一个参数同样指定执行函数,第二个参数指定每次执行的间隔毫秒数,与setTimeout()函数不同的是,setInterval()函数会严格地按照指定的时间间隔重复执行代码;此外,setInterval()函数同样会返回一个计时器标识,可以使用clearInterval()函数终止指定标识的计时器代码。下面的代码演示了setInterval()和clearInterval()函数的应用。
HTML |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <h1 id="title1"></h1> <button type="button" onclick="clearTimeout(timerFlag);">停止</button> </body> </html> <script> function showTime() { let h1 = document.getElementById("title1"); h1.innerText = new Date().toLocaleTimeString(); } // showTime(); let timerFlag = setInterval(showTime, 1000); </script> |
代码功能与前例相同,打开页面后会在h1元素中显示系统当前时间,点击“停止”按钮后会停止时间更新。
setTimeout()函数调用的代码会在每次执行完成后再按指定的间隔时间执行,可以保证每次代码执行的完整性,适用于每次执行代码都需要完整性和正确性的场景。而setInterval()则会保证在指定间隔时间时准时开始执行新的代码,即使上一次代码还没有执行完成也是这样,典型的情况可以参考游戏循环,以执行的时效性为主;但是,如果一个循环没有完成就开始下一循环,就可能出现“跳帧”的情况。实际应用中,可以根据执行代码的性质和功能需要合理选择合适的计时器函数。
夜雨聆风