本章是第五章DOM与事件相关的内容。
DOM是文档对象模型,全称为Document Object Model。DOM用一个逻辑树来表示一个文档,树的每个分支终点都是一个节点,每个节点都包含着对象。DOM提供了对文档结构化的表述,通过绑定不同的事件可以改变文档的结构、样式和内容,从而能实现“动态”的页面。
在学完后,希望掌握下面知识点:
DOM选择器用于快速定位DOM元素。在原生的JavaScript中有提供根据id
、name
等属性来查找的传统选择器,也有新型的、更高效的querySelector
选择器和querySelectorAll
选择器,支持丰富的元素、属性、内容选择等。
getElementById()
函数:通过id定位元素,返回匹配到id的第一个元素。
document.getElementById('one').innerText;
一般都会避免写具有相同 id 元素的HTML页面,但如果发生了,则只会匹配第一个元素。
getElementsByClassName()
函数:通过类名定位元素,返回由匹配到的元素构成的HTMLCollection
对象,它是一个类数组结构。
对于下面的HTML和JS:
<li class="one">节点1.2li>
<li class="one">节点1.3li>
document.getElementsByClassName('one');
返回值为一个HTMLCollection对象,里面包含匹配到的两个li元素值:
HTMLCollection(2) [li.one, li.one]
- 0: li.one
- 1: li.one
- length: 2
- __proto__: HTMLCollection
getElementsByName()
函数:通过元素的name属性进行定位,返回由匹配到的元素构成的NodeList
对象,它是一个类数组结构。
对于下面的HTML和JS:
<ul>
<li id="one">节点1.1li>
<li name="node">节点1.4li>
<li name="node">节点1.5li>
ul> <ul>
<li name="node">节点2.1li>
<li>节点2.2li> ul>
document.getElementsByName('node');
返回的值为一个NodeList对象,里面包含匹配到的name属性为“node”的元素
NodeList(3) [li, li, li]
- 0: li
- 1: li
- 2: li
- length: 3
- __proto__: NodeList
getElementsByTagName()
函数:通过标签名定位元素,返回由匹配到的元素构成的HTMLCollection
对象。
通过标签名获取页面上的两个ul元素:
document.getElementsByTagName('ul');
返回值为一个HTMLCollection对象,里面包含匹配到的两个ul元素值:
HTMLCollection(2) [ul, ul]
- 0: ul
- 1: ul
- length: 2
- __proto__: HTMLCollection
querySelector选择器和querySelectorAll选择器能更高效地使用选择器,让其定位到特定的元素或者子元素中。都是按照CSS选择器的规范来实现的。
返回的是在基准元素下,选择器匹配到的元素集合中的第一个元素:
element = baseElement.querySelector(selectors);
例子1, 获取class为content的元素的第一个span元素:
document.querySelector('.content span').innerText;
例子2,获取第一个span或者h5元素:
document.querySelector('h5, span').innerText;
querySelectorAll选择器与querySelector选择器类似,区别在于querySelectorAll选择器会返回基准元素下匹配到的所有子元素的集合
elementList = baseElement.querySelectorAll(selectors);
它同样包含基准元素与选择器,返回值是一个NodeList的集合
HTMLCollection对象与NodeList对象都是DOM节点的集合,但是在节点处理方式上是有差异的。
HTMLCollection对象具有length
属性,返回集合的长度,可以通过item()
函数和namedItem()
函数来访问特定的元素。
item()
:通过序号来获取特定的某个节点,超过索引则返回null
。
<div id="main">
<p class="first">firstp>
<p class="second">secondp>
<p class="third">thirdp>
<p class="four">fourp>
div> <script>
var main = document.getElementById("main").children;
console.log(main.item(0));
console.log(main.item(2));
script>
通过item()函数定位第一个和第三个子元素,输出结果如下所示:
<p class="first">firstp>
<p class="third">thirdp>
namedItem()
:用来返回一个节点。首先通过id
属性去匹配,然后如果没有匹配到则使用name
属性匹配,如果还没有匹配到则返回 null。当出现重复的 id或者 name属性时,只返回匹配到的第一个值。
<form id="main">
<input type="text" id="username">
<input type="text" name="username">
<input type="text" name="password">
form>
<script>
var main = document.getElementById("main").children; console.log(main.namedItem('username'));
script>
优先id,没匹配到再找name,因此返回id匹配到的项
<input type="text" id="username">
NodeList对象也具有length
属性,返回集合的长度,也同样具有item()
函数,通过 索引定位子元素的位置。由于和HTMLCollection对象的item()函数一致,这里就不赘述了。
HTMLCollection对象和NodeList对象并不是历史文档状态的静态快照,而是具有实时性的。对DOM树新增或者删除一个相关节点,都会立刻反映在HTMLCollection对象与NodeList对象中。
HTMLCollection对象与NodeList对象都只是类数组结构,并不能直接调用数组的函数。而通过call()函数和apply()函数处理为真正的数组后,它们就转变为一个真正的静态值了,不会再动态反映DOM的变化。
NodeList对象与HTMLCollection对象相比,存在一些细微的差异,主要表现在不是所有的函数获取的NodeList对象都是实时的。例如通过querySelectorAll()
函数获取的NodeList对象就不是实时的。
length
属性,可以通过call()
函数或apply()
函数处理成真正的数组item()
函数,通过索引定位元素namedItem()
函数,可以通过id或者name属性定位元素文档树是由各种类型节点构成的集合,DOM操作实际是对文档结构中节点的操作。文档结构树中的节点类型众多,但是操作的主要节点类型为元素节点、属性节点和文本节点。
下面通过一段完整的HTML代码来看看主要由元素节点、属性节点和文本节点构成的文档树结构。
DOCTYPE html>
<html>
<head>
<title>文档标题title>
head>
<body>
<a href="http://www.mianshiting.com">我的链接a>
<h1>我的标题h1>
body>
html>
其中元素节点和文本节点存在父子关系,而元素节点与属性节点并不存在父子关系。
常用的DOM操作包括查找节点、新增节点、删除节点、修改节点。其中查找节点在上面 5.1 DOMu选择器 已经介绍过,因此下面会记录另外三个操作。
新增节点分为 2 步:
比如针对下面的HTML代码:
<ul id="container">
<li class="first">文本1li>
<li class="second">文本2li>
<li>文本3li>
<li id="target">文本4li>
<li>文本5li>
<li>文本6li>
ul>
接下来会完成下面这些操作:第一步,在ul的末尾添加一个li元素,其类名为“last”,内容为“新增文本1”;第二步,在新增的li之前再新增第二个li,内容为“新增文本2”。
var container = document.querySelector('#container'); // 获取指定元素
var newLiOne = document.createElement('li'); //新创建一个元素节点
var newLiAttr = document.createAttribute('class'); // 新创建一个属性节点
newLiAttr.value = 'last'; // 给新创建的属性节点设值
newLiOne.setAttributeNode(newLiAttr); // 将属性节点绑定在元素节点上
var newTextOne = document.createTextNode('新增文本1'); // 新创建一个文本节点
newLiOne.appendChild(newTextOne); // 将文本节点作为元素节点的子元素
container.appendChild(newLiOne); // 使用appendChild()函数将新增元素节点添加至末尾
var newLiTwo = document.createElement('li'); // 新创建第二个元素节点
var newTextTwo = document.createTextNode('新增文本2'); // 新创建第二个文本节点
newLiTwo.appendChild(newTextTwo); // 将文本节点作为元素节点的子元素
container.insertBefore(newLiTwo, newLiOne); // 使用insertBefore()函数将节点添加至第一个新增节点的前面
额外知识:在新增属性节点时,还有另外一种更简单的setAttribute()
函数。以上面代码为例, 可以通过下面这一行代码完成上述创建属性节点并赋值以及将属性节点绑定在元素节点上这三步共3行代码的功能:
newLiOne.setAttribute('class', 'last');
但是set Attribute()
函数不兼容IE8及更早的版本,使用时需要考虑兼容性。
删除节点包含 3 种:
比如对于下面的代码:
<ul id="main">
<li>文本1</li>
<li>文本2</li>
<li>文本3</li>
</ul>
<a id="link" href="http://www.mianshiting.com">面试厅</a>
接下来会有三种需求和场景:
删除一个元素节点需要 3 步:
var main = document.querySelector('#main');
firstElementChild
属性,不能使用firstChild
属性(在此例中实际为一个换行符)var firstChild = main.firstElementChild;
main.removeChild(firstChild);
删除一个元素的属性需要 2 步:
var link = document.querySelector('#link');
link.removeAttribute('href');
删除一个元素的文本节点需要 3 步(方法一):
lastElementChild
属性而不是lastChild
属性var lastChild = main.lastElementChild;
var textNode = lastChild.childNodes[0];
lastChild.removeChild(textNOde);
方法二:将元素节点的innerHTML 属性设置为空。
在删除文本节点时更推荐使用这个方法。
lastChild.innerHtml = '';
修改节点包含 3 种:
下面的修改都会基于下面的代码:
<div id="main">
<!-- 测试修改元素节点 -->
<div id="div1">替换之前的元素</div>
<!-- 测试修改属性节点 -->
<div id="div2" class="classA" style="color: green;">这是修改属性的节点</div>
<!-- 测试修改文本节点 -->
<div id="last">这是最后一个节点内容</div>
</div>
一般直接将节点元素替换为另一个元素,使用replaceChild()
函数。
replaceChild()
函数的调用方是父元素,接收 2 个参数,第 1 个参数表示新元素,第 2 个参数表示将要被替换的旧元素。
<script>
// 1.获取父元素与待替换的元素
var main = document.querySelector('#main');
var div1 = document.querySelector('#div1');
// 2.创建新元素
var newDiv = document.createElement('div');
var newText = document.createTextNode('这是新创建的文本');
newDiv.appendChild(newText);
// 3.使用新元素替换旧的元素
main.replaceChild(newDiv, div1);
</script>
有 2 种处理方式:
getAttribute()
函数和setAttribute()
函数获取和设置属性节点值下面是修改元素节点的class属性和style属性的color值:
var div2 = document.querySelector('#div2');
// 方法1: 通过setAttribute()函数设置
div2.setAttribute('class', 'classB');
// 方法2: 直接修改属性名,注意不能直接用class,需要使用className
div2.className = 'classC';
// 方法1: 通过setAttribute()函数设置
div2.setAttribute('style', 'color: red;');
// 方法2: 直接修改属性名
div2.style.color = 'blue';
修改文本节点与删除文本节点一样,将innerHTML属性修改为需要的文本内容即 可
var last = document.querySelector('#last');
// 直接修改innerHTML属性
last.innerHTML = '这是修改后的文本内容';
//如果设置的innerHTML属性值中包含HTML元素,则会被解析
//使用如下代码进行验证
last.innerHTML = '这是修改后的文本内容
';
//在浏览器中渲染后,可以看到“这是修改后的文本内容”为红色
在浏览器中,JavaScript和HTML之间的交互是通过事件去实现的。
在事件发生时,会相对应地触发绑定在元素上的事件处理程序,以处理对应的操作。
事件流描述的是从页面中接收事件的顺序。
一个完整的事件流实际包含了3个阶段:
如果有元素绑定了捕获类型事件,则会优先于冒泡类型事件而先执行。
事件处理程序,就是响应某个事件的函数,例如onclick()
函数、onload()
函数就是响应单击、加载事件的函数,对应的是一段JavaScript的函数代码。
根据W3C DOM标准,事件处理程序分为DOM0、DOM2、DOM3这3种级别的事件处理程序。由于在DOM1中并没有定义事件的相关内容,因此没有所谓的DOM1级事件处理程序。
DOM0级事件处理程序是将一个函数赋值给一个事件处理属性。
有 2 种表现形式:
var btn = document.getElementById("btn");
btn.onclick = function(){}
//设值方式一:执行的函数体
<button onclick="alert('面试厅');">单击</button>
//设值方式二:函数名+script标签中具体定义
<button onclick="clickFn()">单击</button>
<script>
function clickFn() {
alert('面试厅');
}
</script>
两种DOM0级事件处理程序同时存在时,第一种在JavaScript中定义的事件处理程序会覆盖掉后面在html标签中定义的事件处理程序。
DOM0级事件处理程序只支持事件冒泡阶段
优点:简单且可以跨浏览器
缺点:一个事件处理程序只能绑定一个函数
在DOM2级事件处理程序中,当事件发生在节点时,目标元素的事件处理函数就会被触发,而且目标元素的每个祖先节点也会按照事件流顺序触发对应的事件处理程序。
支持对同一个事件绑定多个处理函数。
规定了添加事件处理程序和删除事件处理程序的方法。
IE10及以下版本,只支持冒泡阶段,attachEvent()
函数添加事件处理程序,detachEvent(
)函数删除事件处理程序:
element.attachEvent("on"+ eventName, handler); //添加事件处理程序
element.detachEvent("on"+ eventName, handler); //删除事件处理程序
IE11及其他非IE浏览器,同时支持事件捕获和事件冒泡阶段。addEventListener()
函数添加事件处理程序,removeEventListener()
函数删除事件处理程序:
addEventListener(eventName, handler, useCapture); //添加事件处理程序
removeEventListener(eventName, handler, useCapture); //删除事件处理程序
在IE浏览器中,使用attachEvent()函数为同一个事件添加多个事件处理函数时, 会按照添加的相反顺序执行
在IE浏览器下,使用attachEvent()
函数添加的事件处理程序会在全局作用域中运行,因此this
指向全局作用域window
。
在非IE浏览器下,使用addEventListener()
函数添加的事件处理程序在指定的元素内部执行,因此this
指向绑定的元素
可以通过EventUtil
工具类来进行与事件有关的兼容性处理
在DOM2级事件基础上重新定义了事件,并添加了一些新的事件。
与DOM2最重要的区别在于允许自定义事件,自定义事件由 createEvent("CustomEvent")
函数创建,返回的对象有一个initCustomEvent()
函数,通过传递对应的参数可以自定义事件。可以通过dispatchEvent()
函数去手动触发,触发自定义事件的元素需要和绑定自定义事件的元素为同一个元素。
函数可以接收以下 4 个参数:
type
:字符串、触发的事件类型、自定义,例如“keyDown”“selectedChange”bubble
(布尔值):事件是否可以冒泡cancelable
(布尔值):事件是否可以取消detail
(对象):任意值,保存在event对象的detail属性中下面通过一个具体的例子来看整个过程:
要实现的场景是,在页面初始化时创建一个自定义事件myEvent,页面上有 个div监听这个自定义事件myEvent,同时有一个button按钮绑定了单击事件;当我们单击button时,触发自定义事件,由div监听到,然后做对应的处理
可以分 3 步实现:
首先是html代码
<div id="watchDiv">监听自定义事件的div元素div>
<button id="btn">单击触发自定义事件button>
接下来就是JavaScript实现
var customEvent;
// 创建自定义事件
(function () {
if (document.implementation.hasFeature('CustomEvents', '3.0')) { //判断是否支持DOM3级事件处理程序
var detailData = {name: 'kingx'};
customEvent = document.createEvent('CustomEvent');
customEvent.initCustomEvent('myEvent', true, false, detailData);
}
})();
//监听自定义事件
// 获取元素
var div = document.querySelector('#watchDiv');
// 监听myEvent事件
div.addEventListener('myEvent', function (e) {
console.log('div监听到自定义事件的执行, 携带的参数为: ', e.detail);
});
//触发自定义事件
// 获取元素
var btn = document.querySelector('#btn');
// 绑定click事件,触发自定义事件
btn.addEventListener('click', function () {
div.dispatchEvent(customEvent);
});
事件在浏览器中是以Event对象的形式存在的,每触发一个事件,就会产生一个 Event对象。该对象包含所有事件相关信息,包括事件的元素、事件的类型及其他与特定事件相关的信息。
方式有 2 种:
我们在同一个事件处理程序中,可以使用上述两种方式获取event对象并输出:
var btn = document.querySelector('#btn'); btn.addEventListener('click', function (event) {
// 方式1:event作为参数传入
console.log(event);
// 方式2:通过window.event获取
var winEvent = window.event;
console.log(winEvent);
});
不同浏览器支持方式不同,可能并不能2种方式都支持,因此需要考虑兼容性处理。
srcElement
属性来表示事件的目标元素target
属性来表示事件的目标元素。某些也能支持使用srcElement
btn.addEventListener('click', function (event) {
// 获取event对象
var event = EventUtil.getEvent(event);
// 使用两种属性获取事件的目标元素
var NoIETarget = event.target;
var IETarget = event.srcElement;
console.log(NoIETarget);
console.log(IETarget);
});
不同浏览器也不同,也需要考虑兼容性
target属性与currentTarget属性都可以表示事件的目标元素,但是在事件流中两者有不同意义
有时我们并不想要事件进行冒泡,比如有一个表示学生信息的容器ul,每个li元素都表示一个学生的基本信息,单击li元素会改变li的背景色以表示选中的标识。在每个li元素内部会有一个表示删除的button按钮,单击button按钮则会提示是否删除,单击确定则会删除该元素。
如果不阻止冒泡,那么在 li 内部点button时,事件同样会上升到父元素 li 上,如果在那里有操作则也会被触发。
解决方法就是阻止冒泡,在上面例子中的话就是button按钮的click事件中调用event.stopPropagation()
函数。
其实还有一个stopImmediatePropagation()
函数,这两个有一定不同:
stopPropagation()
:仅会阻止事件冒泡,其他事件处理程序仍然可以调用stopImmediatePropagation()
:不仅会阻止冒泡,也会阻止其他事件处理程序 的调用通过event.preventDefault()
函数实现。
比如这个场景,限制用户输入的值只能是数字和大小写字母,其他的值则不能输入,如输 入其他值则给出提示信息,提示信息在两秒后消失
<input type="text" id="text">
<div id="tip">div>
<script>
var text = document.querySelector('#text');
var tip = document.querySelector('#tip');
text.addEventListener('keypress', function (event) {
var charCode = event.keyCode || event.which || event.charCode;
// 满足输入数字
var numberFlag = charCode <= 57 && charCode >= 48;
// 满足输入大写字母
var lowerFlag = charCode <= 90 && charCode >= 65;
// 满足输入小写字母
var supperFlag = charCode <= 122 && charCode >= 97;
if (!numberFlag && !lowerFlag && !supperFlag) {
// 阻止默认行为,不允许输入
event.preventDefault();
tip.innerText = '只允许输入数字和大小写字母';
}
// 设置定时器,清空提示语
setTimeout(function () {
tip.innerText = '';
}, 2000);
});
script>
事件委托是利用事件冒泡原理,管理某一类型的所有事件,利用父元素来代表子元素的某一类型事件的处理方式。
具体会根据 2 种常见场景来理解和分析,在下面两种场景中,事件委托会起关键作用:
场景一:假如页面上有一个ul标签,里面包含1000个li子标签,我们需要在单击每个li时,输出li中的文本内容。
方法一:常规做法。给每个li标签绑定一个click事件,在click 事件中输出li标签的文本内容
<script>
// 1.获取所有的li标签
var children = document.querySelectorAll('li');
// 2.遍历添加click事件处理程序
for (var i = 0; i < children.length; i++) {
children[i].addEventListener('click', function () {
console.log(this.innerText);
});
}
</script>
但这样性能会极差。
方法二:使用事件委托。
// 1.获取父元素
var parent = document.querySelector('ul');
// 2.父元素绑定事件
parent.addEventListener('click', function (event) {
// 3.获取事件对象
var event = EventUtil.getEvent(event);
// 4.获取目标元素
var target = EventUtil.getTarget(event);
// 5.判断当前事件流所处的元素
if (target.nodeName.toLowerCase() === 'li') {
// 6.与目标元素相同,做对应的处理
console.log(target.innerText);
}
});
事件是绑定在父元素ul上的,不管子元素li有多少个,也不会影响到页面中事件处理程序的个数,因此可以极大地提高浏览器的性能。
对于不同元素做不同处理也是可以的。
场景二:在页面上有4个button按钮,分别表示增加、删除、修改、查询这4个功能。每个按钮绑定相同的click事件处理程序,但是具体的行为不同。在这4个按钮触发click事件后,分别输出“新增”“删除”“修改”“查询”等文字。
方法一:在获取4个button后同时绑定click事件处理程序,在事件回调中输出对应的文字
<script>
var add = document.querySelector('#add');
var remove = document.querySelector('#remove');
var update = document.querySelector('#update');
var search = document.querySelector('#search');
// 新增按钮绑定事件
add.addEventListener('click', function () {
console.log('新增');
});
// 删除按钮绑定事件
remove.addEventListener('click', function () {
console.log('删除');
});
// 修改按钮绑定事件
update.addEventListener('click', function () {
console.log('修改');
});
// 查询按钮绑定事件
search.addEventListener('click', function () {
console.log('查询');
});
</script>
和第一个实例一样,对于不同的按钮都需要绑定一个click事件处理程序,这样在性能上会存在一定的影响。
方法二:使用事件委托
主要遵循以下 3 步:
// 1.获取父元素,并绑定事件处理程序
var parent = document.querySelector('#parent');
parent.addEventListener('click', function (event) {
// 2.获取event和target
var event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
// 3.判断id属性,输出对应的文字
switch (target.id) {
case 'add':
console.log('新增');
break;
case 'remove':
console.log('删除');
break;
case 'update':
console.log('修改');
break;
case 'search':
console.log('查询');
break;
}
});
场景:在页面上有一个button按钮,单击button按钮会创建一个新的li元素,单击新创建的li元素,输出它的文本内容
方法一:手动绑定
<button id="add">新增</button>
// 1.获取所有的li标签
var children = document.querySelectorAll('li');
// 2.遍历添加click事件处理程序
for (var i = 0; i < children.length; i++) {
children[i].addEventListener('click', function () {
console.log(this.innerText);
});
}
// 封装一个遍历添加click事件处理程序,用于新增元素后刷新元素
function bindEvent() {
for (var i = 0; i < children.length; i++) {
children[i].addEventListener('click', function () {
console.log(this.innerText);
});
}
}
var ul = document.querySelector('ul');
var add = document.querySelector('#add');
add.addEventListener('click', function () {
// 创建新的li元素
var newLi = document.createElement('li');
var newText = document.createTextNode('文本10');
newLi.appendChild(newText);
// 添加至父元素ul中
ul.appendChild(newLi);
// 重新添加事件处理程序
bindEvent();
});
我们通过querySelectorAll()
函数获取到的li元素虽然会实时感知到数量的变化,但并不会实时增加对事件的绑定。因此需要在添加完新元素后重新进行手动绑定。
这样一是操作繁琐,二是会影响性能
方法二:事件委托
使用事件委托机制,我们可以更加方便快捷地实现新创建元素的事件绑定。由于事件委托机制是利用的事件冒泡机制,即使在元素自身没有绑定事件的情况下,事件仍然会冒泡到父元素中,因此对于新增的元素,只要处理事件流就可以触发其事件。
<script>
// 1.获取父元素
var parent = document.querySelector('ul');
// 2.父元素绑定事件
parent.addEventListener('click', function (event) {
// 3.获取事件对象
var event = EventUtil.getEvent(event);
// 4.获取目标元素
var target = EventUtil.getTarget(event);
// 5.判断当前事件流所处的元素
if (target.nodeName.toLowerCase() === 'li') {
// 6.与目标元素相同,做对应的处理
console.log(target.innerText);
}
});
</script>
新增按钮的事件不变,和方法1中的一样,不过不需要重新添加事件处理程序
Context Menu是一个与用户进行友好交互的菜单,例如鼠标的右键产生的效果。默认情况下,在网页上右击可以看到“重新加载”“打印”“查看页面源码”等选项;在图片上右击会出现“保存至本地”“另存为”等选项。
也可以通过contextmenu
事件来实现一个定制化的鼠标右键效果。
此处主要是具体的实现,不重复摘抄了,需要时再去看书中对应章节
由于后续会使用Vue,因此jQuary相关的就不记录在此了,本小节只记录一下原生javascript涉及加载完成的知识。
原生javascript中有load事件,onload事件的触发表示页面中包含的图片、flash等所有元素都加载完成。jQuery中有ready事件,此处不过多记述。
load事件会在页面、脚本或者图片加载完成后触发。其中,支持onload事件的标签有body、frame、frameset、iframe、img、link、script
如果load事件用于页面初始化,则有 2 种实现方式:
类似于onclick属性的设置,其实就是DOM0级事件处理程序
<!-- 使用onload属性 -->
<body onload="bodyLoad()">
<script>
function bodyLoad() {
console.log('文档加载完成,执行onload方法');
}
</script>
</body>
属性值为一个函数
<script>
window.onload = function () {
console.log('文档加载完成,执行onload方法');
};
</script>
在load事件的两种实现方式中,第一种方式的优先级会高于第二种方式,如果同时采用两种方式,则只有第一种方式会生效。也就是body标签上使用onload属性的优先级比设置window对象的onload属性更高。
ready事件不同于load事件,ready事件只需要等待文档结构加载完成就可以执行,不需要加载完全部资源。
因此在很多场景中,我们更推荐在ready事件中做初始化处理。
需要注意的是,ready事件并不是原生JavaScript所具有的,而是在jQuery中实现的,ready事件挂载在document对象上
HTML的渲染过程如下:
重绘和重排发生在后两步,对它们的理解将有助于我们使用一定的手段进行性能优化
因为浏览器渲染页面默认是基于流式布局的,因此对某一个DOM节点信息进行修改时,就需要对该DOM结构进行重新计算。
而重排的过程就发生在DOM节点信息修改的时候,重排实际是根据渲染树中每个渲染对象的信息,计算出各自渲染对象的几何信息,例如DOM元素的位置、尺寸、大小等,然后将其安置在界面中正确的位置。
因此一般发生大小、位置、字体变化或添加删除可见的DOM元素会引起重排
重绘只是改变元素在页面中的展现样式,而不会引起元素在文档流中位置的改变。例如更改了元素的字体颜色、背景色、透明度等,浏览器均会将这些新样式赋予元素并重新绘制。
浏览器的重排与重绘是比较消耗性能的操作,所以我们应该尽量地减少重排与重绘的操作,这也是优化网页性能的一种方式
比如将要修改的css属性合并为一个class类,再通过JS直接修改元素的class类
需要进行重排的元素都是处于正常的文档流中的,如果这个元素不处于文档流中,那么它的变化就不会影响到其他元素的变化,这样就不会引起重排的操作。常见的操作就是设置其position为absolute或者fixed。
假如一个页面有动画元素,如果它会频繁地改变位置、宽高等信息,那么最好将其设置为绝对定位。
因为display属性为none的元素不会出现在渲染树中,所以对其进行处理并不会引起其他元素的重排。当我们需要对一个元素做复杂处理时,可以将其display属性设置为none,操作完成后,再将其显示出来,这样就只会在隐藏和显示的时候引发两次重排操作。
如果table中任何一个元素触发了重排的操作,那么整个table都会触发重排的操作,尤其是当一个table内容比较庞大时,更加不推荐使用table布局。
如果不得已使用了table,可以设置table-layout:auto或者是table-layout:fixed。这样可以让table一行一行地渲染,这种做法也是为了限制重排的影响范围。
DocumentFragment是一个没有父级节点的最小文档对象,它可以用于存储已经排好版或者尚未确定格式的HTML片段。DocumentFragment最核心的知识点在于它不是真实DOM树的一部分,它的变化不会引起DOM树重新渲染的操作,也就不会引起浏览器重排和重绘的操作,从而带来性能上的提升。
因为DocumentFragment具有的特性,在需要频繁进行DOM新增或者删除的操作中,它将变得非常有用。一般的操作方法分为以下两步: