文章目录
一. DOM
1.DOM是什么:
DOM(Document Object Model)文档对象模型,是处理可扩展标志语言的标准编程接口。
DOM 是 W3C 的标准; [所有浏览器公共遵守的标准]
2.DOM0级和DOM2级有什么区别:
DOM0级中为某个dom元素绑定多个事件时,只有最后一个事件有效。onclick
DOM2级中可以为单个元素绑定多个事件,每个事件都可以被触发。addEventListener
3.textContent、innerText、innnerHTML、value的区别:
- textContent用来获取和设置文本内容,与innerText的差别是:textContent获取到的内容包括了元素中的style标签和script标签的内容。
- innerText只能获取和设置文本内容,不能获取和设置html代码
- innerHTML可以获取和设置html代码
- value获取的是表单元素的值
4.关于dom的api有什么:
https://www.cnblogs.com/betgar/articles/5084855.html
节点创建型api:
- document.createElement()
- document,createTextNode()
- parent.cloneNode(true)
- document.createDocumentFragment() 创建文档片段,解决大量添加节点造成的回流问题
页面修改型API:
- parent.appendChild(child)
- parent.insertBefore(newNode,referenceNode) 将新元素添加到父元素中指定的子元素前面
- parent.removeChild(child)
- parent.replcaeChild(newChild,oldChild)
节点查询型API:
- document.getElementById()
- document.getElementsByTagName() 返回的是一个即时的HTMLCollection类型
- document.getElementsByName() 根据指定的name属性获取元素,返回的是一个即时的NodeList
- document.getElementsByClassName() 返回的是一个即时的HTMLCollection
- document.querySelector() 获取匹配到的第一个元素,采用的是深度优先搜索。
- docuemnt.querySelectorAll()
- 返回的是一个非即时的NodeList,也就是说结果不会随着文档树的变化而变化
节点关系型api:
- 父关系型:
- node.parentNode()
- 兄弟关系型
- node.previouSibling() 返回节点的前一个节点(包括元素节点,文本节点,注释节点)
- node.previousElementSibling() 返回前一个元素节点
- node.nextSibling() 返回下一个节点
- node.nextElementSibling() 返回下一个元素节点
- 子关系型
- parent.childNodes() 返回一个即时的NodeList,包括了文本节点和注释节点
- parent.children() 一个即时的HTMLCollection,子节点都是Element
- parent.firsrtNode()
- parent.lastNode()
- hasChildNodes()
元素属性型api:
- element.setAttribute(“name”,“value”) 为元素添加属性
- element.getAtrribute(“name”) 获取元素的属性
元素样式型api:
- window.getComputedStyle(element) 返回一个CSSStyleDeclaration,可以从中访问元素的任意样式属性。
- element.getBoundingClientRect() 返回一个DOMRect对象,里面**⭐️ 包括了元素相对于可视区的位置top,left**,以及元素的大小,单位为纯数字。可用于判断某元素是否出现在了可视区域。
5.什么是事件监听:
addEventListener()方法,用于向指定元素添加事件句柄,它可以更简单的控制事件,语法为
element.addEventListener(event, function, useCapture);
第一个参数是事件的类型(如 “click” 或 “mousedown”).
第二个参数是事件触发后调用的函数。
第三个参数是个布尔值用于描述事件是冒泡还是捕获。该参数是可选的。
事件传递有两种方式,冒泡和捕获
事件传递定义了元素事件触发的顺序,如果你将P元素插入到div元素中,用户点击P元素,
在冒泡中,内部元素先被触发,然后再触发外部元素,
捕获中,外部元素先被触发,在触发内部元素,
6.说说前端中的事件流
什么叫Dom事件流?
事件发生时会在元素节点之间按照特定的顺序传播,整个过程分为捕获阶段,目标阶段和冒泡阶段,这个传播过程叫做Dom事件流。
事件冒泡:从事件源逐级向上传播到DOM最顶层节点的过程。
事件捕获:从DOM最顶层节点逐级向下传播到事件源的过程。
addEventListener用于指定事件处理程序,共接收三个参数。分别是触发事件,事件处理程序函数以及一个布尔值。第三个参数默认为false,表示在该事件的处理函数会在冒泡阶段被调用。若改为true,则表示事件处理函数会在捕获阶段被调用。
IE只支持事件冒泡。
7.如何让事件先冒泡后捕获
原本的事件流中,是先捕获再冒泡。
对于目标元素来说,如果DOM节点通过addEventListener同时绑定了两个事件监听函数,一个用于捕获,一个用于冒泡,那么两个事件的执行顺序是按照代码添加的顺序执行的。所以,先绑定冒泡的函数,再绑定捕获的函数,即可实现。
对于非目标元素来说,可以给捕获事件的处理程序添加一个定时器,将处理程序推入下一个宏任务执行。
8.说一下事件代理:
事件委托是指 不在子节点单独设置事件监听器,而将事件监听器设置在父节点上,再利用冒泡原理使每一个子节点都能触发该事件。
事件委托的优点:只操作一次Dom,提高了程序的性能。
常用于
ul和li标签的事件监听,一般采用事件委托机制将事件监听器绑定在ul上。
还适合动态元素的绑定,新添加的子元素不需单独添加事件处理程序。
(1)了解事件代理吗,这样做有什么好处
事件代理/事件委托:利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的事件,
简而言之:事件代理就是说我们将事件添加到本来要添加的事件的父节点,将事件委托给父节点来触发处理函数,这通常会使用在大量的同级元素需要添加同一类事件的时候,比如一个动态的非常多的列表,需要为每个列表项都添加点击事件,这时就可以使用事件代理,通过判断e.target.nodeName来判断发生的具体元素,这样做的好处是减少事件绑定,同事动态的DOM结构任然可以监听,事件代理发生在冒泡阶段
(2)事件委托以及冒泡原理:
事件委托是利用冒泡阶段的运行机制来实现的,就是把一个元素响应事件的函数委托到另一个元素,一般是把一组元素的事件委托到他的父元素上。
委托的优点是减少内存消耗,节约效率
动态绑定事件
事件冒泡,就是元素自身的事件被触发后,如果父元素有相同的事件,如onclick事件,那么元素本身的触发状态就会传递,也就是冒到父元素,父元素的相同事件也会一级一级根据嵌套关系向外触发,直到document/window,冒泡过程结束。
(3)事件代理在捕获阶段的实际应用:
可以在父元素层面阻止事件向子元素传播,也可代替子元素执行某些操作。
9.事件类型相关:
(1)mouseover和mouseenter的区别
mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移出事件是mouseout。
mouseenter:鼠标移入子元素时不会再次触发mouseenter事件,对应的移出事件是mouseleave。
(2)三种键盘事件的区别:
keyup: 松开键盘触发
keydown:按下键盘触发
keypress:不能识别功能键,比如ctrl,alt,shift,左右箭头。可以区分大小写。
在输入框中按下一个键的全过程:触发keydown/keypress事件->文字键入输入框中->触发keyup事件
按下按键后自动对焦输入框,应该使用keyup,不应该使用keydown/keypress,因为后者会使按键落入输入框中,对于回车键的话还不能使用keypress。
10.静态绑定事件与动态绑定事件的区别:
静态绑定事件是指直接在Html标签上通过οnclick="hide()"来绑定事件。
缺点:
- html和js文件存在耦合,不符合结构和行为分离的原则。
- 可能存在引发错误,如果js代码还没加载就触发该事件则会抛出错误
动态绑定事件是指通过js动态绑定事件,element.onclick() element.addEventListener()。
11.元素的位置和大小-三大系列
① offset系列:
⭐️ offsetTop(获取元素位置): 相对于带有‘定位’的父元素的偏移量
offsetHeight: content+padding+border
offsetParent: 返回带有定位的父元素
② cilent系列:
clientTop: 上边框border-top的宽度
⭐️ clientHeight(获取元素宽高): content+padding,不包含border。
③ scroll系列:
⭐️ scrollTop(获取滚动的距离): 向下滚动后,上面被卷去的距离,即隐藏的高度。
scrollHeight: content+padding ,其中的content包含了因为滚动被隐藏的部分。
④ document.clientWidth与document.style.width的区别:
区别1:前者可以获取任意样式表中的width样式值,包括行内样式的,内嵌样式的,外部样式的;后者只能获取行内的样式。
区别2:clientWidth获取的是数字型的,style获取的带有px后缀
区别3:clientWidth包含了padding,而style.width只包含content。
区别4:clientWidth是只读属性,所以一般用于获取元素的大小;而style.width是可读可写的,可用于获取,也可用于修改。
⑤ 特殊:
**获取html元素:**document.documentElement
**获取body元素:**document.body
获取可视区域的宽高:
- window.innerWeight 获取的宽度包括纵向滚动条的宽度。
- ⭐️ document.documentElement.clientWidth 获取的是正宗的可视区域的宽度
- **document.body.clientWidth ** 获取的是body的宽度,即content+padding。
获取window向下滚动时被卷去的高度: window.pageYOffset (注意:不能使用window.scrollTop)
⑥ 判断一个元素是否已经出现在了可视区域:(此问题可应用在懒加载中)
方法一:计算比较麻烦
需满足条件: xxx.offsetTop(需要递归叠加获取)<= window.pageYOffset+document.documentElement.clientHeight
即该元素距页面顶端的距离 <= window向下滚动隐藏的距离+window的可视区域的高度。
方法二:使用element.getBoundingClientRect().top获取在可视区的位置。
window.addEventListener("scroll", function () {
let viewPortHeight = window.pageYOffset;
let offset = box2.getBoundingClientRect().top;
if (offset < viewPortHeight) {
if (offset + box2.offsetHeight < 0) {
console.log("他走了");
} else {
console.log("他来啦他来啦");
}
}
});
12.鼠标坐标
clientX,clientY: 鼠标在可视区的坐标,可视区即展示在用户面前的页面区域
pageX,pageY: 鼠标在整个html页面的坐标。一般实际应用使用pageX和pageY
screenX,screenY: 鼠标在电脑屏幕的坐标,即整个电脑屏幕,15寸这个s
13.js拖动及拖拽功能的实现
(1)拖动功能的实现:
前置条件:
1.拖动事件的三个过程:鼠标按下mousedown,鼠标移动mousemove,鼠标松开mouseup
鼠标按下后执行mousemove事件。
2.盒子采用绝对定位,通过left和top属性来修改位置。
方法一:(直接根据鼠标移动的距离确定元素移动的距离)
鼠标的坐标通过clientX,clientY获取:
盒子的定位信息:鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的offetLeft.
方法二:
鼠标的坐标通过pageX,pageY获取:
先计算鼠标在盒子中的坐标,这是不变的。然后在mousemove的时候通过pageX和pageY减去在盒子中的坐标计算出盒子边缘应该修改为的偏移量。
(2)拖拽功能的实现:
使用html5提供的拖拽API(Drag 和 drop)
拖拽功能涉及的基本事件:
dragstart:在开始拖放元素时触发。(事件源:被拖拽的元素)
- 这一步需要做的是获取被拖拽元素的id。拖拽事件对象中的dataTransfer属性是专门用来存储拖动过程中的数据的。
ev.dataTransfer.setData("key",value)
dragover:在被拖放在某元素内移动时触发。(事件源:目标元素)
- 阻止dragover的默认事件(不允许被拖拽)
drop:目标元素完全接受被拖放元素时触发。(事件源:目标元素)
- 阻止drop的默认事件(以链接的形式打开),然后获取之前保存的元素的id
ev.dataTransfer.getData("key")
,然后将该元素添加到目标元素中。
二. (BOM)浏览器对象模型:
1.实用的BOM属性对象方法:
-
location对象
- location.href– 返回或设置当前文档的URL
- location.search – 返回URL中的查询字符串部分。例如 http://www.dreamdu.com/dreamdu.php?id=5&name=dreamdu 返回包括(?)后面的内容?id=5&name=dreamdu
- location.hash – 返回URL#后面的内容,如果没有#,返回空
- location. – 返回URL中的域名部分,例如www.dreamdu.com
- location.hostname – 返回URL中的主域名部分,例如dreamdu.com
- location.pathname – 返回URL的域名后的部分。例如 http://www.dreamdu.com/xhtml/ 返回/xhtml/
- location.port – 返回URL中的端口部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回8080
- location.protocol – 返回URL中的协议部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回(//)前面的内容http:
- location.assign() – 重定向页面,与location.href一样,会记录历史,能后退页面
- location.replace() – 设置当前文档的URL,不记录历史,不能后退页面
- location.reload() – 重载当前页面,相当于F5。添加参数true则表示强制刷新,直接从服务器获取数据,不从浏览器缓存中取数据,相当于Ctrl+F5
-
history对象
- history.go(n) – 前进或后退指定的页面数;
- history.back() – 后退一页
- history.forward() – 前进一页
-
navigator对象
- navigator包含了用户浏览器的信息
- navigator.userAgent – 返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串)
- navigator.cookieEnabled – 返回浏览器是否支持(启用)cookie
2. setTimeout(fn,100);100毫秒是如何权衡的:
100ms指的是将回调函数加入到任务队列所花的时间。至于具体什么时候执行,需要看主线程的执行栈中是否还有任务在执行。
3.定时器实现动画的最佳时间:16.6ms
大多数电脑显示器的刷新频率是60HZ,大概相当于每秒钟重绘60次。因此,最平滑的动画效的最佳循环间隔是1000ms/60,约等于16.6ms
4.setInterval存在的问题:
定时器的代码执行部分不断的被调入任务队列中,如果定时器的执行时间比间隔时间长,最终可能导致定时器堆叠在一起执行。
js引擎为了解决这个问题,采用的方式是若任务队列中存在这个定期器,则不会将新的定时器放入任务队列,这样做的弊端是可能导致某些间隔被跳过。
解决方法:
循环调用setTimeout来实现setInterval:(即用setTimeout来实现setInterval)
setTimeout(function fn(){
...
setTimeout(fn,delay)
},delay)
5.requestAnimationFrame:
js动画的要求:
一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。
用定时器实现js动画存在的问题:
定时器回调函数执行的时机不精确。定时器中的延时指的是将回调函数加入到任务队列所需花的时间,如果主线程中还有任务在执行,就不能确保回调函数在放入队列后马上执行,这就造成了执行时机的不精确。
requestAnimationFrame:
特点:requestAnimationFrame采用系统时间间隔,保证了最佳的绘制效率。
使用方法:requestAnimationFrame接收一个回调函数,这个回调函数会在下一次浏览器重绘之前调用。
6.分别用setInterval,setTimeout,requestAnimationFrame制作有个简单的进度条效果:
setInterval:
<div
style="width: 0; height: 20px; background-color: orange"
id="div"
></div>
<script>
let timer = setInterval(() => {
if (parseInt(div.style.width) >= 500) {
return clearInterval(timer);
}
console.log(div.style.width);
div.style.width = parseInt(div.style.width) + 5 + "px";
div.innerHTML = parseInt(div.style.width) / 5 + "%";
}, 16);
</script>
setTimeout:
<div
style="width: 0; height: 20px; background-color: orange"
id="div"
></div>
<script>
let timer = setTimeout(function fn() {
if (parseInt(div.style.width) < 500) {
div.style.width = parseInt(div.style.width) + 5 + "px";
div.innerHTML = parseInt(div.style.width) / 5 + "%";
timer = setTimeout(fn, 16);
} else {
clearTimeout(timer);
}
}, 16);
</script>
requestAnimationFrame:类似于setTimeout,需要一次次的调用
<div
style="width: 0; height: 20px; background-color: orange"
id="div"
></div>
<script>
let timer = requestAnimationFrame(function fn() {
if (parseInt(div.style.width) < 500) {
div.style.width = parseInt(div.style.width) + 5 + "px";
div.innerHTML = parseInt(div.style.width) / 5 + "%";
requestAnimationFrame(fn);
} else {
cancelAnimationFrame(timer);
}
});
</script>
7.js中的轮播实现原理?假如一个页面上有两个轮播,你会怎么实现?
1.让图片存在一个数组中,然后将最后一张图片重复添加在数组的头部,将第一张图片重复添加在数组的最后。
2.然后准备一个只能显示一张图片的盒子,对盒子做溢出隐藏处理。
3.通过定时器增减索引,显示对应的图片,实现轮播功能。
如果有两个轮播,可封装一个轮播组件,将需要轮播的图片作为参数传递。