第十一篇 文档对象模型 - DOM

DOM 即文档对象模型(Document Object Model,DOM)是一种用于HTML和XML文档的编程接口

它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式

DOM 把网页和脚本以及其他的编程语言联系起来

DOM 属于浏览器的,而非JavaScript语言规范里规定的核心内容

DOM 这个名词应该有两层含义,第一个是根据文档建模出来的一个树形模型,即是DOM 树,这是站在浏览器方面的看法

从编程语言来看,它提供了一套操纵文档的 API。作为程序员,更多的理解是后者

节点层次

文档节点: 只有一个根节点,为 <html> 元素,也称作文档元素

html 页面中文档元素始终都是 <html> 元素

但是在 xml 中,没有预定定义的元素,任何元素都可能成为文档元素

<html>
    <head>test page</head>
    <body>
        <p>hello</p>
    </body>
</html>

每一段标记都可以通过树中的一个节点表示,一共有12种节点类型
Node 类型

在 JavaScript 中所有节点类型都继承自 Node 类型,所以这些节点类型都拥有着 Node 类型定义的属性和方法

所有节点类型都拥有 nodeType 属性,用于表明该节点的类型

节点类型由在 Node 类型中定义的下列 12 个数值常量来表示,任何节点类型必然是其中一种

Node 类型 常量 描述
Node.ELEMENT_NODE 1 元素 例如
Node.ATTRIBUTE_NODE 2 属性
Node.TEXT_NODE 3 文本节点
Node.CDATA_SECTION_NODE 4 文档中的 CDATA 部(不会由解析器解析的文本)
Node.ENTITY_REFERENCE_NODE 5 实体引用
Node.ENTITY_NODE 6 实体
Node.PROCESSING_INSTRUCTION_NODE 7 处理指令
Node.COMMENT_NODE 8 注释
Node.DOCUMENT_NODE 9 整个文档
Node.DOCUMENT_TYPE_NODE 10 向为文档定义的实体提供接口
Node.DOCUMENT_FRAGMENT_NODE 11 轻量级的 Document 对象(文档的某个部分)
Node.NOTATION_NODE 12 DTD 中声明的符号
节点属性
Node.nodeName    => 返回节点名称,只读
Node.nodeType    => 返回节点类型的常数值,只读
Node.nodeValue   => 返回Text或Comment节点的文本值,只读
Node.textContent  => 返回当前节点和它的所有后代节点的文本内容,可读写
Node.baseURI     => 返回当前网页的绝对路径

Node.ownerDocument     => 返回当前节点所在的顶层文档对象,即document
Node.nextSibling      => 返回紧跟在当前节点后面的第一个兄弟节点
Node.previousSibling  => 返回当前节点前面的、距离最近的一个兄弟节点
Node.parentNode       => 返回当前节点的父节点
Node.parentElement    => 返回当前节点的父Element节点
Node.childNodes       => 返回当前节点的所有子节点
Node.firstChild       => 返回当前节点的第一个子节点
Node.lastChild        =>返回当前节点的最后一个子节点
节点操作
Node.appendChild(node)                

   向节点添加最后一个子节点

Node.hasChildNodes()                  

   返回布尔值,表示当前节点是否有子节点

Node.cloneNode(true);                 

   默认为false(克隆节点), true(克隆节点及其属性,以及后代)

Node.insertBefore(newNode,oldNode)    

   在指定子节点之前插入新的子节点

Node.removeChild(node)                

   删除节点,在要删除节点的父节点上操作
   
Node.replaceChild(newChild,oldChild)  

   替换节点
   
Node.contains(node)                   

   返回一个布尔值,表示参数节点是否为当前节点的后代节点
   
Node.compareDocumentPosition(node)    

   返回一个7个比特位的二进制值,表示参数节点和当前节点的关系
   
Node.isEqualNode(noe)                 

   返回布尔值,用于检查两个节点是否相等。所谓相等的节点,指的是两个节点的类型相同、属性相同、子节点相同。
   
Node.normalize()                      

   用于清理当前节点内部的所有Text节点。它会去除空的文本节点,并且将毗邻的文本节点合并成一个。
parentNode 接口
Node.children           => 返回指定节点的所有Element子节点

Node.firstElementChild  => 返回当前节点的第一个Element子节点

