网页其实是一棵树
JS如何操作这颗树?浏览器往window上加一个 document即可
JS用document 操作网页,这就是Document Object Model 文档对象模型简称DOM
获取元素,也叫标签
有很多API
document.getElementByid('idxxx')document.getElementsByTagName('div')[0](找到全部为div的元素)- document.getElementsByClassName('red')[0] (根据css class获取)
document.querySelector('#idxxx')(css选择器怎么写这里怎么写)document.querySelector('div>span:nth-child(2)')document.querySelectorAll('red')[0]
用哪一个
- 工作中用querySelector 和 querySelectorAll
- 要兼容IE才用getElement(s)Byxxx (前3个)
获取特定元素
-
获取html 元素
document.documentElement(只获取了html标签,不含里面的元素) -
获取head 元素
document.head -
获取body 元素
document.body -
获取窗口 (窗口不是元素)
window -
获取所有元素
document.all(可以获取到页面上所有标签) 这个document.all为第6个falsy值//它是个假的,它开始是IE发明的,早期的时候,用它检测是否是ie浏览器 if(documents.all){console.log('ie 浏览器');代码只能在IE里运行}else{ console.log('其他浏览器');只能在非IE 运行 } //但是你不做判断,直接用是可以直接用的 document.all[3]
获取到元素是什么
它是一个对象,我们需要搞清楚它的原型
抓一只div 对象来看看(console.dir()
let div = document.getElementsByTagName('div')[9];
console.dir(div)
- 自身属性:className、id、style等等(都是由构造函数添加上去的this.xx =xx)
- 第一层原型 HTMLDivElement.prototype,这里面是所有div共有属性
- 第二层原型HTMLElement.prototype,这里面是所有HTML标签共有属性
- 第三层原型 Element.prototype,这里面是所有XML、HTML标签的共有属性,你不会以为浏览器只能展示HTML吧
- 第四层原型Node.prototype,这里面是所有节点共有属性,节点包括XML标签文本注释、HTML标签文本注释等等
- 第五层原型 EventTarget.prototype 里面最重要的函数属性是 addEventListener
- 最后一层原型 就是Object.prototype
节点?元素?分不清
节点Node包括以下几种
- MDN有完整描述,x.nodeType得到一个数字
div.nodeType//1 - 1表示元素Element,也叫标签Tag
- 3表示文本Text
- 8表示注释 Comment
- 9表示文档Document
- 11表示文档片段DocumentFragment
节点的增删改查
增
创建一个标签节点
let div1 = document.createElement('div')document.createElement('style')document.createElement('script')document.createElement('li')
创建一个文本节点
text1 = document.createTextNode('你好')
标签里面插入文本
-
div1.appendChild(text1)(node函数提供的)let div = document.createElement('div') let text1 = document.createTextNode('你好') div.appendChild(text1) div <div>你好 </div> -
div1.innerText='你好'或者div1.textContent='你好'(element 提供的)let div = document.createElement('div') div.innerHTML='hello' div <div>hello</div>
插入页面中
- 你创建的标签默认处于JS线程中
- 你必须把它插到head 或者 body里面,它才会生效
document.body.appendChild(div)- 或者已在页面中的元素
.appendChild(div)
appendChild
-
页面中有div#test1 和 div#test2
let div = document.createElement('div') test1.appendChild(div) test2.appendChild(div) //最终div在test2里面,一个元素不能出现在两个地方,除非复制一份 //复制 div2 = div.cloneNode(true) document.head.appendChild(div) document.body.appendChild(div2)
删
两种方法
-
旧方法:
parentNode.childChild(childNode) -
新方法:
childNode.remove()(IE不支持)div.remove()//直接删除
思考
-
如果一个node 被移出页面(DOM树)
-
那么它还可以再次回到页面中吗
//可以 div.parentNode.removeChild(div);//删除了 document.body.appendChild(div)//又回来了,只是把它从树删到内存里了,它还在内存里面,如果要加回来就还是可以加 //如何在内存中移除 div2.remove() div2 = null //当等于空时,div2就和内存断开了联系,然后内存就被垃圾回收了
改
写标准属性
-
改class名:
div.className = 'red'(全覆盖)div.className = 'red' //一开始改成red //如果在后面想加必须加上前面写的,如果只写一个会被覆盖 div.className += ' blue' -
改class名:
div.classList.add('red')(新语法)div.className = 'red'//一开始的 //加新的 div.classList.add('blue') -
改Id :
div.id = 'div1' -
改style:
div.style = 'width:100px;color:blue'(把之前的样式全部覆盖) -
改style的一部分:
div.style.width = '200px'(只改style中width) -
大小写:
div.style.backgroundColor ='white' -
改date-*属性:
div.dataset.x = 'frank'
读标准属性
-
div.classList/div.className/div.id..... -
但是a 标签不一样
<a href="/xxxx" id=test>/xxxx</a> //在一个相对路径里 console.log(test.href) "http://xx.xxxxx.com/xxxx" //打印出这个,js会把前面的域名补齐 //如果就只想获取原本的href console.log(test.getAttribute('href'))//"/xxxx" -
div.getAttribute('class')/a.getAttribute('href')
-
两种方法都可以,但值可能稍微有些不同
改事件处理函数
-
div.onclick 默认为null
- 默认点击div不会有任何事情发生
- 但是如果你把div.onclick 改为一个函数fn
- 那么点击div的时候,浏览器就会调用这个函数
- 并且是这样调用的 fn.call(div,event)
- div会被当做this
- event则包含了点击事件的所有信息,如坐标
<button id=test>按钮</button> <script> console.log(test.onclick) //开始onclick为空 test.onclick= function(e){ console.log(this) console.log(e)//console.log(test.onclick) test.onclick= function(e){ console.log(this)//<button id="test">按钮</button> console.log(e)//MouseEvent {isTrusted: true, screenX: 646, screenY: 203, clientX: 32, clientY: 21, …} } //test.onclick.call(test,event) this和e是怎么来的,是浏览器用call 传进来的 } </script> -
div.addEventListener
- 是div.onclick 的升级版
改
改文本内容
div.innerText = 'xxx'div.textContent = 'xxx'- 两者没有区别,你想用哪个就用哪个
改HTML内容
div.innerHTML ='<strong>内容</strong>'- 如果字符串中超过2万个字符,网页会卡
改标签
div.innerHTML = ''//先清空div.appendChild(div2)//再添加内容
改爸爸
newParent.appendChild(div)- 直接这样就可以了,直接从原来的地方消失了
查
查爸爸
node.parentNode或者node.paentElement
查爷爷
node.parentNode.parentNode
查子代
-
node.childNodes或者node.children(优先使用)<ul id=test> <li>1</li> <li>2</li> <li>3</li> </ul> <script> console.log(test.childNodes.length)//打印出了7 会包含空格 console.log(test.children.length) // 打印3 不包含空格 </script> -
思考:当子代变化时,两者也会实时变化吗?会
console.log(test.childNodes.length) //7 console.log(test.children.length)//3 test.querySelector('li').remove() console.log(test.childNodes.length)//6 console.log(test.children.length)//2 //test.querySelectorAll('li')不会实时改变
查兄弟姐妹
-
node.parentNode.childNodes 要排除自己,和所有文本节点,所以更麻烦
-
node.parentNode.children 还有排除自己
div.parentElement.children let siblings= [] let c = div.parentElement.children for(let i = 0; i < c.length; i++){ if(c[i] !== div){ siblings.push(c[i]) } }
查看老大
node.firstChild
查看最小孩子
node.lastChild
查看上一个哥哥/姐姐
node.previousSibling包含文本节点node.previousElementSibling只有元素
查看下一个弟弟/妹妹
node.nextSibling
查看div所有元素,遍历一个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操作是跨线程的
浏览器分为渲染引擎和JS引擎
各线程各司其职
- JS引擎不能操作页面,只能操作JS
- 渲染引擎不能操作JS,只能操作页面
- document.body.appendChild(div1),这句JS是如何改变页面的?
跨线程通信
-
当浏览器发现JS在body里面加了一个div1对象
-
浏览器就会通知渲染引擎在页面里也新增一个div元素
-
新增的div元素所有属性都照抄div1对象
插入新标签的完整过程
-
在div1 放入页面之前,你对div1 所有操作都属于JS线程内的操作
-
把div1 放入页面之时, 浏览器会发现JS的意图,就会通知渲染线程在页面中渲染div1对应的元素
-
把div1放入页面后
div1 的操作都 有可能 会触发渲染
div1.id = 'newid' 可能会重新渲染(当id有css样式),也可能不会
div1.title = 'new' 可能会重新渲染 ,也可能不会
如果你 连续对 div1 多次操作,浏览器可能会 合并成一次操作, 也可能不会
<div id="test"></div> .start{ border: 1px solid red; width: 100px; height: 100px; transition: width 1s; } .end{ width: 200px; } test.classList.add('start') // test.clientWidth // 这句话看似无用,实际会触发重新渲染,如果没有这句话,浏览器会合并成一次操作,就没有100px到200px这个动画 test.classList.add('end')
属性同步
-
标准属性:
- 对div1的标准属性的修改,会被浏览器同步到页面中
- 例:id、className、title等
-
date-* 属性:
- 同上
-
非标准属性
- 对非标准属性的修改,则只会停留在JS线程中
- 不会同步 到页面里
-
启示
- 如果你有自定义属性,又想被同步到页面中,请使用date- 作为前缀
property vs Attribute
property属性
- JS线程中div1的所有属性,也叫做 div1 的property
attribute 也是属性
- 渲染引擎中 div1 对应标签的属性,叫做 attribute
区别
- 大部分时候,同名property 和 attribute值相等
- 但如果不是标准属性,那么它俩只会在一开始相等
- 但是注意 attribute 只支持字符串
- 而property 支持字符串、布尔等类型