如果这篇博客能够帮助到你,点个赞再走吧~
CC BY 4.0 (除特别声明或转载文章外)
笔记来源:尚硅谷最新版 JavaScript 基础全套教程完整版(140 集实战教学,JS 从入门到精通)_哔哩哔哩_bilibili
[TOC]
DOM
1、DOM 简介
DOM,全称 Document Object Model 文档对象模型。
JS 中通过 DOM 来对 HTML 文档进行操作。只要理解了 DOM 就可以随心所欲的操作 WEB 页面。
文档
文档表示的就是整个的 HTML 网页文档
对象
对象表示将网页中的每一个部分都转换为了一个对象
模型
使用模型来表示对象之间的关系,这样方便我们获取对象
DOM 树体现了节点与节点之间的关系
2、节点
节点 Node,是构成我们网页的最基本的组成部分,网页中的每一个部分都可以称为是一个节点
比如:html 标签、属性、文本、注释、整个文档等都是一个节点
虽然都是节点,但是实际上他们的具体类型是不同的。比如:
- 标签称为元素节点
- 属性称为属性节点
- 文本称为文本节点
- 文档称为文档节点
节点的类型不同,属性和方法也都不尽相同
节点类型
节点:Node——构成 HTML 文档最基本的单元
常用节点分为四类
- 文档节点:整个 HTML 文档
- 元素节点:HTML 文档中的 HTL 标签
- 属性节点:元素的属性
- 文本节点:HTML 标签中的文本内容
节点属性
文档节点(Document)
文档节点document
,代表的是整个 HTML 文档,网页中的所有节点都是它的子节点
document
对象作为window
对象的属性存在的,我们不用获取可以直接使用
通过该对象我们可以在整个文档访问内查找节点对象,并可以通过该对象创建各种节点对象
元素节点(Element)
HTML 中的各种标签都是元素节点,这也是我们最常用的一个节点
浏览器会将页面中所有的标签都转换为一个元素节点,我们可以通过document
的方法来获取元素节点
比如:document.getElementById()
根据 id 属性值获取一个元素节点对象。
文本节点(Text)
文本节点表示的是 HTML 标签以外的文本内容,任意非 HTML 的文本都是文本节点
它包括可以字面解释的纯文本内容
文本节点一般是作为元素节点的子节点存在的
获取文本节点时,一般先要获取元素节点,再通过元素节点获取文本节点。例如:元素节点.firstChild;
获取元素节点的第一个子节点,一般为文本节点
属性节点(Attr)
属性节点表示的是标签中的一个一个的属性,这里要注意的是属性节点并非是元素节点的子节点,而是元素节点的一部分
可以通过元素节点来获取指定的属性节点。例如:元素节点.getAttributeNode("属性名");
注意:我们一般不使用属性节点
浏览器已经为我们提供文档节点对象,这个对象是window
属性可以在页面中直接使用,文档节点代表的是整个网页
// 获取button对象
var btn = document.getElementById("btn");
console.log(btn); // <button type="button" id="btn">我是一个按钮</button>
// 修改btn的文本节点内容
btn.innerHTML = "I'm a button.";
3、事件
事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间
JavaScript 与 HTML 之间的交互是通过事件实现的
对于 Web 应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上方、按下键盘上某个键,等等
我们可以在事件对应的属性中设置一些 js 代码,这样当事件被触发时,这些代码将会执行
<button type="button" id="btn" onclick="alert('Fuck');">
我是一个按钮
</button>
这种写法我们称为结构和行为耦合,不方便维护,不推荐使用
可以为按钮的对应事件绑定处理函数的形式来响应事件,这样当事件被触发时,其对应的函数将会被调用
// 绑定一个单击事件
btn.onclick = function () {
alert("Don't touch me.");
};
像这种为单击事件绑定的函数,我们称为单击响应函数
4、文档的加载
当我们把script
标签放到head
中时,会报错UncaughtTypeError: Cannot set property 'innerHTML' of null
,这是为什么呢?
浏览器在加载一个页面时,是按照自上向下的顺序加载的,读取到一行就运行一行,如果将script
标签写到页面的上边,在代码执行时,页面还没有加载,DOM 对象也没有加载,会导致无法获取到 DOM 对象
如果非要这么干,也不是没有办法
onload
事件会在整个页面加载完成之后才触发,可以为window
对象绑定一个onload
事件
window.onload = function () {
// 获取button对象
var btn = document.getElementById("btn");
// 绑定一个单击事件
btn.onclick = function () {
alert("Don't touch me.");
};
};
该事件对应的响应函数将会在页面加载完成之后执行,这样可以确保我们的代码执行时所有的 DOM 对象已经加载完毕了
5、DOM 查询
获取元素节点
通过 document 对象调用
为了方便,定义一个通用的函数,专门用来为指定元素绑定单击响应函数
// 参数:
// idstr 要绑定单击响应函数的对象的id属性值
// fun 事件的回调函数,当单击元素时,该函数将会被触发
function myClick(idStr, fun) {
var btn = document.getElementById(idStr);
btn.onclick = fun;
}
getElementById()
通过 id 属性获取一个元素节点对象myClick("btn01", function () { // innerHTML 通过这个属性可以获取到元素内部的html代码 alert(document.getElementById("bj").innerHTML); // 北京 });
getElementsByTagName()
通过标签名获取一组元素节点对象myClick("btn02", function () { // getElementsByTagName()可以根据标签名来获取一组元素节点对象 // 这个方法会给我们返回一个类数组对象,所有查询到的元素都会封装到对象中 // 即使查询到的元素只有一个,也会封装到数组中返回 var li_list = document.getElementsByTagName("li"); alert(li_list.length); // 14 var arr = []; for (var i = 0; i < li_list.length; i++) { arr.push(li_list[i].innerHTML); } alert(arr); // 北京,上海,东京,首尔,红警,实况,极品飞车,魔兽,IOS,Android,Windows Phone,IOS,Android,Windows Phone });
getElementsByName()
通过 name 属性获取一组元素节点对象myClick("btn03", function () { var inputs = document.getElementsByName("gender"); alert(inputs.length); // 2 var arr = []; for (var i = 0; i < inputs.length; i++) { // innerHTML用于获取元素内战的HTML代码的 // 如果需要读取元素节点属性,直接使用`元素.属性名` // 例子:`元素.id` `元素.name` `元素.value` arr.push(inputs[i].value); // 注意:class属性不能采用这种方式,读取class属性时需要使用`元素.className` arr.push(inputs[i].className); } alert(arr); // male,hello,female,hello });
练习:图片切换
HTML 代码
<div class="outer">
<p id="info">共5张图片,当前第1张</p>
<img src="img/1.jpg" alt="冰棍" />
<button type="button" id="prev">上一张</button>
<button type="button" id="next">下一张</button>
</div>
CSS 代码
* {
margin: 0;
padding: 0;
}
.outer {
width: 500px;
margin: 50px auto;
padding: 10px;
background-color: greenyellow;
/* 文本居中:内联样式当成是文本 */
text-align: center;
}
JS 代码
// 上一张
var prev = document.getElementById("prev");
// 下一张
var next = document.getElementById("next");
// 图片
var img = document.getElementsByTagName("img")[0];
// 信息
var info = document.getElementById("info");
// 图片集合
var imgArr = ["img/1.jpg", "img/2.jpg", "img/3.jpg", "img/4.jpg", "img/5.jpg"];
// 记录第几张
var index = 0;
// 上一张绑定单击相应事件
prev.onclick = function () {
// 循环切换
index = index < 0 ? imgArr.length - 1 : index;
// 修改img的src属性,以切换图片
img.src = imgArr[index];
// 修改文字提示
info.innerHTML = "共" + imgArr.length + "张图片,当前第" + (index + 1) + "张";
// 切换上一张
index--;
};
// 下一张绑定单击相应事件
next.onclick = function () {
// 循环切换
index = index > imgArr.length - 1 ? 0 : index;
// 修改img的src属性,以切换图片
img.src = imgArr[index];
// 修改文字提示
info.innerHTML = "共" + imgArr.length + "张图片,当前第" + (index + 1) + "张";
// 切换下一张
index++;
};
效果
获取元素节点的子节点
通过具体的元素节点调用
getElementsByTagName()
方法,返回当前节点的指定标签名后代节点myClick("btn04", function () { var city = document.getElementById("city"); // 获取city下1i节点 var list = city.getElementsByTagName("li"); alert(list.length); // 4 var arr = []; for (var i = 0; i < list.length; i++) { arr.push(list[i].innerHTML); } alert(arr); // 北京,上海,东京,首尔 });
childNodes
属性,表示当前节点的所有子节点myClick("btn05", function () { var city = document.getElementById("city"); // childNodes属性会获取包括文本节点在内的所有节点 // 根据DOM标签标签间空白也会当成文本节点 // 注意:在IE8及以下的浏览器中,不会将空白文本当成子节点 // 所以该属性在IE8中会返回4个子元素,而其他浏览器是9个 var list = city.childNodes; alert(list.length); // 9 var arr = []; for (var i = 0; i < list.length; i++) { arr.push(list[i]); } alert(arr); // [object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text] }); myClick("btn05", function () { var city = document.getElementById("city"); // children属性可以获取当前元素的所有子元素 var list = city.children; alert(list.length); // 4 var arr = []; for (var i = 0; i < list.length; i++) { arr.push(list[i].innerHTML); } alert(arr); // 北京,上海,东京,首尔 });
firstChild
属性,表示当前节点的第一个子节点myClick("btn06", function () { var phone = document.getElementById("phone"); // firstChild可以获取到当前元素的第一个子节点(包括空白文本节点) var firstChild = phone.firstChild; alert(firstChild); // [object HTMLLIElement] alert(firstChild.innerHTML); // IOS }); myClick("btn06", function () { var phone2 = document.getElementById("phone2"); // firstChild可以获取到当前元素的第一个子节点(包括空白文本节点) var firstChild = phone2.firstChild; alert(firstChild); // [object Text] alert(firstChild.innerHTML); // undefined }); myClick("btn06", function () { var phone2 = document.getElementById("phone2"); // firstElementchild不支持IE8及以下的浏览器,如果需要兼容他们尽量不要使用 var firstElementChild = phone2.firstElementChild; alert(firstElementChild); // [object HTMLLIElement] alert(firstElementChild.innerHTML); // IOS });
lastChild
属性,表示当前节点的最后一个子节点document.getElementById("btn062").onclick = function () { var phone = document.getElementById("phone"); // children属性可以获取当前元素的所有子元素 var lastChild = phone.lastChild; alert(lastChild); // [object HTMLLIElement] alert(lastChild.innerHTML); // Windows Phone });
获取父节点和兄弟节点
通过具体的节点调用
parentNode
属性,表示当前节点的父节点myClick("btn07", function () { var bj = document.getElementById("bj"); var parentNode = bj.parentNode; alert(parentNode); // [object HTMLUListElement] alert(parentNode.innerHTML); // <li id="bj">北京</li> // <li>上海</li> // <li>东京</li> // <li>首尔</li> // innerText // -该属性可以获取到元素内部的文本内容 // -它和innerHTML类似,不同的是它会自动将htm1去除 alert(parentNode.innerText); // 北京 // 上海 // 东京 // 首尔 });
previousSibling
属性,表示当前节点的前一个兄弟节点myClick("btn08", function () { var android = document.getElementById("android"); // 返回#android的前一个兄弟节点(也可能获取到空白的文本) var previousSibling = android.previousSibling; alert(previousSibling); // [object HTMLLIElement] alert(previousSibling.innerHTML); // IOS }); myClick("btn08", function () { var android2 = document.getElementById("android2"); // 返回#android的前一个兄弟节点(也可能获取到空白的文本) var previousSibling = android2.previousSibling; alert(previousSibling); // [object Text] alert(previousSibling.innerHTML); // undefined }); myClick("btn08", function () { var android2 = document.getElementById("android2"); // previousElementSibling获取前一个兄弟元素,IE8及以下不支持 var previousElementSibling = android2.previousElementSibling; alert(previousElementSibling); // [object HTMLLIElement] alert(previousElementSibling.innerHTML); // IOS });
nextSibling
属性,表示当前节点的后一个兄弟节点myClick("btn082", function () { var android = document.getElementById("android"); // 返回#android的前一个兄弟节点(也可能获取到空白的文本) var nextSibling = android.nextSibling; alert(nextSibling); // [object HTMLLIElement] alert(nextSibling.innerHTML); // Windows Phone });
6、全选练习
HTML 代码
<form method="post" action="">
你爱好的运动是?<input type="checkbox" id="checkedAllBox" />全选/全不选
<br />
<input type="checkbox" name="items" value="足球" />足球
<input type="checkbox" name="items" value="篮球" />篮球
<input type="checkbox" name="items" value="羽毛球" />羽毛球
<input type="checkbox" name="items" value="乒乓球" />乒乓球
<br />
<input type="button" id="checkedAllBtn" value="全 选" />
<input type="button" id="checkedNoBtn" value="全不选" />
<input type="button" id="checkedRevBtn" value="反 选" />
<input type="button" id="sendBtn" value="提 交" />
</form>
全选
document.getElementById("checkedAllBtn").onclick = function () {
var items = document.getElementsByName("items");
for (var i = 0; i < items.length; i++) {
// 通过多选框的checked属性可以来获取或设置多选框的选中状态
items[i].checked = true;
}
// 全选按钮也要同步选中
document.getElementById("checkedAllBox").checked = true;
};
全不选
document.getElementById("checkedNoBtn").onclick = function () {
var items = document.getElementsByName("items");
for (var i = 0; i < items.length; i++) {
items[i].checked = false;
}
// 全选按钮也要同步不选中
document.getElementById("checkedAllBox").checked = false;
};
反选
document.getElementById("checkedRevBtn").onclick = function () {
var items = document.getElementsByName("items");
var flag = true;
for (var i = 0; i < items.length; i++) {
items[i].checked = !items[i].checked;
if (!items[i].checked) {
flag = false;
}
}
// 全选按钮也要同步选中或不选中
document.getElementById("checkedAllBox").checked = flag;
};
提交
document.getElementById("sendBtn").onclick = function () {
var items = document.getElementsByName("items");
var arr = [];
for (var i = 0; i < items.length; i++) {
if (items[i].checked) {
arr.push(items[i].value);
}
}
alert(arr);
};
全选/全不选
document.getElementById("checkedAllBox").onclick = function () {
var items = document.getElementsByName("items");
for (var i = 0; i < items.length; i++) {
// 在事件的响应函数中,响应函数是给谁绑定的this就是谁
items[i].checked = this.checked;
}
};
items
var flag;
var items = document.getElementsByName("items");
for (var i = 0; i < items.length; i++) {
items[i].onclick = function () {
flag = true;
for (var j = 0; j < items.length; j++) {
if (!items[j].checked) {
flag = false;
break;
}
}
document.getElementById("checkedAllBox").checked = flag;
};
}
效果
7、DOM 查询的剩余方法
document.body
在document
中有一个属性body
,它保存的是body
的引用
// 注意:如果script标签是定义在head中的,则这里需要window.onload = function(){}包裹,否则会出现null的情况
var body = document.getElementsByTagName("body");
console.log(body); // HTMLCollection [body]
body = document.body;
console.log(body); // <body></body>
console.log(typeof body); // object
document.documentElement
document.documentElement
保存的是html
根标签
var html = document.documentElement;
console.log(html);
document.all
document.all
代表页面中所有的元素
var all = document.all;
console.log(all); // HTMLAllCollection(11) [html, head, meta, title, script, script, script, body, script, script, script]
console.log(all.length); // 11
console.log(typeof all); // undefined
for (var i = 0; i < all.length; i++) {
console.log(all[i]);
}
var el = document.getElementsByTagName("*");
console.log(el); // HTMLCollection(11) [html, head, meta, title, script, script, script, body, script, script, script]
console.log(all.length); // 11
console.log(typeof all); // undefined
for (var i = 0; i < el.length; i++) {
console.log(el[i]);
}
document.getElementsByClassName()
根据元素的class
属性值查询一组元素节点对象
getElementsByClassName()
可以根据class
属性值获取一组元素节点对象,但是该方法不支持 IE8 及以下的浏览器
var boxs = document.getElementsByClassName("box");
console.log(boxs); // HTMLCollection(3) [div.box, div.box, div.box]
console.log(boxs.length); // 3
console.log(typeof boxs); // object
document.querySelector()
需要一个选择器的字符串作为参数,可以根据一个 CSS 选择器来查询一个元素节点对象
虽然 IE8 中没有getElementsByClassName()
但是可以使用querySelector()
代替
使用该方法总会返回唯一的一个元素,如果满足条件的元素有多个,那么它只会返回第一个
var div = document.querySelector(".box div");
console.log(div.innerHTML); // I'm first div.
boxs = document.querySelector(".box");
console.log(boxs);
// <div class="box">
// <div>I'm first div.</div>
// </div>
document.querySelectorAll()
该方法和querySelector()
用法类似,不的是它会将符合条件的元素封装到一个数组中返回
即使符合条件的元素只有一个,它也会返回数组
boxs = document.querySelectorAll(".box");
console.log(boxs); // NodeList(3) [div.box, div.box, div.box]
console.log(boxs.length); //3
8、DOM 增删改
document.createElement()
可以用于创建一个元素节点对象,它需要一个标签名作为参数,将会根据该标签名创建元素节点对象,并将创建好的对象作为返回值返回
document.createTextNode()
可以用来创建一个文本节点对象,它需要一个文本内容作为参数,将会根据该内容创建文本节点,并将新的节点返回
appendChild()
向一个父节点中添加一个新的子节点,用法:父节点.appendChild(子节点);
insertBefore()
可以在指定的子节点前插入新的子节点,语法:父节点.insertBefore(新节点, 旧节点);
replaceChild()
可以使用指定的子节点替换已有的子节点,语法:父节点.replaceChild(新节点, 旧节点);
removeChild()
可以删除一个子节点,语法:父节点.removeChild(子节点);
、子节点.parentNode.removeChild(子节点);
// 创建一个"广州"节点,添加到#city下
var city = document.getElementById("city");
myClick("btn01", function () {
// 创建元素节点
var li = document.createElement("li");
// 创建文本节点
var gz = document.createTextNode("广州");
// 将文本节点添加到元素节点中
li.appendChild(gz);
// 将元素节点添加至#city下
city.appendChild(li);
});
// 将"广州"节点插入到#bj前面
var bj = document.getElementById("bj");
myClick("btn02", function () {
var li = document.createElement("li");
var gz = document.createTextNode("广州");
li.appendChild(gz);
// 将元素节点插入到#bj前面
city.insertBefore(li, bj);
});
// 使用"广州"节点替换#bj节点
myClick("btn03", function () {
var li = document.createElement("li");
var gz = document.createTextNode("广州");
li.appendChild(gz);
// 将元素节点替换#bj节点
city.replaceChild(li, bj);
});
// 删除#bj节点
myClick("btn04", function () {
// 将元素节点替换#bj节点
// city.removeChild(bj);
// 更常用,不需要知道父节点是什么
bj.parentNode.removeChild(bj);
});
// 使用innerHTML将"广州"节点添加到#city下
myClick("btn07", function () {
// 使用innerHTML也可以完成DOM的增删改的相关操作
// city.innerHTML += "<li>广州</li>";
// 不过这种方式会先删除再替换,耗费性能,所以一般我们会两种方式结合使用
var li = document.createElement("li");
li.innerHTML = "广州";
city.appendChild(li);
});
9、增删练习
准备
HTML 代码
<table id="employeeTable">
<tr>
<th>Name</th>
<th>Email</th>
<th>Salary</th>
<th> </th>
</tr>
<tr>
<td>Tom</td>
<td>tom@tom.com</td>
<td>5000</td>
<td><a href="deleteEmp?id=001">Delete</a></td>
</tr>
<tr>
<td>Jerry</td>
<td>jerry@sohu.com</td>
<td>8000</td>
<td><a href="deleteEmp?id=002">Delete</a></td>
</tr>
<tr>
<td>Bob</td>
<td>bob@tom.com</td>
<td>10000</td>
<td><a href="deleteEmp?id=003">Delete</a></td>
</tr>
</table>
<div id="formDiv">
<h4>添加新员工</h4>
<table>
<tr>
<td class="word">name:</td>
<td class="inp">
<input type="text" name="empName" id="empName" />
</td>
</tr>
<tr>
<td class="word">email:</td>
<td class="inp">
<input type="text" name="email" id="email" />
</td>
</tr>
<tr>
<td class="word">salary:</td>
<td class="inp">
<input type="text" name="salary" id="salary" />
</td>
</tr>
<tr>
<td colspan="2" align="center">
<button id="addEmpButton" value="abc">Submit</button>
</td>
</tr>
</table>
</div>
JS 代码
// a的单击相应函数
function delRow() {
// 添加提示信息
grandPrentNode = this.parentNode.parentNode;
var name = grandPrentNode.children[0].innerHTML;
if (confirm("确认删除" + name + "吗?")) {
// 删除祖先节点
grandPrentNode.parentNode.removeChild(grandPrentNode);
}
// 点击超链接以后,超链接会跳转页面,这个是超链接的默认行为,
// 但是此时我们不希望出现默认行为,可以通过在响应函数的最后return false来取消默认行为
return false;
}
window.onload = function() {
// 1、删除
// 为delete绑定单击相应函数
var a;
var grandPrentNode;
var aList = document.getElementsByTagName("a");
for (var i = 0; i < aList.length; i++) {
aList[i].onclick = delRow;
}
// 2、添加
document.getElementById("addEmpButton").onclick = function() {
// 获取name/email/salary
var empName = document.getElementById("empName").value;
var email = document.getElementById("email").value;
var salary = document.getElementById("salary").value;
// 校验数据是否为空
if (!empName || !email || !salary) {
alert("有数据为空,无法添加!");
return;
}
// 创建文本节点
var empName_text = document.createTextNode(empName);
var email_text = document.createTextNode(email);
var salary_text = document.createTextNode(salary);
var delete_text = document.createTextNode("Delete");
// 创建元素节点
var tr = document.createElement("tr");
var empName_td = document.createElement("td");
var email_td = document.createElement("td");
var salary_td = document.createElement("td");
var a_td = document.createElement("td");
var a = document.createElement("a");
// 添加内容
a.href = "javascript:;";
a.onclick = delRow;
// 添加子节点
empName_td.appendChild(empName_text);
email_td.appendChild(email_text);
salary_td.appendChild(salary_text);
a.appendChild(delete_text);
a_td.appendChild(a);
tr.appendChild(empName_td);
tr.appendChild(email_td);
tr.appendChild(salary_td);
tr.appendChild(a_td);
// 将tr添加至table中
// document.getElementById("employeeTable").appendChild(tr);
// 注意:浏览器生成的table结构会在内部套一层tbody,为了以防万一,也为了结构一致性和样式一致性,应该将其添加至tbody中
var employeeTable = document.getElementById("employeeTable");
var tbody = employeeTable.getElementsByTagName("tbody")[0];
tbody.appendChild(tr);
}
添加优化
结合createElement
和innerHTML
,优化修改上述添加代码逻辑
document.getElementById("addEmpButton").onclick = function () {
// 获取name/email/salary
var empName = document.getElementById("empName").value;
var email = document.getElementById("email").value;
var salary = document.getElementById("salary").value;
// 校验数据是否为空
if (!empName || !email || !salary) {
alert("有数据为空,无法添加!");
return;
}
// 创建元素节点
var tr = document.createElement("tr");
// 添加子节点
var empNameTd = "<td>" + empName + "</td>";
var emailTd = "<td>" + email + "</td>";
var salaryTd = "<td>" + salary + "</td>";
var aTd = '<td><a href="javascript:;">Delete</a></td>';
tr.innerHTML = empNameTd + emailTd + salaryTd + aTd;
// 为a绑定单击相应函数
tr.getElementsByTagName("a")[0].onclick = delRow;
// 将tr添加至table中
// document.getElementById("employeeTable").appendChild(tr);
// 注意:浏览器生成的table结构会在内部套一层tbody,为了以防万一,也为了结构一致性和样式一致性,应该将其添加至tbody中
var employeeTable = document.getElementById("employeeTable");
var tbody = employeeTable.getElementsByTagName("tbody")[0];
tbody.appendChild(tr);
};
a 的索引问题
上述中,我们为每个 a 都添加了单击响应函数,使用了this
获取遍历中的 a 元素,通过this.parentNode.parentNode
获取了 tr 元素,如果这里改成aList[i].parentNode.parentNode
,能够拿到 tr 元素吗?
看起来好像毫无悬念,但实际上是拿不到的,这是为什么呢?
我们可以改造下 for 循环中 a 元素的单击相应函数,打印下每次拿到的 i
for (var i = 0; i < aList.length; i++) {
aList[i].onclick = function () {
alert(i);
return false;
};
}
会发现,每次打印的结果都是 3,而 aList 的长度为 3 最大索引是 2
原因其实很简单,因为单击相应函数的执行是晚于 for 循环的执行的。也就是说,我们在点击 Delete 前,for 循环就已经执行完毕了。即当 i=2 的循环执行之后,会执行 i++,此时 i=3,这是循环条件判断 i ≮ 2,即不满足循环条件,for 循环退出。所以每次拿到的都是 for 循环执行完毕之后的 i,因此通过 aList[i] 的方式是无法取得对应的 a 元素的
总结: for 循环会在页面加载完成之后立即执行,而响应函数会在超链接被点击时才执行当响应函数执行时,for 循环早已执行完毕
10、操作内联样式
修改元素内联样式
通过 JS 修改元素的内联样式,语法:元素.style.样式名 = 样式值
box1.style.height = "200px";
box1.style.width = "200px";
注意:如果 CSS 的样式名中含有一,这种名称在 JS 中是不合法的,比如background-color
需要将这种样式名修改为驼峰命名法,去掉-
,然后将-
后的字母大写
// box1.style.background-color = "red"; // Uncaught SyntaxError: Invalid left-hand side in assignment
box1.style.backgroundColor = "red";
在 w3school 手册中,可以查看到每个样式所对应的 JS 代码
我们通过 style 属性设置的样式都是内联样式,而内联样式有较高的优先级,所以通过 JS 修改的样式往往会立即显示
但是如果在样式中写了!important
,则此时样式会有最高的优先级,即使通过 JS 也不能覆盖该样式,此时将会导致 JS 修改样式失效,所以尽量不要为样式添加!important
我们给 background-color
设置!important
之后,通过 box1.style.backgroundColor = "red";
设置的样式就“废”了
background-color: yellow !important;
读取元素内联样式
通过 JS 读取元素的内联样式,语法:元素.style.样式名
通过 style 属性设置和读取的都是内联样式,无法读取样式表中的样式
alert(box1.style.height); //
box1.style.height = "200px";
alert(box1.style.height); // 200px
别急,耐心往下看
读取元素样式
获取元素的当前显示的样式,语法:元素.currentStyle.样式名
它可以用来读取当前元素正在显示的样式,如果当前元素没有设置该样式,则获取它的默认值
alert(box1.currentStyle.height); // 100px
box1.style.height = "200px";
alert(box1.currentStyle.height); // 200px
不过currentstyle
只有 IE 浏览器支持,其他的浏览器都不支持。我们在 IE 中测试是可行的,在 Chrome 或 Edge 中报错的:UncaughtTypeError: Cannot read property 'height' of undefined
不过,在其他浏览器中可以使用getComputedStyle()
,这个方法来获取元素当前的样式
这个方法是window
的方法,可以直接使用,需要两个参数
- 第一个:要获取样式的元素
- 第二个:可以传递一个伪元素,一般都传
null
该方法会返回一个对象,对象中封装了当前元素对应的样式
可以通过对象.样式名
来读取样式,如果获取的样式没有设置,则会获取到真实的值,而不是默认值
比如:没有设置 width,它不会获取到 auto,而是一个长度
但是该方法不支持 IE8 及以下的浏览器
var obj = getComputedStyle(box1, null);
alert(obj); // [object CSSStyleDeclaration]
alert(obj.width); // 200px
alert(obj.height); // 200px
alert(obj.backgroundColor); // rgb(2 55, 0, 0)
那么问题来了,如果想要兼容 IE8 及以下的浏览器,就会陷入一个两难的境地, 该怎么办呢?
通过currentStyle
和getComputedStyle()
读取到的样式都是只读的,不能修改,如果要修改必须通过style
属性
那么我就只能自己写个函数,来兼容所有浏览器
// 自定义兼容所有浏览器获取元素样式的方法
function getStyle(obj, name) {
// 判断是否有getComputedStyle方法
if (getComputedStyle) {
// 正常浏览器的方式
return getComputedStyle(obj, null)[name];
} else {
// IE的方式
return obj.currentStyle[name];
}
}
测试结果
Hbuilder 内置浏览器
Chrome
Edge
IE11
IE8
怎么 IE8 还是不行,提示“getComputedStyle”未定义
?
这是因为执行到 if 语句时,会先在 function 中找,找不到会在全局作用域中找,全局作用域中也找不到getComputedStyle
,就会报错了
那么怎么解决这个问题呢?
我们先改造一下 function 代码,将getComputedStyle
改成window.getComputedStyle
function getStyle(obj, name) {
// 判断是否有getComputedStyle方法
if (window.getComputedStyle) {
// 正常浏览器的方式
return getComputedStyle(obj, null)[name];
} else {
// IE的方式
return obj.currentStyle[name];
}
}
效果
为什么呢?
因为变量找不到会报错,而属性找不到返回的是undefined
而不会报错,这样就可以利用undefined != true
的特点,执行 else 中的代码
同理,下面代码同样可以判断,只不过,会优先走currentStyle
的方式,而我们希望的优先走getComputedStyle
方法,所以不建议用
function getStyle(obj, name) {
// 判断是否有currentStyle属性
if (obj.currentStyle) {
// IE的方式
return obj.currentStyle[name];
} else {
// 正常浏览器的方式
return getComputedStyle(obj, null)[name];
}
}
那么上述代码有没有优化或者说简化的空间呢?当然,我们可以使用三元运算符对其进行精简
function getStyle(obj, name) {
return window.getComputedStyle
? getComputedStyle(obj, null)[name]
: obj.currentStyle[name];
}
三元运算符更加简洁,if-else 的方式更加清晰,建议使用 if-else 的方式,不过本质上是一样的,看个人习惯
11、其他样式相关的属性
clientWidth、clientHeight
这两个属性可以获取元素的可见宽度和高度
这些属性都是不带px
的,返回都是一个数字,可以直接进行计算
会获取元素宽度和高度,包括内容区和内边距
这些属性都是只读的,不能修改(改只有一种方式,就是通过元素.style.样式 = 样式值
)
// #box1 {
// width: 100px;
// height: 100px;
// background-color: red;
// padding: 10px;
// border: 10px solid yellow;
// }
alert(box1.clientHeight); // 120
alert(box1.clientWidth); // 120
offsetWidth、offsetHeight
获取元素的整个的宽度和高度,包括内容区、内边距和边框
// #box1 {
// width: 100px;
// height: 100px;
// background-color: red;
// padding: 10px;
// border: 10px solid yellow;
// }
alert(box1.offsetHeight); // 140
alert(box1.offsetWidth); // 140
offsetParent
可以用来获取当前元素的定位父元素
会获取到离当前元素最近的开启了定位(只要position
不是sticky
)的祖先元素
如果所有的祖先元素都没有开启定位,则返回body
// <div id="box1"></div>
alert(box1.offsetParent); // [object HTMLBodyElement]
// <div id="box2">
// <div id="box1"></div>
// </div>
alert(box1.offsetParent); // [object HTMLBodyElement]
//<div id="box3">
// <div id="box2">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetParent); // [object HTMLBodyElement]
//<div id="box3" style="position: relative;">
// <div id="box2" style="position: relative;">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetParent); // [object HTMLDivElement]
alert(box1.offsetParent.id); // box2
//<div id="box3" style="position: relative;">
// <div id="box2">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetParent); // [object HTMLDivElement]
alert(box1.offsetParent.id); // box3
offsetLeft、offsetTop
当前元素相对于其定位父元素的水平或垂直偏移量
//<div id="box3">
// <div id="box2">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetLeft); // 8 浏览器的默认样式
alert(box1.offsetTop); // 54
//<div id="box3">
// <div id="box2" style="position: relative;">
// <div id="box1"></div>
// </div>
//</div>
alert(box1.offsetLeft); // 0
alert(box1.offsetTop); // 0
scrollHeight、scrollWidth
可以获取元素整个滚动区域的宽度和高度
// #box4 {
// width: 200px;
// height: 300px;
// background-color: yellow;
// overflow: auto;
// }
// #box5 {
// width: 400px;
// height: 600px;
// background-color: #bfa;
// }
alert(box4.scrollHeight); // 600
alert(box4.scrollWidth); // 400
scrollLeft、scrollTop
可以获取水平或垂直滚动条滚动的距离
// #box4 {
// width: 200px;
// height: 300px;
// background-color: yellow;
// overflow: auto;
// }
// #box5 {
// width: 400px;
// height: 600px;
// background-color: #bfa;
// }
alert(box4.scrollLeft); // 0/71.19999694824219/92/... 随着水平滚动条滚动而发生变化
alert(box4.scrollTop); // 0/163.1999969482422/116/... 随着垂直滚动条滚动而发生变化
看这么一个问题,打印如下值,将水平和垂直滚动条滚动到底
alert(box4.clientHeight + ", " + (box4.scrollHeight - box4.scrollTop)); // 283, 283.20001220703125
alert(box4.clientWidth + ", " + (box4.scrollWidth - box4.scrollLeft)); // 183, 183.1999969482422
PS:我这里打印的结果存在小数点,不知为何
- 当满足
scrollHeight - scrollTop == clientHeight
,说明垂直滚动条滚动到底了 - 当满足
scrollWidth - scrollLeft == clientwidth
,说明水平滚动条滚动到底
那么这个原理有什么用呢?
爱到底到底,管我什么事 有些网站注册时会有一个 霸王条款 用户协议,要确保用户阅读协议了,才允许注册。那问题来了,怎么确保用户阅读了协议呢?就是利用了上述原理,当滚动条拖至最底部时,就可以注册了。
那么接下来,我们就做一个 霸王条款 用户协议
练习
HTML 代码
<div id="outer">
<h3>亲爱的用户,欢迎注册本网站</h3>
<p id="info">
亲爱的用户,请仔细阅读以下协议,如果你不仔细阅读你就别注册
此处省略一万字。。。
</p>
<div id="checkDiv">
<input
type="checkbox"
name="checkInput"
value="1"
id="checkInput"
disabled="disabled"
/>我已仔细阅读协议,一定遵守
</div>
<div id="submitDiv">
<input type="submit" id="submitInput" disabled="disabled" value="注册" />
</div>
</div>
CSS 代码
#outer {
width: 500px;
}
#outer,
h3,
#checkDiv,
#submitDiv,
#submitInput {
margin: 10px auto;
}
#checkDiv {
width: 250px;
}
#submitInput {
display: block;
}
#info {
height: 600px;
overflow: auto;
}
JS 代码
// 为滚动条绑定事件,就是为有滚动条的元素绑定事件
var info = document.getElementById("info");
var checkInput = document.getElementById("checkInput");
var submitInput = document.getElementById("submitInput");
info.onscroll = function () {
// 当滚动条滚动到底时,启用并自动勾选协议,并启用注册按钮
if (
parseInt(info.scrollHeight - info.scrollTop) == parseInt(info.clientHeight)
) {
// 自动勾选协议
checkInput.disabled = false;
checkInput.checked = true;
// 启用注册按钮
submitInput.disabled = false;
}
};
// 为checkInput绑定勾选响应事件
checkInput.onclick = function (ret) {
// 如果勾选了协议,则启用注册按钮,否则禁用注册按钮
if (!checkInput.checked) {
submitInput.disabled = true;
} else {
submitInput.disabled = false;
}
};
// 为submit绑定单击响应函数
submitInput.onclick = function () {
if (confirm("确认注册吗?")) {
alert("注册成功");
}
};
效果