Node.lastElementChild   => 返回当前节点的最后一个Element子节点

Node.childElementCount  => 返回当前节点所有Element子节点的数目
ChildNode 接口
Node.remove()      => 用于删除当前节点

Node.before()     

   => 在ChildNode这个节点的父节点中插入一些列的Node或者 DOMString对象,位置就是在ChildNode节点的前面

Node.after()       

   => 方法会在其父节点的子节点列表中插入一些 Node 或 DOMString 对象。插入位置为该节点之后
   
Node.replaceWith()   => 替换了该节点父节点下的子节点
Document 类型

在 JavaScript 中用 Document 类型表示整个文档

在浏览器中 document 对象表示整个 HTML 文档

document 对象是 HTMLDocument 的实例

而 HTMLDocument 又继承自 Document 类型

特点
nodeType 9
nodeName #document
nodeValue null
parentNode null
ownerDocument null
子节点 可能为DocumentType(最多一个)、Element(最多一个)、ProcessingInstruction、Comment
Document 属性
documentElement 属性,始终指向 <html> 元素;

childNodes 属性,文档元素列表

let html = document.documentElement; => <html>元素
html === document.childeNodes[0]; => true
html === document.firstChild; => true

body 属性

let body = document.body; => <body> 元素

docType 属性

Document 类型可能还存在另外一个子节点,即 DocumentType

let docType = document.docType; => 取得对 <!DOCTYPE> 的引用
Document 的信息

document 对象作为 HTMLDocument 的一个实例,其还存在一些标准Document 类型没有的属性。

这些属性提供了 document 对象所表现的网页信息

其实这些我们能在 http 请求头中看到

1、title 属性

title 属性显示在浏览器的标题栏或者标签栏上,修改title属性可改变网页的title

let title = document.title; => 获得原始的title值
document.title = 'new title'; => 为网页设置新的title

2、URL 属性(只读)

URL 属性包含页面完整的 URL(即地址栏中显示的URL)

let url = document.URL; => 取得完整的 URL

3、domain 属性

domain 属性只包含页面的域名

var domain = document.domain;  => 取得网页的域名

domain属性只可设置为当前域名的子域名,且遵循“松散紧绷”规则

4、referrer属性(只读)

referrer 属性保存着链接到当前页面的那个页面的 URL

若在没有来源页面的情况下,referrer 属性可能为空字符串

var referrer = document.referrer;  => 取得来源网页的URL
查找元素
    <div class="box" id="app">
          hello world
    </div>
        <ul class="list_box">
        <li>薛之谦</li>
        <li>薛之谦</li>
    </ul>
    
   遵循 想操作那个元素  就首先获取哪个元素
1、通过 id 名获取元素

document.getElementById('app')

document.getElementById('box') 

   => 如果想要获取的 id 不存在 返回值是一个 null
   
2、通过类名(ClassName)获取元素 获得到的是一个集合 NodeList

document.getElementsByClassName('box')

document.getElementsByClassName('box')[i]

   => 通过索引获取想要的元素
   
3、通过标签获(TagName)取元素 获取的也是一个集合 NodeList

document.getElementsByTagName('li')

document.getElementsByTagName('li')[i]

   => 通过索引获取想要的元素
   
4、通过querySelector(css选择器)但是只能获取一个元素(第一个)

document.querySelector('#app')

document.querySelector('.box')

5、queryselectorAll(css选择器) 获取的也是一个集合 NodeList

document.querySelectorAll('.box')

document.querySelectorAll('.box')[2]

   => 通过索引获取想要的元素
   
6、如何取 NodeList 的某个值

var allTags = document.getElementsByTagName('*'); 

   => 取得包含文档所有元素的 NodeList
   
var imgs = document.getElementsByTagName('img'); 

   => 取得包含页面所有 <img> 元素的 NodeList
   
alert(imgs.length); 

   => 输出页面 <img> 元素的数量

alert(imgs[0]); 

   => 输出页面第一个 <img> 元素

alert(imgs[0].src); 

   => 输出页面第一个 <img> 元素的 src 属性

alert(imgs.item(0).src); 

   => 输出页面第一个 <img> 元素的 src 属性

