一、什么是JavaScript
JavaScript 是一种运行在客户端(浏览器)的编程语言
JavaScript由什么组成
JavaScript的组成
-
ECMAScript:
- 规定了js基础语法核心知识。
- 比如:变量、分支语句、循环语句、对象等等
-
Web APIs :
- DOM 文档对象模型, 定义了一套操作HTML文档的API
- BOM 浏览器对象模型,定义了一套操作浏览器窗口的API
引入JavaScript的方式
内联方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 引入方式</title>
</head>
<body>
<!-- 内联形式:通过 script 标签包裹 JavaScript 代码 -->
<script>
alert('嗨,欢迎学习JavaScript')
</script>
</body>
</html>
外部方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 引入方式</title>
</head>
<body>
<!-- 外部形式:通过 script 的 src 属性引入独立的 .js 文件 -->
<script src="demo.js"></script>
</body>
</html>
注释符和结束符
单行: //
多行:/* */
结束: ; 也可以不写
输入和输出
输入 : prompt()
输出 : alert()、document.write() 、consle.log()
二、JavaScript基础
关键字
JavaScript 使用专门的关键字 let 和 var 来声明(定义)变量,在使用时需要注意一些细节:
以下是使用 let 时的注意事项:
- 允许声明和赋值同时进行
- 不允许重复声明
- 允许同时声明多个变量并赋值
- JavaScript 中内置的一些关键字不能被当做变量名
以下是使用 var 时的注意事项:
- 允许声明和赋值同时进行
- 允许重复声明
- 允许同时声明多个变量并赋值
大部分情况使用 let 和 var 区别不大,但是 let 相较 var 更严谨,因此推荐使用 let,
变量命名规则
关于变量的名称(标识符)有一系列的规则需要遵守:
- 只能是字母、数字、下划线、$,且不能能数字开头
- 字母区分大小写,如 Age 和 age 是不同的变量
- JavaScript 内部已占用于单词(关键字或保留字)不允许使用
- 尽量保证变量具有一定的语义,见字知义
常量
概念:使用 const 声明的变量称为“常量”。
使用场景:当某个变量永远不会改变的时候,就可以使用 const 来声明,而不是let。
命名规范:和变量一致
数据类型
数值:number
字符串:string
布尔: boolean
未定义: undefined
空: null
可以使用typeof 进行数据类型的检测
运算符
数字是用来计算的,比如:乘法 * 、除法 / 、加法 + 、减法 - 等等,所以经常和算术运算符一起。
算术运算符:也叫数学运算符,主要包括加、减、乘、除、取余(求模)等
// 算术运算符
console.log(1 + 2 * 3 / 2) // 4
let num = 10
console.log(num + 10) // 20
console.log(num + num) // 20
// 1. 取模(取余数) 使用场景: 用来判断某个数是否能够被整除
console.log(4 % 2) // 0
console.log(6 % 3) // 0
console.log(5 % 3) // 2
console.log(3 % 5) // 3
// 2. 注意事项 : 如果我们计算失败,则返回的结果是 NaN (not a number)
console.log('JavaScript' - 2)
console.log('JavaScript' * 2)
console.log('JavaScript' + 2) // JavaScript2
赋值运算符
赋值运算符:对变量进行赋值的运算符
= 将等号右边的值赋予给左边, 要求左边必须是一个容器
<script>
let num = 1
// num = num + 1
// 采取赋值运算符
// num += 1
num += 3
console.log(num)
</script>
自增/自减运算符
++ 自增
– 自减
【注意】
- ++在前和++在后在单独使用时二者并没有差别,而且一般开发中我们都是独立使用
- ++在后(后缀式)我们会使用更多
<script>
// let num = 10
// num = num + 1
// num += 1
// // 1. 前置自增
// let i = 1
// ++i
// console.log(i)
// let i = 1
// console.log(++i + 1)
// 2. 后置自增
// let i = 1
// i++
// console.log(i)
// let i = 1
// console.log(i++ + 1)
// 了解
let i = 1
console.log(i++ + ++i + i)
</script>
比较运算符
使用场景:比较两个数据大小、是否相等,根据比较结果返回一个布尔值(true / false)
<script>
console.log(3 > 5)
console.log(3 >= 3)
console.log(2 == 2)
// 比较运算符有隐式转换 把'2' 转换为 2 双等号 只判断值
console.log(2 == '2') // true
// console.log(undefined === null)
// === 全等 判断 值 和 数据类型都一样才行
// 以后判断是否相等 请用 ===
console.log(2 === '2')
console.log(NaN === NaN) // NaN 不等于任何人,包括他自己
console.log(2 !== '2') // true
console.log(2 != '2') // false
console.log('-------------------------')
console.log('a' < 'b') // true
console.log('aa' < 'ab') // true
console.log('aa' < 'aac') // true
console.log('-------------------------')
</script>
逻辑运算符
使用场景:可以把多个布尔值放到一起运算,最终返回一个布尔值
<script>
// 逻辑与 一假则假
console.log(true && true)
console.log(false && true)
console.log(3 < 5 && 3 > 2)
console.log(3 < 5 && 3 < 2)
console.log('-----------------')
// 逻辑或 一真则真
console.log(true || true)
console.log(false || true)
console.log(false || false)
console.log('-----------------')
// 逻辑非 取反
console.log(!true)
console.log(!false)
console.log('-----------------')
let num = 6
console.log(num > 5 && num < 10)
console.log('-----------------')
</script>
逻辑运算符优先级: !> && > ||
分支语句
if 分支语句
<script>
// 1. 用户输入
let score = +prompt('请输入成绩:')//+是为了转换为数字类型
// 2. 判断输出
if (score >= 90) {
alert('成绩优秀,宝贝,你是我的骄傲')
} else if (score >= 70) {
alert('成绩良好,宝贝,你要加油哦~~')
} else if (score >= 60) {
alert('成绩及格,宝贝,你很危险~')
} else {
alert('成绩不及格,宝贝,我不想和你说话,我只想用鞭子和你说话~')
}
</script>
三元运算符(三元表达式)
条件 ? 表达式1 : 表达式2
// 三元运算符(三元表达式)
// 1. 语法格式
// 条件 ? 表达式1 : 表达式2
// 2. 执行过程
// 2.1 如果条件为真,则执行表达式1
// 2.2 如果条件为假,则执行表达式2
// 3. 验证
// 5 > 3 ? '真的' : '假的'
console.log(5 < 3 ? '真的' : '假的')
// let age = 18
// age = age + 1
// age++
// 1. 用户输入
let num = prompt('请您输入一个数字:')
// 2. 判断输出- 小于10才补0
// num = num < 10 ? 0 + num : num
num = num >= 10 ? num : 0 + num
alert(num)
switch语句
使用场景: 适合于有多个条件的时候,也属于分支语句,大部分情况下和 if多分支语句 功能相同
注意:
- switch case语句一般用于等值判断, if适合于区间判断
- switchcase一般需要配合break关键字使用 没有break会造成case穿透
- if 多分支语句开发要比switch更重要,使用也更多
// switch分支语句
// 1. 语法
// switch (表达式) {
// case 值1:
// 代码1
// break
// case 值2:
// 代码2
// break
// …
// default:
// 代码n
// }
<script>
switch (2) {
case 1:
console.log('您选择的是1')
break // 退出switch
case 2:
console.log('您选择的是2')
break // 退出switch
case 3:
console.log('您选择的是3')
break // 退出switch
default:
console.log('没有符合条件的')
}
</script>
循环语句
while循环
while : 在…. 期间, 所以 while循环 就是在满足条件期间,重复执行某些代码。
while (条件表达式) {
// 循环体
}
// while循环: 重复执行代码
// 1. 需求: 利用循环重复打印3次 '月薪过万不是梦,毕业时候见英雄'
let i = 1
while (i <= 3) {
document.write('月薪过万不是梦,毕业时候见英雄~<br>')
i++ // 这里千万不要忘了变量自增否则造成死循环
}
可以使用break和continue终止循环
break
中止整个循环,一般用于结果已经得到, 后续的循环不需要的时候可以使用(提高效率)
continue
中止本次循环,一般用于排除或者跳过某一个选项的时候
for 语句
<script>
// 1. continue
for (let i = 1; i <= 5; i++) {
if (i === 3) {
continue // 结束本次循环,继续下一次循环
}
console.log(i)
}
// 2. break
for (let i = 1; i <= 5; i++) {
if (i === 3) {
break // 退出结束整个循环
}
console.log(i)
}
</script>
do while
do {
//循环体代码–条件表达式为true 时重复循环体代码
} while (条件表达式)
let i=100
do{
console.log('Hello JavaScript')
i--
}while(i<=0)
1.while(true) 来构造“无限”循环,需要使用break退出循环。(常用)
2.for( ; ; ) 也可以来构造“无限”循环,同样需要使用break退出循环
数组
数组:(Array)是一种可以按顺序保存数据的数据类型
使用场景:**如果有多个数据可以用数组保存起来,然后放到一个变量中,管理非常方便
数组的基本使用
<script>
// 1. 语法,使用 [] 来定义一个空数组
// 定义一个空数组,然后赋值给变量 classes
// let classes = [];
// 2. 定义非空数组
let classes = ['小明', '小刚', '小红', '小丽', '小米']
</script>
通过 []
定义数组,数据中可以存放真正的数据,如小明、小刚、小红等这些都是数组中的数据,我们这些数据称为数组单元,数组单元之间使用英文逗号分隔。
索引
数据单元值类型
数组做为数据的集合,它的单元值可以是任意数据类型
<script>
// 6. 数组单值类型可以是任意数据类型
// a) 数组单元值的类型为字符类型
let list = ['HTML', 'CSS', 'JavaScript']
// b) 数组单元值的类型为数值类型
let scores = [78, 84, 70, 62, 75]
// c) 混合多种类型
let mixin = [true, 1, false, 'hello']
</script>
数组长度属性
<script>
// 定义一个数组
let arr = ['html', 'css', 'javascript']
// 数组对应着一个 length 属性,它的含义是获取数组的长度
console.log(arr.length) // 3
</script>
数组方法
数组做为对象数据类型,不但有 length
属性可以使用,还提供了许多方法:
- push 动态向数组的尾部添加一个单元
- unshit 动态向数组头部添加一个单元
- pop 删除最后一个单元
- shift 删除第一个单元
- splice 动态删除任意单元或添加任意单元
使用以上4个方法时,都是直接在原数组上进行操作,即成功调任何一个方法,原数组都跟着发生相应的改变。并且在添加或删除单元时 length
并不会发生错乱。
<script>
// 定义一个数组
let arr = ['html', 'css', 'javascript']
// 1. push 动态向数组的尾部添加一个单元
arr.push('Nodejs')
console.log(arr)
arr.push('Vue')
// 2. unshit 动态向数组头部添加一个单元
arr.unshift('VS Code')
console.log(arr)
// 3. pop 删除最后一个单元
arr.pop()
console.log(arr)
// 4. shift 删除第一个单元
arr.shift()
console.log(arr)
// 5. 删除的使用方式splice 动态删除任意单元
arr.splice(2, 1) // 从索引值为2的位置开始删除1个单元
console.log(arr)
//6.添加元素
let arr = ['red', 'green', 'blue']
arr.splice(1, 0, 'pink', 'hotpink') // 在索引号是1的位置添加 pink hotpink
console.log(arr) // ['red', 'pink', 'hotpink', 'green', 'blue']
</script>
splice() 方法用于添加或删除数组中的元素。
注意:这种方法会改变原始数组。
语法
删除元素
splice(起始位置, 删除的个数)
添加元素
splice(起始位置,删除个数,添加数组元素)
函数
函数表达式
// 声明
let fn = function() {
console.log('函数表达式')
}
// 调用
fn()
函数的声明
函数的调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript 基础 - 声明和调用</title>
</head>
<body>
<script>
// 声明(定义)了最简单的函数,既没有形式参数,也没有返回值
function sayHi() {
console.log('嗨~')
}
// 函数调用,这些函数体内的代码逻辑会被执行
// 函数名()
sayHi()
// 可以重复被调用,多少次都可以
sayHi()
</script>
</body>
</html>
带括号直接调用 不带括号的时候触发事件进行调用
参数
通过向函数传递参数,可以让函数更加灵活多变,参数可以理解成是一个变量。
声明(定义)一个功能为打招呼的函数
- 传入数据列表
- 声明这个函数需要传入几个数据
- 多个数据用逗号隔开
<script>
// 声明(定义)一个功能为打招呼的函数
// function sayHi() {
// console.log('嗨~')
// }
// 调用函数
// sayHi()
// 这个函数似乎没有什么价值,除非能够向不同的人打招呼
// 这就需要借助参数来实现了
function sayHi(name) {
// 参数 name 可以被理解成是一个变量
console.log(name)
console.log('嗨~' + name)
}
// 调用 sayHi 函数,括号中多了 '小明'
// 这时相当于为参数 name 赋值了
sayHi('小明')// 结果为 小明
// 再次调用 sayHi 函数,括号中多了 '小红'
// 这时相当于为参数 name 赋值了
sayHi('小红') // 结果为 小红
</script>
形参和实参
形参:声明函数时写在函数名右边小括号里的叫形参(形式上的参数)
实参:调用函数时写在函数名右边小括号里的叫实参(实际上的参数)
形参可以理解为是在这个函数内声明的变量(比如 num1 = 10)实参可以理解为是给这个变量赋值
开发中尽量保持形参和实参个数一致
<script>
// 声明(定义)一个计算任意两数字和的函数
// 形参 x 和 y 分别表示任意两个数字,它们是两个变量
function count(x, y) {
console.log(x + y);
}
// 调用函数,传入两个具体的数字做为实参
// 此时 10 赋值给了形参 x
// 此时 5 赋值给了形参 y
count(10, 5); // 结果为 15
</script>
- 声明(定义)函数时的形参没有数量限制,当有多个形参时使用
,
分隔 - 调用函数传递的实参要与形参的顺序一致
返回值
函数的本质是封装(包裹),函数体内的逻辑执行完毕后,函数外部如何获得函数内部的执行结果呢?要想获得函数内部逻辑的执行结果,需要通过 return
这个关键字,将内部执行结果传递到函数外部,这个被传递到外部的结果就是返回值。
<script>
// 定义求和函数
function count(a, b) {
let s = a + b
// s 即为 a + b 的结果
// 通过 return 将 s 传递到外部
return s
}
// 调用函数,如果一个函数有返回值
// 那么可将这个返回值赋值给外部的任意变量
let total = count(5, 12)
</script>
总结:
- 在函数体中使用return 关键字能将内部的执行结果交给函数外部使用
- 函数内部只能出现1 次 return,并且 return 下一行代码不会再被执行,所以return 后面的数据不要换行写
- return会立即结束当前函数
- 函数可以没有return,这种情况默认返回值为 undefined
作用域
通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
全局作用域
作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件
处于全局作用域内的变量,称为全局变量
局部作用域
作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域。
处于局部作用域内的变量称为局部变量
如果函数内部,变量没有声明,直接赋值,也当全局变量看,但是强烈不推荐
但是有一种情况,函数内部的形参可以看做是局部变量那就是立即执行函数。
匿名函数
函数可以分为具名函数和匿名函数
匿名函数:没有名字的函数,无法直接使用。
function(){
xxx
}
立即执行函数
(function(){ xxx })();
(function(){xxxx}());
无需调用,立即执行,其实本质已经调用了
多个立即执行函数之间用分号隔开
【注意】 在能够访问到的情况下 先局部 局部没有在找全局
对象
什么是对象
对象是 JavaScript 数据类型的一种,之前已经学习了数值类型、字符串类型、布尔类型、undefined。对象数据类型可以被理解成是一种数据集合。它由属性和方法两部分构成。
语法
声明一个对象类型的变量与之前声明一个数值或字符串类型的变量没有本质上的区别。
<script>
// 声明字符串类型变量
let str = 'hello world!'
// 声明数值类型变量
let num = 199
// 声明对象类型变量,使用一对花括号
// user 便是一个对象了,目前它是一个空对象
let user = {}
</script>
属性和访问
数据描述性的信息称为属性,如人的姓名、身高、年龄、性别等,一般是名词性的。
- 属性都是成 对出现的,包括属性名和值,它们之间使用英文
:
分隔 - 多个属性之间使用英文
,
分隔 - 属性就是依附在对象上的变量
- 属性名可以使用
""
或''
,一般情况下省略,除非名称遇到特殊符号如空格、中横线等
<script>
// 通过对象描述一个人的数据信息
// person 是一个对象,它包含了一个属性 name
// 属性都是成对出现的,属性名 和 值,它们之间使用英文 : 分隔
let person = {
name: '小明', // 描述人的姓名
age: 18, // 描述人的年龄
stature: 185, // 描述人的身高
gender: '男', // 描述人的性别
}
声明对象,并添加了若干属性后,可以使用 .
或 []
获得对象中属性对应的值,称之为属性访问。
<script>
// 通过对象描述一个人的数据信息
// person 是一个对象,它包含了一个属性 name
// 属性都是成对出现的,属性名 和 值,它们之间使用英文 : 分隔
let person = {
name: '小明', // 描述人的姓名
age: 18, // 描述人的年龄
stature: 185, // 描述人的身高
gender: '男', // 描述人的性别
};
// 访问人的名字
console.log(person.name) // 结果为 小明
// 访问人性别
console.log(person.gender) // 结果为 男
// 访问人的身高
console.log(person['stature']) // 结果为 185
// 或者
console.log(person.stature) // 结果同为 185
</script>
【重要】
也可以动态为对象添加属性,动态添加与直接定义是一样的,只是语法上更灵活。
<script>
// 声明一个空的对象(没有任何属性)
let user = {}
// 动态追加属性
user.name = '小明'
user['age'] = 18
// 动态添加与直接定义是一样的,只是语法上更灵活
</script>
方法和调用
数据行为性的信息称为方法,如跑步、唱歌等,一般是动词性的,其本质是函数。
- 方法是由方法名和函数两部分构成,它们之间使用 : 分隔
- 多个属性之间使用英文
,
分隔 - 方法是依附在对象中的函数
- 方法名可以使用
""
或''
,一般情况下省略,除非名称遇到特殊符号如空格、中横线等
<script>
// 方法是依附在对象上的函数
let person = {
name: '小红',
age: 18,
// 方法是由方法名和函数两部分构成,它们之间使用 : 分隔
singing: function () {
console.log('两只老虎,两只老虎,跑的快,跑的快...')
},
run: function () {
console.log('我跑的非常快...')
}
}
</script>
声明对象,并添加了若干方法后,可以使用 .
或 []
调用对象中函数,称之为方法调用。
<script>
// 方法是依附在对象上的函数
let person = {
name: '小红',
age: 18,
// 方法是由方法名和函数两部分构成,它们之间使用 : 分隔
singing: function () {
console.log('两只老虎,两只老虎,跑的快,跑的快...')
},
run: function () {
console.log('我跑的非常快...')
}
}
// 调用对象中 singing 方法
person.singing()
// 调用对象中的 run 方法
person.run()
也可以动态为对象添加方法,动态添加与直接定义是一样的,只是语法上更灵活。
<script>
// 声明一个空的对象(没有任何属性,也没有任何方法)
let user = {}
// 动态追加属性
user.name = '小明'
user.['age'] = 18
// 动态添加方法
user.move = function () {
console.log('移动一点距离...')
}
</script>
注:无论是属性或是方法,同一个对象中出现名称一样的,后面的会覆盖前面的。
null 也是 JavaScript 中数据类型的一种,通常只用它来表示不存在的对象。使用 typeof 检测类型它的类型时,结果为 object
。
遍历对象
let obj = {
uname: 'pink'
}
for(let k in obj) {
//遍历属性名
// k 属性名 字符串 带引号 obj.'uname' k === 'uname'
//遍历属性值
// obj[k] 属性值 obj['uname'] obj[k]
}
for in 不提倡遍历数组 因为 k 是 字符串 变量不了其他类型
内置对象
Math
Math` 是 JavaScript 中内置的对象,称为数学对象,这个对象下即包含了属性,也包含了许多的方法。
属性
Math.PI,获取圆周率
// 圆周率
console.log(Math.PI);
方法
Math.random,生成 0 到 1 间的随机数
// 0 ~ 1 之间的随机数, 包含 0 不包含 1
Math.random()
Math.ceil,数字向上取整
// 舍弃小数部分,整数部分加1
Math.ceil(3.4)
Math.floor,数字向下取整
// 舍弃小数部分,整数部分不变
Math.floor(4.68)
Math.round,四舍五入取整
// 取整,四舍五入原则
Math.round(5.46539)
Math.round(4.849)
Math.max,在一组数中找出最大的
// 找出最大值
Math.max(10, 21, 7, 24, 13)
Math.min,在一组数中找出最小的
// 找出最小值
Math.min(24, 18, 6, 19, 21)
Math.pow,幂方法
// 求某个数的多少次方
Math.pow(4, 2) // 求 4 的 2 次方
Math.pow(2, 3) // 求 2 的 3 次方
Math.sqrt,平方根
// 求某数的平方根
Math.sqrt(16)
三、WebAPIs
什么是WebAPIs
严格意义上讲,我们在 JavaScript 阶段学习的知识绝大部分属于 ECMAScript 的知识体系,ECMAScript 简称 ES 它提供了一套语言标准规范,如变量、数据类型、表达式、语句、函数等语法规则都是由 ECMAScript 规定的。浏览器将 ECMAScript 大部分的规范加以实现,并且在此基础上又扩展一些实用的功能,这些被扩展出来的内容我们称为 Web APIs。
ECMAScript 运行在浏览器中然后再结合 Web APIs 才是真正的 JavaScript,Web APIs 的核心是 DOM 和 BOM。
DOM
什么是DOM
DOM(Document Object Model)是将整个 HTML 文档的每一个标签元素视为一个对象,这个对象下包含了许多的属性和方法,通过操作这些属性或者调用这些方法实现对 HTML 的动态更新,为实现网页特效以及用户交互提供技术支撑。
DOM树
![在这里插入图片描述](https://img-blog.csdnimg.cn/0d8757bc85b841d382a27ac13b85c6b2.png
document节点
document 是 JavaScript 内置的专门用于 DOM 的对象,该对象包含了若干的属性和方法,
document` 是学习 DOM 的核心。
<script>
// document 是内置的对象
// console.log(typeof document);
// 1. 通过 document 获取根节点
console.log(document.documentElement); // 对应 html 标签
// 2. 通过 document 节取 body 节点
console.log(document.body); // 对应 body 标签
// 3. 通过 document.write 方法向网页输出内容
document.write('Hello World!');
</script>
获取DOM对象
- querySelector 满足条件的第一个元素
- querySelectorAll 满足条件的元素集合 返回伪数组
- 了解其他方式
- getElementById
- getElementsByTagName
<body>
<h3>查找元素类型节点</h3>
<p>从整个 DOM 树中查找 DOM 节点是学习 DOM 的第一个步骤。</p>
<ul>
<li>元素</li>
<li>元素</li>
<li>元素</li>
<li>元素</li>
</ul>
<script>
const p = document.querySelector('p') // 获取第一个p元素
const lis = document.querySelectorAll('li') // 获取所有的li
</script>
</body>
操作元素内容
通过修改 DOM 的文本内容,动态改变网页的内容。
innerText
将文本内容添加/更新到任意标签位置,文本中包含的标签不会被解析。
<script>
// innerText 将文本内容添加/更新到任意标签位置
const intro = document.querySelector('.intro')
// intro.innerText = '嗨~ 我叫李雷!'
// intro.innerText = '<h4>嗨~ 我叫李雷!</h4>'
</script>
innerHTML
将文本内容添加/更新到任意标签位置,文本中包含的标签会被解析。
<script>
// innerHTML 将文本内容添加/更新到任意标签位置
const intro = document.querySelector('.intro')
intro.innerHTML = '嗨~ 我叫韩梅梅!'
intro.innerHTML = '<h4>嗨~ 我叫韩梅梅!</h4>'
</script>
总结:如果文本内容中包含 html
标签时推荐使用 innerHTML
,否则建议使用 innerText
属性。
操作元素属性
有4种方式可以实现对属性的修改
常用属性修改
直接能过属性名修改,最简洁的语法
<script>
// 1. 获取 img 对应的 DOM 元素
const pic = document.querySelector('.pic')
// 2. 修改属性
pic.src = './images/lion.webp'
pic.width = 400;
pic.alt = '图片不见了...'
</script>
控制样式属性
应用【修改样式】
通过修改行内样式 style
属性,实现对样式的动态修改。
通过元素节点获得的 style
属性本身的数据类型也是对象,如 box.style.color
、box.style.width
分别用来获取元素节点 CSS 样式的 color
和 width
的值。
<body>
<div class="box">随便一些文本内容</div>
<script>
// 获取 DOM 节点
const box = document.querySelector('.intro')
box.style.color = 'red'
box.style.width = '300px'
// css 属性的 - 连接符与 JavaScript 的 减运算符
// 冲突,所以要改成驼峰法
box.style.backgroundColor = 'pink'
</script>
</body>
任何标签都有 style
属性,通过 style
属性可以动态更改网页标签的样式
【注意】如要遇到 css
属性中包含字符 -
时,要将 -
去掉并将其后面的字母改成大写,如 background-color
要写成 box.style.backgroundColor
操作类名(className) 操作CSS
如果修改的样式比较多,直接通过style属性修改比较繁琐,我们可以通过借助于css类名的形式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>练习 - 修改样式</title>
<style>
.pink {
background: pink;
color: hotpink;
}
</style>
</head>
<body>
<div class="box">随便一些文本内容</div>
<script>
// 获取 DOM 节点
const box = document.querySelector('.box')
box.className = 'pink'
</script>
</body>
</html>
注意:
1.由于class是关键字,所以使用className去代替
2.className是使用新值换旧值,如果需要添加一个类,需要保留之前的类名
通过 classList 操作类控制CSS
为了解决className 容易覆盖以前的类名,我们可以通过classList方式追加和删除类名
<!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>
div {
width: 200px;
height: 200px;
background-color: pink;
}
.active {
width: 300px;
height: 300px;
background-color: hotpink;
margin-left: 100px;
}
</style>
</head>
<body>
<div class="one"></div>
<script>
// 1.获取元素
// let box = document.querySelector('css选择器')
let box = document.querySelector('div')
// add是个方法 添加 追加
// box.classList.add('active')
// remove() 移除 类
// box.classList.remove('one')
// 切换类
box.classList.toggle('one')
</script>
</body>
</html>
操作表单元素属性
表单很多情况,也需要修改属性,比如点击眼睛,可以看到密码,本质是把表单类型转换为文本框
正常的有属性有取值的跟其他的标签属性没有任何区别
获取:DOM对象.属性名
设置:DOM对象.属性名= 新值
<!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>
<input type="text" value="请输入">
<button disabled>按钮</button>
<input type="checkbox" name="" id="" class="agree">
<script>
// 1. 获取元素
let input = document.querySelector('input')
// 2. 取值或者设置值 得到input里面的值可以用 value
// console.log(input.value)
input.value = '小米手机'
input.type = 'password'
// 2. 启用按钮
let btn = document.querySelector('button')
// disabled 不可用 = false 这样可以让按钮启用
btn.disabled = false
// 3. 勾选复选框
let checkbox = document.querySelector('.agree')
checkbox.checked = false
</script>
</body>
</html>
自定义属性
标准属性: 标签天生自带的属性 比如class id title等, 可以直接使用点语法操作比如: disabled、checked、selected
自定义属性:
在html5中推出来了专门的data-自定义属性
在标签上一律以data-开头
在DOM对象上一律以dataset对象方式获取
<!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 data-id="1"> 自定义属性 </div>
<script>
// 1. 获取元素
let div = document.querySelector('div')
// 2. 获取自定义属性值
console.log(div.dataset.id)
</script>
</body>
</html>
间歇函数setInterval
setInterval 是 JavaScript 中内置的函数,它的作用是间隔固定的时间自动重复执行另一个函数,也叫定时器函数。
<script>
// 1. 定义一个普通函数
function repeat() {
console.log('不知疲倦的执行下去....')
}
// 2. 使用 setInterval 调用 repeat 函数
// 间隔 1000 毫秒,重复调用 repeat
setInterval(repeat, 1000)
</script>
事件
事件是编程语言中的术语,它是用来描述程序的行为或状态的,一旦行为或状态发生改变,便立即调用一个函数。
事件监听
结合 DOM 使用事件时,需要为 DOM 对象添加事件监听,等待事件发生(触发)时,便立即调用一个函数。
addEventListener
是 DOM 对象专门用来添加事件监听的方法,它的两个参数分别为【事件类型】和【事件回调】。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件监听</title>
</head>
<body>
<h3>事件监听</h3>
<p id="text">为 DOM 元素添加事件监听,等待事件发生,便立即执行一个函数。</p>
<button id="btn">点击改变文字颜色</button>
<script>
// 1. 获取 button 对应的 DOM 对象
const btn = document.querySelector('#btn')
// 2. 添加事件监听
btn.addEventListener('click', function () {
console.log('等待事件被触发...')
// 改变 p 标签的文字颜色
let text = document.getElementById('text')
text.style.color = 'red'
})
// 3. 只要用户点击了按钮,事件便触发了!!!
</script>
</body>
</html>
完成事件监听分成3个步骤:
- 获取 DOM 元素
- 通过
addEventListener
方法为 DOM 节点添加事件监听 - 等待事件触发,如用户点击了某个按钮时便会触发
click
事件类型 - 事件触发后,相对应的回调函数会被执行
事件类型
click单击事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('click',function () {
alert('触发了click事件')
})
</script>
</body>
</html>
dblclick双击事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('dblclick',function () {
alert('触发了dblclick事件')
})
</script>
</body>
</html>
contextmenu鼠标右击事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('contextmenu',function () {
alert('触发了contextmenu事件')
})
</script>
</body>
</html>
mousedown鼠标按下事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('mousedown',function () {
alert('触发了mousedown事件')
})
</script>
</body>
</html>
mouseup鼠标抬起事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('mouseup',function () {
alert('触发了mouseup事件')
})
</script>
</body>
</html>
mouseenter鼠标移入事件【没有冒泡】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('mouseenter',function () {
alert('触发了mouseenter事件')
})
</script>
</body>
</html>
mouseleave 鼠标移出事件【没有冒泡】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('mouseleave',function () {
alert('触发了mouseleave事件')
})
</script>
</body>
</html>
mousemove鼠标移动事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('mousemove',function () {
console.log('123')
})
</script>
</body>
</html>
mouseover鼠标移动到某元素之上事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('mouseover',function () {
alert('触发了mouseover事件')
})
</script>
</body>
</html>
mouseout鼠标从某元素移开事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>按钮</button>
<script>
document.querySelector('button').addEventListener('mouseout',function () {
alert('触发了mouseout事件')
})
</script>
</body>
</html>
keydown键盘按下事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input class="ipt">
<script>
document.querySelector('.ipt').addEventListener('keydown',function (e) {
if(e.key === 'Enter'){
alert('触发了keydown事件')
}
})
</script>
</body>
</html>
keyup键盘抬起事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input class="ipt">
<script>
document.querySelector('.ipt').addEventListener('keyup',function (e) {
if(e.key === 'Enter'){
alert('触发了keyup事件')
}
})
</script>
</body>
</html>
focus获得焦点事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input class="ipt">
<script>
document.querySelector('.ipt').addEventListener('focus',function () {
alert('触发了focus事件')
})
</script>
</body>
</html>
blur失去焦点事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input class="ipt">
<script>
document.querySelector('.ipt').addEventListener('blur',function () {
alert('触发了blur事件')
})
</script>
</body>
</html>
input文本框输入事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input class="ipt">
<script>
document.querySelector('.ipt').addEventListener('input',function () {
alert('触发了input事件')
})
</script>
</body>
</html>
change内容改变进行触发事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
选择一种冰淇淋口味:
<select class="ice-cream" name="ice-cream">
<option value="">选择一个...</option>
<option value="chocolate">巧克力</option>
<option value="sardine">沙丁鱼</option>
<option value="vanilla">香草</option>
</select>
<div class="result"></div>
<script>
document.querySelector('.ice-cream').addEventListener('change',function (e){
const result = document.querySelector('.result')
result.textContent = `你喜欢 ${e.target.value}`
})
</script>
</body>
</html>
给input注册 change 事件,值被修改并且失去焦点后触发
DOMContentLoaded文档解析完成后触发事件
document.addEventListener('DOMContentLoaded',function () {
const but = document.querySelector('button')
but.addEventListener('click',function () {
alert('点击了2')
})
})
load页面完全加载后触发事件
window.addEventListener('load',function () {
const but = document.querySelector('button')
but.addEventListener('click',function () {
alert('点击了')
})
})
scroll页面滚动条滚动时触发事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body{
height: 3000px;
padding-top: 100px;
}
div{
display: none;
margin: 100px;
overflow: scroll;
width: 200px;
height: 200px;
border: 1px solid black;
}
</style>
</head>
<body>
<div>
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
我这里有很多文字我这里有很多文字我这里有很多文字
</div>
<script>
const div = document.querySelector('div')
window.addEventListener('scroll',function () {
let n = document.documentElement.scrollTop
if (n>=100){
div.style.display = 'block'
}else {
div.style.display = 'none'
}
})
</script>
</body>
</html>
resize页面尺寸变化时触发事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
window.addEventListener('resize', function () {
alert('已经点击右上角第二个放大缩小框按钮改变了窗口大小调用了resize事件')
});
</script>
</body>
</html>
M端(移动端)事件
touchstart点击的时候进行触发事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div{
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
<script>
const div = document.querySelector('div')
//点击的时候进行触发事件
div.addEventListener('touchstart',function () {
console.log('执行了touchstart方法')
})
</script>
</body>
</html>
touchmove点击不松开滑动的时候触发事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div{
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
<script>
const div = document.querySelector('div')
//点击不松开滑动的时候触发事件
div.addEventListener('touchmove',function () {
console.log('执行了touchmove方法')
})
</script>
</body>
</html>
touchend松开的时候触发事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div{
width: 200px;
height: 200px;
background-color: pink;
}
</style>
</head>
<body>
<div></div>
<script>
const div = document.querySelector('div')
//松开的时候触发事件
div.addEventListener('touchend',function () {
console.log('执行了touchend方法')
})
</script>
</body>
</html>
事件对象
任意事件类型被触发时与事件相关的信息会被以对象的形式记录下来,我们称这个对象为事件对象。
通常使用event、
ev 、
ev
ev.type
当前事件的类型ev.clientX/Y
光标相对浏览器窗口的位置ev.offsetX/Y
光标相于当前 DOM 元素的位置
<body>
<h3>事件对象</h3>
<p>任意事件类型被触发时与事件相关的信息会被以对象的形式记录下来,我们称这个对象为事件对象。</p>
<hr>
<div class="box"></div>
<script>
// 获取 .box 元素
const box = document.querySelector('.box')
// 添加事件监听
box.addEventListener('click', function (e) {
console.log('任意事件类型被触发后,相关信息会以对象形式被记录下来...');
// 事件回调函数的第1个参数即所谓的事件对象
console.log(e)
})
</script>
</body>
注:在事件回调函数内部通过 window.event 同样可以获取事件对象。
环境对象
能够分析判断函数运行在不同环境中 this 所指代的对象。
环境对象指的是函数内部特殊的变量 this
,它代表着当前函数运行时所处的环境。
<script>
// 声明函数
function sayHi() {
// this 是一个变量
console.log(this);
}
// 声明一个对象
let user = {
name: '张三',
sayHi: sayHi // 此处把 sayHi 函数,赋值给 sayHi 属性
}
let person = {
name: '李四',
sayHi: sayHi
}
// 直接调用
sayHi() // window
window.sayHi() // window
// 做为对象方法调用
user.sayHi()// user
person.sayHi()// person
</script>
- this 本质上是一个变量,数据类型为对象
- 函数的调用方式不同
this
变量的值也不同 - 【谁调用
this
就是谁】是判断this
值的粗略规则 - 函数直接调用时实际上
window.sayHi()
所以this
的值为window
回调函数
如果将函数 A 做为参数传递给函数 B 时,我们称函数 A 为回调函数。
<script>
// 声明 foo 函数
function foo(arg) {
console.log(arg);
}
// 普通的值做为参数
foo(10);
foo('hello world!');
foo(['html', 'css', 'javascript']);
function bar() {
console.log('函数也能当参数...');
}
// 函数也可以做为参数!!!!
foo(bar);
</script>
函数 bar
做参数传给了 foo
函数,bar
就是所谓的回调函数了!!!
结合setInterval
<script>
//第一种写法
function fn() {
console.log('我是回调函数...');
}
// 调用定时器
setInterval(fn, 1000);
//第二种写法
// 调用定时器,匿名函数做为参数
setInterval(function () {
console.log('我是回调函数...');
}, 1000);
</script>
- 回调函数本质还是函数,只不过把它当成参数使用
- 使用匿名函数做为回调函数比较常见
事件流
事件流是对事件执行过程的描述,了解事件的执行过程有助于加深对事件的理解,提升开发实践中对事件运用的灵活度。
如上图所示,任意事件被触发时总会经历两个阶段:【捕获阶段】和【冒泡阶段】。
简言之,捕获阶段是【从父到子】的传导过程,冒泡阶段是【从子向父】的传导过程。
当单击事件触发时,其祖先元素的单击事件也【相继触发】
结合事件流的特征,我们知道当某个元素的事件被触发时,事件总是会先经过其祖先才能到达当前元素,然后再由当前元素向祖先传递,事件在流动的过程中遇到相同的事件便会被触发。
再来关注一个细节就是事件相继触发的【执行顺序】,事件的执行顺序是可控制的,即可以在捕获阶段被执行,也可以在冒泡阶段被执行。
如果事件是在冒泡阶段执行的,我们称为冒泡模式,它会先执行子盒子事件再去执行父盒子事件,默认是冒泡模式。
如果事件是在捕获阶段执行的,我们称为捕获模式,它会先执行父盒子事件再去执行子盒子事件。
阻止冒泡
阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。
e.stopPropagation()
<body>
<h3>阻止冒泡</h3>
<p>阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。</p>
<div class="outer">
<div class="inner">
<div class="child"></div>
</div>
</div>
<script>
// 获取嵌套的3个节点
const outer = document.querySelector('.outer')
const inner = document.querySelector('.inner')
const child = document.querySelector('.child')
// 外层的盒子
outer.addEventListener('click', function () {
console.log('outer...')
})
// 中间的盒子
inner.addEventListener('click', function (ev) {
console.log('inner...')
// 阻止事件冒泡
ev.stopPropagation()
})
// 内层的盒子
child.addEventListener('click', function (ev) {
console.log('child...')
// 借助事件对象,阻止事件向上冒泡
ev.stopPropagation()
})
</script>
</body>
事件对象中的 ev.stopPropagation
方法,专门用来阻止事件冒泡。
鼠标经过事件:
mouseover 和 mouseout 会有冒泡效果
mouseenter 和 mouseleave 没有冒泡效果 (推荐)
事件委托
事件委托是利用事件流的特征解决一些现实开发需求的知识技巧,主要的作用是提升程序效率。
利用事件流的特征,可以对上述的代码进行优化,事件的的冒泡模式总是会将事件流向其父元素的,如果父元素监听了相同的事件类型,那么父元素的事件就会被触发并执行,正是利用这一特征对上述代码进行优化,
<script>
// 假设页面中有 10000 个 button 元素
let buttons = document.querySelectorAll('table button');
// 假设上述的 10000 个 buttom 元素共同的祖先元素是 table
let parents = document.querySelector('table');
parents.addEventListener('click', function () {
console.log('点击任意子元素都会触发事件...');
})
</script>
我们的最终目的是保证只有点击 button 子元素才去执行事件的回调函数
事件对象中的属性 target
或 srcElement
属性表示真正触发事件的元素,它是一个元素类型的节点。
<script>
// 假设页面中有 10000 个 button 元素
const buttons = document.querySelectorAll('table button')
// 假设上述的 10000 个 buttom 元素共同的祖先元素是 table
const parents = document.querySelector('table')
parents.addEventListener('click', function (ev) {
// console.log(ev.target);
// 只有 button 元素才会真正去执行逻辑
if(ev.target.tagName === 'BUTTON') {
// 执行的逻辑
}
})
</script>
元素尺寸与位置
获取元素的自身宽高、包含元素自身设置的宽高、padding、border
offsetWidth和offsetHeight以及offsetLeft和offsetTop
获取出来的是数值,方便计算
注意: 获取的是可视宽高, 如果盒子是隐藏的,获取的结果是0
getBoundingClientRect
相当于当前视口的大小
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body{
height: 3000px;
}
div{
width: 200px;
height: 200px;
background-color: pink;
margin: 100px;
}
</style>
</head>
<body>
<div></div>
<script>
const div = document.querySelector('div')
console.log(div.getBoundingClientRect())
</script>
</body>
</html>
【总结】
日期对象
掌握 Date 日期对象的使用,动态获取当前计算机的时间。
ECMAScript 中内置了获取系统时间的对象 Date,使用 Date 时与之前学习的内置对象 console 和 Math 不同,它需要借助 new 关键字才能使用。
// 1. 实例化
// const date = new Date(); // 系统默认时间
const date = new Date('2020-05-01') // 指定时间
// date 变量即所谓的时间对象
console.log(typeof date)
日期方法
// 1. 实例化
const date = new Date();
// 2. 调用时间对象方法
// 通过方法分别获取年、月、日,时、分、秒
const year = date.getFullYear(); // 四位年份
const month = date.getMonth(); // 0 ~ 11
getFullYear 获取四位年份
getMonth 获取月份,取值为 0 ~ 11
getDate 获取月份中的每一天,不同月份取值也不相同
getDay 获取星期,取值为 0 ~ 6
getHours 获取小时,取值为 0 ~ 23
getMinutes 获取分钟,取值为 0 ~ 59
getSeconds 获取秒,取值为 0 ~ 59
时间戳
时间戳是指1970年01月01日00时00分00秒起至现在的总秒数或毫秒数,它是一种特殊的计量时间的方式。
注:ECMAScript 中时间戳是以毫秒计的。
// 1. 实例化
const date = new Date()
// 2. 获取时间戳
console.log(date.getTime())
// 还有一种获取时间戳的方法
console.log(+new Date())
// 还有一种获取时间戳的方法
console.log(Date.now())
获取时间戳的方法,分别为 getTime 和 Date.now 和 +new Date()
DOM节点
节点是文档树的组成部分,每一个节点都是一个 DOM 对象,主要分为元素节点、属性节点、文本节点等。
- 【元素节点】其实就是 HTML 标签,如上图中
head
、div
、body
等都属于元素节点。 - 【属性节点】是指 HTML 标签中的属性,如上图中
a
标签的href
属性、div
标签的class
属性。 - 【文本节点】是指 HTML 标签的文字内容,如
title
标签中的文字。 - 【根节点】特指
html
标签。
查找节点
查找父节点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="yeye">
<div class="dad">
<div class="son"></div>
</div>
</div>
<script>
//parentNode只能获取到最近的也就是上一级的父节点
const son = document.querySelector('.son')
console.log(son.parentNode) //获取dad
console.log(son.parentNode.parentNode) //获取yeye
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.guang{
position: relative;
width: 1000px;
height: 200px;
background-color: pink;
margin: 100px auto;
text-align: center;
font-size: 50px;
line-height: 200px;
font-weight: 700;
}
.close{
position: absolute;
right: 20px;
top: 10px;
background-color: skyblue;
text-align: center;
line-height: 20px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="guang" id="1">
这是一条广告1
<div class="close">x</div>
</div>
<div class="guang" id="2">
这是一条广告2
<div class="close">x</div>
</div>
<div class="guang" id="3">
这是一条广告3
<div class="close">x</div>
</div>
<script>
const closeAll = document.querySelectorAll('.close')
for (let i=0 ;i<closeAll.length; i++){
closeAll[i].addEventListener('click',function () {
this.parentNode.style.display = 'none'
})
}
</script>
</body>
</html>
查找子节点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
const ul = document.querySelector('ul')
console.log(ul.children)
</script>
</body>
</html>
获取兄弟节点
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
const li = document.querySelector('ul li:nth-child(2)')
console.log(li.previousElementSibling) //查找相邻的上一个节点
console.log(li.nextElementSibling) //查找相邻的下一个节点
</script>
</body>
</html>
插入节点
在已有的 DOM 节点中插入新的 DOM 节点时,需要关注两个关键因素:首先要得到新的 DOM 节点,其次在哪个位置插入这个节点。
<body>
<h3>插入节点</h3>
<p>在现有 dom 结构基础上插入新的元素节点</p>
<hr>
<!-- 普通盒子 -->
<div class="box"></div>
<!-- 点击按钮向 box 盒子插入节点 -->
<button class="btn">插入节点</button>
<script>
// 点击按钮,在网页中插入节点
const btn = document.querySelector('.btn')
btn.addEventListener('click', function () {
// 1. 获得一个 DOM 元素节点
const p = document.createElement('p')
p.innerText = '创建的新的p标签'
p.className = 'info'
// 复制原有的 DOM 节点
const p2 = document.querySelector('p').cloneNode(true)
p2.style.color = 'red'
// 2. 插入盒子 box 盒子
document.querySelector('.box').appendChild(p)
document.querySelector('.box').appendChild(p2)
})
</script>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li class="lione">我是真实的li1</li>
<li class="litwo">我是真实的li2</li>
</ul>
<script>
const ul = document.querySelector('ul')
const litwo = document.querySelector('.litwo')
const li = document.createElement('li')
li.innerHTML = '我是createElement创建的li'
//在最后的位置进行追加
// ul.appendChild(li)
//在某一个指定的元素节点之前追加
// ul.insertBefore(li,litwo)
ul.insertBefore(li,ul.children[0])
</script>
</body>
</html>
克隆节点
元素.cloneNode
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
//先获取到ul
const ul = document.querySelector('ul')
//得到ul的第一个元素之后使用cloneNode方法进行clone 为true的话就可以连值都复制
ul.appendChild(ul.children[0].cloneNode(true))
</script>
</body>
</html>
删除节点
删除现有的 DOM 节点,也需要关注两个因素:首先由父节点删除子节点,其次是要删除哪个子节点。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li></li>
</ul>
<script>
//获取ul
const ul = document.querySelector('ul')
//删除ul中的第一个节点元素
ul.removeChild(ul.children[0])
</script>
</body>
</html>
removeChild 删除节点时一定是由父子关系。
BOM
- window对象是一个全局对象,也可以说是JavaScript中的顶级对象
- 像document、alert()、console.log()这些都是window的属性,基本BOM的属性和方法都是window的
- 所有通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法
- window对象下的属性和方法调用的时候可以省略window
定时器-延时函数clearTimeou
JavaScript 内置的一个用来让代码延迟执行的函数,叫 setTimeout
只执行一次
setTimeout(回调函数, 延迟时间)
清除延时函数
clearTimeout(timerId)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>d</button>
<script>
//延迟函数
let time = setTimeout(function () {
console.log('时间到了')
},2000)
//清除延迟函数
clearTimeout(time)
</script>
</body>
</html>
延时器需要等待,所以后面的代码先执行
每一次调用延时器都会产生一个新的延时器
五秒关闭广告案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
img{
position: fixed;
left: 0;
bottom: 0;
}
</style>
</head>
<body>
<img src="./images/ad.png">
<script>
const img = document.querySelector('img')
setTimeout(function () {
console.log(img)
img.style.display = 'none'
},5000)
</script>
</body>
</html>
js执行机制
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
这是因为 Javascript 这门脚本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作
DOM 而诞生的。比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之
后再删除。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是:
如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个
线程。于是,JS 中出现了同步和异步。
同步
前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同
步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。
异步
你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事
情。比如做饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。
他们的本质区别: 这条流水线上各个流程的执行顺序不同。
事件循环
js是一个单线程模型,但是在js执行任务中,有着同步任务和异步任务两种,如果仅靠着单线程去执行任务,响应结果会非常的慢,所以js在一开始把同步任务和异步任务分开,同步任务交给任务栈自上往下的执行,异步任务交给了浏览器,当浏览器处理完异步任务之后,会将处理好的异步任务放到任务队列里面去,任务栈在执行完js分配的同步任务之后,会去任务队列中去寻找已经在任务队列里的异步任务,如果有就放到任务栈中进行执行,如果没有就回去然后再次来查看,有的话就放到任务栈中执行,这个一直往复的过程叫事件循环
location对象
l location 的数据类型是对象,它拆分并保存了 URL 地址的各个组成部分
l 常用属性和方法:
href 属性获取完整的 URL 地址,对其赋值时用于地址的跳转
search 属性获取地址中携带的参数,符号 ?后面部分
hash 属性获取地址中的啥希值,符号 # 后面部分
reload 方法用来刷新当前页面,传入参数 true 时表示强制刷新
<body>
<form>
<input type="text" name="search"> <button>搜索</button>
</form>
<a href="#/music">音乐</a>
<a href="#/download">下载</a>
<button class="reload">刷新页面</button>
<script>
// location 对象
// 1. href属性 (重点) 得到完整地址,赋值则是跳转到新地址
console.log(location.href)
// location.href = 'http://www.xxx.cn'
// 2. search属性 得到 ? 后面的地址
console.log(location.search) // ?search=笔记本
// 3. hash属性 得到 # 后面的地址
console.log(location.hash)
// 4. reload 方法 刷新页面
const btn = document.querySelector('.reload')
btn.addEventListener('click', function () {
// location.reload() // 页面刷新
location.reload(true) // 强制页面刷新 ctrl+f5
})
</script>
</body>
navigator对象
navigator是对象,该对象下记录了浏览器自身的相关信息
常用属性和方法:
- 通过 userAgent 检测浏览器的版本及平台
// 检测 userAgent(浏览器信息)
(function () {
const userAgent = navigator.userAgent
// 验证是否为Android或iPhone
const android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)
const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/)
// 如果是Android或iPhone,则跳转至移动站点
if (android || iphone) {
location.href = 'http://m.itcast.cn'
}})();
histroy对象
history (历史)是对象,主要管理历史记录, 该对象与浏览器地址栏的操作相对应,如前进、后退等
常见方法
<body>
<button class="back">←后退</button>
<button class="forward">前进→</button>
<script>
// histroy对象
// 1.前进
const forward = document.querySelector('.forward')
forward.addEventListener('click', function () {
// history.forward()
history.go(1)
})
// 2.后退
const back = document.querySelector('.back')
back.addEventListener('click', function () {
// history.back()
history.go(-1)
})
</script>
</body>
本地存储
本地存储介绍
1、数据存储在用户浏览器中
2、设置、读取方便、甚至页面刷新不丢失数据
3、容量较大,sessionStorage和localStorage约 5M 左右
常见的使用场景:
https://todomvc.com/examples/vanilla-es6/ 页面刷新数据不丢失
本地存储分类
localStorage
作用: 数据可以长期保留在本地浏览器中,刷新页面和关闭页面,数据也不会丢失
特性: 以键值对的形式存储,并且存储的是字符串, 省略了window
localStorage存值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
localStorage.setItem('uname','张三')
sessionStorage.setItem('uname','李四')
</script>
</body>
</html>
localStorage取值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
console.log(localStorage.getItem('uname'))
</script>
</body>
</html>
localStorage删除数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
localStorage.removeItem('uname')
</script>
</body>
</html>
sessionStorage
特性:
- 用法跟localStorage基本相同
- 区别是:当页面浏览器被关闭时,存储在 sessionStorage 的数据会被清除
存储:sessionStorage.setItem(key,value)
获取:sessionStorage.getItem(key)
删除:sessionStorage.removeItem(key)
JSON字符串:
- 首先是1个字符串
- 属性名使用双引号引起来,不能单引号
- 属性值如果是字符串型也必须双引号
localStorage 存取复杂数据类型
存储
问题: 本地只能存储字符串,无法存储复杂数据类型.
解决: 需要将复杂数据类型转换成 JSON字符串,在存储到本地
语法: JSON.stringify(复杂数据类型)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const obj = {
uname : '张三',
age : 18,
gender : '男'
}
//存
localStorage.setItem('obj',JSON.stringify(obj))
//取
console.log(JSON.parse(localStorage.getItem('obj')).uname)
</script>
</body>
</html>
取出
问题: 因为本地存储里面取出来的是字符串,不是对象,无法直接使用
解决: 把取出来的字符串转换为对象
语法: JSON.parse(JSON字符串)
<body>
<script>
// 本地存储复杂数据类型
const obj = {
uname : '张三',
age : 18,
gender : '男'
}
//把JSON字符串转换为对象 JSON.parse
console.log(JSON.parse(localStorage.getItem('obj')))
</script>
</body>
Array
Array
是内置的构造函数,用于创建数组。
<script>
// 构造函数创建数组
let arr = new Array(5, 7, 8);
// 字面量方式创建数组
let list = ['html', 'css', 'javascript']
</script>
数组map方法
实例方法 map
迭代原数组,生成新数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const arr = ['red','pink','blue']
const newArr =arr.map(function (item,i) {
return item + i
})
console.log(newArr)
</script>
</body>
</html>
数组join方法
实例方法join
数组元素拼接为字符串,返回字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const arr =['red','blue','green']
//不用符号隔开
console.log(arr.join(''))
//用符号隔开
console.log(arr.join('-'))
</script>
</body>
</html>
数组forEach方法
实例方法 forEach
用于遍历数组,替代 for
循环
注意:
1.forEach 主要是遍历数组
2.参数当前数组元素是必须要写的, 索引号可选。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const arr = ['张三','李四','王五']
arr.forEach((item,i) => {
console.log(item)
console.log(i)
})
</script>
</body>
</html>
数组filter方法
实例方法 filter
过滤数组单元值,生成新数组
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const arr = [10,20,30]
//方法一
//item是数据 i代表数组索引
const newArr = arr.filter(function (item,i) {
return arr[i]
return item >= 20
})
console.log(newArr)
//方法二
const newArr = arr.filter(item => item>=20)
console.log(newArr)
</script>
</body>
</html>
数组find方法
实例方法 find
查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined
const arr = ['red', 'blue', 'green']
arr.find(item => console.log(item === 'blue')) //false true false
//对象练习
const arr = [
{
name: '小米',
price: 1999
},
{
name: '华为',
price: 3999
},
]
const newArr = arr.find(item => item.name === '小米')
console.log(newArr)
数组every方法
实例方法every
检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false
//every 每一个都符合返回true,有一个不符合返回false
const arr = [10, 20, 30]
const newArr = arr.every(item => item >= 10)
const newArr = arr.every(item => item >= 20)
console.log(newArr)
数组some方法
实例方法some
检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false
//some有一个符合就返回true,全不符合返回false
const arr = [10, 20, 30]
const newArr = arr.some(item => item === 20)
const newArr = arr.some(item => item >= 40)
console.log(newArr)
数组concat方法
实例方法 concat
合并两个数组,返回生成新数组
数组sort方法
实例方法 sort
对原数组单元值排序
数组splice方法
实例方法 splice
删除或替换原数组单元
数组reverse方法
实例方法 reverse
反转数组
数组findIndex方法
实例方法 findIndex
查找元素的索引值
数组reduce方法
reduce方法 求和计算
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const arr = [1,2,3]
//不带初始值的写法
const total1 = arr.reduce(function (prev,current) {
return prev + current
})
console.log(total1)
//带初始值的写法
const total2 = arr.reduce(function (prev,current) {
return prev + current
},10)
console.log(total2)
//箭头函数写法,箭头函数省略return
const total3 = arr.reduce((prev,current) => prev + current,100)
console.log(total3)
</script>
</body>
</html>
reduce练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const arr = [{
name: '张三',
salary: 10000
}, {
name: '李四',
salary: 10000
}, {
name: '王五',
salary: 10000
},
]
//计算求和
const total1 = arr.reduce((prev,current) => prev + current.salary,0)
console.log(total1)
//计算求涨薪30%
const total2 = arr.reduce((prev,current) => prev + current.salary *1.3,0)
console.log(total2)
</script>
</body>
</html>
数组from方法
伪数组转换成真数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
const lis = document.querySelectorAll('li')
// lis.pop() 此时为假数组,报错
const liss = Array.from(lis)
liss.pop() //此时为真数组可以使用数组的pop方法
</script>
</body>
</html>
综合案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>学生信息管理</title>
<link rel="stylesheet" href="css/index.css" />
</head>
<body>
<h1>新增人员</h1>
<form class="info" autocomplete="off">
姓名:<input type="text" class="uname" name="uname" />
年龄:<input type="text" class="age" name="age" />
性别:
<select name="gender" class="gender">
<option value="男">男</option>
<option value="女">女</option>
</select>
爱好:<input type="text" class="salary" name="salary" />
城市:<select name="city" class="city">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
<option value="深圳">深圳</option>
<option value="曹县">曹县</option>
</select>
<button class="add">录入</button>
</form>
<h1>人员榜</h1>
<table>
<thead>
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>爱好</th>
<th>城市</th>
<th>操作</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
//首先查localStorage本地数据
const data =localStorage.getItem('student-data')
//null证明没有,创建一个空数组
//出现值证明有,用parse转成数组
const arr = data ? JSON .parse(data): []
//1.渲染
//获取tbody
const tbody = document.querySelector('tbody')
function render() {
const trArr = arr.map(function (item,i) {
return `
<tr>
<td>${item.stuId}</td>
<td>${item.uname}</td>
<td>${item.age}</td>
<td>${item.gender}</td>
<td>${item.salary}</td>
<td>${item.city}</td>
<td>
<a href="javascript:" data-id="${i}">删除</a>
</td>
</tr>
`
})
tbody.innerHTML = trArr.join('')
}
render()
//2.添加
const info = document.querySelector('.info')
const items = info.querySelectorAll('[name]')
info.addEventListener('submit',function (e) {
e.preventDefault()
//创建空对象
const obj = {}
obj.stuId = arr.length ? arr[arr.length - 1].stuId + 1 : 1
//判断不允许为空即赋值
for (let i=0; i<items.length;i++){
if (items[i].value === '' ){
return alert('数据不允许为空')
}
const item = items[i]
obj[item.name] = item.value
}
//上边执行的时候刷新了,已经获取到了arr 将它添加进数组
arr.push(obj)
//存入
localStorage.setItem('student-data',JSON.stringify(arr))
//表单清空
this.reset()
//刷新
render()
})
//3.删除
tbody.addEventListener('click',function (e) {
if(e.target.tagName === 'A'){
arr.splice(e.target.dataset.id,1)
localStorage.setItem('student-data',JSON.stringify(arr))
render()
}
})
</script>
</body>
</html>
正则
什么是正则表达式
正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在 JavaScript中,正则表达式也是对象,通常用来查找、替换那些符合正则表达式的文本
正则表达式在JavaScript中的使用场景
正则表达式在 JavaScript中的使用场景:
例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)
比如用户名:/^[a-z0-9_-]{3,16}$/
过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
语法
const reg = /表达式/
- 其中
/ /
是正则表达式字面量 - 正则表达式也是
对象
test语法
test() 方法 用来查看正则表达式与指定的字符串是否匹配
判断是否有符合规则的字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const str = '你好世界世界'
const reg1 = /世界/
const reg2 = /JavaScript/
//使用test
//看reg1和reg2中是否包含str是返回的是true 不是false
console.log(reg1.test(str))
console.log(reg2.test(str))
</script>
</body>
</html>
exec语法
exec() 方法 在一个指定字符串中执行一个搜索匹配
检索(查找)符合规则的字符串,如果匹配成功,exec() 方法返回一个数组,否则返回null
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const str = '你好世界世界'
const reg1 = /世界/
const reg2 = /JavaScript/
//使用exec
//看reg1和reg2中是否包含str是返回的是数组以及下角标 没有则为null
console.log(reg1.exec(str))
console.log(reg2.exec(str))
</script>
</body>
</html>
元字符
- 普通字符:
- 大多数的字符仅能够描述它们本身,这些字符称作普通字符,例如所有的字母和数字。
- 普通字符只能够匹配字符串中与它们相同的字符。
- 比如,规定用户只能输入英文26个英文字母,普通字符的话 /[abcdefghijklmnopqrstuvwxyz]/
- 元字符(特殊字符)
- 是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能。
- 比如,规定用户只能输入英文26个英文字母,换成元字符写法: /[a-z]/
边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3U7EZ6Q8-1680869734080)(assets/1676080081650.png)]
如果 ^ 和 $ 在一起,表示必须是精确匹配
<body>
<script>
// 元字符之边界符
// 1. 匹配开头的位置 ^
const reg = /^web/
console.log(reg.test('web前端')) // true
console.log(reg.test('前端web')) // false
console.log(reg.test('前端web学习')) // false
console.log(reg.test('we')) // false
// 2. 匹配结束的位置 $
const reg1 = /web$/
console.log(reg1.test('web前端')) // false
console.log(reg1.test('前端web')) // true
console.log(reg1.test('前端web学习')) // false
console.log(reg1.test('we')) // false
// 3. 精确匹配 ^ $
const reg2 = /^web$/
console.log(reg2.test('web前端')) // false
console.log(reg2.test('前端web')) // false
console.log(reg2.test('前端web学习')) // false
console.log(reg2.test('we')) // false
console.log(reg2.test('web')) // true
console.log(reg2.test('webweb')) // flase
</script>
</body>
量词
量词用来设定某个模式重复次数
【注意】 逗号左右两侧千万不要出现空格
<body>
<script>
// 元字符之量词
// 1. * 重复次数 >= 0 次
const reg1 = /^w*$/
console.log(reg1.test('')) // true
console.log(reg1.test('w')) // true
console.log(reg1.test('ww')) // true
console.log('-----------------------')
// 2. + 重复次数 >= 1 次
const reg2 = /^w+$/
console.log(reg2.test('')) // false
console.log(reg2.test('w')) // true
console.log(reg2.test('ww')) // true
console.log('-----------------------')
// 3. ? 重复次数 0 || 1
const reg3 = /^w?$/
console.log(reg3.test('')) // true
console.log(reg3.test('w')) // true
console.log(reg3.test('ww')) // false
console.log('-----------------------')
// 4. {n} 重复 n 次
const reg4 = /^w{3}$/
console.log(reg4.test('')) // false
console.log(reg4.test('w')) // flase
console.log(reg4.test('ww')) // false
console.log(reg4.test('www')) // true
console.log(reg4.test('wwww')) // false
console.log('-----------------------')
// 5. {n,} 重复次数 >= n
const reg5 = /^w{2,}$/
console.log(reg5.test('')) // false
console.log(reg5.test('w')) // false
console.log(reg5.test('ww')) // true
console.log(reg5.test('www')) // true
console.log('-----------------------')
// 6. {n,m} n =< 重复次数 <= m
const reg6 = /^w{2,4}$/
console.log(reg6.test('w')) // false
console.log(reg6.test('ww')) // true
console.log(reg6.test('www')) // true
console.log(reg6.test('wwww')) // true
console.log(reg6.test('wwwww')) // false
// 7. 注意事项: 逗号两侧千万不要加空格否则会匹配失败
</script>
范围
表示字符的范围,定义的规则限定在某个范围,比如只能是英文字母,或者数字等等,用表示范围
<body>
<script>
// 元字符之范围 []
// 1. [abc] 匹配包含的单个字符, 多选1
const reg1 = /^[abc]$/
console.log(reg1.test('a')) // true
console.log(reg1.test('b')) // true
console.log(reg1.test('c')) // true
console.log(reg1.test('d')) // false
console.log(reg1.test('ab')) // false
// 2. [a-z] 连字符 单个
const reg2 = /^[a-z]$/
console.log(reg2.test('a')) // true
console.log(reg2.test('p')) // true
console.log(reg2.test('0')) // false
console.log(reg2.test('A')) // false
// 想要包含小写字母,大写字母 ,数字
const reg3 = /^[a-zA-Z0-9]$/
console.log(reg3.test('B')) // true
console.log(reg3.test('b')) // true
console.log(reg3.test(9)) // true
console.log(reg3.test(',')) // flase
// 用户名可以输入英文字母,数字,可以加下划线,要求 6~16位
const reg4 = /^[a-zA-Z0-9_]{6,16}$/
console.log(reg4.test('abcd1')) // false
console.log(reg4.test('abcd12')) // true
console.log(reg4.test('ABcd12')) // true
console.log(reg4.test('ABcd12_')) // true
// 3. [^a-z] 取反符
const reg5 = /^[^a-z]$/
console.log(reg5.test('a')) // false
console.log(reg5.test('A')) // true
console.log(reg5.test(8)) // true
</script>
</body>
字符类
某些常见模式的简写方式,区分字母和数字
替换和修饰符
replace 替换方法,可以完成字符的替换
修饰符约束正则执行的某些细节行为,如是否区分大小写、是否支持多行匹配等
- i 是单词 ignore 的缩写,正则匹配时字母不区分大小写
- g 是单词 global 的缩写,匹配所有满足正则表达式的结果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const str = 'JAVA是一门编程语言,学完java薪资很高'
//i 不区分大小写 但是只能替换一个
const result1 = str.replace(/java/i,'前端')
console.log('result1: ' + result1)
//加上g的话是所有的都给替换掉
const result2 = str.replace(/java/ig,'前端')
console.log('result2: ' + result2)
//ig和gi一样
const result3 = str.replace(/java/gi,'前端')
console.log('result3: ' + result3)
</script>
</body>
</html>
综合案例
<script>
//1、短信验证码倒计时
const code = document.querySelector('.code')
let flag = true
code.addEventListener('click',function () {
if (flag){
flag = false
let i = 10
code.innerHTML = `${i = i>=10 ? i:0+`${i}`}秒后重新获取`
let timerId = setInterval(function () {
i--
code.innerHTML = `${i = i>=10 ? i:0+`${i}`}秒后重新获取`
if (i === '00'){
clearInterval(timerId)
code.innerHTML = `重新获取`
flag = true
}
},1000)
}
})
//2、验证用户名
const username = document.querySelector('[name=username]')
username.addEventListener('change',verifyName)
function verifyName() {
const span = username.nextElementSibling
const reg = /^[a-zA-Z0-9-_]{6,10}$/
if (!reg.test(username.value)){
span.innerText = '输入不合法,请输入6-10位'
return false
}
span.innerText = ''
return true
}
//3、验证手机号
const phone = document.querySelector('[name=phone]')
phone.addEventListener('change',verifyPhone)
function verifyPhone() {
const span = phone.nextElementSibling
const reg = /^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$/
if(!reg.test(phone.value)){
span.innerText = '请输入正确的手机号'
return false
}
span.innerText = ''
return true
}
//4、验证验证码
const phoneCode = document.querySelector('[name=code]')
phoneCode.addEventListener('change',verifyPhoneCode)
function verifyPhoneCode() {
const span = phoneCode.nextElementSibling
const reg = /^\d{6}$/
if (!reg.test(phoneCode.value)){
span.innerText = '请输入6位验证码'
return false
}
span.innerText = ''
return true
}
//5、验证密码
const password = document.querySelector('[name=password]')
password.addEventListener('change',verifyPassword)
function verifyPassword() {
const span = password.nextElementSibling
const reg = /^[a-zA-Z0-9-_]{6,20}$/
if (!reg.test(password.value)){
span.innerText = '请输入6~20位字母数字以及符号'
return false
}
span.innerText = ''
return true
}
//6、再次输入密码
const confirm = document.querySelector('[name=confirm]')
confirm.addEventListener('change',verifyConfirm)
function verifyConfirm() {
const span = confirm.nextElementSibling
if (confirm.value !== password.value){
span.innerText = '两次密码输入不正确'
return false
}
span.innerHTML = ''
return true
}
//7、已阅读并同意按钮
const queren = document.querySelector('.icon-queren')
queren.addEventListener('click',function () {
this.classList.toggle('icon-queren2')
})
//8、提交
const form = document.querySelector('form')
form.addEventListener('submit',function (e) {
//不包含icon-queren2 证明没有勾选
if (!queren.classList.contains('icon-queren2')){
alert('请勾选同意按钮')
e.preventDefault()
}else {
// if中只能执行true的
// 如果没输入对返回的是个false 取反为true 那么就执行if中的语句
// 如果输入对了返回的是个true 取反为false 那么就不执行if中的语句
if (!verifyName()) e.preventDefault()
if (!verifyPhone()) e.preventDefault()
if (!verifyPhoneCode()) e.preventDefault()
if (!verifyPassword()) e.preventDefault()
if (!verifyConfirm()) e.preventDefault()
}
})
</script>
判断是否有类
元素.classList.contains() 看看有没有包含某个类,如果有则返回true,么有则返回false
四、JavaScript进阶
作用域
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,作用域分为全局作用域和局部作用域。
局部作用域
局部作用域分为函数作用域和块作用域。
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
<script>
// 声明 counter 函数
function counter(x, y) {
// 函数内部声明的变量
const s = x + y
console.log(s) // 18
}
// 设用 counter 函数
counter(10, 8)
// 访问变量 s
console.log(s)// 外部引用s会报错
</script>
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
块作用域
在 JavaScript 中使用 {}
包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
<script>
{
// age 只能在该代码块中被访问
let age = 18;
console.log(age); // 正常
}
// 超出了 age 的作用域
console.log(age) // 报错
let flag = true;
if(flag) {
// str 只能在该代码块中被访问
let str = 'hello world!'
console.log(str); // 正常
}
// 超出了 age 的作用域
console.log(str); // 报错
for(let t = 1; t <= 6; t++) {
// t 只能在该代码块中被访问
console.log(t); // 正常
}
// 超出了 t 的作用域
console.log(t); // 报错
</script>
全局作用域
常量的概念
JavaScript 中除了变量外还有常量,常量与变量本质的区别是【常量必须要有值且不允许被重新赋值】,常量值为对象时其属性和方法允许重新赋值。
<script>
// 必须要有值
const version = '1.0.0';
// 不能重新赋值
// version = '1.0.1';
// 常量值为对象类型
const user = {
name: '小明',
age: 18
}
// 不能重新赋值
user = {};
// 属性和方法允许被修改
user.name = '小小明';
user.gender = '男';
</script>
总结
let
声明的变量会产生块作用域,var
不会产生块作用域const
声明的常量也会产生块作用域- 不同代码块之间的变量无法互相访问
- 推荐使用
let
或const
注:开发中 let
和 const
经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const
声明成常量。
全局作用域的概念
<script>文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
<script>
// 此处是全局
function sayHi() {
// 此处为局部
}
// 此处为全局
</scrip>
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
<script>
// 全局变量 name
const name = '小明'
// 函数作用域中访问全局
function sayHi() {
// 此处为局部
console.log('你好' + name)
}
// 全局变量 flag 和 x
const flag = true
let x = 10
// 块作用域中访问全局
if(flag) {
let y = 5
console.log(x + y) // x 是全局的
}
</script>
总结
- 为
window
对象动态添加的属性默认也是全局的,不推荐! - 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
作用域链
作用域链本质上是底层的变量查找机制。
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
作用域链本质上是底层的变量查找机制
查找规则是会优先查找当前函数作用域中查找变量,查找不到则会依次逐级查找父级作用域直到全局作用域
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
JS的垃圾回收机制
垃圾回收机制的概念
垃圾回收机制(Garbage Collection) 简称 GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。
正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收)的情况
不再用到的内存,没有及时释放,就叫做内存泄漏
内存的生命周期
JS环境中分配的内存, 一般有如下生命周期:
- 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
- 内存使用:即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕,由垃圾回收自动回收不再使用的内存
- 说明:
全局变量一般不会回收(关闭页面回收);
一般情况下局部变量的值, 不用了, 会被自动回收掉
JS垃圾回收机制的算法
堆栈空间分配区别:
- 栈(操作系统): 由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆(操作系统): 一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
一把常见的浏览器垃圾回收算法为两种分别是引用计数法 和 标记清除法
引用计数法
IE采用的引用计数算法, 定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象
算法:
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会累加 ++
- 如果减少一个引用就减1 –
- 如果引用次数是0 ,则释放内存
嵌套引用
标记清除法
现代的浏览器已经不再使用引用计数算法了。
现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
标记清除法的核心思路是从根部扫描对象,能查找到的就是使用的,查找不到的就要回收
闭包
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function outer() {
let i=0
// console.log(i)
function inter() {
i++
console.log(`访问了${i}次`)
}
return inter
}
const fn = outer()
//fn接收到outer()之后调用fn()函数
fn()
</script>
</body>
</html>
总结:
1.怎么理解闭包?
- 闭包 = 内层函数 + 外层函数的变量
2.闭包的作用?
- 封闭数据,实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
3.闭包可能引起的问题?
- 内存泄漏
变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问
<script>
// 访问变量 str
console.log(str + 'world!');
// 声明变量 str
var str = 'hello ';
</script>
总结:
- 变量在未声明即被访问时会报语法错误
- 变量在声明之前即被访问,变量的值为
undefined
let
声明的变量不存在变量提升,推荐使用let
- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let
可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可查阅资料。
说明:
JS初学者经常花很多时间才能习惯变量提升,还经常出现一些意想不到的bug,正因为如此,ES6 引入了块级作用域,用let 或者 const声明变量,让代码写法更加规范和人性化。
用var关键字声明变量会有变量提升
变量提升流程
先把var 变量提升到当前作用域于最前面
只提升变量声明, 不提升变量赋值
然后依次执行代码
函数进阶
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
<script>
// 调用函数
foo()
// 声明函数
function foo() {
console.log('声明之前即被调用...')
}
// 不存在提升现象
bar() // 错误
var bar = function () {
console.log('函数表达式不存在提升现象...')
}
</script>
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
函数参数
函数参数的使用细节,能够提升函数应用的灵活度。
默认值
<script>
// 设置参数默认值
function sayHi(name="小明", age=18) {
document.write(`<p>大家好,我叫${name},我今年${age}岁了。</p>`);
}
// 调用函数
sayHi();
sayHi('小红');
sayHi('小刚', 21);
</script>
总结:
- 声明函数时为形参赋值即为参数的默认值
- 如果参数未自定义默认值时,参数的默认值为
undefined
- 调用函数时没有传入对应实参时,参数的默认值被当做实参传入
动态参数
arguments` 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//动态参数像一个数组,但是不能使用数组中的方法
//求传进来的不定数字中的和
function getSum() {
let sum = 0;
for (let i=0; i<arguments.length; i++){
sum += arguments[i]
}
console.log(sum)
}
getSum(1,2,3)
getSum(1,2,3,4,5)
</script>
</body>
</html>
总结:
arguments
是一个伪数组arguments
的作用是动态获取函数的实参
剩余参数
是一个真数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//剩余参数可使用数组中的方法
function getSum(...arr) {
let sum = 0
for (let i=0; i<arr.length; i++){
sum += arr[i]
}
console.log(sum)
}
getSum(1,2,3)
getSum(1,2,3,4,5)
</script>
</body>
</html>
- …` 是语法符号,置于最末函数形参之前,用于获取多余的实参
- 借助
...
获取的剩余实参,是个真数组
展开运算符
展开运算符(…),将一个数组进行展开
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//展开运算符
const arr1 = [1,2,3,4,5]
console.log(...arr1)
//嵌套
function f(...arr) {
console.log(Math.max(...arr))
console.log(Math.min(...arr))
}
f(1,2,3)
//整合数组
const array1 = [1,2,3]
const array2 = [4,5,6]
const newArr = [...array1,...array2]
console.log(newArr)
</script>
</body>
</html>
说明
不会修改原数组
展开运算符 和 剩余参数 的区别
剩余参数
:函数参数使用,得到真数组
展开运算符
:数组中使用,数组展开
典型运用场景
: 求数组最大值(最小值)、合并数组等
箭头函数
什么是箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
使用场景
:箭头函数更适用于那些本来需要匿名函数的地方
箭头函数的基本用法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 普通函数的写法
function f(x) {
console.log(x)
}
f(1)
//箭头函数的写法
const fn = (x) =>{
console.log(x)
}
fn(1)
//当只有一个参数的时候小括号可以省略 没有参数或多个参数的时候小括号不省略
const fn = x => {
console.log(x)
}
fn(1)
//当代码块内只需要输出一句话的时候大括号可以省略
const fn = x => console.log(x)
fn(1)
//只有一行代码当代码块返回return结果时 大括号可以省略
const fn = x => x + x
console.log(fn(1))
//加括号的函数体返回对象字面量表达式 箭头函数可以返回一个对象
const fn = (name) => ({uname : name})
console.log(fn('张三'))
</script>
</body>
</html>
总结
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
()
- 箭头函数函数体只有一行代码时可以省略花括号
{}
,并自动做为返回值被返回 - 加括号的函数体返回对象字面量表达式
箭头函数参数
箭头函数中没有 arguments
,只能使用 ...
动态获取实参
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//只有剩余参数没有动态参数 arguments
const fn = (...arr) => {
let sum=0
for (let i=0; i<arr.length; i++){
sum += arr[i]
}
console.log(sum)
}
fn(1,2,3,4,5)
</script>
</body>
</html>
箭头函数 this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
在开发中【使用箭头函数前需要考虑函数中 this 的值】,事件回调函数使用箭头函数时,this 为全局的 window,因此
DOM事件回调函数为了简便,还是不太推荐使用箭头函数
解构赋值
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构
、对象解构
两大类型。
数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//第一种方式
const arr = [1,2,3]
const [a,b,c] = arr
console.log(a)
console.log(b)
console.log(c)
// //第二种方式
const [a,b,c] = [1,2,3]
console.log(a)
console.log(b)
console.log(c)
//变量交换
let a = 1
let b = 2
let c = 3;
[a,b,c] = [c,b,a]
console.log(a)
console.log(b)
console.log(c)
//剩余参数接收值
const [a,b,...c] = [1,2,3,4]
console.log(a)
console.log(b)
console.log(c) //c是真数组 包含3和4
//防止undefined传递给默认值在定义a和b时传一个默认值
const [a=0,b=0] = [1]
console.log(a)
console.log(b)
//按需导入赋值
const [a,b,,d] = [1,2,3,4]
console.log(a)
console.log(b)
console.log(d)
//按需导入求值
const [a,b,c,d] = [1,2,[3,4]]
console.log(a)
console.log(b)
console.log(c)
console.log(d)
//按需导入嵌套数组解构
const [a,b,[c,d]] = [1,2,[3,4]]
console.log(a)
console.log(b)
console.log(c)
console.log(d)
</script>
</body>
</html>
总结:
- 赋值运算符
=
左侧的[]
用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 - 变量的顺序对应数组单元值的位置依次进行赋值操作
- 变量的数量大于单元值数量时,多余的变量将被赋值为
undefined
- 变量的数量小于单元值数量时,可以通过
...
获取剩余单元值,但只能置于最末位 - 允许初始化变量的默认值,且只有单元值为
undefined
时默认值才会生效
注
:支持多维解构赋值,比较复杂后续有应用需求时再进一步分析
对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//方法一
const obj = {
uname:'张三',
age:18
}
const {uname,age} = obj
console.log(uname)
console.log(age)
//方法二
const {uname,age} = {uname:'张三',age:18}
console.log(uname)
console.log(age)
//声明变量的名字和属性的名字必须一模一样 且不能和外面声明的变量名有冲突
//冲突的话要使用 结构名:新名 的方式
const uname = '李四'
const {uname:userName, age} = {uname:'张三',age:18}
console.log(uname)
console.log(userName)
console.log(age)
//数组对象进行解构
const pig = [
{
uname: '佩奇',
age: 6
}
]
const [{uname,age}] = pig
console.log(uname)
console.log(age)
//多级对象解构
const pig = {
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
brother: '乔治'
},
age: 18
}
const {name, family: {mother,father,brother}, age} = pig
console.log(name)
console.log(mother)
console.log(father)
console.log(brother)
console.log(age)
//多级数组对象解构
const pig = [
{
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
brother: '乔治'
},
age: 18
}
]
const [{name, family: {mother,father,brother}, age}] = pig
console.log(name)
console.log(mother)
console.log(father)
console.log(brother)
console.log(age)
</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>
// 1. 这是后台传递过来的数据
const msg = {
"code": 200,
"msg": "获取新闻列表成功",
"data": [
{
"id": 1,
"title": "5G商用自己,三大运用商收入下降",
"count": 58
},
{
"id": 2,
"title": "国际媒体头条速览",
"count": 56
},
{
"id": 3,
"title": "乌克兰和俄罗斯持续冲突",
"count": 1669
},
]
}
// 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面
// const {data} = msg
console.log(JSON.parse(JSON.stringify(msg)))
// // 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
function render({data}) {
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)
// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({data: myData}) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
</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>商品渲染</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.list {
width: 990px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
}
.item {
width: 240px;
margin-left: 10px;
padding: 20px 30px;
transition: all .5s;
margin-bottom: 20px;
}
.item:nth-child(4n) {
margin-left: 0;
}
.item:hover {
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
transform: translate3d(0, -4px, 0);
cursor: pointer;
}
.item img {
width: 100%;
}
.item .name {
font-size: 18px;
margin-bottom: 10px;
color: #666;
}
.item .price {
font-size: 22px;
color: firebrick;
}
.item .price::before {
content: "¥";
font-size: 14px;
}
.filter {
display: flex;
width: 990px;
margin: 0 auto;
padding: 50px 30px;
}
.filter a {
padding: 10px 20px;
background: #f5f5f5;
color: #666;
text-decoration: none;
margin-right: 20px;
}
.filter a:active,
.filter a:focus {
background: #05943c;
color: #fff;
}
</style>
</head>
<body>
<div class="filter">
<a data-index="1" href="javascript:;">0-100元</a>
<a data-index="2" href="javascript:;">100-300元</a>
<a data-index="3" href="javascript:;">300元以上</a>
<a href="javascript:;">全部区间</a>
</div>
<div class="list">
<!-- <div class="item">
<img src="" alt="">
<p class="name"></p>
<p class="price"></p>
</div> -->
</div>
<script>
// 2. 初始化数据
const goodsList = [
{
id: '4001172',
name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
price: '289.00',
picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
},
{
id: '4001594',
name: '日式黑陶功夫茶组双侧把茶具礼盒装',
price: '288.00',
picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg',
},
{
id: '4001009',
name: '竹制干泡茶盘正方形沥水茶台品茶盘',
price: '109.00',
picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',
},
{
id: '4001874',
name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
price: '488.00',
picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',
},
{
id: '4001649',
name: '大师监制龙泉青瓷茶叶罐',
price: '139.00',
picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',
},
{
id: '3997185',
name: '与众不同的口感汝瓷白酒杯套组1壶4杯',
price: '108.00',
picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg',
},
{
id: '3997403',
name: '手工吹制更厚实白酒杯壶套装6壶6杯',
price: '100.00',
picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg',
},
{
id: '3998274',
name: '德国百年工艺高端水晶玻璃红酒杯2支装',
price: '139.00',
picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg',
},
]
// 1. 渲染函数 封装
function render(arr) {
// 声明空字符串
let str = ''
// 遍历数组
arr.forEach(item => {
// 解构
const { name, picture, price } = item
str += `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div>
`
})
// 追加给list
document.querySelector('.list').innerHTML = str
}
render(goodsList) // 页面一打开就需要渲染
// 2. 过滤筛选
document.querySelector('.filter').addEventListener('click', e => {
// e.target.dataset.index e.target.tagName
const { tagName, dataset } = e.target
// 判断
if (tagName === 'A') {
// console.log(11)
// arr 返回的新数组
let arr = goodsList
if (dataset.index === '1') {
arr = goodsList.filter(item => item.price > 0 && item.price <= 100)
} else if (dataset.index === '2') {
arr = goodsList.filter(item => item.price >= 100 && item.price <= 300)
} else if (dataset.index === '3') {
arr = goodsList.filter(item => item.price >= 300)
}
// 渲染函数
render(arr)
}
})
</script>
</body>
</html>
深入对象
创建对象的三种方式
构造函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//构造函数只能使用function修饰不能使用箭头函数.因为箭头函数没有this
function Pig(name,age){
this.name = name
this.age = age
}
const pig1 = new Pig('佩奇',6)
const pig2 = new Pig('乔治',10)
Object
console.log(pig1)
console.log(pig2)
</script>
</body>
</html>
总结:
- 使用
new
关键字调用函数的行为被称为实例化 - 实例化构造函数时没有参数时可以省略
()
- 构造函数的返回值即为新创建的对象
- 构造函数内部的
return
返回的值无效!
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写
实例成员和静态成员
实例成员:
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
说明:
- 实例对象的属性和方法即为实例成员
- 为构造函数传入参数,动态创建结构相同但值不同的对象
- 构造函数创建的实例对象彼此独立互不影响。
静态成员:
构造函数的属性和方法被称为静态成员
说明:
- 构造函数的属性和方法被称为静态成员
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的 this 指向构造函数本身
内置构造函数
在 JavaScript 中最主要的数据类型有 6 种:
基本数据类型:
字符串、数值、布尔、undefined、null
引用类型:
对象
但是,我们会发现有些特殊情况:
其实字符串、数值、布尔、等基本类型也都有专门的构造函数,这些我们称为包装类型。
JS中几乎所有的数据都可以基于构成函数创建。
引用类型
Object,Array,RegExp,Date 等
包装类型
String,Number,Boolean 等
Object常用静态方法
keys
获取所有的属性名
values
获取所有的属性值
assign
拷贝对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
const obj = {uname: 'zhangsan',age: 18}
//获取所有的属性名
console.log(Object.keys(obj))
//获取所有的属性值
console.log(Object.values(obj))
//拷贝对象
const newObj = {}
Object.assign(newObj,obj)
console.log(newObj)
//给对象进行追加属性
Object.assign(obj,{gender: '女'})
console.log(obj)
</script>
</body>
</html>
String方法
String
是内置的构造函数,用于创建字符串。
<script>
// 使用构造函数创建字符串
let str = new String('hello world!');
// 字面量创建字符串
let str2 = '你好,世界!';
// 检测是否属于同一个构造函数
console.log(str.constructor === str2.constructor); // true
console.log(str instanceof String); // false
</script>
length方法
实例属性 length
用来获取字符串的度长
//空格符号也算
const str = 'pink, red'
console.log(str.length)
split方法
实例方法 split('分隔符')
用来将字符串拆分成数组
//split将字符串转为数组,和join正好相反,join是将数组转换为字符串
const str = 'pink,red'
console.log(str.split(','))
substring方法
实例方法 substring(需要截取的第一个字符的索引[,结束的索引号])
用于字符串截取
//subString 从0开始算起,
//如果省略 结束的索引号,默认取到最后 结束的索引号不包含想要截取的部分
const str = '我正在学习javascript'
console.log(str.substring(2))
console.log(str.substring(2,3))
startsWith方法
实例方法 startsWith(检测字符串[, 检测位置索引号])
检测是否以某字符开头
//startsWith
const str = '我正在学习javascript'
console.log(str.startsWith('我'))
includes方法
实例方法 includes(搜索的字符串[, 检测位置索引号])
判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false
//includes
const str = '我正在学习javascript'
console.log(str.includes('java'))
toUpperCase方法
实例方法 toUpperCase
用于将字母转换成大写
//文字不识别
const str = 'abcdefg'
console.log(str.toUpperCase())
toLowerCase方法
实例方法 toLowerCase
用于将就转换成小写
//文字不识别
const str = 'ABCDEFG'
console.log(str.toLowerCase())
indexOf方法
实例方法 indexOf
检测是否包含某字符
//有返回对应索引号,没有返回-1,不区分大小写
const str = 'ABCDEFG'
console.log(str.indexOf('A'))
endsWith方法
实例方法 endsWith
检测是否以某字符结尾
//是返回true,否返回false
const str = 'abcdefg'
console.log(str.endsWith('g'))
replace方法
实例方法 replace
用于替换字符串,支持正则匹配
```javascript
//可替换多个字符,不识别大小写
const str = 'abcdefg你好'
console.log(str.replace('好','真棒'))
```
match方法
实例方法 match
用于查找字符串,支持正则匹配
```javascript
//返回的是object
const str = 'abcdefg你好'
console.log(str.match('c'))
```
toString方法
注:String 也可以当做普通函数使用,这时它的作用是强制转换成字符串数据类型。
//转换为字符串
const num = 10
console.log(String(num))
console.log(num.toString())
Number方法
Number
是内置的构造函数,用于创建数值。
<script>
// 使用构造函数创建数值
let x = new Number('10')
let y = new Number(5)
// 字面量创建数值
let z = 20
</script>
总结:
推荐使用字面量方式声明数值,而不是 Number
构造函数
toFixed方法
实例方法 toFixed
用于设置保留小数位的长度
const num1 = 10.923
const num2 = 10
console.log(num1.toFixed(2)) //10.92
console.log(num2.toFixed(2)) //10.00
综合案例
<!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-sizing: border-box;
}
.list {
width: 990px;
margin: 100px auto 0;
}
.item {
padding: 15px;
transition: all .5s;
display: flex;
border-top: 1px solid #e4e4e4;
}
.item:nth-child(4n) {
margin-left: 0;
}
.item:hover {
cursor: pointer;
background-color: #f5f5f5;
}
.item img {
width: 80px;
height: 80px;
margin-right: 10px;
}
.item .name {
font-size: 18px;
margin-right: 10px;
color: #333;
flex: 2;
}
.item .name .tag {
display: block;
padding: 2px;
font-size: 12px;
color: #999;
}
.item .price,
.item .sub-total {
font-size: 18px;
color: firebrick;
flex: 1;
}
.item .price::before,
.item .sub-total::before,
.amount::before {
content: "¥";
font-size: 12px;
}
.item .spec {
flex: 2;
color: #888;
font-size: 14px;
}
.item .count {
flex: 1;
color: #aaa;
}
.total {
width: 990px;
margin: 0 auto;
display: flex;
justify-content: flex-end;
border-top: 1px solid #e4e4e4;
padding: 20px;
}
.total .amount {
font-size: 18px;
color: firebrick;
font-weight: bold;
margin-right: 50px;
}
</style>
</head>
<body>
<div class="list">
<!-- <div class="item">
<img src="https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg" alt="">
<p class="name">称心如意手摇咖啡磨豆机咖啡豆研磨机 <span class="tag">【赠品】10优惠券</span></p>
<p class="spec">白色/10寸</p>
<p class="price">289.90</p>
<p class="count">x2</p>
<p class="sub-total">579.80</p>
</div> -->
</div>
<div class="total">
<div>合计:<span class="amount">1000.00</span></div>
</div>
<script>
const goodsList = [
{
id: '4001172',
name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
price: 289.9,
picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
count: 2,
spec: { color: '白色' }
},
{
id: '4001009',
name: '竹制干泡茶盘正方形沥水茶台品茶盘',
price: 109.8,
picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',
count: 3,
spec: { size: '40cm*40cm', color: '黑色' }
},
{
id: '4001874',
name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
price: 488,
picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',
count: 1,
spec: { color: '青色', sum: '一大四小' }
},
{
id: '4001649',
name: '大师监制龙泉青瓷茶叶罐',
price: 139,
picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',
count: 1,
spec: { size: '小号', color: '紫色' },
gift: '50g茶叶,清洗球'
}
]
//渲染数据
document.querySelector('.list').innerHTML = goodsList.map(item => {
const { name, price, picture, count, spec, gift} = item
//规格文字模块处理
const sp = Object.values(spec).join('/')
//赠品
const gf = gift ? gift.split(',').map(item => `<span class="tag">【赠品】${item}</span>`).join('') : ''
//小计模块
const subTotal = ((price * 100 * count)/100).toFixed(2)
return`
<div class="item">
<img src=${picture} alt="">
<p class="name">${name} <span class="tag">${gf}</span></p>
<p class="spec">${sp}</p>
<p class="price">${price.toFixed(2)}</p>
<p class="count">x${count}</p>
<p class="sub-total">${subTotal}</p>
</div>
`
}
).join('')
//合计模块
document.querySelector('.amount').innerHTML =
goodsList.reduce((prev,item) => prev + (item.price *100 * item.count) / 100,0).toFixed(2)
</script>
</body>
</html>
构造函数整合面向对象
<script>
function Person() {
this.name = '佚名'
// 设置名字
this.setName = function (name) {
this.name = name
}
// 读取名字
this.getName = () => {
console.log(this.name)
}
}
// 实例对像,获得了构造函数中封装的所有逻辑
let p1 = new Person()
p1.setName('小明')
console.log(p1.name)
// 实例对象
let p2 = new Person()
console.log(p2.name)
</script>
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
同样的将变量和函数组合到了一起并能通过 this 实现数据的共享,所不同的是借助构造函数创建出来的实例对象之
间是彼此不影响的
总结:
- 构造函数体现了面向对象的封装特性
- 构造函数实例创建的对象彼此独立、互不影响
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
构造函数方法很好用,但是 存在浪费内存
的问题
原型对象
构造函数通过原型分配的函数是所有对象所 共享的。
- JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象
- 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
- 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
- 构造函数和原型对象中的this 都指向 实例化的对象
<script>
function Person() {
}
// 每个函数都有 prototype 属性
console.log(Person.prototype)
</script>
了解了 JavaScript 中构造函数与原型对象的关系后,再来看原型对象具体的作用,如下代码所示:
<script>
function Person() {
// 此处未定义任何方法
}
// 为构造函数的原型对象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~');
}
// 实例化
let p1 = new Person();
p1.sayHi(); // 输出结果为 Hi~
</script>
构造函数 Person
中未定义任何方法,这时实例对象调用了原型对象中的方法 sayHi
,接下来改动一下代码:
<script>
function Person() {
// 此处定义同名方法 sayHi
this.sayHi = function () {
console.log('嗨!');
}
}
// 为构造函数的原型对象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~');
}
let p1 = new Person();
p1.sayHi(); // 输出结果为 嗨!
</script>
构造函数 Person
中定义与原型对象中相同名称的方法,这时实例对象调用则是构造函中的方法 sayHi
。
通过以上两个简单示例不难发现 JavaScript 中对象的工作机制:当访问对象的属性或方法时,先在当前实例对象是查找,然后再去原型对象查找,并且原型对象被所有实例共享。
<script>
function Person() {
// 此处定义同名方法 sayHi
this.sayHi = function () {
console.log('嗨!' + this.name)
}
}
// 为构造函数的原型对象添加方法
Person.prototype.sayHi = function () {
console.log('Hi~' + this.name)
}
// 在构造函数的原型对象上添加属性
Person.prototype.name = '小明'
let p1 = new Person()
p1.sayHi(); // 输出结果为 嗨!
let p2 = new Person()
p2.sayHi()
</script>
总结:结合构造函数原型的特征,实际开发重往往会将封装的功能函数添加到原型对象中。
constructor 属性
在哪里? 每个原型对象里面都有个constructor 属性(constructor 构造函数)
作用:该属性指向该原型对象的构造函数, 简单理解,就是指向我的爸爸,我是有爸爸的孩子
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值.
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
对象原型
对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype
原型对象的属性和方法,就是因为对象有 proto 原型的存在。
注意:
- proto 是JS非标准属性
- [[prototype]]和__proto__意义相同
- 用来表明当前实例对象指向哪个原型对象prototype
- __proto__对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
原型继承
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承
的特性。
龙生龙、凤生凤、老鼠的儿子会打洞描述的正是继承的含义。
<body>
<script>
// 继续抽取 公共的部分放到原型上
// const Person1 = {
// eyes: 2,
// head: 1
// }
// const Person2 = {
// eyes: 2,
// head: 1
// }
// 构造函数 new 出来的对象 结构一样,但是对象不一样
function Person() {
this.eyes = 2
this.head = 1
}
// console.log(new Person)
// 女人 构造函数 继承 想要 继承 Person
function Woman() {
}
// Woman 通过原型来继承 Person
// 父构造函数(父类) 子构造函数(子类)
// 子类的原型 = new 父类
Woman.prototype = new Person() // {eyes: 2, head: 1}
// 指回原来的构造函数
Woman.prototype.constructor = Woman
// 给女人添加一个方法 生孩子
Woman.prototype.baby = function () {
console.log('宝贝')
}
const red = new Woman()
console.log(red)
// console.log(Woman.prototype)
// 男人 构造函数 继承 想要 继承 Person
function Man() {
}
// 通过 原型继承 Person
Man.prototype = new Person()
Man.prototype.constructor = Man
const pink = new Man()
console.log(pink)
</script>
</body>
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对
象的链状结构关系称为原型链
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kb6F7DgJ-1680947312051)(assets/1676793388695.png)]
<body>
<script>
// function Objetc() {}
console.log(Object.prototype)
console.log(Object.prototype.__proto__)
function Person() {
}
const ldh = new Person()
// console.log(ldh.__proto__ === Person.prototype)
// console.log(Person.prototype.__proto__ === Object.prototype)
console.log(ldh instanceof Person)
console.log(ldh instanceof Object)
console.log(ldh instanceof Array)
console.log([1, 2, 3] instanceof Array)
console.log(Array instanceof Object)
</script>
</body>
① 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
② 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)
③ 如果还没有就查找原型对象的原型(Object的原型对象)
④ 依此类推一直找到 Object 为止(null)
⑤ __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥ 可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
综合案例
<!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>面向对象封装消息提示</title>
<style>
.modal {
width: 300px;
min-height: 100px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
border-radius: 4px;
position: fixed;
z-index: 999;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
background-color: #fff;
}
.modal .header {
line-height: 40px;
padding: 0 10px;
position: relative;
font-size: 20px;
}
.modal .header i {
font-style: normal;
color: #999;
position: absolute;
right: 15px;
top: -2px;
cursor: pointer;
}
.modal .body {
text-align: center;
padding: 10px;
}
.modal .footer {
display: flex;
justify-content: flex-end;
padding: 10px;
}
.modal .footer a {
padding: 3px 8px;
background: #ccc;
text-decoration: none;
color: #fff;
border-radius: 2px;
margin-right: 10px;
font-size: 14px;
}
.modal .footer a.submit {
background-color: #369;
}
</style>
</head>
<body>
<button id="delete">删除</button>
<button id="login">登录</button>
<!-- <div class="modal">
<div class="header">温馨提示 <i>x</i></div>
<div class="body">您没有删除权限操作</div>
</div> -->
<script>
//写一个构造函数用来创建盒子
function Modal(title = '',message = '') {
this.title =title
this.message = message
//创建盒子
this.modalBox = document.createElement('div')
//添加类名
this.modalBox.className = 'modal'
//添加内容
this.modalBox.innerHTML = `
<div class="modal">
<div class="header">${title} <i>x</i></div>
<div class="body">${message}</div>
</div>
`
}
//创建open方法
//该方法不要使用箭头函数,因为箭头函数没有this
Modal.prototype.open = function(){
//判断有没有盒子,有删除没有添加
const box = document.querySelector('.modal')
//逻辑中断,获取不到box为假执行的是前面的代码,只是单纯的获取,不做任何操作
// 获取到了为真执行的是后面的代码--进行移除
box && box.remove()
//一定要加this证明是当前调用的函数
document.body.append(this.modalBox)
//close是在渲染页面也就是open的时候进行绑定的
this.modalBox.querySelector('i').addEventListener('click',() =>{
//用箭头函数因为想让父级关闭,箭头函数本身没有this所以指向父级
//推荐使用箭头函数但是使用function也可以
this.close()
})
}
Modal.prototype.close = function(){
//modalBox是上边创建的
this.modalBox.remove()
}
//获取按钮
//删除
document.querySelector('#delete').addEventListener('click',() => {
const del = new Modal('温馨提示', '您没有权限删除')
del.open()
})
//登录
document.querySelector('#login').addEventListener('click',() => {
const login = new Modal('友情提示', '您还么有注册账号')
login.open()
})
</script>
</body>
</html>
深浅拷贝
浅拷贝
浅拷贝:拷贝的是地址
拷贝另一个对象的基本数据类型是值进行复制,但是引用数据类型是地址进行一个复制,也就是说新旧对象里面的引用数据类型还是一样的地址
常见方法:
- 拷贝对象:Object.assgin() / 展开运算符 {…obj} 拷贝对象
- 拷贝数组:Array.prototype.concat() 或者 […arr]
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
<!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>
const obj = {
uname: '张三',
age: 18,
family: {
baby: '小张三'
}
}
// 浅拷贝
const o = { ...obj }
const o = obj
console.log(o)
o.age = 20
console.log(o === obj)
console.log(obj)
const o = {}
Object.assign(o, obj)
o.age = 20
o.family.baby = '老张三'
console.log(o === obj)
// console.log(obj)
</script>
</body>
</html>
深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
- 通过递归实现深拷贝
- lodash/cloneDeep
- 通过JSON.stringify()实现
递归实现深拷贝
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
- 简单理解:函数内部自己调用自己, 这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div></div>
<script>
//setTimeOut实现递归函数
const getTime = () => {
document.querySelector('div').innerHTML = new Date().toLocaleString()
setTimeout(getTime,1000)
}
getTime()
</script>
</body>
</html>
lodash实现深拷贝
<!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 src="./lodash.min.js"></script>
<script>
const obj = {
uname: '张三',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小张三'
}
}
const o = _.cloneDeep(obj)
console.log(o)
o.family.baby = '老张三'
console.log(obj)
</script>
</body>
</html>
改变this指向
call
参数中不能写数组
<!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>
const obj = {
uname: 'pink'
}
function fn(x, y) {
console.log(this) // window
console.log(x + y)
}
// 1. 调用函数
// 2. 改变 this 指向
fn.call(obj, 1, 2)
</script>
</body>
</html>
总结:
1. call
方法能够在调用函数的同时指定 this
的值
2. 使用 call
方法调用函数时,第1个参数为 this
指定的值
3. call
方法的其余参数会依次自动传入函数做为函数的参数
apply
参数中可以写数组
<!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>
const obj = {
age: 18
}
function fn(x, y) {
console.log(this) // {age: 18}
console.log(x + y)
}
// 1. 调用函数
// 2. 改变this指向
// fn.apply(this指向谁, 数组参数)
fn.apply(obj, [1, 2])
// 3. 返回值 本身就是在调用函数,所以返回值就是函数的返回值
// 使用场景: 求数组最大值
// const max = Math.max(1, 2, 3)
// console.log(max)
const arr = [100, 44, 77]
const max = Math.max.apply(Math, arr)
const min = Math.min.apply(null, arr)
console.log(max, min)
// 使用场景: 求数组最大值
console.log(Math.max(...arr))
</script>
</body>
</html>
总结:
apply
方法能够在调用函数的同时指定this
的值- 使用
apply
方法调用函数时,第1个参数为this
指定的值 apply
方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数
bind
bind
方法并不会调用函数,而是创建一个指定了 this
值的新函数,优先级在call和apply之后
<!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>
<button>发送短信</button>
<script>
const obj = {
age: 18
}
function fn() {
console.log(this)
}
// 1. bind 不会调用函数
// 2. 能改变this指向
// 3. 返回值是个函数, 但是这个函数里面的this是更改过的obj
const fun = fn.bind(obj)
// console.log(fun)
fun()
// 需求,有一个按钮,点击里面就禁用,2秒钟之后开启
document.querySelector('button').addEventListener('click', function () {
// 禁用按钮
this.disabled = true
window.setTimeout(function () {
// 在这个普通函数里面,我们要this由原来的window 改为 btn
// debugger
this.disabled = false
//使用call或者是apply那就是立即执行函数不会等2000ms到了再执行
//因为使用bind的话先执行的是同步任务,然后再执行setTimeout
//使用apply或者使用call的时候优先级比较高也就是立即执行,
//然后针对当前项目来说依次往下执行
}.bind(this), 1000)// 这里的this 和 btn 一样
})
</script>
</body>
</html>
bind
方法创建新的函数,与原函数的唯一的变化是改变了 this
的值。
throw
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行
总结:
- throw 抛出异常信息,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
<script>
function counter(x, y) {
if(!x || !y) {
// throw '参数不能为空!';
throw new Error('参数不能为空!')
}
return x + y
}
counter()
</script>
总结:
throw
抛出异常信息,程序也会终止执行throw
后面跟的是错误提示信息Error
对象配合throw
使用,能够设置更详细的错误信息
try … catch
<script>
function foo() {
try {
// 查找 DOM 节点
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (error) {
// try 代码段中执行有错误时,会执行 catch 代码段
// 查看错误信息
console.log(error.message)
// 终止代码继续执行
return
}
finally {
alert('执行')
}
console.log('如果出现错误,我的语句不会执行')
}
foo()
</script>
总结:
try...catch
用于捕获错误信息- 将预估可能发生错误的代码写在
try
代码段中 - 如果
try
代码段中出现错误后,会执行catch
代码段,并截获到错误信息
JSON序列化
使用stringify
<body>
<script>
const obj = {
uname: 'pink',
age: 18,
hobby: ['乒乓球', '足球'],
family: {
baby: '小pink'
}
}
// 把对象转换为 JSON 字符串
// console.log(JSON.stringify(obj))
const o = JSON.parse(JSON.stringify(obj))
console.log(o)
o.family.baby = '123'
console.log(obj)
</script>
</body>
断点调试
在想调试的代码上面添加debugger
防抖和节流
防抖
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间
开发使用场景- 搜索框防抖
假设输入就可以发送请求,但是不能每次输入都去发送请求,输入比较快发送请求会比较多
我们设定一个时间,假如300ms, 当输入第一个字符时候,300ms后发送请求,但是在200ms的时候又输入了一个字符,
则需要再等300ms 后发送请求
防抖底层
<!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: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
let i = 1 // 让这个变量++
// 鼠标移动函数
function mouseMove() {
box.innerHTML = i++
// 如果里面存在大量操作 dom 的情况,可能会卡顿
}
// 防抖函数
function debounce(fn, t) {
let timeId
//不写return的话返回的是值,但是下面box的监听事件那里需要一个函数
return function () {
// 如果有定时器就清除
if (timeId) clearTimeout(timeId)
// 开启定时器 200
timeId = setTimeout(function () {
fn()
}, t)
}
}
// box.addEventListener('mousemove', mouseMove)
box.addEventListener('mousemove', debounce(mouseMove, 200))
</script>
</body>
</html>
节流
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数,n秒时间到了之后才能接着执行
举例:
只有等到了上一个人公交车刷完卡,整个动作完成了, 第二个人才能排队跟上
节流底层
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
function throttle(fn,t){
let timer = null
return function () {
if (!timer) {
timer = setTimeout(function () {
fn()
timer = null
}, t)
}
}
}
box.addEventListener('mousemove', throttle(mouseMove,50))
</script>
</body>
</html>
防抖和节流的区别
防抖
是n秒钟之内
执行某一个函数
如果在n秒内
又执行该函数
则在此基础上延续n秒
,
节流
是n秒钟之内
执行某一个函数
如果在n秒内
又执行该函数
则执行无效
,到达n秒之后
才能执行
(如果n秒内点击了排队等待知道n秒到了之后按顺序进行执行也就是java中的重量锁)
综合案例
<!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" />
<meta name="referrer" content="never" />
<title>综合案例</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
.container {
width: 1200px;
margin: 0 auto;
}
.video video {
width: 100%;
padding: 20px 0;
}
.elevator {
position: fixed;
top: 280px;
right: 20px;
z-index: 999;
background: #fff;
border: 1px solid #e4e4e4;
width: 60px;
}
.elevator a {
display: block;
padding: 10px;
text-decoration: none;
text-align: center;
color: #999;
}
.elevator a.active {
color: #1286ff;
}
.outline {
padding-bottom: 300px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<a href="http://pip.itcast.cn">
<img src="https://pip.itcast.cn/img/logo_v3.29b9ba72.png" alt="" />
</a>
</div>
<div class="video">
<video src="https://v.itheima.net/LapADhV6.mp4" controls></video>
</div>
<div class="elevator">
<a href="javascript:;" data-ref="video">视频介绍</a>
<a href="javascript:;" data-ref="intro">课程简介</a>
<a href="javascript:;" data-ref="outline">评论列表</a>
</div>
</div>
<!-- 引用的是线上的lodash-->
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script>
const video = document.querySelector('video')
//使用setInterval直接就执行了因为我们需要进行触发,所以不能写定时器
//做一个节流,无论执行多少次,都是1s之后再进行赋值操作
video.ontimeupdate = _.throttle(() => {
localStorage.setItem('currentTime',video.currentTime)
},1000)
//使用onloadeddate ---> 意思为刷新页面的时候执行
video.onloadeddata = () => {
//考虑到第一次打开视频的情况,所以currentTime要 或上 0
video.currentTime = localStorage.getItem('currentTime') || 0
}
</script>
</body>
</html>