❤️ Author: 老九
☕️ 个人博客:老九的CSDN博客
🙏 个人名言:不可控之事 乐观面对
😍 系列专栏:
文章目录
script标签
- 注意script标签,要么写src,里面不要写内容,要么就里面写内容,不要写src,上面图片的写法是错误的
DOM
- Document Object Model,文档对象模型,浏览器拿到html代码之后,会将这个html的代码解析成dom树,每遇到一个标签,都会为其创建一个对象,运行的时候是操作或者读取dom树,它就像一个实时的数据结构,只要是修改了dom树,对象嵌套的结构就会改变,页面展示也会改变。
- DOM树的根节点,就放在了document这个对象上面
- 我们可以想象html是一些嵌套的盒子,标签可以嵌套标签,盒子可以嵌套盒子。
- document对象,就表示整个文档,展示的和面板是一样的,这个是页面创建的时候就创建了这个对象,注意和documentElement区分。
- document.documentElement,这个是遇到html标签了之后,创建这个对象的这个属性
- 注意我们的标签的属性,和对应的dom对象的属性这是两回事,如果想看dom对象属性,通过console.dir(document.documentElement)可以看到,里面包含滚动位置,内容,标题等等一系列内容。
- 想看documentElement的第一个子节点,可以用firstElementChild属性
- 通过children数组获取对应的dom树上的标签
- 当你在面板中选中一个对象,就会创建一个全局对象$0,后面有一个==$0,我们可以通过$0获取对应的对象
文档树
- 语法树只是把源代码所有信息解析出来,DOM树远远不止源代码的信息
- 这个是语法树,可以看到远远小于DOM树的属性值的。
- 每一个dom结点都包含一个nodeType属性,这个是一个数字,表明它的类型,标签类型是1,文本类型是3,注释结点的nodeType是8
- DOM对象有一个childNodes的属性,这个属性指向一个类数组对象,有一个length属性,里面存的是该元素包含的子节点;childNodes属性包含文本节点,children属性不包含文本节点
- 下面高光的就是text文本节点。
- 如果创建标签
- 这样创建是非常繁琐的,这时候就出现了一些库,最有名的就是jQuery,解决DOM代码的繁琐问题。
- JQuery是封装了DOM,Lodash是封装了js的函数,这两个要区分开 。
DOM指针
- DOM结点包含了大量的指针指向周围的结点。
- 最后那个是lastchild指向最后一个元素。
- 下图是firstChild和firstElementChild的区别,firstChild是获取Node节点,而firstElementChild是获取元素,节点包含元素节点,文本节点等
- 我们理论上可以利用这些属性,移动页面上的任何节点;下面这段代码可以判断我们的页面里的文本节点中有没有对应的字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script>
function talksAbout(node, string) {
if (node.nodeType == document.ELEMENT_NODE) {
for (var child of node.childNodes) {
if (talksAbout(child, string)) {
return true;
}
}
} else if (node.nodeType == document.TEXT_NODE) {
return node.nodeValue.includes(string);
}
return false;
}
</script>
</head>
<body>
<div>bbb</div>
<script>
document.body.firstChild.nextSibling.nextSibling.nextSibling.textContent =
"ccc";
</script>
<strong>ccc</strong>
<input type="text" />
ccc
</body>
</html>
- 这种的编码方式叫做硬编码,这种编码方式有个坏处,就是如果你改变了代码的结构,整个都需要修改,这样是非常麻烦的。
- 通过getElementsByClassName方法,可以通过类名找到对应的结点;getElementById,getElementByName,getElementByTagName同理。
调试小技巧:如果想在控制台找到一个函数,先在console中输入这个函数,然后点击一下这个函数输出的东西,就可以自动跳转到当前函数了
- 注意我们获取的getElementsByTagName等等,这种获取的集合是动态的集合,下面这段代码我们想把所有的img图片变成文字节点,但是我们下面这种写法只能改变第一个,改变完之后,imgs就动态改变了。
- 如果我们要解决这个问题,我们把一个类数组对象变成一个数组,然后使用for of循环也可以实现。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<p>
the cat
<img src="cat.jpg" alt="Cat" />
is run after the
<img src="hat.jpg" alt="Hat" />
</p>
<button onclick="replaceImages()">Replace</button>
<script>
function replaceImages() {
var imgs = document.getElementsByTagName("img");
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
var textNode = document.createTextNode(img.alt);
img.parentNode.replaceChild(textNode, img);
}
}
</script>
</body>
</html>
介绍稀疏数组,当数组里面存在下标不存在的数组,就叫做稀疏数组
当我们创建 a = new Array(100000000)的时候,如果一个数组项存一个整数,一个整数占4个字节,那就要占4亿个字节,先除一个1024变成k,再除1024变成兆(M),最后算出来占381兆左右,但是在js中是不会占用这么多空间的,js会把它想成一个对象,以一个对象的形式存储,这个对象就有一个length属性。
因此我们可以得出一个结论,一个对象也可以看作是一个数组,称为类数组。
那么如果想让类数组获得数组本身的一些方法的话,我们可以通过Array.prototype.reverse.call(obj),实现this的转移,或者直接将obj._ proto _ = Array.prototype,直接将数组的原型属性赋给obj的原型上面,使得类数组获得数组的自有方法。
上面那个必须是类数组对象,才能使用reverse()
- 下面这个函数可以实现创建嵌套标签
function elt(tagName, ...children) {
var node = document.createElement(tagName);
for (var child of children) {
//如果child是文本结点,需要判断把字符串转化成文本节点
if (typeof child === "string") {
child = document.createTextNode(child);
}
node.appendChild(child);
}
return node;
}
- 我们还可以对DOM结点的属性进行操作
function elt(tagName, attrs = {}, ...children) {
var node = document.createElement(tagName);
if (attrs) {
for (var key in attrs) {
debugger;
var val = attrs[key];
node.setAttribute(key, val);
}
}
for (var child of children) {
if (typeof child === "string") {
child = document.createTextNode(child);
}
node.appendChild(child);
}
return node;
}
节点的属性
tagName,nodeName
- tagName可以获取到元素标签的名字
innerHTML
- 获取标签包裹的元素中的内容,以字符串形式返回
outerHTML
- 包含了元素的完整的HTML
textContent
- 仅仅获取元素中的文本内容
创建元素
两个步骤:1.创建一个元素2.插入元素到DOM的某一个位置
移除和克隆元素
元素节点
- 元素的查找:
getElements?.*() - 元素的周围指针
对结点进行增删改
el.appendChild(node)
el.removeChild(node)
el.replaceChild(node,baseNode)
el.insertBefore(node,baseNode)
属性操作
node.xxxx,标准属性大都可以使用node.prop的形式访问,如id,title,alt,src,href,type,name,value,class要用className,label的for属性要用htmlFor,如果一个标签对应多个类名,我们通过node.classList.add/remove/contains/replace/toggle(切换功能,有的话就加上,没有的话就删掉)可以操作多个class类名
node.getAttribute()/setAttribute()/removeAttribute()/hasAttribute(),这个可以访问到html的属性
通过dataset可以获得以data-开头的自己定义的属性的值,如果有-的话会自动转成驼峰式
元素的className和classList
元素的style属性
注意驼峰式
查找函数实现方法
- 遍历DOM结点,类似二叉树的先序遍历
function traverseDOM(node, action = console.log) {
if (node.nodeType == document.ELEMENT_NODE) {
action(node);
for (var child of node.children) {
traverseDOM(child, action);
}
}
}
function traverse(root) {
if (root) {
console.log(root);
traverse(root.left);
traverse(root.right);
}
}
- 实现getElementsByTagName
function getElementsByTagName(node, tagName) {
var result = [];
for (var child of node.children) {
if (child.tagName == tagName) {
result.push(child);
}
result.push(...getElementsByTagName(child, tagName));
}
return result;
}
function getElementsByTagName2(node, tagName) {
var result = [];
traverseDOM(node, (it) => {
if (it.tagName == tagName) {
result.push(it);
}
});
if (result[0] == node) {
result.shift();
}
return result;
}
- 实现getElementById
function getElementById(id, node = document.documentElement) {
if (node.id === id) {
return node;
} else {
for (var child of node.children) {
var result = getElementById(id, child);
if (result) {
return result;
}
}
return null;
}
}
function getElementById2(id) {
var result = null;
traverseDOM(document.documentElement, (it) => {
if (it.id === id) {
result = it;
}
});
return result;
}
获取DOM结点尺寸和位置
- 通过$0.offsetWidth和$0.offsetHeight可以获取结点的宽高,包含边框及以内的区域
- 通过getBoundingClientRect()方法,可以获得一个元素的位置
简单理解clientHeight、scrollHeight、offsetHeight
scrollBy和scrollTo的区别
事件处理
- 有些程序需要处理用户的输入,浏览器通过事件处理函数,在用户点击的设定的按钮的时候,页面才会发生相应的变化,最常用的是通过addEventListener进行事件监听
常见的事件列表
事件处理器
- 每一个事件处理器都是注册在一个上下文中的
- 有三种写法
第一个是写在< div οnclick=“foo()”>< /div>,这个写法属性前面要加on,并且属性的内容必须要写函数的调用,不能只写函数名,当该标签被解析构建DOM结点的时候,属性的内容会作为一个函数的函数体源代码。 实际上里面的函数是这个样子的,div就相当于this,然后通过一个with函数,实现onclick
第二种写法是node.onclick = function(){},只能赋值一个函数,所以只能为某一个时间绑定一个处理函数
第三种是node.addEventListener(‘click’,function(e){}),这个可以多次调用,以绑定多个函数,e里面包含很多东西,例如位置,坐标等等;还有一个函数是removeEventListener(‘click’,foo),这个是删除这个事件
事件对象
- 事件处理器里面的函数会接受一个参数,这个参数叫做事件对象
- 事件处理函数现在在处理哪个元素的事件,它的this就是谁
- 事件对象常用的属性和方法
事件冒泡
- 注册在祖先元素上的处理程序在后代元素发生相应事件时也会被调用,比如说:p标签内的一个button按钮被点击时,也会触发p元素的click事件,事件会从内向外传播。
- 如果一个事件处理函数可以调用事件对象的stopPropagation()方法,事件就不会传播到外层标签上去了。
这段代码中body click会执行,但是html click就不会执行了。 - e.stopPropagation()调用不会阻止事件在当前元素上剩余事件函数的运行,只会阻止其传入当前元素的父元素及祖先。e.stopImmediatePropagation()调用可以阻止当前元素上剩余事件函数的执行,以及阻止事件传入外层元素
补充:回流一定会触发重绘。
事件代理(事件委托)
下面这段代码可以实现点击按钮获得按钮里面的值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button>1</button><button>2</button><button>3</button><button>4</button
><button>5</button><button>6</button><button>7</button><button>8</button
><button>9</button><button>10</button><button>11</button><button>12</button
><button>13</button><button>14</button><button>15</button><button>16</button
><button>17</button><button>18</button><button>19</button
><button>20</button>
<script>
var buttons = document.querySelectorAll("button");
for (var button of buttons) {
button.addEventListener("click", function (e) {
console.log(this.textContent);
});
}
</script>
</body>
</html>
- 我们也可以通过一个div,框住里面所有的button,通过绑定一个div的事件,然后通过event.target就可以获得div里面的元素事件了;这个也就是所谓的事件代理。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<button title="foo" class="bar baz">1</button>
<button title="fod" class="bar baz">2</button>
<button title="fod" class="bar baa">3</button>
<button>4</button><button>5</button><button>6</button><button>7</button
><button>8</button><button>9</button><button>10</button><button>11</button
><button>12</button><button>13</button><button>14</button
><button>15</button><button>16</button><button>17</button
><button>18</button><button>19</button><button>20</button>
</div>
<script>
var div = document.querySelector("div");
div.addEventListener("click", function (e) {
//下面这个if我们可以替换成if(e.target.matches('button.bar[title="fod"]')
//matches函数判断当前DOM节点是否能完全匹配对应的CSS选择器,如果匹配成功,返回true,反之则返回false
if (
e.target.tagName === "BUTTON" &&
e.target.classList.contains("bar")
) {
console.log(e.target.textContent);
}
});
</script>
</body>
</html>
事件捕获
- 事件捕获阶段就加一个参数写成true就可以了,模型是先从外到内进行捕获,然后从内到外进行冒泡
- 事件对象的stopPropagation()方法一定会阻止事件的传递进入下一个阶段;事件对象的stopPropagation()方法不会阻止事件在当前元素当前阶段继续执行
- 之前的事件顺序是三个阶段,分别是事件捕获,目标阶段,事件冒泡,现在已经是两个阶段了,只有事件捕获和事件冒泡
默认行为
- 例如a标签点击会自动打开页面,滚轮会自动滚动网页,表单里按tab会自动跳到下一项,点击表单提交将会提交表单
- 在F12中的event Listeners我们可以看到元素具体绑定了什么事件
- 如果我们想阻止默认行为,我们可以通过preventDefault()方法,就可以阻止默认行为了,下面是使用方式,这样点击它的默认方式就消失了。
被动事件和主动事件
被动事件和主动事件是与浏览器滚动行为相关的两种事件类型。被动事件的处理不会阻止浏览器的默认滚动行为,而主动事件的处理可能会阻止浏览器的默认滚动行为。
具体来说,被动事件是指那些不会调用preventDefault()方法来阻止默认滚动行为的事件。这些事件在处理时,浏览器会立即执行默认的滚动行为,而不需要等待JavaScript代码的处理。这种行为可以提高页面滚动的流畅度和响应速度,因为浏览器可以在事件处理程序之前就开始处理滚动事件。一般来说,被动事件的处理速度比主动事件更快,因为它不需要等待JavaScript代码的执行。
主动事件则是指那些可能会调用preventDefault()方法来阻止默认滚动行为的事件。这些事件在处理时,如果事件处理程序调用了preventDefault()方法,浏览器就会停止执行默认的滚动行为,从而实现自定义的滚动行为。主动事件常常用于实现一些特殊的滚动效果,比如平滑滚动和惯性滚动等。
总的来说,被动事件和主动事件的区别在于它们处理滚动事件的方式。被动事件处理时不会阻止默认滚动行为,而主动事件可能会阻止默认滚动行为。在实际开发中,需要根据具体的需求选择合适的事件类型来处理滚动事件,以实现更好的用户体验。
passive属性
在 JavaScript 中,passive 是一个可选的事件监听器选项,控制事件是被动事件还是主动事件,它被用来优化页面的滚动性能。当你给一个元素添加滚动事件监听器时,当滚动事件被触发时,浏览器会先执行监听器的回调函数,然后再更新页面的滚动位置,这可能会导致滚动时的卡顿和延迟。如果你添加了 passive 选项,浏览器就会认为这个事件监听器不会阻止滚动事件的默认行为,从而允许浏览器优化滚动事件的处理,提高页面的滚动性能。
具体来说,当你添加一个滚动事件监听器时,如果你设置了 passive 选项为 true,那么浏览器就会将这个事件监听器标记为“被动”的,表示这个监听器不会调用 preventDefault() 方法来阻止默认的滚动行为。这样,当滚动事件被触发时,浏览器就可以直接执行默认的滚动行为,而不需要等待事件监听器执行完毕,从而提高页面的滚动性能。
需要注意的是,passive 选项只在现代浏览器中生效,不支持的浏览器会忽略这个选项。另外,在某些情况下,如果你错误地将 passive 设置为 false,可能会导致页面的滚动卡顿和延迟,因为浏览器会等待事件监听器执行完毕再执行默认的滚动行为。因此,建议在添加滚动事件监听器时,尽可能地将 passive 设置为 true,以提高页面的滚动性能。
————————————————————————
♥♥♥码字不易,大家的支持就是我坚持下去的动力♥♥♥
版权声明:本文为CSDN博主「亚太地区百大最帅面孔第101名」的原创文章