alert(imgs.namedItem('myImg')); 

   => 输出页面 <img> 元素中包含name 属性为 “myImg” 的 NodeList

alert(imgs.['myImg']); 

   => 同 namedItem() 方法
特殊集合

出了属性和方法,document 对象还有一些特殊集合。这些集合都是 HTMLCollection 对象

集合 描述
document.anchors 包含文档中所有带name属性元素
document.applets 包含文档中所有元素,因为不再推荐使用元素,所以这个集合已经不再建议使用了
document.forms 包含文档中所有的
元素,与document.getElementsByTagName('form')得到的结果相同
document.images 包含文档中所有的元素,与document.getElementsByTagName('img')得到的结果相同
document.links 包含文档中所有带href属性的元素
文档写入

为了将输出流写入网页文档中,document 对象提供了 4 个 方法

write()、writeln()、open()、 close()

write() 和 writeln() 方法都接受一个字符串参数,即要写入到输出流中的文本。

write() 会原样写入

writeln() 会在字符串末尾加一个换行符 \n
<html>
  <body></body>
  <script>
    document.write(`this time is ${new Date().toString()}`);
  </script>
</html>

通过 write() 方法,动态地向文档写入了当前的日期和时间
<html>
  <body></body>
  <script>
    document.write('<script src="index.js">');
  </script>
</html>

通过 write() 和 writeln() 方法,还可以动态的引入外部资源,例如JavaScript 文件等

注意若在文档加载完成后再使用write()或writeln()方法,那么输出的内容将重写整个页面
open() 和 close() 方法

open() 和 close() 方法分别用于 打开 和 关闭 网页的输出流
Element 类型

除了 Document 类型之外,Element 类型就要算是 Web 编程中最常用的类型了

Element 类型用于表现 XML 或 HTML 元素,提供了对元素标签名、子节点及特性的访问

Element 是一个通用性非常强的基类,Element 表示 html 对外暴露访问元素标签名,节点和属性的能力

特点
nodeType 1
nodeName 元素的标签名
nodeValue null
parentNode Document 或 Element
子节点 可能为 Element、Text、Comment、ProcessingInstruction、CDATASection 或 EntityReference
html 元素

所有 html 元素都通过 HTMLElement 类型表示,简单说一下所有 html 元素都有的基本属性

 <div id="caixin" title="name" class="myName" dir="ltr" lang="en">我是jackson</div>
 
    let caixin = document.getElementById('caixin');
    console.log(caixin.id);
    console.log(caixin.title);
    console.log(caixin.className);
    console.log(caixin.dir);
    console.log(caixin.lang);

1、id,表示唯一标识符。

2、title,包含元素的信息。

3、lang,属性规定元素内容的语言。

4、dir,表示我们书写方向,(几乎不用)

5、className,class属性,也就是我们常说的类。(这个类不是js中的类)
获取属性

每个元素都有 0个 或者 多个 的属性

三个与 DOM 相关的 主要方法 这些方法主要是操作属性

getAttribute(), setAttribute(), removeAttrbute()

getAttribute()
getAttribute() 方法主要用于获取属性的值,它也可以获取自定义属性的值

html5 中规定自定义的属性加上 data- 前缀

不能通过属性的方式获取自定义的属性值,可以通过 getAttribute() 的形式获取

 <div id="caixin" title="name" data-abcd="abcd" class="myName" dir="ltr" lang="en">我是jackson</div>
 
 let caixin = document.getElementById('caixin'); 
 
 console.log(caixin.getAttribute('id')) => caixin 
 
 console.log(caixin.getAttribute('abcd')) => abcd
 
 属性是 onclik 事件处理程序
 
 let div = document.getElementById('myDiv');
 
 div.getAttribute('onclick') => 代码字符串
 
 div.onclick => 返回一个函数
 
 style
 
 var div = document.getElementById('myDiv');
 
 div.getAttribute('style') => css文本,字符串
 
 div.style => CSSStyleDeclaration对象
setAttribute()
setAttribute() 设置属性的值

该方法接收俩个参数,一个是要设置的属性名,一个是该属性值,如果没有的话会自动创建,如果有的话直接覆盖

    caixin.setAttribute('id','bear');
    
    console.log(caixin.getAttribute('id')); => bear
    
    注意: setAttribute() 设置的属性名会自动变成小写
    
    自定义属性 和 特性 都可以使用s etAttribute() 赋值的
    
    但是属性赋值方式只能 设置特性的值,自定义属性的值 没法设置
