什么是DOM?
网页其实是一棵树。

为了让网页能实现各种各样的效果,我们需要对这些元素进行操作。
那么JS如何操作这些元素呢?
浏览器为此,给window添加了一个document对象,我们将网页抽象成一个document对象,通过这个document对象操作整个页面的所有的节点。
而上面所描述的模型 Document Object Model 就是 我们的 DOM。
题外话:DOM,非常反人类,通常我们会使用VUE框架来避免直接操作DOM,或者通过JQuery来比较人性化地操作DOM。
获取元素(标签)
元素也叫标签!
获取特定的元素
在整个DOM树中,有几个比较特殊的元素:html,head,body。以及一个非元素的特殊对象:窗口window
-
获取html元素:
document.documentElement -
获取head元素:
document.head -
获取body元素:
document.body -
获取窗口(非元素):
window -
获取页面中所有元素:
document.alldocument.all是一个奇葩的falsy:
document.all是一个falsy值。为什么:因为一开始这个API是 IE 发明的只在 IE 中才有,当时的程序员会通过document.all来判断浏览器环境,是 IE 还是其他浏览器:true就是 IE ,false就是其它。后来其它浏览器也使用了这个API,但是为了兼容以前的项目,所以在其他浏览器中,document.all是一个falsy值。
获取常规的元素
- 通过元素的id
window.id或id直接就能获取到拥有id属性的元素,id不能和全局属性或关键字冲突。 document.getElementById('id')通过id获取元素。document.getElementsByTagName('div')[0]通过标签名来获取元素,注意Elements是复数,获取的是一个数组。document.getElementsByClassName('red')[0]通过class来获取元素,注意Elements是复数,获取的是一个数组。document.querySelector('css选择器语法')通过css选择器来获取第一个满足条件的元素。- 如:
document.querySelector('#haha')获取id为 haha 的元素。 - 如:
document.querySelector('.red')获取第一个class含有 red 的元素。 - 如:
document.querySelector('div')获取第一个 div 元素。
- 如:
document.querySelectorAll('css选择器语法')[0]通过css选择器来获取所有满足条件的元素,获取的是一个数组。
小贴士:
- 上面 1 的方法是不太规范的方法,个人项目中可以使用,但是不要在实际开发中使用,而且前提是这个id名不和全局某些全局属性冲突如果冲突了,就只能通过
getElementById来获取元素了。 - 2、3、4 的方法是兼容 IE 浏览器的方法,如果不需要兼容 IE 使用 5、6 的方法更佳。
了解我们获取到的元素
我们通过 console.dir(div) 来查看页面某个 div 的详细信息。
可以发现,这个div元素也是一个对象,有着自己的属性,同时也有原型链。
- 第一层原型是 HTMLDivElement.prototype 。包含了所有 div元素 的共同属性。
- 第二层原型是 HTMLElement.prototype 。包含了所有HTML元素的属性。
- 第三层原型是 Element.prototype 。包含了XML、HTML标签的共有属性,因为浏览器不仅仅可以展示HTML,还能够展示其他标签语言。
- 第四层原型是 Node.prototype 。包含了所有节点的共有属性。
- 第五层原型是 EventTarget.prototype 。包含了 addEventListener 等几个方法。
- 第六层原型是 Object.prototype 。

新概念——节点
HTML文档中的所有内容都是节点。
如:元素Element或者叫标签Tag、文本Text、注释Comment、文档Document等,都属于节点。
我们可以通过 Node.nodeType 来查看Node节点属于哪种类型。

