一、网页其实是一棵树
- JS是如何操作这棵树?
- 浏览器往window上加一个document即可(Document Object Model,文档对象模型DOM)
- JS用document操作网页(把网页抽象成一个document对象,并对其进行操作)
二、DOM API
1、获取元素(element),也叫标签(tag)
window.idxxx或者直接idxxx(最方便)document.getElementById('idxxx')(如果id的名字和全局属性名字一样,就用这个,但大部分时候都用1.)document.getElementsByTagName('div')[0](获取第0个div标签,如果不写[0],就会获取到所有div,且不能操作)document.getElementsByClassName('red')[0](获取第0个类名为'red'的标签)
- 以上2.3.4.永远不要用,除非是要兼容IE
document.querySelector('#idxxx')(和1.的区别在于,括号里的内容可以写得十分复杂,如document.querySelector('div>span:nth-child(2)'),获取div里的span,且这个span还是第2个元素(只要找到满足条件的第1个span,如用6. All可找到所有满足条件的span))document.querySelectorAll('.red')[0]/document.querySelectorAll('#idxxx')[0](不写[0]就不能指定获取第几个,且不能操作)
- 工作中用5.6.,如果是demo可以用1.,但还是推荐用正规5.6.
2、获取特定元素
document.documentElement,获取html元素(document.documentElement.tagName//相对应获取到的标签名是HTML)document.head,获取head元素document.body,获取body元素
- 以上,对应获取到的标签名都是大写,如HTML
window,获取窗口(窗口不是元素),有什么用呢?比如可以通过window添加一些监听事件,如window.onclick=()=>{console.log('hi')}document.all,获取所有元素,且这是第6个falsy值,对此,展开介绍如下:
document.all是由IE发明的,所以早期程序员会用document.all来检测当前页面是不是IE
if(document.all){
console.log('IE浏览器');...在IE运行的代码
}else{
console.log('非IE浏览器');...在非IE运行的代码
}
- 现在所有浏览器都有
document.all功能,但原在IE运行的代码现也只能在IE运行,在非IE不能运行,故“赐”document.all为假,执行else if(document.all){console.log('3')}else{console.log('4')},打出4
三、获取到的元素是?——一个对象
- 这个对象有六层原型
(截图来自饥人谷课件)
(截图来自饥人谷课件)
四、节点
1、节点node包括(x.nodeType得到一个数字,如div.nodeType//1)
- 1表示元素Element,也叫标签Tag
- 3表示文本Text
- 8表示注释Comment
- 9表示文档Document
- 11表示文档片段DocumentFragment
- 记住1.2.即可
2、节点的增删改查
2.1、增
- 创建一个标签节点,
let div1=document.creatElement('div'),style、script、li等 - 创建一个文本节点,
let text1=document.creatTextNode('你好') - 标签里面插入文本
div1.appendChild(text1),Node提供的函数div1.innerText='你好'/div1.textContent='你好',Element提供的函数- 但是不能用
div1.appendChild('你好')
- 插入页面中
- 创建的标签默认处于JS线程中,须插入head或body里才生效
document.body.appendChild(div)/已在页面中的元素.appendChild(div)- 一个标签做两次插入至不同元素中,最终这个标签会出现在哪?
2.2、删
div1.parentNode.removeChild(div1),旧方法div2.remove(),新方法,但是不兼容IE
- 以上只是把一个node移出页面,但还在内存中
2.3、改
2.3.1、写标准属性
- 改class
div.className='red blue'(全覆盖),若想再加一个black,div.className+='black'//'red blue black'- 也可用
div.classList.add('black')//'red blue black'
- 改style
div.style='width:100px;color:blue;',全改了,比如之前的position也没有了- 所以用
div.style.width='200px',只改width,其余还在
- 注意大小写
div.style['background-color'],这是页面原来的,改写时需写成div.style.backgroundColor='black'
- 改data-*属性
2.3.2、读标准属性
2.3.3、改事件处理函数,如何理解?
- 先给一个
<div id="test">test</div> div.onclick默认为null,点击div不会有任何事件发生,即console.log(test.onclick)//null- 但是如果把
div.onclick改为一个函数fn
test.onclick=function(x){
console.log(this)
console.log(x)
}
- 那么点击div时,浏览器就会调用这个函数,并且是这样调用的
fn.call(div,event)
test.onclick.call(test,event)
- div会被当做this,event则包含了点击事件的所有信息
div.addEventListener是div.onclick的升级版
2.3.4、改内容(改“儿子”)
- 改文本内容
div.innerText='xxx'/div.textContent='xxx'
- 改HTML内容
div.innerHTML='<span>hi</span>',这里面的内容可以写得很复杂,但不能超过约两万字
- 改标签
div.innerHTML=''//先清空,div.appendChild(div2)//再加内容
2.3.5、改“爸爸”
newParent.appendChild(div),直接从原来地方消失,到新这里
2.4、查
2.4.1、查“爸爸”
node.parentNode/node.parentElement
2.4.2、查“爷爷”
node.parentNode.parentNode
2.4.3、查“子代”
node.childNodes/node.children- 探讨长度问题
childNodes包括文本节点的
- 但如果
<ul id="test"><li>1</li><li>2</li><li>3</li></ul>,console.log(test.childNodes.length)//3 - 但
2.4.4、查“兄弟姐妹”
node.parentNode.childNodes/node.parentNode.children- 这是找到爸爸的小孩,还要排除掉自己,并且childNodes还要排除掉文本节点,更麻烦
- 所以使用children,遍历爸爸的小孩,不是自己就装进一个空数组里
let Siblings=[]
let c2=div2.parentNode.children
for(let i=0;i<c2.length;i++){
if(c2[i]!==div2){
Siblings.push(c2[i])
}
}
2.4.5、查看“老大”
node.firstChild
2.4.6、查看“老幺”
node.lastChild
2.4.7、查看上一个“孩子”、下一个“孩子”
node.previousSiblingnode.nextSibling- 以上,有可能会查到文本节点,可以用
node.previousElementSibling和node.nextElementSibling
2.4.8、遍历一个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))
五、DOM操作是跨线程的
浏览器功能划分为渲染引擎(渲染HTML和CSS)和JS引擎(执行JS),这是两个线程。
1、各线程各司其职
- JS引擎不能操作页面,只能操作JS
- 渲染引擎不能操作JS,只能操作页面
- 那么
document.body.appendChild(div1),这句JS是如何操作页面的?
2、跨线程通信
- 当浏览器发现JS在body里加了个div1对象
- 浏览器就会通知渲染引擎在页面里也新增一个div元素
- 新增的div元素所有属性都照抄div1对象
(截图来自饥人谷课件)
- 插入新标签的完整过程
(截图来自饥人谷课件)
(截图来自饥人谷课件)
- 以上例子,若无
test.clientWidth,浏览器只会执行test.classList.add('end')操作,就无宽度从100-200的动画了,而test.clientWidth是获取这个div的客户端宽度,test.classList.add('start')必须立即渲染,不然无法得知
3、两个线程的属性同步问题
- 标准属性的修改,会被浏览器同步到页面中,如id、className、title等
- data-*属性也一样
- 非标准属性的修改,只停留在JS线程中,如自定义的x属性
- 所以,如果有自定义属性,又想同步到页面中,使用data-作为前缀,data-x
4、property属性和attribute属性
- JS线程中的属性叫做property(对象的属性)
- 渲染线程中的属性叫做attribute(标签的属性)
- 大部分,同名的property和attribute值相等
- 如果不是标准属性,那么只在一开始时相等
- attribute只支持字符串
- 而property支持字符串、布尔等类型