1.DOM(Document Object Model)
文档对象模型
2.获取元素的API
windows.idXXX或者直接idXXX
,举例:document.getElementById('idxxx')
,一般用上面的方式获取元素,对于id名称为关键字比如:parent
的时候,用这种方法。document.getElementsByTagName('div')[0]
document.getElemetsByClassName('red')[0]
document.querySelector('#idXXX')
,这种写法与CSS选择器的写法相同。document.querySelectorAll('.red')[0]
推荐使用最下面两种,需要兼容IE的时候,才用getElement(s)ByXXX
。
对于 获取特定元素:- 获取HTML元素:document.documentElement
- 获取head元素:document.head
- 获取body元素:document.body
- 获取窗口(窗口不是元素):window
- 获取所有元素:document.all,需要注意:由于这种方式最开始只能在IE浏览器中使用,后来其他浏览器才沿用过去的。但是为了区分,
document.all
的值,只对ie为true,所以可以用于检测当前浏览器是不是IE,举例: 在IE中: 在Chorme中:
2.1 获取到的元素的原型链
随意获取页面上的一个元素,用console.dir()
可以看到该元素的元素本身->HTMLDivElement.prototype->HTMLElement->Element->Node->EventTarget->Object
完整原型链。图示:
原图地址: github.com/Scott-YuYan…
- 第一层:
HTMLDivElement.prototype --所有div共有的属性
- 第二层:
HTMLElement.prototype --所有HTML标签共有的属性
- 第三层:
Element.prototype -- XML/HTML所共有的属性
- 第四层:
Node.prototype -- (Node是接口)所有节点的共有属性
- 第五层:
EventTarget.prototype -- 最重要的函数属性:addEventListener
- 最后一层:
Object.prototype
3.节点的增删改查
3.1 增加节点
创建标签节点:
let div1 = document.createElement('div')
document.createElement('style')
document.createElement('script')
document.createElement('li')
创建一个文本节点
text1=document.createTextNode('你好')
标签中插入文本
div1.appendChild(text1)
div1.innerText = 'ni hao' 或者 div1.textContent='ni hao'
- 但是不能用
div1.appendChild('ni hao')
插入到页面中
- 创建的标签默认处于JS线程中,需要将标签插入到
body
或者head
中,才会生效 document.body.appendChild(div)或者 已存在的元素.appendChild(div)
注意,
例:页面中有div#test1 和div#test2
let div = document.createElement('div');
test1.appendChild(div);
test2.appendChild(div);
问:最终的div出现在哪里?(test1? test2? 还是两个都有?)
答案是只会在test2
中,同一个元素不能出现在两个地方,除非复制一份,可使用div.cloneNode(true)
--true表示深拷贝,即将所有后代一起拷贝。
3.2 删节点
两种形式:
- 旧方法:
child.parentNode.removeChild(child) --找到自己的父亲节点,然后用父亲节点删掉自己
- 新方法,但是不兼容IE:
child.remove()
- 节点删除后,只是被放在内存中,还是可以被恢复的。如果节点被置为空:
child = null
那么该节点就会被垃圾回收所彻底删除。
3.3 改节点
写标准属性:
改class:div.className='red blue'(会将原属性直接覆盖)
改class:div.classList.add('red')
改style:div.style='width:100px;color:blue;'
改style的一部分:div.style.width='200px'
改大小写:div.style.backgroundColor='white'(-c被简化为C)
改attribute属性:div.setAttribute('','')
改data-* 属性:div.dataset.x='frank'
读标准属性:
div.classList / a.href
div.getAttribute('class')/a.getAttribute('href')
两种方式都可,有些地方稍微有些不同:
<a href="/xxx" id="aTarget">链接</a>
改事件处理函数
div.onclick()函数默认为null,点击不会有任何事情产生。如果修改div.onclick()为调用函数fn(),那么点击该div的时候,就会通过fn.call(div,event)调用这个函数
。
改文本内容:
div.innerText = 'xxx'
div.textContent = 'xxx'
改HTML内容:
div.innerHTML = '<h2>二级标题</h2>' //大小写不能错,且比较消耗内存
3.4 查节点
- 查父亲:
node.parentNode | node.parentElement
- 查爷爷:
node.parentNode.parentNode
- 查子代:
node.childNodes | node.children
注意:
<ul id="liTest">
<li>儿子</li>
<li>儿子</li>
<li>儿子</li>
</ul>
竟然有7个childNodes
,原因在于需要注意前后的回车空格。将li
写成一行,或者使用children
可避免这种情况。
- 查兄弟姐妹:
node.parentNode.childNodes | children还要排除自己
- 查第一个 | 最后一个孩子:
node.firstChild | node.lastChild
- 查上一个 | 下一个兄弟节点:
node.previousSibling | node.nextSibling --这种方式会获取到文本内容
- 查上一个 | 下一个兄弟元素节点:
node.previousElementSibling | node.nextElementSibling --这种方式不会获取到文本内容
4.为什么DOM操作会比较慢
原因在于:DOM操作是跨线程的。浏览器分为渲染引擎和JS引擎,JS引擎不能操作页面,只能操作JS,而渲染引擎不能操作JS只能渲染页面。当使用document.body.appendChild(div1)
为body增加元素时,浏览器发现JS在body里面加了div1对象,浏览器会通知渲染引擎也新增一个div对象。新增加的div元素的所有属性,全部照抄div1对象。
推荐阅读:
1.为什么说DOM操作很慢
5.捕获与冒泡
由外向里叫事件捕获-网景发明,由内向外叫事件冒泡-IE发明 示意图:
5.1 添加事件绑定的API
ie5*: dom.attachEvent('onlick',fn)
//冒泡网景: dom.addEventListener('click'.fn)
//捕获W3C;dom.addEventListener('click',fn,bool)
//bool为空或者为falsy就让fn走冒泡,为true的话fn就走捕获
问,如下代码:
<div id="ye">
<div id="ba">
<div id="er">
测试
</div>
</div>
</div>
当我们点击测试的时候,算不算ye ba er
被点击了,如果算,那么是先执行ye
还是先执行er?
问题的答案是:不确定,因为在不同的浏览器中,结果是不一样的。
继续问,那么如果我对同一个对象即增加了监听事件,又增加了捕获事件,那么触发的顺序是什么?
问题的答案是:谁先添加先执行谁。
5.2 事件event.target与event.currentTarget的区别?
- event.target-用户操作的元素
- event.currentTarget-程序员监听的元素
- this是event.currentTarget 举例:
<div>
<span>文字</span>
</div>
当我们给div添加事件监听,用户点击文字的时候,e.target指的就是span,而event.currentTarget指的就是div。
5.3 取消冒泡
对于需要取消冒泡的情况,可以使用e.stopPropagation
,那么浏览器就不会继续执行外层的绑定函数,一般适用于封装某些独立的组件。但是注意,有些事件可以取消冒泡,有些事件是不能取消冒泡的,举例:
-
点击事件:
-
鼠标滚动事件:
那么如何阻止鼠标滚动事件,举例: 代码-css:
::-webkit-scrollbar {
display: none;
}
代码-js:
x.addEventListener('wheel', (e)=>{
e.preventDefault();
});
x.addEventListener('touchstart',(e)=>{
e.preventDefault();
});
6.自定义事件
前面我们提到了基本的冒泡和捕获事件,那么浏览器是自带100多种事件的,可查看MDN-事件看到浏览器所包括的100多种事件。
除此之外,我们还可以自定义事件,举例(还是以5.1中的html为例子):
<script>
let ye = document.querySelector("#ye");
ye.addEventListener('click',()=>{
const myEvent = new CustomEvent('myEvent',{
detail:{'name':'zhangsan',},
bubbles:true,//设置自定义事件的冒泡
cancelable:false,//设置冒泡是否可被取消
})
ye.dispatchEvent(myEvent)
})
ye.addEventListener('myEvent',(event)=>{
console.log(event.detail);
})
</script>
7.事件委托
事件委托,顾名思义,就是将本该由事件监听对象去完成的事件,委托给其他对象完成。比如以下场景:
- 场景1:需要给100个按钮添加点击事件,需要怎么做?
<div id="outer">
button#id$*100
</div>
那么可以将监听事件委托给button的外面一层:
<script>
let outer = document.querySelector("#outer");
outer.addEventListener('click',(event)=>{
let target = event.target;
if (target.tagName.toLowerCase() === 'button'){
console.log(target.id)
}
})
</script>
- 2.场景2 需要监听目前不存在的事件 举例:
<div id="outer">
</div>
<script>
let outer = document.querySelector("#outer");
const button = document.createElement("button");
button.textContent = 'click';
setTimeout(()=>{
outer.appendChild(button);
},1000)
</script>
click不存在页面上,那么同样可以把事件监听委托到外部元素上:
outer.addEventListener('click',(event)=>{
const tag = event.target;
if (tag.tagName.toLowerCase() === 'button'){
console.log(tag.innerText)
}
})
这么做的优点:1 节约监听数量 2 可以监听动态生成的元素。
8.属性同步
- 标准属性:对div标准属性的修改,会被浏览器同步到页面中,比如
id、className、title
data-*
属性:同上- 对非标准属性的修改,只会停留在JS线程中,不会同步到页面中。举例:
<div id="test" x="test" data-x="test"></div>
function changeAttr() {
let div1 = document.querySelector('#test');
div1.id = 'newTest';
div1.dataset.x = 'newTest'; // 同步过去了
div1.x = 'newTest'; // 没有同步过去
}
运行后: 可以看到非标准属性x的属性值没有改变。
8.1 property vs attribute
- property属性:JS线程中div1的所有属性,叫做div1的property
- attribute属性:渲染引擎中div1对应标签的属性,叫做attribute 二者区别:
- 二者大部分情况相同,但是如果不是标准属性,只在一开始的时候相同
- attribute只支持字符串,而property支持字符串,布尔等类型