节点的增删改查
创建与增加节点
-
创建一个标签(Tag)节点
let myDiv = document.createElement('div') let myStyle = document.createElement('style') document.createElement('script') document.createElement('li') -
创建一个文本节点
let myText = document.createTextNode('你好') -
标签Tag中插入文本Text
myDiv.appendChild(myText)myDiv.innerText = '你好'或myDiv.textContent = '你好'- 不能使用
myDiv.appendChild('你好')
-
将创建的标签节点插入HTML中
因为我们通过JS创建的节点是存储在内存中的JS线程里面的。
如果要将这些节点展示在页面上,我们就需要将它们插入到 head 或 body 中它们才能让我看得见。
// 插入到head 或 body中 document.head.appendChild(myStyle) document.body.appendChild(myDiv) // 插入到页面中的其他元素中 let div1 = document.getElementById('test') div1.appendChild(myDiv) -
节点的唯一性
// 节点的唯一性 let myDiv = createElement('div') window.test1.appendChild(myDiv) window.test2.appendChild(myDiv) // myDiv只出现在了test2中,test1中并没有myDiv节点,这就是节点的唯一性如果要把同样的一个节点插入到页面的2个位置,我们需要先通过
Node.cloneNode克隆一份节点(默认是浅拷贝,但可以设置为深拷贝)再把这克隆后的两节点分别插入到页面中。
移除节点
-
旧方法:先找自己的父元素(parentNode),再通过父元素删除自己
myDiv.parentNode.removeChild(myDiv) -
新方法:直接移除
myDiv.remove()这个方法 IE 不支持。 -
上面的方法都只是把节点移除出页面,但是节点仍然存在于JS线程中,如果想要从新渲染到页面上,则再
appendChild即可。
修改属性
-
写标准属性
-
改class
-
div.className='red blue'这种方法会直接覆盖原来的所有class。如果想要不影响原来的属性,使用div.className += 'red blue'.为什么要使用className来修改class属性,因为class是一个关键字,所以不能直接 Node.class='',这样JS引擎就会优先把这个class当作关键字看待。
-
div.classList.add('red')classList是所有class的数组,这种改写class的方法不会影响原来的class。
-
-
改style
div.style='width:100px;color:blue'同样这种修改style的方式会覆盖原先的style,尽量不要使用,而是只修改希望改变的某属性。- 如果只是希望修改style的某样属性,请直接修改那个属性:
div.style.width = '100px'。 - 关于style直接修改某属性的属性名:css中某些属性过长,以
-符号连接,在DOM修改style时,这些以-符号连接的属性名要改用驼峰命名法来取代-。如:background-color 改写成 div.style.backgroundColor。
-
改其他标准属性
- 这些标准属性其实就是div对象中的属性,直接通过对象的点语法就能获取并修改属性。
console.dir就能够获取对象的详细信息。 div.id = 'newID'就可以修改 div 的id属性。
- 这些标准属性其实就是div对象中的属性,直接通过对象的点语法就能获取并修改属性。
-
1个特别的自定义属性 data-
- 以 data- 开头的属性,如:data-x data-haha 等,可以使用
Node.dataset.xNode.dataset.haha来直接修改属性。
let div = document.querySelector('#div') div.setAttribute('data-x','AnX') // 此时div节点多了一个 data-x 属性,值为:AnX div.dataset.x = 'BnX' // 此时 data-x 属性值被修改成了:BnX - 以 data- 开头的属性,如:data-x data-haha 等,可以使用
-
-
读标准属性
- 点语法:
div.classLista.href - getAttribute函数:
div.getAttribute('class')a.getAttribute('href') - 上面2种方法有些区别:
- getAttribute函数获取的是直接在标签中显示的属性如:class、id、style等;但是div对象还有很多不直接显示在标签上的属性,如:classList、className等。
- 如:
a.href输出的是一个经过加工的完整链接地址;而a.getAttribute('href')则是标签上的原始值。 - 如:
div.className和div.getAttribute('class')效果是一样的,但是一个是className 一个是class。
- 点语法:
-
改事件处理函数
事件机制也是元素对象的一个属性,通过给这个属性添加处理函数,当事件触发时,浏览器就能调用这个处理函数。
以onclick为例:
- 点语法修改事件处理函数:
- 默认情况下,
div.onclick = null;此时点击div并没有什么效果。 - 当传入处理函数后,
div.onclick=fn(),点击div时浏览器就会调用这个函数 - 调用函数是通过fn.call(div,event)这样调用的
- 此时div就是函数中的this
- event则包含了点击事件的所有信息,如点击的坐标等。
- 默认情况下,
- addEventListener函数
- 这个方法是点语法修改事件处理函数的升级版。
- 点语法修改事件处理函数:
-
改内容
-
修改文本内容
div.innerText = '哈哈'div.textContent = '哈哈'- 两种修改方式基本没有区别
-
修改具有HTML结构的内容
div.innerHTML = '<strong>Hi</strong>'这里的<strong>会被认为是一个html标签来处理
div.innerHTML = '<strong>Hi</strong>' // 加粗的 Hi div.innerText = '<strong>Hi</strong>' // <strong>Hi</strong>- 使用 innerHTML 时注意内容长度,过长的内容会导致页面严重卡顿
-
改标签
- 先清空原先的内容:
div.innerHTML = '' - 添加修改后的内容:
div.appendChild(div2)
- 先清空原先的内容:
-
改节点位置
newParent.appendChild(div)由于节点的唯一性,所以原来位置的 div 会消失
-
-
查节点及其相关节点
-
查父级节点
Node.parentNode或Node.parentElement
-
查爷爷级节点
Node.parentNode.parentNode
-
查子代节点
-
Node.childNodes或Node.children -
上面2种方法的区别:chilren只会关注元素节点,childNodes会关注文本和元素节点。
<ul id='test1'> <li>1</li> <li>2</li> <li>3</li> </ul> <ul id='test2'><li>1</li><li>2</li><li>3</li></ul> test1.childNodes.length //7 [text, li, text, li, text, li, text] test1.children.length // 3 [li, li, li] test2.childNodes.length //3 [li, li, li]
-
-
查兄弟姐妹节点Siblings
-
先找父亲节点
Node.parentNode -
让父亲节点找它的自己节点
Node.parentNode.childNodes这里也包括自己 -
把父亲节点的子节点排除掉自己就是所有的兄弟姐妹节点
for(let i = 0;i<Node.parentNode.childNodes.length;i++){ if(Node.parentNode.childNodes[i]===Node){ return Node.parentNode.childNodes.splice(i,1) } }
-
-
查第一个子节点
Node.firstChild
-
查最后一个子节点
Node.lastChild
-
查临近的兄弟节点
- 上一个兄弟节点:
Node.previousSibling - 上一个节点类型为Element的兄弟节点:
Node.previousElementSibling - 下一个兄弟节点:
Node.nextSibling - 下一个节点类型为Element的兄弟节点:
Node.nextElementSibling
- 上一个兄弟节点:
-
查某个节点下的所有节点
let traval (node,fn) =>{ fn(node) if(node.children){ for(let i = 0;i<node.children.length;i++){ traval (node.children[i],fn) } } } traval(Node,(a)=>{console.log(a)}) // 输出所有节点
-