前置知识
- 理解JS语法,如变量、if els、循环
- 会背JS的七种数据类型
- 会背JS的五个falsy值
- 知道函数是对象,数组也是对象
- 会用div和span标签
- 会简单的CSS布局
一、DOM简介
1.网页其实是一棵树
也就是数据结构里面的树
注意:文字也是一个节点
2.JS如何操作这棵树
2.1 浏览器往window
上加一个document
即可
- 有了这个
document
对象就可以操作这棵树了 window.document
就可以获取整个网页的所有元素
当点中document时,整个网页都被选中了
2.2 JS用document操作网页
这种思想叫做:Document Object Model 文档对象模型
意思是:把网页抽象成一个document对象,并对它进行操作的这么一种方式叫做DOM
2.3 DOM很难用
从来不会用DOM自带的功能来操作DOM,开始用JQuery来操作,后面会用vue和react来操作DOM
二、获取元素的API
什么是API?
它提供的所有函数都是API
1.获取元素,也叫标签
(div标签和span标签这些都叫做元素,Elemt:元素、Tag:标签)
- 有很多API
window.id
或者直接id
可以直接输入其id名(id是唯一的)document.getElementByld('id')
——IE才用
一般情况下都用1比较方便,但某些情况下,如id是parent这种在window上的全局属性时,只有用2才能找到document.getElementsByTagName('div')[0]
——IE才用
注意有个’s’,找到所有标签名为 div 的元素,找到的所有div是一个整体数组,想要操作其中一个必须加上下标document.getElementsByClassName('red')[0]
——IE才用
把所有 class 为 red 的标签都获取到,一个整体的数组,想要某一个加下标document.querySelector('#id')
注意要加 # ,这个div可以写得很复杂
如:document.querySelector(‘div>span:nth-child(2)’)
找一个div中的span,并且这个span是div的第二个儿子,CSS怎么写,这里就可以写document.querySelectorAll('.red')[0]
5是按照条件找到第一个,6是把满足条件的全部找到
- 用哪一个
工作中用querySelector
和querySelectorAll
做demo
直接用id
,千万别让人发现
需要兼容IE的可怜虫才用document.getElementsByTagXXX
2.获取特定元素
- 获取 html 元素
document.documentElement
- 获取 head 元素
document.head - 获取 body 元素
document.body - 获取窗口(窗口不是元素)
window
window虽然不是一个标签,但是有时候可以获取window然后添加一些全局的事件监听
window.onclick = () =>{console.log('点击')}
:点击页面监听 - 获取所有元素
document.all
1.这个 document.all 是个奇葩,第6个falsy值(虽然是falsy值,但功能不受影响依旧能获取元素,只是可以用来区分IE):
因为它是IE发明的,很多程序员就用 document.all 是否存在来检测当前页面是否是IE,如果是则运行只能在IE上运行的代码,后续其他浏览器也有了document.all,为了保证那些代码还是只能在IE上运行,则统一规定它为假值
2.当我们获取html标签的时候,实际上只获取了html元素,html里面的children(如:head、body等)没有获取到
而获取所有标签则是:[html , head, body, div,…]
用document.all
3.获取到的元素是个啥?
获取到的所有元素都是一个对象,我们需要搞清它的自身属性和原型
- HTMLElment和Element的区别(HTML元素和元素)
以前的网页要同时处理HTML元素和XML元素(AJAX里面的),Element是属于HTML元素、XML元素、SVG元素各种不同标准的元素
3.1 抓一只div对象来看看
- console.dir(div1)看原型链(div1是给获取到的div对象命的名)
- 自身属性:className、id、style等等
- 第一层原型:HTMLDivElement.prototype
这里面是所有div共有的属性,不用细看 - 第二层原型:HTMLElement.prototype
这里面是所有HTML标签共有的属性,不用细看 - 第三层原型:Element.prototype
这里面是所有 XML、HTML标签共有的属性,浏览器不止展示HTML - 第四层原型:Node.prototype
这里面是所有节点共有的属性,节点包括 标签 和 文字 - 第五层原型:EventTarget.prototype
里面最重要的函数属性是:addEventListener、dispatchEvent、removeEventListener - 最后一层原型就是:Object.prototype
根对象
3.2 div完整原型链
- div是一个对象,一共有6层属性
- 每一层构造函数都会往div上加东西
4.1 节点Node包括以下几种节点
- MDN有完整描述,在任何一个节点上输入
x.nodeType
得到一个数字
如:div.nodeType - 1 表示元素Element,也叫标签 Tag
- 3 表示文本Text
- 8 表示注释Comment
- 9 表示文档Document
- 11 表示文档片段DocumentFragment
- 记住1和2即可,节点分为标签和文字
4.2 节点与元素的区别
在 HTML DOM (文档对象模型)中,每个部分都是节点:
- 文档本身是文档节点
- 所有 HTML 元素是元素节点
- 所有 HTML 属性是属性节点
- HTML 元素内的文本是文本节点 (包括回车符也是属于文本节点)
- 注释是注释节点
Element 对象可以拥有类型为元素节点、文本节点、注释节点的子节点。
NodeList 对象表示节点列表,比如 HTML 元素的子节点集合。
元素也可以拥有属性。属性是属性节点。
总结:元素是元素节点,是节点中的一种,但元素节点中可以包含很多的节点。
节点的增删改查
1.增
- 创建一个标签节点
let div1 = document.createElement('div')
document.createElement('style')
document.createElement('script')
document.createElement('li')
- 创建一个文本节点
document.createTextNode('你好')
‘你好’ 是一个字符串,文本节点是一个对象(有原型和函数) - 标签里面插入文本
div1.appendChild(text1)
div1.innerText = '你好' 或者 div1.tx=textContent = '你好'
以上两个是不同的构造函数提供的属性,都可以用但不能混着用
注意:不能用div1.appendChild('你好')
1.1 增(续)
- 插入页面中
- 创建的标签默认处于JS线程中,不会显示到页面上
- 必须把它插到 head(如:style和link元素) 或者 body 里面,它才会生效
document.body.appendChild(div)
已在页面中的元素.appendChild(div)
1.2 appendChild
- 页面中有 div#test1 和 div#test1(id为test1和2)
let div = document.createElement('div')
test1.appendChild(div)
test2.appendChild(div)
- 最终div会出现在 test2 中
因为每个元素只能有一个爸爸(这是树结构的特点),除非复制一份(克隆)
孩子先给了1,然后给了2,因此最后孩子在2哪里
2.删
- 两种方法
旧方法:div1.parentNode.removeChild(div1)
只能找父亲删掉儿子
新方法:div1.remove()
不能兼容IE - 思考
如何一个 node 被移出页面(DOM树)
那么它还可以再次回到页面中吗?——可以
答:因为移出之后还在内存中,并没有消失,用appendChild(div1)
即可
如何让它完全消失?
答:先移除,然后让它为空,div2就和内存断开联系,垃圾回收了
3.改
3.1 改写属性
- 改 class:
div.className = 'red bule'
(全覆盖,会把之前的覆盖掉)后面加个name的原因是早期的JS对象不能用保留字(如class、if)作为它的key,因为后面用的时候会产生混淆 - 改 class:
div.classList.add('red')
:不会覆盖之前的 - 改 style:
div.style = 'width: 100px; color:bule;'
这个会把前面的所有属性都覆盖掉,不推荐 - 改 style 的一部分:
div.style.width='200px'
大小写:div.style.backgroundColor='white'
大写字母相当于background-color
,就是把中划线用大写字母代替了 - 改 data-* 属性:
div.dataset.x = 'frank'
- 读属性
1.div.classList / a.href :这个有个bug,有些时候浏览器会默认添加一些前缀
2.div.getAttribute(‘class’) / a.getAttribute(‘href’) :虽然更复杂,但想得到什么,就是什么
3.两种方法都可以,但值稍微有些不同
<a id=test href="/xxx">/xxx</a>
console.log(test.href)
console.log(test.getAttribute('href'))
3.2 改事件处理函数
- div.onclick 默认为 null
1.默认点击 div 不会有任何事情发生
2.但是如果你把 div.onclick 改为一个函数 fn
3.那么点击 div 的时候,浏览器就会调用这个函数
4.并且是这样调用的fn.call(div,event)
:前面是触发元素的引用,后面接事件的所有详细信息
5.div 会被当做 this
6.event 则包含了点击事件的所有信息,是浏览器用call传进来的,如坐标(在哪点的) - div.addEventListener
是上一个的升级版,onclick只能写一个函数,这个能写无数个
<div id="test">test</div>
test.onclick = function(x){
console.log(this)
console.log(x)
}
//test.onclick.call(test,evet)
3.3 改内容
- 改文本内容
div.innerText = 'xxx'
div.textContent = 'xxx'
两个几乎没区别 - 改 HTML 内容
div.innerHTML = '<strong>重要内容</strong>'
如果里面的字符太多或者太复杂,容易把浏览器卡住 - 改标签
div.innerHTML = ''
//先清空
div.appendChild(div2)
//再加内容
3.4 改爸爸
- 想要找一个新的父级
newParent.appendChild(div)
直接这样就可以了,直接从原来的地方消失
查
- 查爸爸
node.parentNode
或者node.parentElement
- 查爷爷
node.parentNode.parentNode
- 查子代
node.childNodes
或者node.children
(优先使用children,不会算其他符号进去)
当用childNodes查子代时,子代会包含文本节点:回车空格(不论多少个会合成1个空格)、标签
中间没有空格就不算子代
<ul id="test">//空格1,多个合成1个
<li>1</li>//空格2
<li>2</li>//空格3
<li>3</li>//空格4
</ul>
console.log(test.childNodes.length)
//答案是:7(包含了3个li和4个空格)
- 查兄弟姐妹
node.parentNode.childNodes
,后面还需要遍历数组排除自己
node.parentNode.children
,后面还需要遍历数组排除自己
- 查看第一个儿子
node.children[0]
node.firstChild - 查看最后一个儿子
node.lastChild - 查看上一个哥哥/姐姐
node.previousSibling - 查看下一个弟弟/妹妹
node.nextSibling - 注意:以上查询时会有node和Element,两个结果可能不同,node中还包含文档节点
- 遍历一个 div 里面的所有元素
- 原理就和数据结构中遍历一棵树一样,这就是数据结构的大用处
travel = (node,fn) =>{
fn(node)
if(node.children){//判断是否存在子元素
for(let i=0; i<node.children.length;i++){
travel(node.children[i],fn)
}
}
}
travel(div1,(node)=>console.log(node))