JavaScript简介
JavaScript介绍
JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”,指的是它不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序的“脚本”。
JavaScript 是一种嵌入式(embedded)语言。它本身提供的核心语法不算很多
JavaScript与ECMAScript的关系
ECMAScript和JavaScript的关系是,前者是后者的规格,后者是前者的一种实现。在日常场合,这两个词是可以互换的。
JavaScript版本
JavaScript语句、标识符
语句
JavaScript 程序的单位是行(line),也就是一行一行地执行。一般情况下,每一行就是一个语句
var num = 10;
语句以分号结尾,一个分号就表示一个语句结束
标识符
标识符(identifier)指的是用来识别各种值的合法名称。最常见的标识符就是变量名
标识符是由:字母、美元符号($)、下划线(_)和数字组成,其中数字不能开头
JavaScript保留关键字
以下关键字不需要强行记忆!
JavaScript有一些保留字,不能用作标识符:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。
变量
var num = 10;
变量的重新赋值
var num = 10;
num = 20;
变量提升
JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
<script>
console.log(age);
var age = 10;
var age;
console.log(age)
age = 10;
</script>
JavaScript引入到文件
嵌入到HTML文件中
<body>
<script>
var age = 20
</script>
</body>
引入本地独立JS文件
<body>
<script type="text/javascript" src="./itbaizhan.js"></script>
</body>
引入网络来源文件
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</body>
JavaScript注释与常见输出方式
JavaScript注释
源码中注释是不被引擎所解释的,它的作用是对代码进行解释。
Javascript 提供两种注释的写法:一种是单行注释,用//起头;另一种是多行注释,放在/和/之间。
// 这是单行注释
/* 这是多行注释 */
嵌入在HTML文件中的注释
<!-- 注释 -->
JavaScript输出方式
JavaScript有很多种输出方式,都可以让我们更直观的看到程序运行的结果
// 在浏览器中弹出一个对话框,然后把要输出的内容展示出来,alert都是把要输出的内容首先转换为字符串然后在输出的
alert("要输出的内容");
document.write("要输出的内容");
// 在控制台输出内容
console.log("要输出的内容");
数据类型
数据类型分类
JavaScript 语言的每一个值,都属于某一种数据类型。JavaScript 的数据类型,共有六种。(ES6 又新增了第七种 Symbol 类型的值和第八种 BigInt类型)
原始类型(基础类型)
合成类型(复合类型)
对象:因为一个对象往往是多个原始类型的值的合成,可以看作是一个存放各种值的容器
注意:
至于undefined和null,一般将它们看成两个特殊值。
<!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>
<script>
// 基本数据类型(原始类型)
// 数值/数字 类型
var age = 20;
// 字符串类型: 被双引号或单引号包裹的值就是字符串类型
var name = "iwen"
var name2 = '尚学堂'
// 布尔类型: 计算机是由 0 和 1 组成 0:false 1:true
var flag = true;
var flags = false;
// 引用数据类型,合成数据类型,复合数据类型:object(对象)
var user = {
age:18,
name:"iwen",
hunyin:false,
}
var hello = null;
var world = undefined;
</script>
</body>
</html>
typeof运算符
JavaScript 有三种方法,可以确定一个值到底是什么类型。而我们现在需要接触到的就是typeof
数值返回number
字符串返回string
布尔值返回boolean
对象返回object
unll和undefined的区别
null与undefined都可以表示“没有”,含义非常相似。将一个变量赋值为undefined或null,老实说,语法效果几乎没区别。
既然含义与用法都差不多,为什么要同时设置两个这样的值,这不是无端增加复杂度,这与历史原因有关
<!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>
<script>
var age = 20;
var name = 'iwen';
var flag = true;
var user = {};
// typeof:判断数据类型(判断基本数据类型使用)
console.log(typeof age); // number
console.log(typeof name); // string
console.log(typeof flag); // boolean
console.log(typeof user); // object(可以返回object的有很多情况)
console.log(typeof []); // object
console.log(typeof null);// object
console.log(typeof undefined); // undefined
// null一般代表对象为 “没有”
// undefined一般代表数值为 “没有”
</script>
</body>
</html>
运算符之算术运算符
<!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>
<script>
var num1 = 100;
var num2 = 10;
// 加减乘除
console.log(num1 + num2); // 110
console.log(num1 - num2); // 90
console.log(num1 * num2); // 1000
console.log(num1 / num2); // 10
// 取余
console.log(num1 % num2); // 0
// 自增或者自减运算符相当于 当前值+1或者-1
var num3 = 20;
console.log(++num3); // num3 + 1 = 21
console.log(--num3); // num3 - 1 = 20
// 自增和自减有两种写法 ++num num++
// ++在前,先自增在运算 ++在后,先运算在自增
var num4 = 10;
// console.log(++num4); // 11
console.log(num4++); // 10
console.log(num4); // 11
</script>
</body>
</html>
运算符之赋值运算符
运算符之比较运算符
JavaScript 一共提供了8个比较运算符。
比较运算符 | 描述 |
---|---|
< | 小于运算符 |
> | 大于运算符 |
<= | 小于或等于运算符 |
>= | 大于或等于运算符 |
== | 相等运算符 |
=== | 严格相等运算符 |
!= | 不相等运算符 |
!== | 严格不相等运算符 |
“==”和“===”的区别
- ==:双等比较值
- ===:三等比较值和类型
运算符之布尔运算符
取反运算符(!)
布尔值取反
!true // false
!false // true
非布尔值取反
对于非布尔值,取反运算符会将其转为布尔值。
可以这样记忆,以下六个值取反后为true,其他值都为false。
undefined
null
false
0
NaN
空字符串(‘’)
!undefined // true
!null // true
!0 // true
!NaN // true
!"" // true
且运算符(&&)
多个条件都要满足
console.log(10 < 20 && 10 >5); // true
或运算符(||)
满足一个条件即可
console.log(10 < 20 || 10 < 5); // true
字符串
字符串就是零个或多个排在一起的字符,放在单引号或双引号之中
单引号字符串的内部,可以使用双引号。双引号字符串的内部,可以使用单引号
如果要在单引号字符串的内部,使用单引号,就必须在内部的单引号前面加上反斜杠,用来转义。双引号字符串内部使用双引号,也是如此
<!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>
<script>
var str1 = "itbaizhan,'百战程序员'"
var str2 = '尚学堂,"sxt"'
// 不能双引号中嵌套双引号,或者单引号中嵌套单引号
// var str3 = "itbaizhan"百战程序员""
// 输出一个带有双引号的文本
var str4 = "我们都知道书籍是要\"阅读\"的";
document.write(str4)
var str5 = '我们都知道书籍是要\'阅读\'的';
document.write(str5)
// 字符串默认只能一行显示,如果要换行,需要转义
var str6 = "不能双引号中嵌套双引号,\
或者单引号 \
中嵌套单引号"
document.write(str6)
var str7 = "大家好,我是iwen";
console.log(str7.length);
</script>
</body>
</html>
length 属性
length属性返回字符串的长度,该属性也是无法改变的
var s = 'JavaScript';
s.length // 10
字符串方法_charAt()
charAt
方法返回指定位置的字符,参数是从 0 开始编号的
如果参数为负数,或大于等于字符串的长度, charAt 返回空字符串
<!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>
<script>
// 打印出最后一个字符
var str = "JavaScript";
console.log(str.length); // 10
console.log(str.charAt(5));// c
console.log(str.charAt(8)); // p
console.log(str.charAt(str.length - 1)); // t
console.log(str.charAt(-2)); // ""
console.log(str.charAt(12)); // ""
</script>
</body>
</html>
字符串方法_concat()
concat
方法用于连接两个字符串,返回一个新字符串,不改变原字符串
<!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>
<script>
var str1 = "Hello";
var str2 = "World";
var str3 = "!";
var num = 100; // 100 => "100"
var result = str1.concat(str2,str3,num)
console.log(typeof result); // HelloWorld!100
console.log(str1);
console.log(str2);
console.log(str3);
console.log(typeof num);
// 咱们做字符串相加,是不需要使用concat的,可以使用+连接字符串
var result = str1 + str2 + str3 + num;
console.log(result);
var one = 1;
var two = 2;
var three = '3';
// concat和加号还是有区别的:
// concat不管什么类型直接合并成字符串
// 加号是遇到数字类型直接做运算,遇到字符串和字符串相连接
console.log(''.concat(one, two, three))
console.log(one + two + three)
</script>
</body>
</html>
字符串方法_substring()
<!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>
<script>
var name = "JavaScript";
// 参数1:开始位置
// 参数2:结束位置(不包含该位置)
var result = name.substring(2,9)
console.log(result);
console.log(name);
// 第二个参数不写,默认到原字符串的结尾
var result2 = name.substring(2);
console.log(result2);
// 注意:在真正写的时候,一定要第二个参数大于第一个参数
var result3 = name.substring(9,2);
console.log(result3); // 自动变成2和9
var result4 = name.substring(-2,3);// 0-3
console.log(result4); // itb
var result5 = name.substring(3,-1); // 0-3
console.log(result5); // itb
</script>
</body>
</html>
字符串方法_substr()
substr
方法用于从原字符串取出子字符串并返回,不改变原字符串,跟 substring
方法的作用相同
substr
方法的第一个参数是子字符串的开始位置(从0开始计算),第二个参数是子字符串的长度
"JavaScript".substr(2,8);//vaScript
如果省略第二个参数,则表示子字符串一直到原字符串的结束
"JavaScript".substr(2);//vaScript
如果第一个参数是负数,表示倒数计算的字符位置。如果第二个参数是负数,将被自动转为0,因此会返回空字符串
"my name is iwen".substr(-11);//ame is iwen
"my name is iwen".substr(2,-1);// ""
字符串方法_indexOf()
indexOf
方法用于确定一个字符串在另一个字符串中第一次出现的位置,返回结果是匹配开始的位置。如果返回 -1 ,就表示不匹配
var hello = "helloworld!";
console.log(hello.indexOf("j"));//-1
var gqlbb = "天上明月光,疑是地上霜,能做我女朋友么,举头望明月,低头思故乡";
console.log(gqlbb.indexOf("能做我女朋友么"));//12
indexOf
方法还可以接受第二个参数,表示从该位置开始向后匹配
var str = "thrusday";
console.log(str.indexOf("u",5)); // -1
字符串方法_trim()
trim
方法用于去除字符串两端的空格,返回一个新字符串,不改变原字符串
' hello world '.trim() // "hello world"
该方法去除的不仅是空格,还包括制表符( \t 、 \v )、换行符( \n )和回车符( \r )
"\r\JavaScript \t".trim() //"JavaScript"
ES6扩展方法, trimEnd() 和 trimStart() 方法
var name4 = " JavaScript ";
console.log(name4.trimStart());//JavaScript
console.log(name4.trimEnd());// JavaScript
字符串方法_split()
split
方法按照给定规则分割字符串,返回一个由分割出来的子字符串组成的数组
var name = "it|sxt|itbaizhan";
console.log(name.split("|")); // ['it', 'sxt', 'itbaizhan'] === 数组
如果分割规则为空字符串,则返回数组的成员是原字符串的每一个字符。
'a|b|c'.split('') // ["a", "|", "b","|","c"]
如果省略参数,则返回数组的唯一成员就是原字符串
'it|sxt|bz'.split() // [it|sxt|bz]
split
方法还可以接受第二个参数,限定返回数组的最大成员数。
'it|sxt|bz'.split('|', 0) // []
'it|sxt|bz'.split('|', 1) // ["it"]
'it|sxt|bz'.split('|', 2) // ["it", "sxt"]
'it|sxt|bz'.split('|', 3) // ["it", "sxt","bz"]
'it|sxt|bz'.split('|', 4) // ["it", "sxt","bz"]
数组
数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。
var arr = ['sxt', 'baizhan', 'it'];
任何类型的数据,都可以放入数组
var arr = [ 100, [1, 2, 3], false ];
如果数组的元素还是数组,就形成了多维数组
var a = [[1, 2], [3, 4]];
a[0][1] // 2
a[1][1] // 4
length 属性
数组的length属性,返回数组的成员数量
["iwen","ime","frank"].length // 3
数组的遍历
数组的遍历可以考虑使用for循环或while循环
<!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>
<script>
var username = ["iwen","ime","sakura","frank"];
// 循环 -> 遍历
// length:数组长度
for(var i = 0;i<username.length;i++){ // i = 0 1 2 3
console.log(username[i]);
}
var i = 0;
while(i<username.length){
console.log(username[i]);
i++
}
//for...in遍历数组
for(var i in username){
console.log(username[i]);
}
</script>
</body>
</html>
数组静态方法_Array.isArray()
Array.isArray
方法返回一个布尔值,表示参数是否为数组。它可以弥补typeof
运算符的不足
var num = 10;
console.log(typeof num); // number
var arr = [10,20,30];
console.log(typeof arr); // object 数组的关键字:Array
console.log(Array.isArray(arr)); // true:是数组
console.log(Array.isArray(num)); // false:不是数组
数组方法_push()/pop()
push
方法用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组
var arr = ["hello"];
// arr[1] = "大家好"
var myLength = arr.push("大家好","world",100,true)
console.log(arr);//["hello","大家好","world",100,true]
console.log(myLength);//5
pop
方法用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组
var arr1 = [100,200,300,400]
arr1.pop();
console.log(arr1);//[100,200,300]
数组方法_shift()/unshift()
shift
方法用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组
var names = ["iwen","ime","frank"]
names.shift();
console.log(names);//["ime","frank"]
shift
方法可以遍历并清空一个数组
var arr1 = [100,200,300];
var item;
while(item = arr1.shift()){
console.log(item);
}
console.log(arr1);//[]
运行结果:
unshift
方法用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组
unshift
方法可以接受多个参数,这些参数都会添加到目标数组头部
var arr2 = [200]
arr2.unshift(100,120,140);
console.log(arr2);//[100,200,140,200]
运行结果:
数组方法_join()
join
方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔
var a = [1, 2, 3, 4];
a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"
如果数组成员是 undefined
或 null
或空位,会被转成空字符串
[undefined, null].join('#')// '#'
['a',, 'b'].join('-') // 'a--b'
数组的 join
配合字符串的 split
可以实现数组与字符串的互换
var arr = ["a","b","c"];
var myArr = arr.join("");
console.log(myArr);//abc
console.log(myArr.split(""));//["a","b","c"]
数组方法_concat()
concat
方法用于多个数组的合并。它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变
['hello'].concat(['world']) // ["hello", "world"]
['hello'].concat(['world'], ['!']) // ["hello", "world", "!"]
除了数组作为参数, concat 也接受其他类型的值作为参数,添加到目标数组尾部。
数组方法_reverse()
reverse
方法用于颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组
var arr = [1,2,3,4,5]
console.log(arr);
arr.reverse();
console.log(arr); // [5,4,3,2,1]
实现一个字符串反转排列
// helloworld字符串:反转显示:dlrowolleh
// split join reverse
var str = "helloworld";
var myArr = str.split("");//["h","e","l","l","o","w","o","r","l","d"]
myArr.reverse();//["d","l","r","o","w","o","l","l","e","h"]
console.log(myArr.join("")); // dlrowolleh
数组方法_indexOf()
indexOf
方法返回给定元素在数组中第一次出现的位置,如果没有出现则返回 -1
var arr = ['a', 'b', 'c'];
arr.indexOf('b') // 1
arr.indexOf('y') // -1
indexOf
方法还可以接受第二个参数,表示搜索的开始位置
var arr = [10,20,30,40,50,30]
console.log(arr.indexOf(20,3)); // -1
console.log(arr.indexOf(40,2)); // 3
函数
函数的声明
function 命令: function命令声明的代码区块,就是一个函数。
function命令后面是函数名,函数名后面是一对圆括号,里面是传
入函数的参数。函数体放在大括号里面。
function print(s) {
console.log(s);
}
函数名的提升
JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部
add();
function add() {}
函数参数
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数
function square(x) {
console.log(x * x);
}
square(2) // 4
square(3) // 9
函数返回值
JavaScript函数提供两个接口实现与外界的交互,其中参数作为入口,接收外界信息;返回值作为出口,把运算结果反馈给外界
function getName(name){
return name;
}
var myName = getName("Javascript")
console.log(myName); // Javascript
注意:
return 后面不能在添加任何代码,因为不会执行
对象概述
什么是对象?对象(object)是 JavaScript 语言的核心概念,也是最重要的数据类型
简单说,对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合
对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。
如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用
var user = {
name: 'gb',
age: '13'
getName: function (name) {
return name;
}
};
user.getName("gb") // gb
如果属性的值还是一个对象,就形成了链式引用
var user = {
name:"gb",
age:13,
container:{
frontEnd:["Web前端","Android","iOS"],
backEnd:["Java","Python"]
}
}
user.container.frontEnd // ["Web前端","Android","iOS"]
Math对象
Math是 JavaScript 的原生对象,提供各种数学功能。
Math.abs()
Math.abs
方法返回参数值的绝对值
Math.abs(1) // 1
Math.abs(-1) // 1
Math.max(),Math.min()
Math.max
方法返回参数之中最大的那个值, Math.min
返回最小的那个值。如果参数为空, Math.min
返回 Infinity
, Math.max
返回 -Infinity
。
Math.max(2, -1, 5) // 5
Math.min(2, -1, 5) // -1
Math.min() // Infinity
Math.max() // -Infinity
Math.floor(),Math.ceil()
Math.floor
方法返回小于参数值的最大整数(向下取整)
Math.floor(3.2) // 3
Math.floor(-3.2) // -4
Math.ceil
方法返回大于参数值的最小整数(向上取整)
Math.ceil(3.2) // 4
Math.ceil(-3.2) // -3
Math.random()
Math.random()
返回0到1之间的一个伪随机数,可能等于0,但是一定小于1
Math.random() // 0.28525367438365223
任意范围的随机数生成函数如下
// 10 20 = 10
function getRandomArbitrary(min,max){
// 最大值 - 最小值 * 随机数 = 0 ~ 最小值之间的随机数 + 最小值 = 10 ~ 20
var result = Math.random() * (max-min) + min
result = Math.floor(result)
console.log(result);
}
getRandomArbitrary(10,20)
Date对象
Date
对象是 JavaScript 原生的时间库。它以1970年1月1日00:00:00作为时间的零点,可以表示的时间范围是前后各1亿天(单位为毫秒)
Date.now()
Date.now 方法返回当前时间距离时间零点(1970年1月1日 00:00:00UTC)的毫秒数,相当于 Unix 时间戳乘以1000
Date.now(); // 1635216733395
时间戳
时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间 1970年01月01日08时00分00秒)起至现在的总秒数。
格林威治和北京时间就是时区的不同
Unix是20世纪70年代初出现的一个操作系统,Unix认为1970年1月1日0点是时间纪元。JavaScript也就遵循了这一约束
Date
对象提供了一系列 get*
方法,用来获取实例对象某个方面的值
实例方法get类
getTime():返回实例距离1970年1月1日00:00:00的毫秒数
getDate():返回实例对象对应每个月的几号(从1开始)
getDay():返回星期几,星期日为0,星期一为1,以此类推
getYear():返回距离1900的年数
getFullYear():返回四位的年份
getMonth():返回月份(0表示1月,11表示12月)
getHours():返回小时(0-23)
getMilliseconds():返回毫秒(0-999)
getMinutes():返回分钟(0-59)
getSeconds():返回秒(0-59)
<!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>
<script>
console.log(Date.now());
// 1693913723921
console.log(new Date(1693913723921));
console.log(new Date());
// 2023-9-5 19:40:03
// Tue Sep 05 2023 19:35:48 GMT+0800 (中国标准时间)
console.log(new Date(1693913723921).getFullYear()); // 2023
// 月份从0~11 0:1月 11:12月
console.log(new Date(1693913723921).getMonth() + 1);//9月
console.log(new Date(1693913723921).getDate());//5日
console.log(new Date(1693913723921).getDay());//星期二
console.log("------------------------------------------");
// 2022-5-23 19:28:30
var year = new Date(1653290041486).getFullYear()
var month = new Date(1653290041486).getMonth() + 1
var day = new Date(1653290041486).getDate()
// 2022-5-23
console.log(year + "-" + month + "-" + day);
// 阳历 距离年底还有多少天
function leftDays() {
var today = new Date();
var endYear = new Date(today.getFullYear(), 11, 31, 23, 59, 59, 999);
var msPerDay = 24 * 60 * 60 * 1000;
var result = (endYear.getTime() - today.getTime()) / msPerDay
console.log(Math.floor(result));
}
leftDays()
</script>
</body>
</html>
运行结果:
DOM概述
DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个JavaScript 对象,从而可以用脚本进行各种操作(比如对元素增删内容)
浏览器会根据 DOM 模型,将结构化文档HTML解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口
DOM 只是一个接口规范,可以用各种语言实现。所以严格地说,DOM 不是 JavaScript 语法的一部分,但是 DOM 操作是 JavaScript最常见的任务,离开了 DOM,JavaScript 就无法控制网页。另一方面,JavaScript 也是最常用于 DOM 操作的语言
节点
DOM 的最小组成单位叫做节点(node)。文档的树形结构(DOM树),就是由各种不同类型的节点组成。每个节点可以看作是文档树的一片叶子
节点的类型有七种
Document:整个文档树的顶层节点
DocumentType:doctype标签
Element:网页的各种HTML标签
Attribute:网页元素的属性(比如class=“right”)
Text:标签之间或标签包含的文本
Comment:注释
DocumentFragment:文档的片段
节点树
一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是 DOM 树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,倒过来就像一棵树
浏览器原生提供document节点,代表整个文档
document// 整个文档树
除了根节点,其他节点都有三种层级关系
父节点关系(parentNode):直接的那个上级节点
子节点关系(childNodes):直接的下级节点
同级节点关系(sibling):拥有同一个父节点的节点
Node.nodeType属性
不同节点的nodeType属性值和对应的常量如下
文档节点(document):9,对应常量 Node.DOCUMENT_NODE
元素节点(element):1,对应常量Node.ELEMENT_NODE
属性节点(attr):2,对应常量Node.ATTRIBUTE_NODE
文本节点(text):3,对应常量Node.TEXT_NODE
文档片断节点(DocumentFragment):11,对应常量Node.DOCUMENT_FRAGMENT_NODE
console.log(document);
if(document.nodeType === 9){
console.log("顶层节点");
}
运行结果:
document对象_方法/获取元素
document.getElementsByTagName()
document.getElementsByTagName
方法搜索 HTML 标签名,返回符合条件的元素。它的返回值是一个类似数组对象( HTMLCollection 实例),可以实时反映 HTML 文档的变化。如果没有任何匹配的元素,就返回一个空集
var paras = document.getElementsByTagName('p');
如果传入 *
,就可以返回文档中所有 HTML 元素
var allElements = document.getElementsByTagName('*');
document.getElementsByClassName()
document.getElementsByClassName
方法返回一个类似数组的对象( HTMLCollection 实例),包括了所有 class 名字符合指定条件的元素,元素的变化实时反映在返回结果中
<p class="text">Hello</p>
var text = document.getElementsByClassName("text")[0];
参数可以是多个 class
,它们之间使用空格分隔
var name = document.getElementsByName("login bar")
document.getElementsByName()
document.getElementsByName
方法用于选择拥有 name 属性的 HTML 元素(比如 <form>
、 <radio>
、 <img>
等),返回一个类似数组的的对象( NodeList
实例),因为 name
属性相同的元素可能不止一个
<form name="login"></form>
// 使用率极低
var name = document.getElementsByName("login")
document.getElementById()
document.getElementById
方法返回匹配指定id
属性的元素节点。如果没有发现匹配的节点,则返回 null
<div id="root">哈哈哈</div>
var root = document.getElementById("root");
document.querySelector()
document.querySelector
方法接受一个 CSS 选择器作为参数,返回匹配该选择器的元素节点。如果有多个节点满足匹配条件,则返回第一个匹配的节点。如果没有发现匹配的节点,则返回 null
document.querySelectorAll()
document.querySelectorAll
方法与 querySelector
用法类似,区别是返回一个NodeList
对象,包含所有匹配给定选择器的节点
<div class="nav">nav1</div>
<div class="nav">nav2</div>
var nav = document.querySelector(".nav");
nav.innerHTML = "我是第一个匹配的节点";
console.log(nav);
var navs = document.querySelectorAll(".nav")[1];
console.log(navs);
运行结果:
document对象_方法/创建元素
document.createElement()
document.createElement
方法用来生成元素节点,并返回该节点
document.createTextNode()
document.createTextNode
方法用来生成文本节点( Text 实例),并返回该节点。它的参数是文本节点的内容
document.createAttribute()
document.createAttribute
方法生成一个新的属性节点( Attr 实例),并返回它
<!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 class="nav">导航</div>
<div id="container"></div>
<script>
var text = document.createElement("p");
var content = document.createTextNode("我是文本")
var id = document.createAttribute("id")
id.value = "root"
// appendChild:将内容或者子元素放到容器中
text.appendChild(content);
text.setAttributeNode(id);
console.log(text);
var container = document.getElementById("container");
container.appendChild(text)
</script>
</body>
</html>
运行结果:
Element对象_属性
Element对象对应网页的 HTML 元素。每一个 HTML 元素,在DOM 树上都会转化成一个Element节点对象(以下简称元素节点)
Element.id
Element.id
属性返回指定元素的 id
属性,该属性可读写
// HTML 代码为 <p id="foo">
var p = document.querySelector('p');
p.id // "foo"
Element.className
className
属性用来读写当前元素节点的 class 属性。它的值是一个字符串,每个 class
之间用空格分割
// HTML 代码 <div class="one two three"
id="myDiv"></div>
var div = document.getElementById('myDiv');
div.className
Element.classList
classList
对象有下列方法
- add() :增加一个 class。
- remove() :移除一个 class。
- contains() :检查当前元素是否包含某个 class。
- toggle() :将某个 class 移入或移出当前元素。
var div = document.getElementById('myDiv');
div.classList.add('myCssClass');
div.classList.add('foo', 'bar');
div.classList.remove('myCssClass');
div.classList.toggle('myCssClass'); // 如果myCssClass 不存在就加入,否则移除
div.classList.contains('myCssClass'); // 返回true 或者 false
Element.innerHTML
Element.innerHTML
属性返回一个字符串,等同于该元素包含的所有HTML 代码。该属性可读写,常用来设置某个节点的内容。它能改写所有元素节点的内容,包括 <HTML>
和 <body>
元素
el.innerHTML = '';
Element.innerText
innerText
和 innerHTML
类似,不同的是 innerText
无法识别元素,会直接渲染成字符串
<!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>
.box{
width: 200px;
height: 200px;
}
.box1{
background-color: red;
}
</style>
</head>
<body>
<div class="box" id="root">Hello</div>
<script>
var root = document.getElementById("root");
root.id = "roots"
root.className = "box box1";// box
console.log(root.classList.add("mybox"));
root.classList.remove("box");
if(root.classList.contains("box1")){
console.log("有");
}else{
console.log("没有");
}
// console.log(root.innerHTML = "大家好呀"); // 读取
console.log(root.innerText = "哇哈哈哈");
// innerHTML和innerText的区别:
// innerHTML可以识别标签
// innerText会把标签识别成一个字符串
var str = "<a href='https://www.itbaizhan.com'>百战</a>";
root.innerText = str
</script>
</body>
</html>
运行结果:
Element获取元素位置
属性 | 描述 |
---|---|
clientHeight | 获取元素高度包括 padding 部分,但是不包括 border 、 margin |
clientWidth | 获取元素宽度包括 padding 部分,但是不包括 border 、 margin |
scrollHeight | 元素总高度,它包括 padding ,但是不包括 border 、 margin 包括溢出的不可见内容 |
scrollWidth | 元素总宽度,它包括 padding ,但是不包括 border 、 margin 包括溢出的不可见内容 |
scrollLeft | 元素的水平滚动条向右滚动的像素数量 |
scrollTop | 元素的垂直滚动条向下滚动的像素数量 |
offsetHeight | 元素的 CSS 垂直高度(单位像素),包括元素本身的高度、 padding 和 border |
offsetWidth | 元素的 CSS 水平宽度(单位像素),包括元素本身的高度、padding 和 border |
offsetLeft | 到定位父级左边界的间距 |
offsetTop | 到定位父级上边界的间距 |
Element.clientHeight,Element.clientWidth
Element.clientHeight
属性返回一个整数值,表示元素节点的 CSS 高度(单位像素),只对块级元素生效,对于行内元素返回 0 。如果块级元素没有设置 CSS 高度,则返回实际高度
除了元素本身的高度,它还包括 padding
部分,但是不包括 border
、margin
。如果有水平滚动条,还要减去水平滚动条的高度。注意,这个值始终是整数,如果是小数会被四舍五入。
Element.clientWidth
属性返回元素节点的 CSS 宽度,同样只对块级元素有效,也是只包括元素本身的宽度和 padding
,如果有垂直滚动条,还要减去垂直滚动条的宽度。
document.documentElement
的 clientHeight
属性,返回当前视口的高度(即浏览器窗口的高度)。 document.body
的高度则是网页的实际高度。
// 视口高度
document.documentElement.clientHeight
// 网页总高度
document.body.clientHeight
Element.scrollHeight,Element.scrollWidth
Element.scrollHeight
属性返回一个整数值(小数会四舍五入),表示当前元素的总高度(单位像素),它包括 padding
,但是不包括 border
、margin
以及水平滚动条的高度(如果有水平滚动条的话)
Element.scrollWidth
属性表示当前元素的总宽度(单位像素),其他地方都与 scrollHeight 属性类似。这两个属性只读整张网页的总高度可以从 document.documentElement
或 document.body
上读取
// 返回网页的总高度
document.documentElement.scrollHeight
document.body.scrollHeight
Element.scrollLeft,Element.scrollTop
Element.scrollLeft
属性表示当前元素的水平滚动条向右侧滚动的像素数量,Element.scrollTop
属性表示当前元素的垂直滚动条向下滚动的像素数量。对于那些没有滚动条的网页元素,这两个属性总是等于0
如果要查看整张网页的水平的和垂直的滚动距离,要从document.documentElement
元素上读取
document.documentElement.scrollLeft
document.documentElement.scrollTop
Element.offsetHeight,Element.offsetWidth
Element.offsetHeight
属性返回一个整数,表示元素的 CSS 垂直高度(单位像素),包括元素本身的高度、padding 和 border,以及水平滚动条的高度(如果存在滚动条)。
Element.offsetWidth
属性表示元素的 CSS 水平宽度(单位像素),其他都与 Element.offsetHeight
一致。
这两个属性都是只读属性,只比 Element.clientHeight
和 Element.clientWidth
多了边框的高度或宽度。如果元素的 CSS 设为不可见(比如 display:none;
),则返回 0
Element.offsetLeft,Element.offsetTop
Element.offsetLeft
返回当前元素左上角相对于 Element.offsetParent
节点的水平位移, Element.offsetTop
返回垂直位移,单位为像素。通常,这两个值是指相对于父节点的位移
<div class="parent">
<div class="box" id="box"></div>
</div>
.parent{
width: 200px;
height: 200px;
background: red;
position: relative;
left: 50px;
top: 50px;
}
.box{
width: 100px;
height: 100px;
background: yellow;
position: relative;
left: 50px;
top: 50px;
}
var box = document.getElementById("box");
console.log(box.offsetLeft);
console.log(box.offsetTop);
<!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>
.box{
width: 200px;
height: 200px;
border: 5px solid red;
padding: 10px;
margin: 20px;
background: green;
}
h3{
height: 500px;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<h3>标题1</h3>
<h3>标题2</h3>
<h3>标题3</h3>
<h3>标题4</h3>
<script>
var box = document.getElementById("box")
console.log(box.clientWidth);
console.log(box.clientHeight);
// 获取视口高度(屏幕高度)
console.log(document.documentElement.clientHeight);
// 获取页面的高度
console.log(document.body.clientHeight);
console.log("--------------");
console.log(box.scrollWidth);
console.log(box.scrollHeight);
// 获取视口高度(屏幕高度)
console.log(document.documentElement.scrollHeight);
// 获取页面的高度
console.log(document.body.scrollHeight);
console.log("--------------");
// 获取滚动高度
console.log(document.documentElement.scrollTop);
console.log("--------------");
console.log(box.offsetWidth);
console.log(box.offsetHeight);
</script>
</body>
</html>
CSS操作
HTML
元素的 style
属性
操作 CSS
样式最简单的方法,就是使用网页元素节点的 setAttribute
方法直接操作网页元素的 style
属性
div.setAttribute(
'style',
'background-color:red;' + 'border:1px solid black;'
);
元素节点的 style
属性
var divStyle =
document.querySelector('div').style;
divStyle.backgroundColor = 'red';
divStyle.border = '1px solid black';
divStyle.width = '100px';
divStyle.height = '100px';
divStyle.fontSize = '10em';
cssText
属性
var divStyle =
document.querySelector('div').style;
divStyle.cssText = 'background-color: red;'
+ 'border: 1px solid black;'
+ 'height: 100px;'
+ 'width: 100px;';
事件处理程序
事件处理程序分为:
- HTML事件处理
- DOM0级事件处理
- DOM2级事件处理
HTML事件
<button onClick="clickHandle()">按钮</button>
<script>
// HTML事件:缺点:HTML和JS没有分开
function clickHandle(){
console.log("点击了按钮");
}
</script>
DOM0级事件处理
<button id="btn">按钮</button>
<script>
// DOM0事件:优点:HTML和JS是分离的 缺点:无法同时添加多个事件
var btn = document.getElementById("btn")
// 被覆盖了
btn.onclick = function(){
console.log("点击了1");
}
btn.onclick = function(){
console.log("点击了2");
}
</script>
执行结果:
DOM2级事件处理
<button id="btn">按钮</button>
<script>
// DOM2事件:优点:事件不会被覆盖 缺点:写起来麻烦
var btn = document.getElementById("btn");
btn.addEventListener("click",function(){
console.log("点击了1");
})
btn.addEventListener("click",function(){
console.log("点击了2");
})
</script>
运行结果:
事件类型之鼠标事件
鼠标事件
鼠标事件指与鼠标相关的事件,具体的事件主要有以下一些
- click:按下鼠标时触发
- dblclick:在同一个元素上双击鼠标时触发
- mousedown:按下鼠标键时触发
- mouseup:释放按下的鼠标键时触发
- mousemove:当鼠标在节点内部移动时触发。当鼠标持续移动时,该事件会连触发。
- mouseenter:鼠标进入一个节点时触发,进入子节点不会触发这个事件
- mouseleave:鼠标离开一个节点时触发,离开父节点不会触发这个事件
- mouseover:鼠标进入一个节点时触发,进入子节点会再一次触发这个事件
- mouseout:鼠标离开一个节点时触发,离开父节点也会触发这个事件
- wheel:滚动鼠标的滚轮时触发
注意:
这些方法在使用的时候,除了DOM2级事件,都需要添加前缀on
Event事件对象
事件发生以后,会产生一个事件对象,作为参数传给监听函数。
Event对象属性
- Event.Target
- Event.type
Event.target
Event.target属性返回事件当前所在的节点
// HTML代码为
// <p id="para">Hello</p>
function setColor(e) {
console.log(this === e.target);
e.target.style.color = 'red';
}
para.addEventListener('click', setColor);
Event.type
Event.type属性返回一个字符串,表示事件类型。事件的类型是在生成事件的时候。该属性只读
Event对象方法
- Event.preventDefault()
- Event.stopPropagation()
Event.preventDefault
Event.preventDefault方法取消浏览器对当前事件的默认行为。比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了
btn.onclick = function(e){
e.preventDefault(); // 阻止默认事件
console.log("点击A标签");
}
Event.stopPropagation()
stopPropagation方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数
btn.onclick = function(e){
e.stopPropagation(); // 阻止事件冒泡
console.log("btn");
}
事件类型之键盘事件
键盘事件由用户击打键盘触发,主要有keydown、keypress、keyup三个事件
- keydown:按下键盘时触发。
- keypress:按下有值的键时触发,即按下 Ctrl、Alt、Shift、Meta 这样无值的键,这个事件不会触发。对于有值的键,按下时先触发keydown事件,再触发这个事件。
- keyup:松开键盘时触发该事件
username.onkeypress = function(e){
console.log("keypress事件");
}
event对象
keyCode:唯一标识
var username = document.getElementById("username");
username.onkeydown = function(e){
if(e.keyCode === 13){
console.log("回车");
}
}
事件类型之表单事件
表单事件是在使用表单元素及输入框元素可以监听的一系列事件
- input事件
- select事件
- Change事件
- reset事件
- submit事件
input事件
input事件当 <input>
、<select>
、<textarea>
的值发生变化时触发。对于复选
框( <input type=checkbox>
)或单选框( <input type=radio>
),用户改变选项时,也会触发这个事件
input事件的一个特点,就是会连续触发,比如用户每按下一次按键,就会触发一次input事件。
<input type="text" id="username">
<input type="text" id="password">
var username = document.getElementById("username");
username.oninput = function(e){
console.log(e.target.value);//读取数据
}
select事件
select事件当在 <input>
、<textarea>
里面选中文本时触发
// HTML 代码如下
// <input id="test" type="text" value="Selectme!" />
var elem = document.getElementById('test');
elem.addEventListener('select', function (e){
console.log(e.type); // "select"
}, false);
Change 事件
Change事件当 <input>
、<select>
、<textarea>
的值发生变化时触发。它与input事件的最大不同,就是不会连续触发,只有当全部修改完成时才会触发
<input type="text" id="username">
<input type="text" id="password">
// 失去焦点和回车的时候触发
var password = document.getElementById("password")
password.onchange = function(e){
console.log(e.target.value);
}
reset 事件,submit 事件
这两个事件发生在表单对象 <form>
上,而不是发生在表单的成员上。
reset事件当表单重置(所有表单成员变回默认值)时触发。
submit事件当表单数据向服务器提交时触发。注意,submit事件的发生对象是 <form>
元素,而不是 <button>
元素,因为提交的是表单,而不是按钮
<form action="服务器地址" id="myForm" onsubmit="submitHandle">
<input type="text" name="username">
<button id="resetBtn">重置</button>
<button>提交表单</button>
</form>
var resetBtn = document.getElementById("resetBtn")
var myForm = document.getElementById("myForm")
resetBtn.onclick = function(){
myForm.reset(); // 触发在表单上:清空表单
}
function submitHandle(){
console.log("想做的事情");
}
事件代理(事件委托)
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理(delegation)
<ul id="list">
<li>列表1</li>
<li>列表2</li>
<li>列表3</li>
<li>列表4</li>
<li>列表5</li>
</ul>
var list = document.getElementById("list")
list.addEventListener("click",function(e){
if(e.target.tagName.toLowerCase() === "li"){
console.log(e.target.innerHTML);
}
})
运行结果:
定时器之 setTimeout()
JavaScript 提供定时执行代码的功能,叫做定时器(timer),主要由 setTimeout()
和 setInterval()
这两个函数来完成。它们向任务队列添加定时任务
setTimeout
函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。
var timerId = setTimeout(func|code, delay);
setTimeout
函数接受两个参数,第一个参数 func|code
是将要推迟执行的函数名或者一段代码,第二个参数 delay
是推迟执行的毫秒数
setTimeout(function(){
console.log("定时器")
},1000)
注意:
还有一个需要注意的地方,如果回调函数是对象的方法,那么setTimeout
使得方法内部的this
关键字指向全局环境,而不是定义时所在的那个对象
var name = "sxt";
var user = {
name: "itbaizhan",
getName: function () {
setTimeout(function(){
console.log(this.name);//sxt
},1000)
}
};
user.getName();
解决方案
var name = "sxt";
var user = {
name: "itbaizhan",
getName: function () {
var that = this;
setTimeout(function(){
console.log(that.name);//itbaizhan
},1000)
}
};
user.getName();
定时器可以进行取消
var id = setTimeout(f, 1000);
clearTimeout(id);
定时器之 setInterval()
setInterval
函数的用法与 setTimeout 完全一致,区别仅仅在于 setInterval
指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行
var i = 0;
setInterval(function(){
i++
console.log(i);//每隔一秒加一
},1000)
运行结果:
通过setInterval方法实现网页动画
<!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>
#someDiv{
width: 100px;
height: 100px;
background: red;
opacity: 1;
}
</style>
</head>
<body>
<div id="someDiv"></div>
<script>
var div = document.getElementById("someDiv");
// 透明度:opacity:取值范围0-1
var opacity = 1;
var fade = setInterval(function(){
if(opacity>0){
opacity -= 0.05
div.style.opacity = opacity
}else{
clearInterval(fade)
}
},60)
</script>
</body>
</html>
定时器可以进行取消
var id = setInterval(f, 1000);
clearInterval(id);
防抖(debounce)
防抖严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。
从滚动条监听的例子说起
function showTop () {
var scrollTop =
document.documentElement.scrollTop;
console.log('滚动条位置:' + scrollTop);
}
window.onscroll = showTop
在运行的时候会发现存在一个问题:这个函数的默认执行频率,太!高!了!。 高到什么程度呢?以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次!
然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后
- 如果在200ms内没有再次触发滚动事件,那么就执行函数
- 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时
效果:如果短时间内大量触发同一事件,只会执行一次函数
实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现
<!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>
h3{
height: 400px;
}
</style>
</head>
<body>
<h3>哈哈</h3>
<h3>哈哈</h3>
<h3>哈哈</h3>
<h3>哈哈</h3>
<h3>哈哈</h3>
<h3>哈哈</h3>
<script>
//调用这个函数表示,每次都是过delay时间内只执行 fn 函数一次
function debounce(fn,delay){
var timer = null;//设置timer为空
return function(){
if(timer){//如果timer 不为空 执行代码 清除定时器
clearTimeout(timer)
}
timer = setTimeout(fn,delay)//如果timer为空 重新设置定时器 将值赋值给timer
}
}
// 滚动事件
window.onscroll = debounce(scrollHandle,200)
//获取滚动高度的函数,并打印到控制台上
function scrollHandle(){
var scrollTop = document.documentElement.scrollTop;
console.log(scrollTop);
}
</script>
</body>
</html>
防抖定义
对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次
节流(throttle)
节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死
继续思考,使用上面的防抖方案来处理问题的结果是
如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离
但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?
其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)
效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效
实现
<!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>
h3 {
height: 400px;
}
</style>
</head>
<body>
<h3>标题1</h3>
<h3>标题1</h3>
<h3>标题1</h3>
<h3>标题1</h3>
<h3>标题1</h3>
<h3>标题1</h3>
<h3>标题1</h3>
<script>
// 定义一个节流函数
function throttle(fn, delay) {
// 初始化一个变量 valid,用于控制节流函数的执行
var valid = true;
// 返回一个匿名函数作为节流函数
return function () {
// 如果 valid 为 false,表示上一个节流时间段内的函数执行还未完成,直接返回 false,不执行回调函数
if (!valid) {
return false;
}
// 将 valid 设置为 false,表示当前节流时间段内的函数执行已经开始
valid = false;
// 在延迟时间 delay 后执行回调函数 fn
setTimeout(function () {
// 调用回调函数
fn();
// 将 valid 设置为 true,表示当前节流时间段内的函数执行已经完成
valid = true;
}, delay);
};
}
// 绑定滚动事件为节流后的回调函数scrollHandle
window.onscroll = throttle(scrollHandle, 2000)
// 滚动事件处理函数
function scrollHandle() {
// 获取页面的滚动距离
var scrollTop = document.documentElement.scrollTop;
console.log(scrollTop);
}
</script>
</body>
</html>
开发中常遇到的场景:
-
搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求
-
页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)