removeAttrbute()
removeAttrbute()是移除

会把整个属性和值都删掉,一般我们在设置比如选项卡中的 active 可以使用移除属性再给当前项设置属性

    caixin.removeAttribute('abcd');
    
    console.log(caixin.getAttribute('abcd')); => null
attributes 属性

attributes 属性包含一个NameNodeMap实例

元素的每个属性都表示为一个attr节点,并且保存在 NameNodeMap 对象中。

NameNodeMap 有四个方法

getNamedItem(name) 返回 nodeName 属性等于 name 的节点

removeNamedItem(name) 删除 nodeName 属性等于 name 的节点

setNamedItem(node) 向列表中添加 node 节点,以其 nodeName 为索引

item(pos) 返回索引位置 pos 处的节点


这四个其中前三个和我们上面的获取,修改,移除属性差不多,一般我们大多数还是用最上面的写法
创建元素

我们可以使用 document.createElement() 方法创建元素

它接受一个参数,是我们要创建的元素的标签名称(不区分大小写)

createElement()
    let p = document.createElement('p');
    
    p.id = 'myP';
    
    document.body.appendChild(p);

注意: 我们设置这些属性需要通过 appendChild() 方法来渲染到页面上
Text 类型

文本节点由 Text 类型表示,包含的是可以照字面解释的纯文本内容

纯文本中可以包含转义后的 HTML 字符,但不能包含 HTML 代码

特点
nodeType 3
nodeName #text
nodeValue 节点所包含的文本
parentNode Element
子节点 没有 (不支持)
创建文本节点
document.createTextNode()
document.createTextNode() 创建新文本节点

这个方法接受一个参数——要插入节点 中的文本

let element = document.createElement('div')

element.className = "message"

let textNode = document.createTextNode("hello world")

element.appendChild(textNode)

document.body.appendChild(element)
规范化文本节点
normalize()
如果在一个包含两个或多个文本节点的父元素上调用 normalize() 方法

则会将所有文本节点合并成 一个 节点,结果节点的 nodeValue 等于将合并前每个文本节点的 nodeValue 值拼接起来的值

let element = document.createElement("div"); element.className = "message";

let textNode = document.createTextNode("Hello world!"); element.appendChild(textNode);

let anotherTextNode = document.createTextNode("Yippee!"); element.appendChild(anotherTextNode);

document.body.appendChild(element);
alert(element.childNodes.length); => 2

element.normalize(); 

alert(element.childNodes.length); => 1 
alert(element.firstChild.nodeValue); => "Hello world!Yippee!"
分割文本节点
splitText()
这个方法会将一个文本节点分成两个文本节点,即按照指定的位置分割 nodeValue 值

原来的文本节点将包含从开始到指定位 置之前的内容,新文本节点将包含剩下的文本

这个方法会返回一个新文本节点,该节点与原节点的 parentNode 相同

let element = document.createElement("div"); element.className = "message";

let textNode = document.createTextNode("Hello world!"); element.appendChild(textNode);
document.body.appendChild(element);

let newNode = element.firstChild.splitText(5); 

alert(element.firstChild.nodeValue); => "Hello"
alert(newNode.nodeValue);  => " world!"
alert(element.childNodes.length); => 2
Comment 类型

在 DOM 中的注释,可以通过 Comment 类型表示

与 Text 类型继承自相同的基类,拥有除了 splitText 方法外的所有字符串操作的方法

Comment 表示的是我们写的注释,它与上面的text类型相识,也可以通过 nodeValue 和 data 属性来访问

如果我们要通过Js来访问或者创建的话,要确保他们在html标签的里面

特点
nodeType 8
nodeName #comment
nodeValue 注释的内容
parentNode Document 或 Element
子节点 没有 (不支持)
创建方法
document.createComment()
let element = document.createElement("div")

let commentText = document.createComment("注释")

element.appendChild(commentText)

document.body.appendChild(element)

<div>
    <!--注释-->
</div>
CDATASection 类型

针对xml类型文档

CDATASection类型表示xml中特有的CDATA区块

