1. 什么是JavaScript
JavaScript是类似于java计算机编程语言的脚本语言,缩写为js。它的作用是提供了一个和html进行动态交互的功能。
1.1 Java 与 JavaScript 的区别
- Java是静态编译,JavaScript作为脚本动态编译
- java属于强类型语言,JavaScript是弱类型语言
1.2 JavaScript的使用方式
- 在浏览器控制台上直接运用js语言片段
- 在html文件的script标签中
- 使用.js文件书写js代码,然后在html中通过script标签引入代码
<!-- script 标签写在body后面 -->
<script>
// console.log 打印日志到控制台
console.log('hello world')
</script>
<!-- 使用 script 标签引入外部js文件,src 是文件路径 -->
<script src="./main.js"></script>
1.3 JavaScript代码执行顺序
- 若有多个script,那么从上而下依序执行
- 在同一个script标签中,js的执行顺序
2.1 以一句话为单位,基本顺序:从上到下,从左到右,每句话用分号隔开“;”
2.2 若代码中每一行只有一句js,那么句尾的分号可以省略 - 赋值运算的顺序:先执行赋值符右侧代码,再赋值给左侧
- 函数调用顺序: 从左到右执行,若存在函数,先执行函数。若在执行的函数中存在其他函数,例如下面的fn1的调用,那么会先执行内部的函数,如 fn3 fn2,最后执行 fn1
c = fn2() + 4 + fn1(fn3(), fn2(), 11)
2. JavaScript的基础知识
2.1 变量
变量是存储数据的容器,数据可以发生变化
变量声明:
var a; // 声明后不赋初始值 默认都为 undefined
var b = 5; // 声明变量并赋予初始值
var c, d = 10, e; // 同时声明多个变量并赋予初始值
// let 变量不能重复定义 var 变量可以重复定义
let x, y, z = 88
变量在存储新值之前将完全弃用原来的值
2.1.2 变量的命名规范
1,必须以字母开头,或者使用 $ 或者 _ 开头 ,不能用数字,变量名建议不要使用中文
2,小驼峰命名法: 多个单词构成的名字,第一个单词全小写,后面的首字母大写
3,为了提高代码的可读性,命名规范,见名知意
4,不能使用关键字(有特殊功能的名字:class、var、this)
let _a // 下划线开头的变量一般是局部变量出现在函数中作为形式参数
let $body // $ 开头一般是jquery对象
let bird // 用字母开头
let blueBird // 多个单词,首个单词的首字母小写,其余单词的首字母大写
let simpleDateFormat
// let const // 由于const是关键字,所以不能用作变量名
2.2 常量
恒定不变的值为常量,声明时必须赋初始值 且声明后无法修改
const g = 10
2.3 var、let 和 const 的区别?
1.var 声明的变量具备变量提升,let,const所声明的变量也有类似效果但是不属于变量提升,应该是‘暂时性死区’
2.var 可以重复声明同名变量,let和const不允许在同一作用域下重复声明同名变量
3.var 和 let 在声明变量时可以不用对变量进行初始化,const声明必须初始化
4.var 不具备块级作用域 ,let 和 const 具备块级作用域 { let }
2.4数据类型
值类型:
boolean布尔型、 number数字型、 string字符串、 bigInt长整数
引用类型:
object 对象类型、symbol 符号类型
undefined 未定义:
undefined 是一个单独的类型,用于给未定义的变量赋值
null 空引用:
null 值的是空引用,是 js 的一个原始数据类型,用来指代引用类型数据的空值
2.4.1值类型与引用类型数据的区别
值类型:变量中直接存贮值本身
引用类型:变量中存储的是引用地址,而值是存在引用地址所指向的内存中的某个对应位置
2.4.2 序列化和反序列化
序列化:将对象转换成字符串
反序列化:将字符串转换成对象
<script>
let obj = {
name: '张三的母亲',
sex: 'other',
age: 40
}
// 序列化
let str = JSON.stringify(obj)
console.log(str);
// 通过json格式声明一个字符串
str = '{"name": "英雄的母亲", "child": "张三", "score": 88, "isOk": false}'
// 反序列化
obj = JSON.parse(str)
console.log(obj);
</script>
3. 节点操作
3.1插入节点
步骤: 1. 创建节点。 2. 插入节点到文档中。
appendChild 追加一个子元素,参数是要追加的元素
// 创建节点
// 参数是标签名
// 返回一个dom对象
let img = document.createElement('img')
// 设置图片源
img.src = '../img/head.png'
// 插入节点
// document.body 就是body标签的dom对象
document.body.appendChild(img)
insertBefore 插入节点到另一个节点前
第一个参数:要插入的元素;第二个参数:插入位置的子节点
let ok = document.querySelector('.ok')
// 插入节点到另一个节点前
document.body.insertBefore(img, ok)
创建元素并插入元素的用途
- 异步加载 js css 等文件
- 动态追加一些以html作为模板的元素
3.2查询节点
dom对象中可以查询其父节点和子节点。
- 访问子节点使用
children
属性,children 属性包含一个子节点的集合,所以访问具体子节点可以使用索引值。 - 访问父节点使用
parentElement
属性。
3.1删除节点
- remove可以删除元素,例如:li.remove()
- removeChild 删除子节点,参数是想要删除的子节点dom对象。例如:ul.removeChild(li)
- 用途: 动态加载一次性js脚本资源,加载完后就可以删除元素了.。例如:下载按钮
let btn = document.querySelector('button')
btn.addEventListener('click', () => {
// 动态创建a标签并点击它 来实现下载
let a = document.createElement('a')
// download 可以指定下载后的文件名
a.download = '1.png'
// 下载文件的地址
a.href = '../img/head.png'
// 无需插入页面即可点击
a.click()
// 若a标签以插入页面则需要调用 remove 来删除
a.remove()
})
3.1替换节点
语法:
parent.replaceChild(newNode, child)
parent: 要替换节点的父节点
newNode: 新的要插入文档的节点
child: 被替换的节点
<body>
<table border>
<tr>
<td>1-1</td>
<td>1-2</td>
<td>1-3</td>
</tr>
<tr class="tr">
<td>2-1</td>
<td>2-2</td>
<td>2-3</td>
</tr>
<tr>
<td>4-1</td>
<td>4-2</td>
<td>4-3</td>
</tr>
</table>
</body>
<script>
let tr = document.querySelector('.tr')
let _td = document.querySelector('.tr>td:nth-of-type(2)')
// 构造一个新的元素用于替换
let td = document.createElement('td')
td.textContent = 'hello world'
tr.replaceChild(td, _td)
</script>
4. 数组
存储一组数据的一个容器称为数组。
// 声明数组
// 使用脚本形式声明数组
let arr = []
// 声明并初始化数组
// 数组中的每一个数据称为 数组成员
arr = [false, 2, 'hello world', { name: '张三' }, null, undefined]
console.log(arr);
// 对象形式的声明方法
arr = new Array()
console.log(arr);
// 声明并初始化
arr = new Array(false, 2, 'hello world', { name: '张三' }, null, undefined)
console.log(arr);
访问数组成员: 使用索引值访问数组中的成员。
索引:数组给每个成员的编号称为索引,索引值从零开始依次递增1。
访问数组成员时,若索引超出数组范围,则获取到的结果为 undefined。
console.log(arr['0']);
console.log(arr['1']);
// 数组索引可以不适用引号
console.log(arr[2]);
给数组成员赋值: 使用数组变量名加上索引值进行赋值,可以给指定索引位置的成员进行赋值。
arr[4] = { name: '李四' }
arr[5] = 333
数组长度: 指的就是数组成员的个数
4.1 数组的操作
// 数组的操作
let arr = [1, 2, true, { name: 'Amy' }, 2]
// push 追加数据到数组末尾
// 参数:被添加的新数组成员
arr.push('hello world')
console.log(arr);
// pop 从尾部取出一个成员
// 返回值是取出的成员
let r = arr.pop()
console.log(r);
console.log(arr);
// unshift 在头部添加数据
// 参数:被添加的新数组成员
arr.unshift('my best day')
console.log(arr);
// shift 从头部取出一个成员
// 返回值是取出的成员
r = arr.shift()
console.log(r);
console.log(arr);
// push 和 unshift 可以批量添加成员
arr.push('a', 'b', 'c')
arr.unshift('x', 'y', 'z')
console.log(arr);
// splice 删除指定位置的成员,并用新成员替换,或插入新成员到指定成员的前面
// 第一个参数:删除成员的起始位置
// 第二个参数:删除成员的个数
// 第三个参数:用于替换被删除成员的新数据,该参数可以省略
// 删除一个成员:
// r = arr.splice(6, 3)
// splice的返回值 就是被删除的成员数组
// console.log(r);
// 在指定成员前追加新数据
// 若第二个参数为0,则可以实现在指定位置的前面添加成员的功能
arr.splice(6, 0, { name: 'Bob' })
console.log(arr);
// concat 连接数组
// 参数:多个被追加进数组的成员,若成员是数组,该数组中每个成员将被加入原数组
// concat 返回一个新数组
// r = arr.concat(7, 8, 9)
// r = arr.concat([7, 8, 9], [10, 11])
// console.log(r);
// console.log(arr);
// concat 的应用场景多用于克隆数组
let arr2 = [].concat(arr)
console.log(arr2);
console.log(arr === arr2);
// join 使数组成员用一个字符连接起来
// join 函数接收一个参数,该参数就是连接数组成员时使用的字符
arr2 = ['abc', 'xyz', '123']
r = arr2.join('-*-')
console.log(r);
// includes 判断是否包含某成员
r = arr.includes('z')
console.log(r);
// indexOf 查询指定数组成员的索引
r = arr.indexOf('b')
console.log(r);
// 可以使用indexOf判断是否包含某个数组成员 若不包含 返回 -1
r = arr.indexOf('g')
console.log(r); // => -1
if (arr.indexOf('g') === -1) {
console.log('该数组成员不存在');
}
// lastIndexOf 查询最后一个指定数组成员的索引(应为数组成员可能重复)
console.log(arr.indexOf(2));
console.log(arr.lastIndexOf(2));
// slice 数组切片 获取子数组
// 切片遵循“前截后不截”原理: 起始位置包含在结果内,结束位置不包含
r = arr.slice(5, 8)
console.log(r);
// 参数只有一个,代表从该位置开始 一直截取到最后
r = arr.slice(5)
console.log(r);
4.2 数组的迭代操作
forEach 循环遍历每一个数组成员
every 和 forEach 一样遍历每个数组成员,但是中途可以跳出循环
let students = [
{ id: 0, name: '张三', sex: 'male', age: 16 },
{ id: 2, name: '隔壁老王', sex: 'other', age: 30 },
{ id: 1, name: '李四', sex: 'female', age: 20 },
]
// forEach 循环遍历每一个数组成员
students.forEach((el, index, arr) => {
console.log(el);
console.log(index)
console.log(arr);
})
// every 和 forEach 一样遍历每个数组成员,但是中途可以跳出循环
students.every((el, index, arr) => {
console.log(el);
console.log(index)
console.log(arr);
if (el.sex === 'female') {
// every 的回调函数中需要返回一个bool值 false 类似于循环语句中的 break 用于跳出循环
return false
}
// 类似于 continue 继续循环
return true
})
map 映射数组到新数组中
map 的回调函数将返回一个值,代表当前被遍历的数组成员在新数组中的投影
map 函数将返回一个新的投影数组
// 例如将students中所有的年龄放入一个新数组
let r = students.map((el, index, arr) => {
console.log(el);
console.log(index)
console.log(arr);
// return 的内容将被放到新的数组中
return el.age
})
console.log(r);
filter 过滤器
filter 返回过滤完后的新数组
let r = students.filter((el, index, arr) => {
console.log(el);
console.log(index)
console.log(arr);
// 过滤掉年龄大于25岁的成员
// if (el.age > 25) {
// // return false 代表过滤掉该数据
// return false
// }
// return true // return true 代表保留该数据
return el.age <= 25
})
console.log(r);
find 查找符合回调函数条件的数组成员并返回它
返回值为查询结果
let r = students.find((el, index, arr) => {
// // 查找女同学
// if (el.sex === 'female') {
// return true // return true 代表当前成员就是要查找的元素
// }
// return false
return el.name === '隔壁老王'
})
console.log(r);
findIndex 查找对应成员所在索引
使用方法和 find 相同,返回结果为查找到的成员索引
let r = students.findIndex((el, index, arr) => {
return el.name === '隔壁老王'
})
console.log(r);
some 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值。
给出一个判断条件,some 函数将对每个成员都做出相同的判断, 任意成员符合条件返回 true 时 some函数则返回true 否则为 false。
// 请判断 arr2 中是否有成员存在于 arr1 中
let arr1 = ['x', 'y', 'z']
let arr2 = ['a', 'b', 'z']
let r = arr2.some((el, index, arr) => {
// 判断当前数组成员是否再 arr1 中存在
if (arr1.includes(el)) {
// 返回 true 代表 找到一个满足条件的数组成员
return true
}
return false
})
console.log(r);
sort 排序,若调用 sort 函数不传参数时,会使用浏览器默认的排序规则
let numList = [90, 12, 110, 9, 40, 214]
// let numList = [90, 12, 11, 92, 40, 21]
numList.sort()
console.log(numList);
// 自定义排序
// 排序规则:按年龄从小到大排序
// sort 参数是一个排序规则的函数
// el1 和 el2 代表的是排序时两两比较的两个数组成员
// 排序原理是,将每个成员都和剩下所有成员进行比较,每一对 el1 和 el2 决定他们的先后顺序
// 也叫“冒泡排序”
// sort 函数执行完后将返回新数组
students.sort((el1, el2) => {
if (el1.age > el2.age) {
// el1.age 若大于 el2.age 则 若从小到达排列 el1 应该放到 el2 的右侧
// 右侧联想 右箭头 >;即大于符号,则此处应该返回一个大于零的值
return 1
} else if (el1.age < el2.age) {
// el1.age 若小于 el2.age 则 若从小到大排列 el1 应该放到 el2 的左侧
// 左侧联想到 左箭头 < ;即小于符号,所以返回一个小于 0 的值
return -1
} else {
// 若 el1 和 el2 不需要交换顺序 则返回 0
return 0
}
})
console.log(students);
4.3 数组去重
let arr = [1, 5, 1, 2, 2, 4, 6, 5, 6, 4]
// 有以下两种方法供参考
// 1. 缓存重复数据
// 缓存
let temp = {}
// 结果数组
let result = []
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
// 判断 item 充当 key 是否存在于 temp 中
// if (temp[item]) {
if (!Reflect.has(temp, item)) {
// item 不存在于 temp 的 key 中
result.push(item)
// 添加缓存
temp[item] = true
}
}
console.log(result);
// 使用 filter 去重
temp = {}
result = arr.filter(el => {
// 若缓存中没有el
if (!temp[el]) {
// 加入缓存
temp[el] = true
return true
}
return false
})
console.log(result);
// 2. 使用 set 集
// 构造set对象,set对象会自动去重
let set = new Set(arr)
console.log(set);
// 将set转换为数组
result = Array.from(set)
console.log(result);
console.log(Array.from(new Set(arr))); // 合成一句话
5. 函数
函数用于包装可以重复使用的代码,实现代码的复用
5.1 定义函数
语法:
function 函数名(参数列表) { // code block 代码块 }
有参数的函数:
参数列表中声明参数,如有多个参数,用逗号隔开
参数列表中的参数,我们称为:形式参数
返回结果
函数的返回结果称为“返回值”
return 还会终止函数内后续代码的运行,return 终止函数运行并返回一个输出,函数若没有返回值(也就是没有return)那么函数默认返回 undefined
调用有参函数:
通过函数名调用,且调用的同时,传入真实的参数。调用函数时,圆括号中的参数叫做:实际参数
可以通过变量(或常量)存储函数和调用函数。
let sub = function (x, y) {
let result = x - y
return result
}
r = sub(1, 2)
console.log(r);
sayHow()
function sayHow() {
console.log('how');
}
let sayHow = function () {
console.log('how');
}
定义参数时,可以给参数设置默认值。
function sayMessage(msg = 'hello message') {
console.log(msg);
}
sayMessage('this is my param')
// 当不给函数提供参数时,参数msg将采用默认值
sayMessage()
自调用函数: 定义后立即调用的匿名函数
定义自调用函数的步骤:
1.先打两个圆括号
2.在第一个圆括号中声明函数
3.第二个圆括号代表调用函数,圆括号中填入实际参数
console.log(((x, y) => x + y)(2, 3))
5.2 预编译
在浏览器执行js脚本前需要进行编译,有些代码在编译前将会率先被翻译执行并存入内存,这个过程称为预编译。
哪些东西会被预编译:
1. 调用函数时,在函数执行前会进行函数的预编译
2. 预编译时,函数内的 形参和函数内声明的变量和函数会被预编译
预编译的顺序:
1.寻找函数内的形参和变量,为其赋值undefined
2.将实参传值给形参
3.寻找函数声明
5.3 变量的作用域
作用域指的是作用范围,范围内可以访问变量,超出范围则无法访问
全局作用域(Global):
具备全局作用域的只有全局变量或常量
全局变量
指的是作用域为全局(Global)的变量,代码中任何位置都能使用。
全局变量有两种:
1.自动全局变量:给未定义的变量直接赋值,该变量会变成 自动全局变量,若在浏览器中,该变量会被存到window对象里
2. (普通的)全局变量: 直接在函数外声明的变量也是全局作用域的变量
块级作用域(Block):
就是在代码块中有效的作用域,let 关键字声明的变量具备块级作用域
函数作用域(Function):
函数中的变量,无论使用var还是let定义的,都是函数作用域,在函数外无法访问
5.4 lambda表达式
什么是 lambda 表达式?是一种用来定义函数的方法,lambda 表达式也称为箭头函数。
// 定义一个无参函数
// 圆括号部分是参数列表
// 花括号部分是代码块
const sayHello = () => {
console.log('hello');
}
sayHello()
// 定义只有一个参数的函数
let sayMessage = (msg) => {
console.log(msg);
}
// 参数只有一个时 圆括号可以省略
sayMessage = msg => {
console.log(msg)
}
sayMessage('hello message')
// lambda 的返回值
let add = (x, y) => {
return x + y
}
// 在箭头后不写花括号 代表直接返回箭头后的内容
add = (x, y) => x + y
console.log(add(1, 2));
// 返回对象的情况
// 在箭头后使用圆括号包裹想要返回的对象
let getUser = () => ({ name: '张三', sex: 'male' })
console.log(getUser());
6. 字符串操作
字符串可以被视为字符数组
let str = 'hello world !!!'
// 查看字符串长度
console.log(str.length);
// 通过索引访问字符串中的字符
console.log(str[4]);
// charAt 函数可以获取指定索引处的字符
// 等价于 str[4]
console.log(str.charAt(4));
// split: 分割字符串
// 参数:用于分割字符串的字符
// 返回值:字符串数组
let r = str.split(' ')
console.log(r);
// split + join 替换字符
// 例如:替换字符 *|& 为 _
str = 'hello*|&world*|&!!!'
r = str.split('*|&')
r = r.join('_')
console.log(r);
// trim: 去掉字符串首尾空格
str = ' hello world !!! '
console.log(str);
r = str.trim()
console.log(r);
// substring: 截取子字符串
// 第一个参数:截取字符串的起始索引位置
// 第二个参数:截取字符串的结束索引位置
// 口诀:前截后不截
// 返回值:截取出来的子字符串
str = 'hello world !!!'
r = str.substring(4, 9)
console.log(r);
// 第二个参数可以省略,如果只写一个参数,substring将从该参数位置一直截取到字符串末尾
r = str.substring(6)
console.log(r)
// indexOf: 查询字符串中指定字符在字符串中的索引位置
// 参数:要查询的字符串
// 返回值:被查询字符串的索引
console.log(str.indexOf('o'));
// lastIndexOf
console.log(str.lastIndexOf('o'))
// 举例: 截取字符串从 o ~ r
console.log(str.substring(str.indexOf('o'), str.lastIndexOf('r') + 1));
// startsWith: 用于判断字符串是否以指定字符串开头
// 参数:指定开头的字符串
// 返回值:bool值,true代表是以指定字符串开头的,false代表不是
console.log(str.startsWith('hello'));
// endsWith: 用于判断字符串是否以指定字符串结尾
console.log(str.endsWith('!!!!'));
// toUpperCase toLowerCase 将字符串中的英文转成全为大写或小写
str = str.toUpperCase()
console.log(str);
str = str.toLowerCase()
console.log(str);
// 例如: 统计一个字符串中出现了多少个a字符,忽略大小写
str = 'alhdAkdjfalKHgladhfdjAhg'
str = str.toLowerCase()
let count = 0
for (let i = 0; i < str.length; i++) {
const char = str[i];
if (char === 'a') count++
}
console.log(count);
// 补充:
// 数字操作:
// toFixed 保留小数点后多少位的函数
// 参数:指定小数点后保留几位
// 返回值:是一个保留了指定小数点位数的字符串
let num = 3.1415926
r = num.toFixed(3)
console.log(r);
6.1 正则表达式
表达式语法:
\ 斜杠:转义;
^ :匹配字符串的开头;
$ :匹配字符串的结尾;
匹配字符个数的符号:这些匹配字符个数的符号,代表的意思是:匹配前一个字符多少次。
*:匹配任意次
? : 匹配0次或1次
+ : 匹配至少1次
{n} : 匹配指定次数
{n,} : 匹配至少n次
{n,m} : 匹配至少n次,至多m次
匹配字符个数的符号:
[xyz]: 匹配字符集合,匹配一个字符,该字符在方括号内
x|y : 或
[^xyz]: 匹配负值集合,匹配一个字符,该字符不在方括号内
[a-z] [0-9] : 取范围值,匹配一个字符,该字符在指定范围内
[^5-7]: 取范围负值,匹配一个字符,该字符不在指定范围内
// [xyz]: 匹配字符集合,匹配一个字符,该字符在方括号内
regex = /^[xyz]$/
console.log(regex.test('z')); // => true
console.log(regex.test('yy')); // => false
// x|y : 或
regex = /^(good|bad)$/
console.log(regex.test('good')); // => true
console.log(regex.test('bad')); // => true
console.log(regex.test('ok')); // => false
// [^xyz]: 匹配负值集合,匹配一个字符,该字符不在方括号内
regex = /^[^xyz]$/
console.log(regex.test('z')); // => false
console.log(regex.test('a')); // => true
console.log(regex.test('abc')); // => false
// [a-z] [0-9] : 取范围值,匹配一个字符,该字符在指定范围内
regex = /^[A-Z][0-9]$/
console.log(regex.test('E7')); // => true
console.log(regex.test('G8')); // => true
console.log(regex.test('c5')); // => false
// [^5-7]: 取范围负值,匹配一个字符,该字符不在指定范围内
regex = /^[^5-7]$/
console.log(regex.test('6')); // => false
console.log(regex.test('x')); // => true
console.log(regex.test('a')); // => true
分组 (pattern)
(pattern): 将pattern里面的所有字符当作一个字符处理
regex = /^abc(123)+xyz$/
console.log(regex.test('abc123xyz')); // => true
console.log(regex.test('abc123123xyz')); // => true
console.log(regex.test('abc112233xyz')); // => false
站在字符串的角度看,圆括号不仅有分组的作用,同时,它将被取值。
regex = /abc123xyz/
let r = '000123abc123xyz444555'.match(regex)
console.log(r);
(?:pattern): 匹配分组内容,但不获取圆括号中的值
regex = /abc(?:123)+xyz/
r = '000123abc123123xyz444555'.match(regex)
console.log(r);
6.2 正则表达式的特殊字符
\d : 10进制数
\D : 非10进制数
\r: 回车 \n: 换行
\s: 所有不可见字符,制表符 回车换行 空格等 \S: 所有可见字符
.: 基本等于任意字符 但不包括 \r\n
// \t: 制表符
let regex = /^$/
// 匹配任意字符任意次数的写法如下:
regex = /[\S\s]*/
7. 闭包和计时器
什么是闭包
闭包也叫函数闭包,通过函数产生一个封闭的内存空间,包裹一些需要被保存的数据, 且函数需要返回一个持续引用的对象,这就叫闭包
应用场景
闭包用于存储一些不让函数外访问的数据,或者为了避免作用域中变量名的冲突,可以使用闭包
计时器
当经过指定时间后触发一段代码的函数就是一个计时器
- setTimeout
// 声明一个计时器:setTimeout
// 第一个参数:计时器计时结束后触发的函数
// 第二个参数:计时时长,单位:毫秒
// 返回值: 计时器id
// 计时器id 用于停止计时
let timerId = setTimeout(() => {
console.log('hello setTimeout')
}, 3000)
document.querySelector('.btn1').addEventListener('click', () => {
// clearTimeout 清空计时器
// 参数是 计时器id
// 清空后计时器将取消掉
clearTimeout(timerId)
})
- setInterval 循环计时函数
每次经过指定时间,触发一次指定的函数,参数和返回值 与 setTimeout 相同
let count = 0
let timerId2 = setInterval(() => {
count++
console.log(count);
}, 1000)
- 清空循环计时器
clearInterval
document.querySelector('.btn2').addEventListener('click', () => {
// 清空循环计时器
clearInterval(timerId2)
})
注意事项:
1. 尽量不使用 setInterval
理由:setInterval 可能由于人为原因忘了关闭,或者内部出现异常,导致代码死循环
2. 若要做大量循环调用甚至是无限循环调用时(例如轮询死循环调用),请使用 setInterval 而不是使用 setTimeout 递归进行循环
理由:setTimeout 会占用大量内存堆栈
8. 时间对象和数学函数
8.1 时间对象
创建时间对象的方法,语法:new Date(params);
Date 时间对象默认修改了对象的 toString 方法 所以转字符串时,会显示成时间字符串;
所有对象都有 toString 方法,转换字符串时,js回调用该方法。
// 1. 当前系统时间
let date = new Date()
console.log(date);
// 2. 创建指定时间字符串的时间
date = new Date('1997-07-07')
console.log(date);
// 不推荐使用,因为这是个非标准时间字符串,不同浏览器解析方式可能不同
// 3. 通过格林威治毫秒时创建时间
date = new Date(1000 * 60 * 60 * 24 * 365)
console.log(date);
// 4. 通过年月日时分秒创建时间
// 参数分别代表 年月日时分秒
// 注意:月份是从0开始计算的
date = new Date(2000, 5, 6, 18, 44, 22)
console.log(date);
// 参数至少写前两个参数,后续参数可以省略
date = new Date(2000, 5, 6, 18, 44)
console.log(date);
date = new Date(2000, 5, 6, 18)
console.log(date);
date = new Date(2000, 5)
console.log(date);
// 如何读取时间?如何设置时间?
// 读取时间
date = new Date()
console.log(date.getFullYear()); // 年
console.log(date.getMonth()); // 月 月份从0开始计算
console.log(date.getDate()); // 日 一个月中的第几天
console.log(date.getDay()); // 一周中的第几天 一周中的第一天是周日 值为 0
console.log(date.getHours()); // 时
console.log(date.getMinutes()); // 分
console.log(date.getSeconds()); // 秒
console.log(date.getMilliseconds()) // 毫秒
// 设置时间
date.setFullYear(2023)
date.setMonth(0)
date.setDate(5)
date.setHours(20)
date.setMinutes(66)
date.setSeconds(66)
date.setMilliseconds(1000)
console.log(date);
// 其他常用时间函数
// Date.now() // 获取当前系统时间的格林威治毫秒时
console.log(Date.now());
// date.getTime() // 获取date对象代表的格林威治毫秒时
console.log(date.getTime());
// 通常来说日期对象需要转换成字符串显示,否则用户看不懂
function format(date) {
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`
}
console.log(format(new Date()));
8.2 数学函数
三角函数
注意三角函数 sin()、cos()、tan()、asin()、acos()、atan() 和 atan2() 返回的值是弧度而非角度。若要转换,弧度除以 (Math.PI / 180) 即可转换为角度,同理,角度乘以这个数则能转换为弧度。
反三角函数就在三角函数前加上a就可以了
abs: 获取数字的绝对值
console.log(Math.abs(-100));
console.log(Math.abs(100));
ceil: 一个小数向上取整
console.log(Math.ceil(5.1));
console.log(Math.ceil(5.2));
console.log(Math.ceil(5.5));
console.log(Math.ceil(5.9));
floor: 一个小数向下取整
console.log(Math.floor(5.1));
console.log(Math.floor(5.2));
console.log(Math.floor(5.5));
console.log(Math.floor(5.9));
round:四舍五入
console.log(Math.round(5.1));
console.log(Math.round(5.2));
console.log(Math.round(5.5));
console.log(Math.round(5.9));
max: 取参数中较大数
console.log(Math.max(1, 2, 3, 4, 5, 6, 7));
min: 取参数中较小数
console.log(Math.min(1, 2, 3, 4, 5, 6, 7));
pow:返回x的y次方
// 语法:Math.pow(x, y)
console.log(Math.pow(2, 3));
sqrt: 返回一个数的平方根
console.log(Math.sqrt(25));
random: 取随机数,范围在[0~1)之间,能取到0但取不到1
console.log(Math.random());
// 假设随机一个 50 ~ 100 的数
console.log(50 + Math.random() * 50);
// 随机一个 n ~ m 的数
// 则公式为: n + Math.random() * (m - n)
sign: 取符号
console.log(Math.sign(-100))
console.log(Math.sign(-32))
console.log(Math.sign(80))
console.log(Math.sign(45))
9 事件和异常处理
9.1 事件
在js中,事件就是:当某种情况发生的时候,能够触发一段代码,这个发生的情况就是事件。事件在js中以对象形式存在。
addEventListener绑定事件:
第一个参数:要绑定的事件名称
第二个参数:当事件发生时,触发的函数
第二个参数的函数,不是有开发人员调用的,当事件发生时由浏览器调用的
// 资源事件
const img = document.querySelector('img')
// load 加载完成
img.addEventListener('load', () => {
console.log('加载完成');
})
// error 加载失败
img.addEventListener('error', () => {
console.log('加载失败');
})
// 焦点事件
const input = document.querySelector('input')
const box = document.querySelector('.box')
// focus 获取焦点
input.addEventListener('focus', () => {
console.log('获取焦点');
})
// blur 失去焦点
input.addEventListener('blur', () => {
console.log('失去焦点');
})
box.addEventListener('focus', () => {
console.log('box获取焦点');
})
box.addEventListener('blur', () => {
console.log('box失去焦点');
})
// 鼠标事件
const box2 = document.querySelector('.box2')
// 点击事件
box2.addEventListener('click', () => {
console.log('单击左键');
})
// 右键菜单
box2.addEventListener('contextmenu', () => {
console.log('右键菜单');
})
// 双击
box2.addEventListener('dblclick', () => {
console.log('双击');
})
// 鼠标点下
box2.addEventListener('mousedown', ev => {
console.log('点下');
console.log(ev);
// ev.button 用于区分点击的是哪个键
// 0: 左键
// 1: 中键
// 2: 右键
console.log(ev.button);
})
// 鼠标抬起
box2.addEventListener('mouseup', ev => {
console.log('抬起');
console.log(ev);
console.log(ev.button);
})
// 进入和离开事件
box2.addEventListener('mouseenter', () => {
console.log('进入');
})
box2.addEventListener('mouseleave', () => {
console.log('离开');
})
// 悬停和出去
box2.addEventListener('mouseover', () => {
console.log('悬停');
})
box2.addEventListener('mouseout', () => {
console.log('出去');
})
// 移动
box2.addEventListener('mousemove', ev => {
console.log('移动');
console.log(ev);
// offsetX offsetY 是鼠标相对于元素左上角的坐标
console.log(ev.offsetX);
console.log(ev.offsetY);
})
// 滚轮
box2.addEventListener('wheel', ev => {
console.log('鼠标滚轮');
console.log(ev);
// deltaY 纵向滚动的变化量
// 正数向下 负数向上
console.log(ev.deltaY);
})
// 拖动事件
// 元素上需要添加 draggable="true"
const box3 = document.querySelector('.box3')
// 拖动
// box3.addEventListener('drag', ev => {
// console.log('drag');
// console.log(ev);
// })
// 开始拖动
box3.addEventListener('dragstart', ev => {
console.log('drag start');
console.log(ev);
})
// 结束拖动
box3.addEventListener('dragend', ev => {
console.log('drag end');
console.log(ev);
})
// 媒体事件:和多媒体播放相关事件,详细请查文档
// 表单元素事件
// 输入事件
input.addEventListener('input', ev => {
console.log('输入');
console.log(ev);
// 本次输入的内容
console.log(ev.data);
// 当前输入框的值
console.log(ev.currentTarget.value);
console.log(ev.target.value);
})
// 变化事件
// 一般除了输入框外都使用 change 事件
input.addEventListener('change', ev => {
console.log('变化');
console.log(ev);
// 当前输入框的值
console.log(ev.currentTarget.value);
console.log(ev.target.value);
})
// 按键事件
// 按下
// 可以按住不放持续触发事件
input.addEventListener('keydown', ev => {
console.log('按下');
console.log(ev);
// 获取用户点击的哪个按键
console.log(ev.key);
console.log(ev.keyCode);
})
box.addEventListener('keydown', () => {
console.log('box按下');
})
// 抬起
input.addEventListener('keyup', ev => {
console.log('抬起');
console.log(ev);
// 获取用户点击的哪个按键
console.log(ev.key);
console.log(ev.keyCode);
})
// 按压
input.addEventListener('keypress', ev => {
console.log('按压');
console.log(ev);
})
// 补充
// 窗口重置大小
window.addEventListener('resize', () => {
console.log('resize');
console.log(window.innerWidth);
console.log(window.innerHeight);
})
// 滚动条滚动事件
// 要监听滚动条的滚动事件,需要先找到产生滚动条的元素
// window.addEventListener('scroll', ev => {
// console.log('scroll');
// console.log(ev);
// })
document.querySelector('.frame').addEventListener('scroll', function (ev) {
console.log('scroll');
console.log(ev);
// this 代表当前触发事件的对象
// this.scrollTop 代表纵向滚动的高度
console.log(this.scrollTop);
})
9.2 异常处理
异常:当程序运行时,运行不下去了,碰到了问题,该问题就是一个异常。
异常的特点:当程序抛出异常后,代码将停止运行
异常处理:当程序出现异常时,开发人员进行的一个手动处理,异常处理的方法叫做捕获异常,只有运行时异常(runtime error)需要进行捕获
运行时异常:程序稳定运行时,可能出现的异常(例如用户输入)
捕获异常:通过 try catch 捕获异常,手动排除异常,并让程序能够继续运行。
语法如下:
try {
// 代码块: 该代码块中可以写入你想要尝试运行的代码
} catch(e) { // e:该参数是一个 Error 对象
当 try 代码块中的容抛出异常时,将触发 catch 代码块的内容
// 代码块: 对异常进行处理的逻辑写在这里
}
注意:try catch 代码块 必须同时存在
try {
// 在try中执行可能会有异常的代码
fn1()
} catch (e) {
// Error 对象包含两个常用属性
// message: 异常消息
console.log(e.message);
// stack: 调用栈的信息
// 调用栈可以用来查看异常程序整个调用的流程,便于找到异常位置
console.log(e.stack);
}
被处理后的异常不会导致程序终止
程序会继续执行
console.log('hello world');
异常对象(Error):当程序出现了系统异常时,程序回自动抛出一个 Error 对象,可以手动创建异常对象
// 这是个除法函数
function div(x, y) {
if (y === 0) {
// 除数不能为零,所以此处我们手动抛出异常
// 1. 创建异常对象
// let error = new Error('除数不能为0')
// // 2. 抛出异常
// // 抛出异常后程序将终止运行
// throw error
throw new Error('除数不能为0')
}
return x / y
}
try {
div(2, 0)
} catch (e) {
console.error(e);
}
console.log('hello world');
finally: 是try catch 之后的一个代码块,作用是,无论try中是否出现异常,finally 中的代码都会执行。
function div(x, y) {
if (y === 0) {
// 除数不能为零,所以此处我们手动抛出异常
// 1. 创建异常对象
let error = new Error('除数不能为0')
// 2. 抛出异常
// 抛出异常后程序将终止运行
throw error
}
return x / y
}
try {
let r = div(3, 1)
console.log(r);
} catch (e) {
console.error(e);
} finally {
// try catch 后 最终一定会执行的程序
console.log('无论try执行成功还是抛出异常,都会执行此处的代码');
}
9.3 自定义异常
自定义异常: 开发人员自己创建的异常类就是自定义异常。
作用:给异常分类,告诉开发人员哪些异常是需要处理的,哪些是系统异常不需要处理。
// 除法
function div(x, y) {
// 参数验证
// if (isNaN(x) || isNaN(y)) throw new Error('参数不是数字')
if (isNaN(x) || isNaN(y)) throw new NaNError()
if (y === 0) {
throw new ArgZeroError()
}
return x / y
}
// 自定义异常类
// 参数不是数字异常
class NaNError extends Error {
// 使用 new 关键字创建类实例时,调用 constructor 构造函数
constructor() {
// 父类构造函数
super('参数不是数字')
}
}
class ArgZeroError extends Error {
constructor() {
super('除数不能为0')
}
}
try {
div('ab7c', 0)
} catch (e) {
// 处理异常
// 判断异常的类型 分别进行处理
// n instanceof m 含义为: n 是否是 m 类型的实例
if (e instanceof NaNError) {
console.error('参数异常');
} else if (e instanceof ArgZeroError) {
console.error('参数为0');
} else {
console.error(e);
}
}