函数
什么是函数
- 函数就是语句的封装,可以让这些代码方便地被复用
- 函数具有“一次定义,多次调用”的优点
- 使用函数,可以简化问题,让代码更具有可读性
函数的定义和调用
函数的定义
- 和变量类似,函数必须先定义然后才能使用
- 使用function关键字定义函数,function是“功能”的意思
函数的调用
- 执行函数体中的所有语句,就称为“调用函数”
- 调用函数非常简单,只需在函数名字后书写圆括号对即可
语句执行顺序
函数声明的提升
- 和变量声明提升类似,函数声明也可以被提升
函数表达式不能提升
- 如果函数是用函数表达式的写法定义的,则没有提升特性
函数优先提升
- 函数和变量同时提升
- 函数提升会将函数声明连带定义一起提升
a();
function a(){
b++;
console.log(b)
}
var b =1;
//Output:2
fun();
var fun = function () {
alert('A');
};
function fun() {
alert('B');
}
fun();
// Output B A
// 由于函数的优先提升,变量表达式形式的函数被后优先提升且仅优先提升定义而不是值,导致第一次输出B,后面执行到fun后又被覆盖,故输出A
函数的参数和返回值
函数的参数
- 参数是函数内的一些待定值,在调用函数时,必须传入这些参数的具体值
- 函数的参数可多可少,函数可以没有参数,也可以有多个参数,多个参数之间需要用逗号隔开
形参和实参个数不同的情况
arguments
- 函数内arguments表示它接收到的实参列表,它是一个类数组对象。
- 类数组对象:所有属性均为从0开始的自然数序列,并且有length属性,和数组类似可以用方括号书写下标访问对象的某个属性值,但是不能调用数组的方法。
// 不管用户传入多少个实际参数,永远能够计算它们的和
function fun() {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
console.log('所有参数的和是' + sum);
}
fun(33, 44, 23, 34);
fun(44);
fun(-3, 4);
函数的返回值
- 函数体内可以使用return关键字表示“函数的返回值”
- 调用一个有返回值的函数,可以被当做一个普通值,从而可以出现在任何可以书写值的地方。
function sum(a, b) {
return a + b;
}
var result = sum(3, 4) * sum(2, 6);
- 调用一个有返回值的函数,可以被当做一个普通值,从而可以出现在任何可以书写值的地方.
function sum(a, b) {
return a + b;
}
var result = sum(3, sum(4, 5));
遇见return即退出函数
- 调用函数时,一旦遇见return语句则会立即退出函数,将执行权交还给调用者
- 结合if语句的时候,往往不需要写else分支了
- 比如题目:请编写一个函数,判断一个数字是否是偶数
function fun() {
console.log('A');
return 'B';
console.log('C');
}
console.log(1);
var char = fun();
console.log(1+char);
console.log(2);
//Outpus:1 A 1B 2
复习浅克隆
- 使用var arr2 = arr1这样的语句不能实现数组的克隆
- 浅克隆:准备一个空的结果数组,然后使用for循环遍历原数组,将遍历到的项都推入结果数组
- 浅克隆只克隆数组的一层,如果数组是多维数组,则克隆的项会“藕断丝连”。
// 准备原数组
var arr1 = [33, 44, 11, 22, [77, 88]];
// 准备一个结果数组
var result = [];
// 遍历原数组,将遍历到的项都推入到结果数组中
for(var i = 0; i < arr1.length; i++){
result.push(arr1[i]);
}
// 输出结果数组
console.log(result);
// 测试是否实现了克隆,就是说本质上是内存中的不同数组了
console.log(arr1 == result);
// 测试这样的克隆是浅克隆,“藕断丝连”
arr1[4].push(99);
console.log(result);
//Output:[33, 44, 11, 22, [77, 88,99]]
实现深克隆
- 使用递归思想,整体思路和浅克隆类似,但稍微进行一些改动:如果遍历到项是基本类型值,则直接推入结果数组;如果遍历到的项是又是数组,则重复执行浅克隆的操作。
// 原数组
var arr1 = [33, 44, 11, 22, [77, 88, [33, 44]]];
// 函数,这个函数会被递归
function deepClone(arr) {
// 结果数组,“每一层”都有一个结果数组
var result = [];
// 遍历数组的每一项
for (var i = 0; i < arr.length; i++) {
// 类型判断,如果遍历到的项是数组
if (Array.isArray(arr[i])) {
// 递归
result.push(deepClone(arr[i]));
} else {
// 如果遍历到的项不是数组,是基本类型值,就直接推入到结果数组中,
// 相当于是递归的出口
result.push(arr[i]);
}
}
// 返回结果数组
return result;
}
// 测试一下
var arr2 = deepClone(arr1);
console.log(arr2);
// 是否藕断丝连
console.log(arr1[4][2] == arr2[4][2]);
arr1[4][2].push(99);
console.log(arr1);
console.log(arr2);
全局变量和局部变量
变量作用域
- JavaScript是函数级作用域编程语言:变量只在其定义时所在的function内部有意义。
全局变量
- 如果不将变量定义在任何函数的内部,此时这个变量就是全局变量,它在任何函数内都可以被访问和更改。
遮蔽效应
- 如果函数中也定义了和全局同名的变量,则函数内的变量会将全局的变量“遮蔽”。
变量声明提升
- 这个程序的运行结果是什么呢?
形参也是局部变量
- 这个程序的运行结果是什么呢?
作用域链
- 先来认识函数的嵌套:一个函数内部也可以定义一个函数。和局部变量类似,定义在一个函数内部的函数是局部函数。
- 在函数嵌套中,变量会从内到外逐层寻找它的定义。
不加var将定义全局变量
- 在初次给变量赋值时,如果没有加var,则将定义全局变量。
function fun() {
a = 3;
}
fun();
console.log(a); // 3
闭包
什么是闭包
-
JavaScript中函数会产生闭包(closure)。闭包是函数本身和该函数声明时所处的环境状态的组合。
-
函数能够“记忆住”其定义时所处的环境,即使函数不在其定义的环境中被调用,也能访问定义时所处环境的变量。
观察闭包现象
- 在JavaScript中,每次创建函数时都会创建闭包。
- 但是,闭包特性往往需要将函数“换一个地方”执行,才能被观察出来。
- 闭包很有用,因为它允许我们将数据与操作该数据的函数关联起来。这与“面向对象编程”有少许相似之处。
- 闭包的功能:记忆性、模拟私有变量。
闭包用途1 - 记忆性
- 当闭包产生时,函数所处环境的状态会始终保持在内存中,不会在外层函数调用后被自动清除。这就是闭包的记忆性。
- 创建体温检测函数checkTemp(n),可以检查体温n是否正常,函数会返回布尔值。
- 但是,不同的小区有不同的体温检测标准,比如A小区体温合格线是37.1℃,而B小区体温合格线是37.3℃,应该怎么编程呢?
function createCheckTemp(standardTemp) {
function checkTemp(n) {
if (n <= standardTemp) {
alert('你的体温正常');
} else {
alert('你的体温偏高');
}
}
return checkTemp;
}
// 创建一个checkTemp函数,它以37.1度为标准线
var checkTemp_A = createCheckTemp(37.1);
// 再创建一个checkTemp函数,它以37.3度为标准线
var checkTemp_B = createCheckTemp(37.3);
checkTemp_A(37.2);
checkTemp_A(37.0);
checkTemp_B(37.2);
checkTemp_B(37.0);
闭包用途2 - 模拟私有变量
- 题目:请定义一个变量a,要求是能保证这个a只能被进行指定操作(如加1、乘2),而不能进行其他操作,应该怎么编程呢?
- 在Java、C++等语言中,有私有属性的概念,但是JavaScript中只能用闭包来模拟。
// 封装一个函数,这个函数的功能就是私有化变量
function fun() {
// 定义一个局部变量a
var a = 0;
return {
getA: function () {
return a;
},
add: function () {
a++;
},
pow: function () {
a *= 2;
}
};
}
var obj = fun();
// 如果想在fun函数外面使用变量a,唯一的方法就是调用getA()方法
console.log(obj.getA());
// 想让变量a进行加1操作
obj.add();
obj.add();
obj.add();
console.log(obj.getA());
obj.pow();
console.log(obj.getA());
使用闭包的注意点
- 不能滥用闭包,否则会造成网页的性能问题,严重时可能导致内存泄露。
- 所谓内存泄漏是指程序中己动态分配的内存由于某种原因未释放或无法释放。
闭包的一道面试题
function addCount() {
var count = 0;
return function () {
count = count + 1;
console.log(count);
};
}
var fun1 = addCount();
var fun2 = addCount();
fun1();
fun2();
fun2();
fun1();
立即执行函数IIFE
什么是IIFE
- IIFE(Immediately Invoked Function Expression,立即调用函数表达式)是一种特殊的JavaScript函数写法,一旦被定义,就立即被调用。
![在这里插入图片描述](https://img-blog.csdnimg.cn/8dfa512255864828945508ac7f8dcc02.png)
形成IIFE的方法
- 函数不能直接加圆括号被调用。
- 函数必须转为“函数表达式”才能被调用。
IIFE的作用1 - 为变量赋值
- 为变量赋值:当给变量赋值需要一些较为复杂的计算时(如if语句),使用IIFE显得语法更紧凑。
var age = 12;
var sex = '男';
var title = (function () {
if (age < 18) {
return '小朋友'
} else {
if (sex == '男') {
return '先生';
} else {
return '女士';
}
}
})();
IIFE的作用 - 将全局变量变为局部变量
- IIFE可以在一些场合(如for循环中)将全局变量变为局部变量,语法显得紧凑。
var arr = [];
for (var i = 0; i < 5; i++) {
arr.push(function () {
alert(i); //变量i是全局变量,所有函数都共享内存中的同一个变量i
}); //由于函数并未被执行,故在被调用执行时才真正被调用赋值,而此时的i已经是5了
}
arr[2](); // 弹出5
var arr = [];
for (let i = 0; i < 5; i++) {
arr.push(function () {
alert(i);
});
}
arr[2](); // 弹出2
var arr = [];
for (var i = 0; i < 5; i++) {
(function (i) {
arr.push(function () {
alert(i);
});
})(i);
}
arr[2](); // 弹出2
DOM
Dom基本概念
DOM是JS操控HTML和CSS的桥梁
DOM使JS操作HTML变得更优雅
- 比如下面的HTML结构,现在想用JavaScript在“牛奶”后面插入一个p标签对,内容是“可乐”。
- 使用字符串思维: 下标为22的字符后面插入“
可乐
” (×) - 使用结点思维:div是一个节点,它内部原有3个p子节点。现在我们要做的就是新创建一个p节点,然后插入到原有0号节点后面 (√)
- 使用字符串思维: 下标为22的字符后面插入“
<div id="box">
<p>牛奶</p>
<p>咖啡</p>
<p>果汁</p>
</div>
DOM简介
- DOM(Document Object Model,文档对象模型)是JavaScript操作HTML文档的接口,使文档操作变得非常优雅、简便
- DOM最大的特定就是将文档表示为结点树
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>慕课网</h1>
<div>
<h2>程序员的梦工厂</h2>
<img src="logo.png">
<div class="box">
慕课专栏
</div>
</div>
</body>
</html>
DOM结点树
nodeType常用属性值
- 节点的nodeType属性可以显示这个节点具体的类型
nodeType值 | 结点类型 |
---|---|
1 | 元素节点,例如<p> 和<div> |
3 | 文字节点 |
8 | 注释节点 |
9 | document节点 |
10 | DTD节点 |
访问元素结点
- 所谓“访问”元素节点,就是指“得到”、“获取”页面上的元素节点
- 对节点进行操作,第一步就是要得到它
- 访问元素节点主要依靠document对象
认识document对象
- document对象是DOM中最重要的东西,几乎所有DOM的功能都封装在了document对象中
- document对象也表示整个HTML文档,它是DOM节点树的根
- document对象的nodeType属性值是9
方法 | 功能 | 兼容性 |
---|---|---|
document.getElementById() | 通过ID得到元素数组 | IE6 |
document.getElementsByTagName() | 通过标签名得到元素数组 | IE6 |
document.getElementsByClassName() | 通过类名得到元素数组 | IE9 |
document.querySelector() | 通过选择器得到元素 | IE8部分兼容、IE9完全兼容 |
document.querySelectorAll() | 通过选择器得到元素数组 | IE8部分兼容、IE9完全兼容 |
getElementById()
- document.getElementById()功能是通过id得到元素节点
<div id="box">我是一个盒子</div>
<p id="para">我是一个段落</p>
<script>
var box = document.getElementById('box'); //这里不需要写#号
var para = document.getElementById('para');
</script>
注意事项:
- 如果页面上有相同id的元素,则只能得到第一个
- 不管元素藏的位置有多深,都能通过id把它找到
延迟运行
- 在测试DOM代码时,通常JS代码一定要写到HTML节点的后面,否则JS无法找到相应HTML节点
- 可以使用window.onload = function(){} 事件,使页面加载完毕后,再执行指定的代码
<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>
window.onload = function(){
var id1 = document.getElementById("id1");
var id2 = document.getElementById("id2");
console.log(id1);
console.log(id2);
}
</script>
</head>
<body>
<div id="id1"></div>
<div id="id2"></div>
</body>
getElementsByTagName()
- getElementsByTagName()方法的功能是通过标签名得到节点数组
<p>我是段落</p>
<p>我是段落</p>
<p>我是段落</p>
<p>我是段落</p>
<script>
// 注意这里的ps是一个数组
var ps = document.getElementsByTagName('p');
</script>
注意事项
- 数组方便遍历,从而可以批量操控元素节点
- 即使页面上只有一个指定标签名的节点,也将得到长度为1的数组
- 任何一个节点元素也可以调用getElementsByTagName()方法,从而得到其内部的某种类的元素节点
get ElementsByClassName()
- getElementsByClassName()方法的功能是通过类名得到节点数组
注意事项
- getElementsByClassName()方法从IE9开始兼容
- 某个节点元素也可以调用getElementsByClassName()方法,从而得到其内部的某类名的元素节点
querySelector()
- querySelector()方法的功能是通过选择器得到元素
注意事项
- querySelector()方法只能得到页面上一个元素,如果有多个元素符合条件,则只能得到第一个元素
- querySelector()方法从IE8开始兼容,但从IE9开始支持CSS3的选择器,如:nth-child()、:[src^=‘dog’]等CSS3选择器形式都支持良好
<div id="box">
<p>我是段落</p>
<p class="spec para">我是段落</p>
<p>我是段落</p>
</div>
<script>
var the_p = document.querySelector('#box p:nth-child(1)');
console.log(the_p);
</script>
querySelectorAll()
- querySelectorAll()方法的功能是通过选择器得到元素数组
- 即使页面上只有一个符合选择器的节点,也将得到长度为1的数组
<ul id="list1">
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
<ul id="list2">
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
<li>我是li</li>
</ul>
<script>
var lis_inlist1 = document.querySelectorAll('#list1 li');
console.log(lis_inlist1);
</script>
节点的关系
关系 | 考虑所有节点 |
---|---|
子节点 | childNodes |
父节点 | parentNode |
第一个子节点 | firstChild |
最后一个子节点 | lastChild |
前一个兄弟节点 | previousSibling |
后一个兄弟节点 | nextSibling |
注意:文本节点也属于节点
- DOM中,文本节点也属于节点,在使用节点的关系时一定要注意
- 在标准的W3C规范中,空白文本节点也应该算作节点,但是在IE8及以前的浏览器中会有一定的兼容问题,它们不把空文本节点当做节点
- childNodes包含了所有的结点,包含文本结点,其中每个标签之间的空格就属于文本结点,例如
<!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 id="box">
0<p>我是段落A</p>
2
<!-- 这是注释 -->4
<p id="para">我是段落B</p>
6<p>我是段落C</p>8
<p>我是段落D</p>
</div>
<script>
var box = document.getElementById("box");
console.log(box.childNodes); //NodeList(11) [text, p, text, comment, text, p#para, text, p, text, p, text]
console.log(box.childNodes[0]); // " 0"
console.log(box.childNodes[2]); // " 2 "
console.log(box.childNodes[4]); // "4 "
console.log(box.childNodes[6]); // " 6"
console.log(box.childNodes[8]); // "8 "
console.log(box.childNodes[10]); // #text
</script>
</body>
</html>
排除文本节点的干扰
- 从IE9开始支持一些“只考虑元素节点”的属性
关系 | 考虑所有节点 | 只考虑元素节点 |
---|---|---|
子节点 | childNodes | children |
父节点 | parentNode | 同 |
第一个子节点 | firstChild | firstElementChild |
最后一个子节点 | lastChild | lastElementChild |
前一个兄弟节点 | previousSibling | previousElementSibling |
后一个兄弟节点 | nextSibling | nextElementSibling |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="box">
<p>我是段落A</p>
<p id="para">我是段落B</p>
<p>我是段落C</p>
</div>
<script>
var box = document.getElementById('box');
var para = document.getElementById('para');
// 所有子节点
console.log(box.childNodes);
// 所有的元素子节点(IE9开始兼容)
console.log(box.children);
// 第一个子节点
console.log(box.firstChild);
console.log(box.firstChild.nodeType);
// 第一个元素子节点(IE9开始兼容)
console.log(box.firstElementChild);
// 最后一个子节点
console.log(box.lastChild);
console.log(box.lastChild.nodeType);
// 最后一个元素子节点(IE9开始兼容)
console.log(box.lastElementChild);
// 父节点
console.log(para.parentNode);
// 前一个兄弟节点
console.log(para.previousSibling);
// 前一个元素兄弟节点(IE9开始兼容)
console.log(para.previousElementSibling);
// 后一个兄弟节点
console.log(para.nextSibling);
// 后一个元素兄弟节点(IE9开始兼容)
console.log(para.nextElementSibling);
</script>
</body>
</html>
书写常见的节点关系函数
- 书写IE6也能兼容的“寻找所有元素子节点”函数
- 书写IE6也能兼容的“寻找前一个元素兄弟节点”函数
- 如何编写函数,获得某元素的所有的兄弟节点?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="box">
<p>我是段落</p>
<p>我是段落</p>
<p>我是段落</p>
<p id="fpara">我是段落fpara</p>
我是文本
<!-- 我是注释 -->
<p id="para">
我是段落para
<span>1</span>
<span>2</span>
<span>3</span>
</p>
<p>我是段落</p>
<p>我是段落</p>
<p>我是段落</p>
</div>
<script>
var box = document.getElementById('box');
var para = document.getElementById('para');
var fpara = document.getElementById('fpara');
// 封装一个函数,这个函数可以返回元素的所有子元素节点(兼容到IE6),类似children的功能
function getChildren(node) {
// 结果数组
var children = [];
// 遍历node这个节点的所有子节点,判断每一个子节点的nodeType属性是不是1
// 如果是1,就推入结果数组
for (var i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].nodeType == 1) {
children.push(node.childNodes[i]);
}
}
return children;
}
console.log(getChildren(box));
console.log(getChildren(para));
// 封装一个函数,这个函数可以返回元素的前一个元素兄弟节点(兼容到IE6),类似previousElementSibling的功能
function getElementPrevSibling(node) {
var o = node;
// 使用while语句
while (o.previousSibling != null) {
if (o.previousSibling.nodeType == 1) {
// 结束循环,找到了
return o.previousSibling;
}
// 让o成为它的前一个节点,就有点“递归”的感觉
o = o.previousSibling;
}
return null;
}
console.log(getElementPrevSibling(para));
console.log(getElementPrevSibling(fpara));
// 封装第三个函数,这个函数可以返回元素的所有元素兄弟节点
function getAllElementSibling(node) {
// 前面的元素兄弟节点
var prevs = [];
// 后面的元素兄弟节点
var nexts = [];
var o = node;
// 遍历node的前面的节点
while(o.previousSibling != null) {
if(o.previousSibling.nodeType == 1){
prevs.unshift(o.previousSibling);
}
o = o.previousSibling;
}
o = node;
// 遍历node的后面的节点
while(o.nextSibling != null) {
if(o.nextSibling.nodeType == 1){
nexts.push(o.nextSibling);
}
o = o.nextSibling;
}
// 将两个数组进行合并,然后返回
return prevs.concat(nexts);
}
console.log(getAllElementSibling(para));
</script>
</body>
</html>
如何改变元素节点中的内容
- 改变元素节点中的内容可以使用两个相关属性:
- ① innerHTML ② innerText
- innerHTML属性能以HTML语法设置节点中的内容
- innerText属性只能以纯文本的形式设置节点中的内容
<body>
<div id="box"></div>
<script>
var oBox = document.getElementById('box');
oBox.innerHTML = '慕课网';
oBox.innerHTML = '<ul><li>牛奶</li><li>咖啡</li></ul>';
// oBox.innerText = '慕课网';
// oBox.innerText = '<ul><li>牛奶</li><li>咖啡</li></ul>';
</script>
</body>
如何改变元素节点的CSS样式
- 改变元素节点的CSS样式需要使用这样的语句:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box{
width: 200px;
height: 200px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div class="box" id="box">
你好
</div>
<script>
var oBox = document.getElementById('box');
// oBox.style.backgroundColor = 'rgb(100, 200, 123)';
// oBox.style.backgroundColor = '#f80';
// oBox.style.backgroundImage = 'url(https://www.imooc.com/static/img/index/logo-recommended.png)';
// oBox.style.backgroundSize = 'contain';
oBox.style.fontSize = '50px';
</script>
</body>
</html>
如何改变元素节点的HTML属性
- 标准W3C属性,如src、href等等,只需要直接打点进行更改即可
<img src="images/1.jpg" id="pic">
<a href="http://www.baidu.com" id="link">
去百度
</a>
<script>
var oPic = document.getElementById('pic');
var oLink = document.getElementById('link');
oPic.src = 'images/2.jpg';
oLink.href = 'http://www.imooc.com';
oLink.innerText = '去慕课网';
</script>
- 不符合W3C标准的属性,要使用setAttribute()和getAttribute()来设置、读取
<div id="box"></div>
<script>
var box = document.getElementById('box');
box.setAttribute('data-n', 10);
var n = box.getAttribute('data-n');
alert(n);
</script>
结点的创建 移除和克隆
节点的创建
- document.createElement() 方法用于创建一个指定tagName的HTML元素
var oDiv = document.createElement('div');
- 新创建出的节点是“孤儿节点”,这意味着它并没有被挂载到DOM树上,我们无法看见它
- 必须继续使用appendChild()或insertBefore()方法将孤儿节点插入到DOM树上
- 任何已经在DOM树上的节点,都可以调用appendChild()方法,它可以将孤儿节点挂载到它的内部,成为它的最后一个子节点
父节点.appendChild(孤儿节点);
- 任何已经在DOM树上的节点,都可以调用insertBefore()方法,它可以将孤儿节点挂载到它的内部,成为它的“标杆子节点”之前的节点
父节点.insertBefore(孤儿节点, 标杆节点);
- 任何已经在DOM树上的节点,都可以调用appendChild()方法,它可以将孤儿节点挂载到它的内部,成为它的最后一个子节点
<div id="box">
<p>我是原本的段落0</p>
<p>我是原本的段落1</p>
<p>我是原本的段落2</p>
</div>
<script>
var oBox = document.getElementById('box');
var oPs = oBox.getElementsByTagName('p');
// 创建孤儿节点
var oP = document.createElement('p');
// 设置内部文字
oP.innerText = '我是新来的';
// 上树
// oBox.appendChild(oP);
oBox.insertBefore(oP, oPs[0]);
</script>
小案例
- 请动态创建出一个20行12列的表格
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
td {
width: 20px;
height: 20px;
border: 1px solid #000;
}
</style>
</head>
<body>
<table id="mytable"></table>
<script>
// 请动态创建出一个20行12列的表格
var mytable = document.getElementById('mytable');
for (var i = 0; i < 20; i++) {
// 创建了新的tr标签
var tr = document.createElement('tr');
for (var j = 0; j < 12; j++) {
// 创建了新的td标签
var td = document.createElement('td');
// 让tr追加td标签
tr.appendChild(td);
}
// 让mytable追加tr标签
mytable.appendChild(tr);
}
</script>
</body>
</html>
- 制作九九乘法表
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
td {
width: 120px;
height: 30px;
border: 1px solid #000;
}
</style>
</head>
<body>
<table id="mytable"></table>
<script>
// 请创建九九乘法表
var mytable = document.getElementById('mytable');
for (var i = 1; i <= 9; i++) {
// 创建了新的tr标签
var tr = document.createElement('tr');
for (var j = 1; j <= i; j++) {
// 创建了新的td标签
var td = document.createElement('td');
// 设置td内部的文字
td.innerText = i + '乘' + j + '等于' + (i * j);
// 让tr追加td标签
tr.appendChild(td);
}
// 让mytable追加tr标签
mytable.appendChild(tr);
}
</script>
</body>
</html>
移动节点
- 如果将已经挂载到DOM树上的节点成为appendChild()或者insertBefore()的参数,这个节点将会被移动
- 新父节点.appendChild(已经有父亲的节点);
- 新父节点.insertBefore(已经有父亲的节点, 标杆子节点);
- 这意味着一个节点不能同时位于DOM树的两个位置
<div id="box1">
<p id="para">我是段落</p>
</div>
<div id="box2">
<p>我是box2的原有p标签</p>
<p>我是box2的原有p标签</p>
<p>我是box2的原有p标签</p>
<p>我是box2的原有p标签</p>
</div>
<script>
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
var para = document.getElementById('para');
var ps_inbox2 = box2.getElementsByTagName('p');
// box2.appendChild(para);
box2.insertBefore(para, ps_inbox2[0]);
</script>
删除节点
- removeChild() 方法从DOM中删除一个子节点
- 父节点.removeChild(要删除子节点);
- 节点不能主动删除自己,必须由父节点删除它
<div id="box">
<p>我是p节点0</p>
<p>我是p节点1</p>
<p>我是p节点2</p>
</div>
<script>
var box = document.getElementById('box');
var the_first_p = box.getElementsByTagName('p')[0];
box.removeChild(the_first_p);
</script>
克隆节点
- cloneNode() 方法可以克隆节点,克隆出的节点是“孤儿节点”
- var 孤儿节点 = 老节点.cloneNode();
- var 孤儿节点 = 老节点.cloneNode(true);
- 参数是一个布尔值,表示是否采用深度克隆:如果为true,则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身
<div id="box1">
<ul>
<li>牛奶</li>
<li>咖啡</li>
<li>可乐</li>
</ul>
</div>
<div id="box2"></div>
<script>
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
var theul = box1.getElementsByTagName('ul')[0];
// 克隆节点
var new_ul = theul.cloneNode(true);
box2.appendChild(new_ul);
</script>
事件监听
什么是 ”什么是事件监听“
- DOM允许我们书写JavaScript代码以让HTML元素对事件作出反应
- 什么是“事件”:用户与网页的交互动作:
- 当用户点击元素时
- 当鼠标移动到元素上时
- 当文本框的内容被改变时
- 当键盘在文本框中被按下时
- 当网页已加载完毕时
- “监听”,顾名思义,就是让计算机随时能够发现这个事件发生了,从而执行程序员预先编写的一些程序
- 设置事件监听的方法主要有onxxx和addEventListener()两种,前者只涉及到事件的冒泡阶段,后者可选择冒泡和捕获阶段
最简单的设置事件监听的方法
-
最简单的给元素设置事件监听的方法就是设置它们的onxxx属性,像这样:
-
oBox.onclick = function(){ // 点击盒子时,将执行这里的语句 }
-
oBox.addEventListener('click',function(){ //code here },true) //true表示监听捕获阶段,false表示监听冒泡阶段
-
常见的鼠标事件监听
事件名 | 事件描述 |
---|---|
onclick | 当鼠标单击某个对象 |
ondbclick | 当鼠标双击某个对象 |
onmousedown | 当某个鼠标按键在某个对象上被按下 |
onmouseup | 当某个鼠标按键在某个对象上被松开 |
onmousemove | 当某个鼠标按键在某个对象上被移动 |
onmouseenter | 当鼠标进入某个对象(相似事件onmouseover) |
onmouseleave | 当鼠标离开某个对象(相似事件onmouseout) |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div{
width: 200px;
height: 200px;
background-color: #333;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
var oBox = document.getElementById('box');
oBox.onclick = function() {
console.log('我是onclick');
};
oBox.ondblclick = function() { //双击鼠标
console.log('我是ondblclick');
};
oBox.onmousedown = function() {
console.log('我是onmousedown');
};
oBox.onmouseup = function() {
console.log('我是onmouseup');
};
oBox.onmouseenter = function() {
console.log('我是onmouseenter');
};
oBox.onmouseleave = function() {
console.log('我是onmouseleave');
};
oBox.onmousemove = function() {
console.log('我是onmousemove');
};
</script>
</body>
</html>
常见的键盘事件监听
事件名 | 事件描述 |
---|---|
onkeypress | 当某个键盘的键被按下(系统按钮如箭头键和功能键无法得到识别) |
onkeydown | 当某个键盘的键被按下(系统按钮可以识别,并且会先于onkeypress发生) |
onkeyup | 当某个键盘的键被松开 |
键盘事件监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
姓名:
<input type="text" id="nameField">
<script>
var nameField = document.getElementById('nameField');
nameField.onkeydown = function () {
console.log('我是onkeydown'); //可以监听功能性按键例如F1-F12,Backspace等
};
nameField.onkeypress = function() { //不可以监听功能性按键例如F1-F12,Backspace等
console.log('我是onkeypress');
}
nameField.onkeyup = function() {
console.log('我是onkeyup');
}
</script>
</body>
</html>
表单事件监听
事件名 | 事件描述 |
---|---|
onchange | 当用户改变域的内容 |
onfocus | 当某元素获得焦点(比如tab键或鼠标点击) |
onblur | 当某元素失去焦点 |
onsubmit | 当表单被提交 |
onreset | 当表单被重置 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form id="myform">
<p>
姓名:
<input type="text" name="nameField">
</p>
<p>
年龄:
<input type="text" name="ageField">
</p>
<p>
<input type="submit">
</p>
</form>
<script>
var myform = document.getElementById('myform');
var nameField = myform.nameField;
var ageField = myform.ageField;
nameField.onchange = function () {
console.log('你已经修改完了姓名');
};
nameField.oninput = function () {
console.log('你正在修改姓名');
};
nameField.onfocus = function() {
console.log('姓名框已经得到了焦点');
}
nameField.onblur = function() {
console.log('姓名框已经失去了焦点');
}
myform.onsubmit = function() {
alert('你正在尝试提交表单');
}
</script>
</body>
</html>
常见的页面事件监听
事件名 | 事件描述 |
---|---|
onload | 当页面或图像被完成加载 |
onunload | 当用户退出页面 |
事件传播
事件的传播
- 实际上,事件的传播是:先从外到内,然后再从内到外
onxxx写法只能监听冒泡阶段
- onxxx这样的写法只能监听冒泡阶段
- DOM0级事件监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box1{
width: 202px;
height: 202px;
border: 1px solid #000;
padding: 50px;
}
#box2{
width: 100px;
height: 100px;
border: 1px solid #000;
padding: 50px;
}
#box3{
width: 100px;
height: 100px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox2.onclick = function () {
console.log('我是box2的onclick');
};
oBox3.onclick = function () {
console.log('我是box3的onclick');
};
oBox1.onclick = function () {
console.log('我是box1的onclick');
};
</script>
</body>
</html>
addEventListener()方法
-
DOM0级事件监听:只能监听冒泡阶段
-
oBox.onclick = function () { };
-
-
DOM2级事件监听:
-
oBox.addEventListener('click', function () { //事件名不加on // 这是事件处理函数 }, true); //true监听捕获阶段,false监听冒泡阶段
-
注意事项
- 最内部元素不再区分捕获和冒泡阶段,会先执行写在前面的监听,然后执行后写的监听
- 如果给元素设置相同的两个或多个同名事件,则DOM0级写法后面写的会覆盖先写的;而DOM2级会按顺序执行
DOM2级事件监听
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box1{
width: 202px;
height: 202px;
border: 1px solid #000;
padding: 50px;
}
#box2{
width: 100px;
height: 100px;
border: 1px solid #000;
padding: 50px;
}
#box3{
width: 100px;
height: 100px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox2.addEventListener('click', function() {
console.log('我是box2的冒泡阶段');
}, false);
oBox3.addEventListener('click', function() {
console.log('我是box3的捕获阶段');
}, true); //最内层元素不区分捕获和冒泡阶段, 其书写顺序会影响执行顺序
oBox3.addEventListener('click', function() {
console.log('我是box3的冒泡阶段');
}, false);
oBox3.onclick = function () {
console.log('我是box3的onclick');
};
oBox1.addEventListener('click', function() {
console.log('我是box1的冒泡阶段');
}, false);
oBox2.addEventListener('click', function() {
console.log('我是box2的捕获阶段');
}, true);
oBox1.addEventListener('click', function() {
console.log('我是box1的捕获阶段');
}, true);
oBox1.onclick = function () {
console.log('我是box1的onclick');
};
oBox2.onclick = function () {
console.log('我是box2的onclick');
};
</script>
</body>
</html>
同名事件的覆盖问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box1{
width: 202px;
height: 202px;
border: 1px solid #000;
padding: 50px;
}
#box2{
width: 100px;
height: 100px;
border: 1px solid #000;
padding: 50px;
}
#box3{
width: 100px;
height: 100px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div id="box1">
<div id="box2">
<div id="box3"></div>
</div>
</div>
<script>
var oBox1 = document.getElementById('box1');
var oBox2 = document.getElementById('box2');
var oBox3 = document.getElementById('box3');
oBox2.onclick = function () {
alert('A');
}; //DOM0级事件将被覆盖
oBox2.onclick = function () {
alert('B');
};
oBox2.addEventListener('click', function() {
alert('C');
}, false);
oBox2.addEventListener('click', function() {
alert('D');
}, false);
</script>
</body>
</html>
事件对象
什么是事件对象
-
事件处理函数提供一个形式参数,它是一个对象,封装了本次事件的细节
-
这个参数通常用单词event或字母e来表示
-
oBox.onmousemove = function (e) { // 对象e就是这次事件的“事件对象” };
-
鼠标位置
属性 | 属性描述 |
---|---|
clientX | 鼠标指针相对于浏览器的水平坐标 |
clientY | 鼠标指针相对于浏览器的垂直坐标 |
pageX | 鼠标指针相对于整张网页的水平坐标 |
pageY | 鼠标指针相对于整张网页的垂直坐标 |
offsetX | 鼠标指针相对于事件源元素的水平坐标 |
offsetY | 鼠标指针相对于事件源元素的垂直坐标 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
#box{
width: 200px;
height: 200px;
background-color: #333;
margin: 100px;
}
body{
height: 2000px;
}
#info{
font-size: 30px;
}
</style>
</head>
<body>
<div id="box">
</div>
<div id="info"></div>
<script>
var oBox = document.getElementById('box');
var oInfo = document.getElementById('info');
oBox.onmousemove = function (e) {
//注意offsetX/Y计算参考物的是最内部的子元素,而非调用的对象
oInfo.innerHTML = 'offsetX/Y:' + e.offsetX + ',' + e.offsetY + '<br>'
+ 'clientX/Y:' + e.clientX + ',' + e.clientY + '<br>'
+ 'pageX/Y:' + e.pageX + ',' + e.pageY;
};
</script>
</body>
</html>
e.charCode和e.keyCode属性
- e.charCode属性通常用于onkeypress事件中,表示用户输入的字符的“字符码”
- e.keyCode属性通常用于onkeydown事件和onkeyup中,表示用户按下的按键的“键码”
charCode字符码
字符 | 字符码 |
---|---|
数字 0-9 | 48-57 |
大写字母 A-Z | 65-90 |
小写字母 a-z | 97-122 |
keyCode键码
按键 | 键码 |
---|---|
数字0 ~ 数字9 | 48 ~ 57(同charCode键码完全相同) |
字母不分大小a ~ z | 65 ~ 90(同charCode键码的大写字母A ~ Z,而keyCode不分大小写,一律为65 ~ 90) |
四个方向键← ↑ → ↓ | 37、38、39、40 |
回车键 | 13 |
空格键 | 32 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" id="field1">
<h1 id="info1"></h1>
<input type="text" id="field2">
<h1 id="info2"></h1>
<script>
var oField1 = document.getElementById('field1');
var oInfo1 = document.getElementById('info1');
var oField2 = document.getElementById('field2');
var oInfo2 = document.getElementById('info2');
oField1.onkeypress = function (e) {
oInfo1.innerText = '你输入的字符的字符码是' + e.charCode;
};
oField2.onkeydown = function (e) {
oInfo2.innerText = '你按下的按键的键码是' + e.keyCode;
};
</script>
</body>
</html>
小案例1:小盒子的移动
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
position: absolute;
top: 200px;
left: 200px;
width: 100px;
height: 100px;
background-color: orange;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
var oBox = document.getElementById('box');
// 全局变量t、l,分别表示盒子的top属性值和left属性值
var t = 200;
var l = 200;
// 监听document对象的键盘按下事件监听,表示当用户在整个网页上按下按键的时候
document.onkeydown = function (e) {
switch (e.keyCode) {
case 37:
l -= 3;
break;
case 38:
t -= 3;
break;
case 39:
l += 3;
break;
case 40:
t += 3;
break;
}
// 更改样式
oBox.style.left = l + 'px';
oBox.style.top = t + 'px';
};
</script>
</body>
</html>
e.preventDefault()方法
- e.preventDefault()方法用来阻止事件产生的“默认动作”
- 一些特殊的业务需求,需要阻止事件的“默认动作”
小案例2:制作一个文本框,只能让用户在其中输入小写字母和数字,其他字符输入没有效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>
只能输入小写字母和数字:
<input type="text" id="field">
</p>
<script>
var oField = document.getElementById('field');
oField.onkeypress = function (e) {
console.log(e.charCode);
// 根据用户输入的字符的字符码(e.charCode)
// 数字0~9,字符码48~57
// 小写字母a~z,字符码97~122
if (!(e.charCode >= 48 && e.charCode <= 57 || e.charCode >= 97 && e.charCode <= 122)) {
// 阻止浏览器的默认行为
e.preventDefault();
}
};
</script>
</body>
</html>
小案例3:preventDefault()方法2
- 制作鼠标滚轮事件:当鼠标在盒子中向下滚动时,数字加1,否则减1
- 鼠标滚轮事件是onmousewheel,它的事件对象e提供deltaY属性表示鼠标滚动方向,向下滚动时返回正值,向上滚动时返回负值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
width: 200px;
height: 200px;
background-color: #333;
}
body{
height: 2000px;
}
</style>
</head>
<body>
<div id="box"></div>
<h1 id="info">0</h1>
<script>
var oBox = document.getElementById('box');
var oInfo = document.getElementById('info');
// 全局变量就是info中显示的数字
var a = 0;
// 给box盒子添加鼠标滚轮事件监听
oBox.onmousewheel = function (e) {
// 阻止默认事件:就是说当用户在盒子里面滚动鼠标滚轮的时候,此时不会引发页面的滚动条的滚动
e.preventDefault();
if (e.deltaY > 0) {
a++;
} else {
a--;
}
oInfo.innerText = a;
}
</script>
</body>
</html>
e. stopPropagation()方法
- e.stopPropagation()方法用来阻止事件继续传播
- 在一些场合,非常有必要切断事件继续传播,否则会造成页面效果显示出bug
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div{
width: 200px;
height: 200px;
background-color: #333;
}
</style>
</head>
<body>
<div id="box">
<button id="btn">按我</button>
</div>
<script>
var oBox = document.getElementById('box');
var oBtn = document.getElementById('btn');
// oBox.onclick = function () {
// console.log('我是盒子');
// };
// oBtn.onclick = function (e) {
// 阻止事件在冒泡阶段继续传播
// e.stopPropagation();
// console.log('我是按钮');
// };
oBox.addEventListener('click', function(e) {
// 阻止事件在捕获阶段继续传播
e.stopPropagation();
console.log('我是盒子');
}, true);
oBtn.addEventListener('click', function() {
console.log('我是按钮');
}, true);
</script>
</body>
</html>
小案例:制作一个弹出层:点击按钮显示弹出层,点击网页任意地方,弹出层关闭
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.modal {
width: 400px;
height: 140px;
background-color: #333;
position: absolute;
top: 50%;
left: 50%;
margin-top: -70px;
margin-left: -200px;
display: none;
}
</style>
</head>
<body>
<button id="btn">按我弹出弹出层</button>
<div class="modal" id="modal"></div>
<script>
var oBtn = document.getElementById('btn');
var oModal = document.getElementById('modal');
// 点击按钮的时候,弹出层显示
oBtn.onclick = function (e) {
// 阻止事件继续传播到document身上
e.stopPropagation();
oModal.style.display = 'block';
};
// 点击页面任何部分的时候,弹出层关闭
document.onclick = function () {
oModal.style.display = 'none';
};
// 点击弹出层内部的时候,不能关闭弹出层的,所以应该阻止事件继续传播
oModal.onclick = function (e) {
// 阻止事件继续传播到document身上
e.stopPropagation();
};
</script>
</body>
</html>
事件委托
批量添加事件监听
- 题目:页面上有一个无序列表
- ,它内部共有20个
- 元素,请批量给它们添加点击事件监听,实现效果:点击个
- 元素,哪个
- 元素就变红
批量添加事件监听的性能问题
- 每一个事件监听注册都会消耗一定的系统内存,而批量添加事件会导致监听数量太多,内存消耗会非常大
- 实际上,每个
- 的事件处理函数都是不同的函数,这些函数本身也会占用内存
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul id="list">
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
</ul>
<script>
var oList = document.getElementById('list');
var lis = oList.getElementsByTagName('li');
// 书写循环语句,批量给元素添加监听
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
// 在这个函数中,this表示点击的这个元素
this.style.color = 'red';
};
}
</script>
</body>
</html>
新增元素动态绑定事件
- 题目:页面上有一个无序列表
- ,它内部没有
- 元素,请制作一个按钮,点击这个按钮就能增加一个
- 元素。并且要求每个增加的
- 元素也要有点击事件监听,实现效果点击哪个
- 元素,哪个
- 元素就变红
动态绑定事件的问题
- 新增元素必须分别添加事件监听,不能自动获得事件监听
- 大量事件监听、大量事件处理函数都会产生大量消耗内存
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">按我添加新的li列表项</button>
<ul id="list"></ul>
<script>
var oBtn = document.getElementById('btn');
var oList = document.getElementById('list');
var lis = oList.getElementsByTagName('li');
// 按钮的点击事件
oBtn.onclick = function () {
// 创建一个新的li列表项,孤儿节点
var oLi = document.createElement('li');
oLi.innerHTML = '我是列表项';
// 上树
oList.appendChild(oLi);
// 给新创建的这个li节点添加onclick事件监听
oLi.onclick = function () {
this.style.color = 'red';
};
};
</script>
</body>
</html>
事件委托
- 利用事件冒泡机制,将后代元素事件委托给祖先元素
- 事件委托通常需要结合使用e.target属性
属性 | 属性描述 |
---|---|
target | 触发此事件的最早元素,即“事件源元素” |
currentTarget | 事件处理程序附加到的元素 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">按我创建一个新列表项</button>
<ul id="list">
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
</ul>
<script>
var oList = document.getElementById('list');
var oBtn = document.getElementById('btn');
oList.onclick = function (e) {
// e.target表示用户真正点击的那个元素
e.target.style.color = 'red';
//e.currentTarget表示当前点击的事件处理函数附加到的元素,类似于this
//e.currentTarget.style.backgroundColor = 'red';
};
oBtn.onclick = function () {
// 创建新的li元素
var oLi = document.createElement('li');
// 写内容
oLi.innerText = '我是新来的';
// 上树
oList.appendChild(oLi);
};
</script>
</body>
</html>
事件委托的使用场景
- 当有大量类似元素需要批量添加事件监听时,使用事件委托可以减少内存开销
- 当有动态元素节点上树时,使用事件委托可以让新上树的元素具有事件监听
使用事件委托时需要注意的事项
- onmouseenter和onmouseover都表示“鼠标进入”,它们有什么区别呢?
- 答:onmouseenter不冒泡,onmouseover冒泡。
- 使用事件委托时要注意:不能委托不冒泡的事件给祖先元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">按我创建一个新列表项</button>
<ul id="list">
<li>列表项</li>
<li>列表项</li>
<li>列表项</li>
</ul>
<script>
var oList = document.getElementById('list');
var oBtn = document.getElementById('btn');
// onmouseenter这个属性天生就是“不冒泡”的,相当于你事件处理函数附加给了哪个DOM节点
// 就是哪个DOM节点自己触发的事件,没有冒泡过程,此时的e.target表示的就是oList
// 注意目标元素是最内层元素否则,可能会导致结果异常
oList.onmouseover = function (e) { // onmouseover
// e.target表示用户真正点击的那个元素
e.target.style.color = 'red';
};
oBtn.onclick = function () {
// 创建新的li元素
var oLi = document.createElement('li');
// 写内容
oLi.innerText = '我是新来的';
// 上树
oList.appendChild(oLi);
};
</script>
</body>
</html>
- 最内层元素不能再有额外的内层元素了,比如:
<ul id="list">
<li><span>我是span</span>列表项</li>
<li><span>我是span</span>列表项</li>
<li><span>我是span</span>列表项</li>
</ul>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">按我创建一个新列表项</button>
<ul id="list">
<li><span>ABCD</span>列表项</li>
<li><span>ABCD</span>列表项</li>
<li><span>ABCD</span>列表项</li>
</ul>
<script>
var oList = document.getElementById('list');
var oBtn = document.getElementById('btn');
oList.onclick = function (e) {
// e.target表示用户真正点击的那个元素
e.target.style.color = 'red';
};
oBtn.onclick = function () {
// 创建新的li元素
var oLi = document.createElement('li');
// 写内容
oLi.innerText = '我是新来的';
// 上树
oList.appendChild(oLi);
};
</script>
</body>
</html>
定时器和延时器
定时器
- setInterval()函数可以重复调用一个函数,在每次调用之间具有固定的时间间隔
- setInterval()函数可以接收第3、4……个参数,它们将按顺序传入函数
- 具名函数也可以传入setInterval
var a = 0;
setInterval(function () {
console.log(++a);
}, 1000);
var b = 0;
function fun(s,q){
console.log(++a);
}
setInterval(fun,1000,88,66); //从第3个参数开始表示传入函数内的参数
var timer = setInterval(function(){},2000);
oBtn.onclick = function(){ //点击按钮清楚定时器
clearInterval(timer);
}
清除定时器
- clearInterval()函数可以清除一个定时器
定时器小案例
<!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>
<h1 id="info">0</h1>
<button id="btn1">Start</button>
<button id="btn2">Stop</button>
<script>
var oInfo = document.getElementById('info');
var oBtn1 = document.getElementById('btn1');
var oBtn2 = document.getElementById("btn2");
var a = 0;
var timer;
oBtn1.onclick = function () {
clearInterval(timer); //注意这里为了防止多次点击定时器出现定时器叠加而加速的情况
timer = setInterval(function () {
oInfo.innerText = ++a;
}, 1000);
};
oBtn2.onclick = function(){
clearInterval(timer);
}
</script>
</body>
</html>
延时器
- setTimeout()函数可以设置一个延时器,当指定时间到了之后,会执行函数一次,不再重复执行。
setTimeout(function () {
// 这个函数会在2秒后执行一次
}, 2000);
清除延时器
- clearTimeout()函数可以清除延时器,和clearInterval()非常类似
var timer = setTimeout(function(){
console.log('A')
},2000);
clearTimeout(timer);
小案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn1">2秒后弹出你好</button>
<button id="btn2">取消弹出</button>
<script>
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var timer;
btn1.onclick = function() {
timer = setTimeout(function () {
alert('你好');
}, 2000);
}
btn2.onclick = function() {
clearTimeout(timer);
}
</script>
</body>
</html>
异步语句
- setInterval()和setTimeout()都是异步语句
- 异步(asynchronous):不会阻塞CPU继续执行其他语句,当异步完成时,会执行“回调函数”(callback)
使用定时器实现动画
使用定时器实现动画
- 使用定时器可以实现动画,利用的就是“视觉暂留”原理
- 使用定时器实现动画较为不便:
① 不方便根据动画总时间计算步长
② 运动方向要设置正负
③ 多种运动进行叠加较为困难(比如一个方形一边移动一边变为圆形)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
position: absolute;
top: 100px;
left: 100px;
width: 100px;
height: 100px;
background-color: orange;
}
</style>
</head>
<body>
<button id="btn">开始运动</button>
<div id="box"></div>
<script>
// 得到元素
var btn = document.getElementById('btn');
var box = document.getElementById('box');
// 全局变量盒子的left值
var left = 100;
// 按钮监听
btn.onclick = function () {
var timer = setInterval(function () {
// 改变全局变量
left += 10;
if (left >= 1000) {
clearInterval(timer);
}
// 设置left属性
box.style.left = left + 'px';
}, 20);
};
</script>
</body>
</html>
JS和CSS3结合实现动画
- 我们知道,CSS3的transition过渡属性可以实现动画
- JavaScript可以利用CSS3的transition属性轻松实现元素
动画 - JS和CSS3结合实现动画规避了定时器制作动画的缺点
函数节流
- 函数节流:一个函数执行一次后,只有大于设定的执行周期后才允许执行第二次
- 函数节流非常容易实现,只需要借助setTimeout()延时器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#box {
width: 100px;
height: 100px;
background-color: orange;
position: absolute;
top: 100px;
left: 100px;
}
</style>
</head>
<body>
<button id="btn">按我运动</button>
<div id="box"></div>
<script>
// 得到元素
var btn = document.getElementById('btn');
var box = document.getElementById('box');
// 标识量,指示当前盒子在左边还是右边
var pos = 1; // 1左边,2右边
// 函数节流锁
var lock = true;
// 事件监听
btn.onclick = function () {
// 首先检查锁是否是关闭
if (!lock) return;
// 把过渡加上
box.style.transition = 'all 2s linear 0s';
if (pos == 1) {
// 瞬间移动,但是由于有过渡,所以是动画
box.style.left = '1100px';
pos = 2;
} else if (pos == 2) {
// 瞬间移动,但是由于有过渡,所以是动画
box.style.left = '100px';
pos = 1;
}
// 关锁
lock = false;
// 指定时间后,将锁打开
setTimeout(function() {
lock = true;
}, 2000);
};
</script>
</body>
</html>
动画效果开发
动画效果开发1 - 无缝连续滚动特效
<!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>
<style>
* {
margin: 0;
padding: 0;
}
.box {
width: 1000px;
height: 130px;
/* border: 1px solid #000; */
margin: 50px auto;
overflow: hidden;
}
.box ul {
list-style: none;
/* 设置大一点,这样li才能浮动 */
width: 5000px;
position: relative;
}
.box ul li {
float: left;
margin-right: 10px;
}
</style>
</head>
<body>
<div id="box" class="box">
<ul id="list">
<li><img src="img/0.png" alt=""></li>
<li><img src="img/1.png" alt=""></li>
<li><img src="img/2.png" alt=""></li>
<li><img src="img/3.png" alt=""></li>
<li><img src="img/4.png" alt=""></li>
<li><img src="img/5.png" alt=""></li>
</ul>
</div>
<script>
var box = document.getElementById("box");
var list = document.getElementById('list');
list.innerHTML += list.innerHTML;
var left = 0;
var timer;
move();
function move(){
clearInterval(timer);
timer = setInterval(function(){
left -= 4;
if(left<= -1260) left = 0; //210*6=1260
list.style.left=left+'px';
},20);
}
box.onmouseenter =function(){
clearInterval(timer);
}
box.onmouseleave = function(){
move();
}
</script>
</body>
</html>
案例2: 跑马车轮播图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
.carousel {
width: 650px;
height: 360px;
border: 1px solid #000;
margin: 50px auto;
position: relative;
overflow: hidden;
}
.carousel ul {
list-style: none;
width: 6000px;
position: relative;
left: 0px;
transition: left .5s ease 0s;
}
.carousel ul li {
float: left;
}
.carousel .leftbtn {
position: absolute;
left: 20px;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
background-color: rgb(28, 180, 226);
border-radius: 50%;
}
.carousel .rightbtn {
position: absolute;
right: 20px;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
background-color: rgb(28, 180, 226);
border-radius: 50%;
}
</style>
</head>
<body>
<div class="carousel">
<ul id="list">
<li><img src="images/beijing/0.jpg" alt=""></li>
<li><img src="images/beijing/1.jpg" alt=""></li>
<li><img src="images/beijing/2.jpg" alt=""></li>
<li><img src="images/beijing/3.jpg" alt=""></li>
<li><img src="images/beijing/4.jpg" alt=""></li>
</ul>
<a href="javascript:;" class="leftbtn" id="leftbtn"></a>
<a href="javascript:;" class="rightbtn" id="rightbtn"></a>
</div>
<script>
// 得到按钮和ul,ul整体进行运动
var leftbtn = document.getElementById('leftbtn');
var rightbtn = document.getElementById('rightbtn');
var list = document.getElementById('list');
// 克隆第一张图片
var cloneli = list.firstElementChild.cloneNode(true);
list.appendChild(cloneli);
// 当前ul显示到第几张了,从0开始数
var idx = 0;
// 节流锁
var lock = true;
// 右边按钮监听
rightbtn.onclick = function () {
// 判断锁的状态
if (!lock) return;
lock = false;
// 给list加过渡,为什么要加??css中不是已经加了么??这是因为最后一张图片会把过渡去掉
list.style.transition = 'left .5s ease 0s';
idx ++;
if (idx > 4) {
// 设置一个延时器,延时器的功能就是将ul瞬间拉回0的位置,延时器的目的就是让过渡动画结束之后
setTimeout(function() {
// 取消掉过渡,因为要的是瞬间移动,不是“咕噜”回去
list.style.transition = 'none';
list.style.left = 0;
idx = 0;
}, 500);
}
list.style.left = -idx * 650 + 'px';
// 函数节流
setTimeout(function() {
lock = true;
}, 500);
}
// 左边按钮监听
leftbtn.onclick = function () {
if (!lock) return;
lock = false;
// 判断是不是第0张,如果是,就要瞬间用假的替换真的
if (idx == 0) {
// 取消掉过渡,因为要的是瞬间移动,不是“咕噜”过去
list.style.transition = 'none';
// 直接瞬间移动到最后的假图片上
list.style.left = -5 * 650 + 'px';
// 设置一个延时器,这个延时器的延时时间可以是0毫秒,虽然是0毫秒,但是可以让我们过渡先是瞬间取消,然后再加上
setTimeout(function() {
// 加过渡
list.style.transition = 'left .5s ease 0s';
// idx改为真正的最后一张
idx = 4;
list.style.left = -idx * 650 + 'px';
}, 0);
} else {
idx --;
list.style.left = -idx * 650 + 'px';
}
// 函数节流
setTimeout(function() {
lock = true;
}, 500);
}
</script>
</body>
</html>
案例3: 呼吸轮播图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.carousel {
width: 650px;
height: 360px;
border: 1px solid #000;
margin: 50px auto;
position: relative;
}
.carousel ul {
list-style: none;
}
.carousel ul li {
position: absolute;
top: 0;
left: 0;
/* 透明度都是0 */
opacity: 0;
transition: opacity 1s ease 0s;
}
/* 只有第一张透明度是1 */
.carousel ul li:first-child {
opacity: 1;
}
.carousel .leftbtn {
position: absolute;
left: 20px;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
background-color: rgb(28, 180, 226);
border-radius: 50%;
}
.carousel .rightbtn {
position: absolute;
right: 20px;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
background-color: rgb(28, 180, 226);
border-radius: 50%;
}
</style>
</head>
<body>
<div class="carousel">
<ul id="list">
<li><img src="images/beijing/0.jpg" alt=""></li>
<li><img src="images/beijing/1.jpg" alt=""></li>
<li><img src="images/beijing/2.jpg" alt=""></li>
<li><img src="images/beijing/3.jpg" alt=""></li>
<li><img src="images/beijing/4.jpg" alt=""></li>
</ul>
<a href="javascript:;" class="leftbtn" id="leftbtn"></a>
<a href="javascript:;" class="rightbtn" id="rightbtn"></a>
</div>
<script>
// 得到按钮和ul,ul整体进行运动
var leftbtn = document.getElementById('leftbtn');
var rightbtn = document.getElementById('rightbtn');
var list = document.getElementById('list');
var lis = list.getElementsByTagName('li');
// 当前是第几张图显示
var idx = 0;
// 节流
var lock = true;
// 右按钮
rightbtn.onclick = function () {
// 判断节流
if (!lock) return;
lock = false;
// 还没有改idx,此时的idx这个图片就是老图,老图淡出
lis[idx].style.opacity = 0;
idx++;
if (idx > 4) idx = 0;
// 改了idx,此时的idx这个图片就是新图,新图淡入
lis[idx].style.opacity = 1;
// 动画结束之后,开锁
setTimeout(function () {
lock = true;
}, 1000);
}
// 左按钮
leftbtn.onclick = function () {
// 判断节流
if (!lock) return;
lock = false;
// 还没有改idx,此时的idx这个图片就是老图,老图淡出
lis[idx].style.opacity = 0;
idx--;
if (idx < 0) idx = 4;
// 改了idx,此时的idx这个图片就是新图,新图淡入
lis[idx].style.opacity = 1;
// 动画结束之后,开锁
setTimeout(function () {
lock = true;
}, 1000);
}
</script>
</body>
</html>
BOM
windos对象
BOM是什么
- BOM(Browser Object Model,浏览器对象模型)是JS与浏览器窗口交互的接口
- 一些与浏览器改变尺寸、滚动条滚动相关的特效,都要借助BOM技术
window对象
- window对象是当前JS脚本运行所处的窗口,而这个窗口中包含DOM结构,window.document属性就是document对象
- 在有标签页功能的浏览器中,每个标签都拥有自己的window 对象;也就是说,同一个窗口的标签页之间不会共享一个 window 对象
全局变量是window的属性
-
全局变量会成为window对象的属性
-
var a = 10; console.log(window.a == a); // true console.log(windows.hasOwnProperty('a')) //true
-
-
这就意味着,多个js文件之间是共享全局作用域的,即js文件没有作用域隔离功能
-
//1.js var a = 1; //2.js console.log(++a); //2
-
内置函数普遍是window的方法
- 如setInterval()、alert()等内置函数,普遍是window的方法
console.log(window.alert == alert); // true
console.log(window.setInterval == setInterval); // true
console.log(window.hasOwnProperty('setInterval')); //true
window.setInterval(function(){
window.console.log("Hello");
},1000);
- 窗口尺寸相关属性
属性 | 意义 |
---|---|
innerHeight | 浏览器窗口的内容区域的高度,包含水平滚动条(如果有的话) |
innerWidth | 浏览器窗口的内容区域的宽度,包含垂直滚动条(如果有的话)。 |
outerHeight | 浏览器窗口的外部高度 |
outerWidth | 浏览器窗口的外部宽度 |
获得不包含滚动条的窗口宽度,要用 : document.documentElement.clientWidth
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
height: 5000px;
}
</style>
</head>
<body>
<script>
console.log('窗口内宽(包含滚动条)', window.innerWidth);
console.log('窗口外宽', window.outerWidth);
console.log('窗口内宽(不含滚动条)', document.documentElement.clientWidth);
</script>
</body>
</html>
resize事件
- 在窗口大小改变之后,就会触发resize事件,可以使用window.onresize或者window.addEventListener(‘resize’)来绑定事件处理函数
// 监听窗口改变尺寸事件
window.onresize = function () {
var root = document.documentElement;
console.log('窗口改变尺寸了', root.clientWidth, root.clientHeight);
};
已卷动高度
- window.scrollY属性表示在垂直方向已滚动的像素值
- document.documentElement.scrollTop属性也表示窗口卷动高度
var scrollTop = window.scrollY || document.documentElement.scrollTop
- document.documentElement.scrollTop不是只读的,而window.scrollY是只读的
console.log(window.scrollY);
console.log(document.documentElement.scrollTop);
//可通过手动修改document.documentElement.scrollTop改变浏览器的滚动条
scroll事件
- 在窗口被卷动之后,就会触发scroll事件,可以使用window.onscroll或者window.addEventListener(‘scroll’)来绑定事件处理函数
window.onscroll = function () {
console.log('窗口卷动了', window.scrollY);
};
Navigator 对象
Navigator对象
- window.navigator 属性可以检索navigator对象,它内部含有用户此次活动的浏览器的相关属性和标识
属性 | 意义 |
---|---|
appName | 浏览器官方名称 |
appVersion | 浏览器版本 |
userAgent | 浏览器的用户代理(含有内核信息和封装壳信息) |
platform | 用户操作系统 |
console.log('浏览器品牌',navigator.appName);
console.log('浏览器版本',navigator.appVersion);
console.log('用户代理',navigator.userAgent);
console.log('操作系统',navigator.platform);
//浏览器品牌 Netscape
//浏览器版本 5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
//用户代理 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
//VM375:4 操作系统 Win32
识别用户浏览器品牌
- 识别用户浏览器品牌通常使用navigator.userAgent属性
var sUsrAg = navigator.userAgent;
if (sUsrAg.indexOf("Firefox") > -1) {
} else if (sUsrAg.indexOf("Opera") > -1) {
} else if (sUsrAg.indexOf("Edge") > -1) {
} else if (sUsrAg.indexOf("Chrome") > -1) {
} else if (sUsrAg.indexOf("Safari") > -1) {
} else {
}
History 对象
History 对象
- window.history 对象提供了操作浏览器会话历史的接口
- 常用操作就是模拟浏览器回退按钮
history.back(); // 等同于点击浏览器的回退按钮
history.go(-1); // 等同于history.back();
<!--tmp.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>我是temp网页</h1>
<a href="history方法.html">去看history方法页面</a>
</body>
</html>
<!--history方法.html-->
<h1>我是history方法网页</h1>
<button id="btn">回退</button>
<a href="javascript:history.back();">回退</a>
<script>
var btn = document.getElementById('btn');
btn.onclick = function() {
// history.back();
history.go(-1);
};
</script>
Location 对象
Location 对象
- window.location标识当前所在网址,可以通过给这个属性赋值命令浏览器进行页面跳转
window.location = 'http://www.imooc.com';
window.location.href = 'http://www.imooc.com';
console.log(window.location);
//
<button id="btn1">点我去看慕课</button>
<button id="btn2">刷新</button>
<script>
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
btn1.onclick = function () {
window.location = 'http://www.imooc.com';
};
btn2.onclick = function () {
window.location.reload(true);
};
</script>
重新加载当前页面
- 可以调用location的reload方法以重新加载当前页面,参数true表示强制从服务器强制加载
window.location.reload(true);
GET请求查询参数
- window.location.search属性即为当前浏览器的GET请求查询参数
- 比如网址 https://www.imooc.com/?a=1&b=2
console.log(window.location.search); // "?a=1&b=2"
BOM特效开发
返回顶部按钮制作
- 返回顶部的原理:改变document.documentElement.scrollTop属性,通过定时器逐步改变此值,则将用动画形式返回顶部。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
height: 5000px;
background-image: linear-gradient(to bottom, blue, green, yellow);
}
.backtotop {
width: 60px;
height: 60px;
background-color: rgba(255, 255, 255, .6);
position: fixed;
bottom: 100px;
right: 100px;
/* 小手状 */
cursor: pointer;
}
</style>
</head>
<body>
<div class="backtotop" id="backtotopBtn">返回顶部</div>
<script>
var backtotopBtn = document.getElementById('backtotopBtn');
var timer;
backtotopBtn.onclick = function () {
// 设表先关
clearInterval(timer);
// 设置定时器
timer = setInterval(function () {
// 不断让scrollTop减少
document.documentElement.scrollTop -= 200;
// 定时器肯定要停
if (document.documentElement.scrollTop <= 0) {
clearInterval(timer);
}
}, 20);
};
</script>
</body>
</html>
楼层导航小效果
- DOM元素都有offsetTop属性,表示此元素到定位祖先元素的垂直距离
- 定位祖先元素:在祖先中,离自己最近的且拥有定位属性的元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
#box {
width: 400px;
height: 400px;
margin: 60px auto;
border: 4px solid red;
}
ul {
padding-top: 10px;
}
li {
padding-top: 10px;
}
#para {
width: 80px;
height: 80px;
background-color: orange;
}
</style>
</head>
<body>
<div id="box">
<ul>
<li>
<p id="para"></p>
</li>
</ul>
</div>
<script>
var para = document.getElementById('para');
// 净top值,使用这个属性的时候,所有的祖先元素不要有定位,有定位就不好用啦。
console.log(para.offsetTop);
</script>
</body>
</html>
小案例:楼层导航
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.content-part {
width: 1000px;
margin: 0px auto;
margin-bottom: 30px;
background-color: #ccc;
font-size: 50px;
}
.floornav {
position: fixed;
right: 40px;
top: 50%;
margin-top: -100px;
width: 120px;
height: 200px;
background-color: orange;
}
.floornav ul {
list-style: none;
}
.floornav ul li {
width: 120px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 26px;
/* 小手指针 */
cursor: pointer;
}
.floornav ul li.current {
background: purple;
color: white;
}
</style>
</head>
<body>
<nav class="floornav">
<ul id="list">
<li data-n="科技" class="current">科技</li>
<li data-n="体育">体育</li>
<li data-n="新闻">新闻</li>
<li data-n="娱乐">娱乐</li>
<li data-n="视频">视频</li>
</ul>
</nav>
<section class="content-part" style="height:674px;" data-n="科技">
科技栏目
</section>
<section class="content-part" style="height:567px;" data-n="体育">
体育栏目
</section>
<section class="content-part" style="height:739px;" data-n="新闻">
新闻栏目
</section>
<section class="content-part" style="height:574px;" data-n="娱乐">
娱乐栏目
</section>
<section class="content-part" style="height:1294px;" data-n="视频">
视频栏目
</section>
<script>
// 使用事件委托给li添加监听
var list = document.getElementById('list');
var contentParts = document.querySelectorAll('.content-part');
var lis = document.querySelectorAll('#list li');
list.onclick = function (e) {
if (e.target.tagName.toLowerCase() == 'li') {
// getAttribute表示得到标签身上的某个属性值
var n = e.target.getAttribute('data-n');
// 可以用属性选择器(就是方括号选择器)来寻找带有相同data-n的content-part
var contentPart = document.querySelector('.content-part[data-n=' + n + ']');
// 让页面的卷动自动成为这个盒子的offsetTop值
document.documentElement.scrollTop = contentPart.offsetTop;
}
}
// 在页面加载好之后,将所有的content-part盒子的offsetTop值推入数组
var offsetTopArr = [];
// 遍历所有的contentPart,将它们的净位置推入数组
for (var i = 0; i < contentParts.length; i++) {
offsetTopArr.push(contentParts[i].offsetTop);
}
// 为了最后一项可以方便比较,我们可以推入一个无穷大
offsetTopArr.push(Infinity);
console.log(offsetTopArr);
// 当前所在楼层
var nowfloor = -1;
// 窗口的卷动
window.onscroll = function () {
// 得到当前的窗口卷动值
var scrollTop = document.documentElement.scrollTop;
// 遍历offsetTopArr数组,看看当前的scrollTop值在哪两个楼层之间
for (var i = 0; i < offsetTopArr.length; i++) {
if (scrollTop >= offsetTopArr[i] && scrollTop < offsetTopArr[i + 1]) {
break;
}
}
// 退出循环的时候,i是几,就表示当前楼层是几
// 如果当前所在楼层,不是i,表示环楼了
if (nowfloor != i) {
console.log(i);
// 让全局变量改变为这个楼层号
nowfloor = i;
// 设置下标为i的项有cur
for (var j = 0; j < lis.length; j++) {
if (j == i) {
lis[j].className = 'current';
} else {
lis[j].className = '';
}
}
}
};
</script>
</body>
</html>