(XML) 是一种结构化数据交换语言

一般我们写代码不会遇到这个的

与Text类型继承自相同的基类,拥有除了splitText方法外的所有字符串操作的方法

特点
nodeType 4
nodeName #cdata-section
nodeValue CDATA区域中的内容
parentNode Document 或 Element
子节点 没有 (不支持)
创建方法
document.createCDataSection()
<div><![CDATA[this is some content]]></div>
DocumentType 类型

包含着与文档 doctype 有关的所有信息

DocumentType 类型包含了文档的文档类型信息(doctype)

它在 DOM Level1 不支持动态创建,只能在解析代码时创建

一般我们也用不到这个

特点
nodeType 10
nodeName doctype名称
nodeValue null
parentNode Document
子节点 没有 (不支持)
document.doctype.name  => <!DOCTYPE后面的文本
DocumentFragment 类型

在文档中没有标记,文档片段继承了 Node 的所有方法

DocumentFragment类型是文档片段接口,一个没有父对象的最小文档对象

它被作为一个轻量版的 Document使用,就像标准的document一样

它不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题

与 document 相比,最大的区别是 DocumentFragment 存在于内存中,不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题

特点
nodeType 11
nodeName document-fragment
nodeValue null
parentNode Document
子节点 没有 (不支持)
<ul id="list"></ul>


const list = document.querySelector('#list');
const fruits = ['Apple', 'Orange', 'Banana', 'Melon'];

const fragment = document.createDocumentFragment();

fruits.forEach(fruit => {
  const li = document.createElement('li');
  li.innerHTML = fruit;
  fragment.appendChild(li);
});

list.appendChild(fragment);
Attr 类型

元素的属性在DOM中以Attr类型来表示

特性节点,不被认为是DOM文档树的一部分

它有三个属性 name,value,specified

Attr 表示元素的特性

在所有浏览器中,都可以访问 Attr类型 的构造函数和原型

特点
nodeType 12
nodeName 特性的名称
nodeValue 特性的值
parentNode null
子节点 没有 (不支持)
name 特性名称,与nodeName值相同
value 特性的值,与nodeValue值相同
specfied 布尔值,用以区分特性是代码指定的,还是默认的
document.createAttribute()

一般我们还是使用 

    getAttribute()

    removeAttribute()

    setAttribute()

方法操作属性,而不是直接操作属性节点

DOM 操作技术

动态脚本
插入外部脚本
funtion loadScript(url){
    let script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    document.body.apendChild(script);
}
loadScript('client.js');

缺点:不知道脚本什么时候加载完成
直接插入 javascript 代码
function loadScriptStr(code){
    let script = document.createElement('script');
    script.type = 'text/javascript';
    try{
       script.appendChild(document.createTextNode(code)); 
    } catch{
        script.text = code;
    }
    document.body.apendChild(script);
}
loadScriptStr('function sayHi(){console.log('hi');}');

ie将 <script> 视为一个特殊的元素,不允许 DOM 访问其子节点,提供 text 属性来指定
动态样式
插入外部文件
funtion loadStyles(url){
    let link = document.createElement('link');
    link.type = 'text/css';
    link.href = url;
    document.head.apendChild(link);
}
loadStyles('main.css');

异步过程,缺点:不知道样式脚本什么时候加载完成
直接插入 style 代码
function loadStylesStr(css){
    let style = document.createElement('style');
    try{
       style.appendChild(document.createTextNode(css)); 
    } catch(){
        style.styleSheet.cssText = css;
    }
   
    document.head.apendChild(style);
}
loadStylesStr('body:{color: #222;}');

ie 将 <style> 视为一个特殊的元素,不允许 DOM 访问其子节点,提供访问元素的 styleSheet 属性的 cssText 属性来接收 css 代码
使用 NodeList
NodeList及其NameNodeMap、HTMLCollention,这三个集合都是动态的

每当文档结构发生变化时,它们都会更新,始终保持着最新、最准确的信息

本质上NodeList对象都是在访问DOM文档时实时运行的查询

尽量减少访问NodeList对象的次数,因为每次访问都会运行一次基于文档的查询,可以考虑将NodeList中取的值缓存起来
posted @ 2023-03-30 11:05  caix-1987  阅读(44)  评论(0)    收藏  举报