JavaScript学习
JavaScript是运行在浏览器端的脚本语言,JavaScript主要解决的是前端与用户交互的问题,包括使用交互与数据交互。
一、入门
1、js代码编写位置
-
-
可以将js编写到网页内部的script标签
<script type="text/javascript"> alert('浏览器弹窗'); document.write('页面输出内容'); console.log('控制台输出内容'); </script>
-
可以将js编写外部的js文件中,然后通过script标签进行引入
<script type="text/javascript" src="js/index.js"></script>
-
可以将js代码编写到标签元素指定属性中
<body> <!--超链接href属性中--> <a href="javascript:alert(123);">超链接</a> <!--行间事件,比如onclick属性--> <button onclick="alert('你点我干嘛!')">点我一下</button> </body>
-
2、 基本语法
-
-
单行注释
- //单行注释内容
-
多行注释
- /*多行注释内容*/
- 注释中的内容会被解释器忽略
- 可以通过注释来对代码进行解释说明
- 也可以通过注释来注释掉不想执行的代码
- /*多行注释内容*/
-
JS严格区分大小写
-
在JS中多个空格和换行会被忽略
- 可以利用这个特点来对代码进行格式化
-
JS中每条语句都应该以分号(;)结尾
- JS中具有自动添加分号的机制,所以如果不写分号解释器会自动添加
-
3、字面量和变量
-
-
字面量
- 字面量其实就是一个值,它所代表的含义就是它字面的意思,比如:1 2 3 4 100 "hello" true null .....
- 在js中所有的字面量都可以直接使用,但是直接使用字面量并不方便
-
变量
- 变量可以用于“存储”字面量,存储的是值的内存地址
- 变量中存储的字面量可以随意的修改
- 通过变量可以对字面量进行描述,并且变量比较方便修改
- 变量声明
- let
- 使用let声明的变量具有块作用域,在代码块中声明的变量无法在代码块的外部访问
- 使用let声明的变量不会存储在window对象中,而存在一个秘密的小地方(无法访问)
- var
- 在全局中使用var声明的变量,都会作为window对象的属性保存
- 使用var声明的变量,不具有块作用,但有函数作用域
- let
- 变量赋值
- 变量 = 值
- 变量声明和赋值同时进行
- let 变量 = 值
- var 变量 = 值
-
4、常量
-
-
在JS中,使用const声明常量,且只能赋值一次,重复赋值会报错
const PI = 3.1415926
-
在JS中除了常规的常量外,有一些对象类型的数据我们也会声明为常量
-
5、标识符
-
- 在JS中,所有可以由我们自主命名的内容,都可以认为是一个标识符,像 变量名 函数名 类名...
- 使用标识符需要遵循如下的命名规范:
- 标识符只能含有字母、数字、下划线、$,且不能以数字开头
- 标识符不能是JS中的关键字和保留字,也不建议使用内置的函数或类名作为变量名
- 命名规范:
- 通常会使用驼峰命名法
- 首字母小写,每个单词开头大写
- maxlength --> maxLength
- 首字母小写,每个单词开头大写
- 类名会使用大驼峰命名法
- 首字母大写,每个单词开头大写
-
- maxlength --> MaxLength
-
- 首字母大写,每个单词开头大写
- 常量的字母会全部大写
- MAX_LENGTH
- 通常会使用驼峰命名法
6、代码块
-
- 使用 {} 来创建代码块
- 代码块可以用来对代码进行分组,同一个代码块中的代码,就是同一组代码
- 一个代码块中的代码要么都执行,要么都不执行
- 使用 {} 来创建代码块
二、数据类型
数据类型,指那些可以赋值给变量的值,由原始值和对象共同组成。
-
- 原始值
- 数值 Number
- 大整数 BigInt
- 字符串 String
- 布尔值 Boolean
- 空值 Null
- 未定义 Undefined
- 符号 Symbol
- 对象
- 对象是JS中的一种复合数据类型,它相当于一个容器,在对象中可以存储各种不同类型数据
- 原始值只能用来表示一些简单的数据,不能表示复杂数据
- 原始值
1、原始值
-
- JS中原始值一共有7种
- 数值(Number)
- 在JS中所有的整数和浮点数都是Number类型
- JS中的数值并不是无限大的,当数值超过一定范围后会显示近似值,因此进行一些精度比较高的运算时要十分注意
- Infinity
- 是一个特殊的数值,表示无穷
- NaN
- 也是一个特殊的数值,表示非法的数值
- 使用typeof检查一个数值时,会返回'number'
- 大整数(BigInt)
- 大整数用来表示一些比较大的整数
- 大整数使用n结尾,它可以表示的数字范围是无限大(取决于内存大小)
let a = 99999999999999999999999n
- 字符串(String)
- 在JS中使用单引号或双引号来表示字符串
- 字符串中转义字符使用反斜杠(\)
- 模板字符串
- 使用反单引号` 来表示模板字符串
- 模板字符串中可以嵌入变量,格式:${变量名}
let b = 10 console.log(`b = ${b}`) // b = 10
- 使用typeof检查一个字符串时会返回"string"
- 布尔值(Boolean)
- 布尔值主要用来进行逻辑判断
- 布尔值只有两个:true 和 false
- 使用typeof检查一个布尔值时会返回 "boolean"
- 空值 (Null)
- 空值用来表示空对象
- 使用typeof检查一个空值时会返回"object",因此无法使用typeof检查空值
- 未定义(Undefined)
- 当声明一个变量而没有赋值时,它的值就是Undefined
- 使用typeof检查一个Undefined类型的值时,会返回 "undefined"
- 符号(Symbol)
- 用来创建一个唯一的标识
- 没有两个相等的Symbol,即使它们具有相同的描述。
- 通过调用Symbol()函数创建
let symbol = Symbol('hello') // 调用Symbol()函数创建了一个符号,并传入了字符串参数作为描述 let obj1 = { [symbol]: 'world1' } let obj2 = { [Symbol('hello')]: 'world2' } console.log(obj1) // { [Symbol(hello)]: 'world1' } console.log(obj1[symbol]) // world1 console.log(obj2[Symbol('hello')]) // undefined 说明了没有两个相等的Symbol,即使它们具有相同的描述
Symbol可以作为对象键名,但注意添加的方式,中括号中的变量除了Symbol类型外,都会被强转成字符串
- 使用typeof检查符号时会返回 "symbol"
- 数值(Number)
- JS中原始值一共有7种
2、类型转换
-
- 转字符串
- 调用toString()方法
- 由于null和undefined中没有toString()方法,所以这两种类型调用toString()时会报错
- 调用String()函数
- 对于拥有toString()方法的值调用String()函数时,实际上就是在调用toString()方法
- 对于null,则直接转换为"null"
- 对于undefined,直接转换为"undefined"
- 任意数据类型+ 空串(隐式转换)
- 其原理和String()函数相同,但使用起来更加简洁
let a = 10 a = a + '' // '10'
- 其原理和String()函数相同,但使用起来更加简洁
- 转数值
- Number()函数
- 字符串
- 如果字符串是一个合法的数字,则会自动转换为对应的数字
- 如果字符串包含非数字部分,则转换为NaN
- 如果字符串是空串或纯空格的字符串,则转换为0
- 布尔值
- true转换为1,false转换为0
- null
- 转换为0
- undefined
- 转换为NaN
- 字符串
- parseInt()函数
- 可以取出以数字开头的字符串中有效整数部分,也可以对一个数字进行取整,否则返回NaN
parseInt('10px20') // 10 parseInt('p10px') // NaN parseInt(true) // NaN parseInt(10.5) // 10
- 可以取出以数字开头的字符串中有效整数部分,也可以对一个数字进行取整,否则返回NaN
- parseFloat()函数
- 可以取出以数字开头字符串中的有效浮点数部分,否则返回NaN
- 任意数据类型前使用+(隐式转换)
- 任意一个数据类型前使用+,可以将其转换成number,原理同Number()函数,但是使用更方便
- 任意数据类型 - 0(隐式转换)
console.log(true - 0) // 1 console.log('hello' - 0) // NaN console.log('100' - 0) // 100 console.log([] - 0) // 0
- Number()函数
- 转布尔值
- Boolean()函数
- 数字
- 0 和 NaN 转换为false,其余均转换为true
- 字符串
- 空串转换为false,其余均转换为true
- null
- 转换为false
- undefined
- 转换为false
- 数字
- 任意数据类型前使用!!(隐式转换)
- ! 可以用来对一个值进行非运算,它会先将其转换为布尔值然后再取反,!!(即两次取反)则可以将其他类型转换为布尔值
- Boolean()函数
三、运算符
运算符可以用来对一个或多个操作数(值)进行运算
1、算术运算符
-
- 常见运算符
- +加法运算符号
- - 减法运算符
- * 乘法运算符
- / 除法运算符
- ** 幂运算
- % 模运算,两个数相除取余数
- 注意
- 算术运算时,除了字符串的加法,其他运算的操作数是非数值时,都会转换为数值然后再运算
- 当任意一个值和字符串做加法运算时,它会先将其他值转换为字符串,然后再做拼串的操作,可以利用这一特点来完成类型转换
let a a = 10 / 0 // Infinity a = 9 ** .5 // 开方 a = 10 - '5' // 10 - 5 = 5 a = 10 + true // 10 + 1 = 11 a = 6 - undefined // 6 - NaN = NaN a = true + 'hello' // 拼串 'truehello' a = 1 + '' // '1'
- 当任意一个值和字符串做加法运算时,它会先将其他值转换为字符串,然后再做拼串的操作,可以利用这一特点来完成类型转换
- 算术运算时,除了字符串的加法,其他运算的操作数是非数值时,都会转换为数值然后再运算
- 常见运算符
2、赋值运算符
-
- 常见运算符
- =
- 将符号右侧的值赋值给左侧的变量
- ??=
- 空赋值
- 只有当变量的值为null或undefined时才会对变量进行赋值
- +=
- a += b 等价于 a = a + b,下同
- -=
- *=
- /=
- %=
- **=
let a,b a = 5 // 将5赋值给变量a b = 1 a += b // a = a + b = 5 + 1 = 6 a ??= 100 // 无法赋值,仍然a = 6 a = null a ??= 100 // 赋值成功,a = 100 b = undefined b ??= 101 // 赋值成功,b = 101
- =
- 常见运算符
3、自增和自减运算符
-
- 自增运算符
- ++
- 自增分为前自增(++a)和后自增(a++)
- 无论是++a还是a++都会使原变量立刻增加1,但是返回的值不同
- a++ 是自增前的值(旧值)
- ++a 是自增后的值(新值)
- 自减运算符
- --
- 自减分为前自减(--a)和后自减(a--)
- 无论是--a还是a--都会使原变量立刻减少1,但是返回的值不同
- a-- 是旧值
- --a 是新值
- --
- 自增运算符
4、逻辑运算符
-
- 逻辑非
- !
- 可以用来对一个值进行非运算,返回值只有true和false两种,可以利用这个特点将其他类型转换为布
- 对于布尔值,会进行取反操作
- !true --> false
- !false --> true
- 对于非布尔值,它会将其转换为布尔值后再取反,可以利用这个特点将其他类型转换为布尔值
- !!0 --> false
- 所有表示空性的没有的错误的值(0、NaN、空串、null、undefined)取反后都会转换为true
- !
- 逻辑与
- &&
- 可以对两个值进行与运算,它会将&&左右的值转换为布尔值然后运算,但是最终会返回原值
- 如果第一个值为false,则直接返回第一个值,不看第二个值(短路)
- 如果第一个值为true,则返回第二个值
let result = true && true // true result = 0 && NaN // 0 true && alert(123) // 第一个值为true,alert会执行
- 可以对两个值进行与运算,它会将&&左右的值转换为布尔值然后运算,但是最终会返回原值
- &&
- 逻辑或
- ||
- 可以对两个值进行或运算,它会将&&左右的值转换为布尔值然后运算,但是最终会返回原值
-
- 如果第一个值为true,则直接返回第一个值,不看第二个值(短路)
- 如果第一个值为false,则返回第二个值
let result = false || true // true result = "hello" || NaN // "hello" true || alert(123) // 第一个值为true,alert不会执行
-
- 可以对两个值进行或运算,它会将&&左右的值转换为布尔值然后运算,但是最终会返回原值
- ||
- 逻辑非
5、关系运算符
-
- 关系运算符用来检查两个值之间的关系是否成立
- 成立返回true,不成立返回false
- 关系运算符有:
- >
- 用来检查左值是否大于右值
- >=
- 用来检查左值是否大于或等于右值
- <
- 用来检查左值是否小于右值
- <=
- 用来检查左值是否小于或等于右值
- >
- 注意
- 当对非数值进行关系运算时,它会先将前转换为数值然后再比较 ,但是两端都是字符串时不会转换,而是逐位的比较字符的Unicode编码
- 利用这个特点可以对字符串按照字母排序
- 比较两个字符串格式的数字时一定要进行类型转换
- 当对非数值进行关系运算时,它会先将前转换为数值然后再比较 ,但是两端都是字符串时不会转换,而是逐位的比较字符的Unicode编码
- 关系运算符用来检查两个值之间的关系是否成立
6、相等运算符
-
- ==
- 相等运算符,用来比较两个值是否相等
- 使用相等运算符比较两个不同类型的值时,它会将其转换为相同的类型(通常转换为数值)然后再比较,类型转换后值相同也会返回true
- null和undefined进行相等比较时会返回true
- NaN不和任何值相等,包括它自身
- ===
- 全等运算符,用来比较两个值是否全等
- 它不会进行自动的类型转换,如果两个值的类型不同直接返回false
- null和undefined进行全等比较时会返回false
- !=
- 不等,用来检查两个值是否不相等
- 会自动的进行类型转换
- !==
- 不全等,比较两个值是否不全等
- 不会自动进行类型转换
- ==
7、条件运算符(或三元运算符)
-
- 条件表达式 ? 表达式1 : 表达式2
- 条件运算符在执行时,会先对条件表达式进行求值判断
- 如果结果为true,则执行表达式1,并返回结果
- 否则执行表达式2,并返回结果
false ? alert(1) : alert(2) // 会执行alert(2) let a = 10,b = 20 let max = a > b ? a : b // max = b = 20
- 条件运算符在执行时,会先对条件表达式进行求值判断
- 条件表达式 ? 表达式1 : 表达式2
四、流程控制
流程控制语句可以用来改变程序执行的顺序,包括:条件语句、循环语句
1、条件语句
-
- if语句
-
-
- 语法
if(条件表达式){ 语句... }
- 执行流程
- 先对if后的条件表达式进行求值判断,表达式不是布尔值,会转换为布尔值然后再运算
- 如果结果为true,则执行if后的语句
- 如果为false则不执行
- 先对if后的条件表达式进行求值判断,表达式不是布尔值,会转换为布尔值然后再运算
- 注意
- if语句只会控制紧随其后其后的那一行代码,如果希望可以控制多行代码,可以使用{}将语句扩起来
- 最佳实践:即使if后只有1行代码,我们也应该编写代码块,这样结构会更加的清晰
- 语法
- if-else语句
- 语法
if(条件表达式){ 语句... }else{ 语句... }
- 执行流程
- 先对条件表达式进行求值判断
- 如果结果为true 则执行if后的语句
- 否则执行else后的语句
- 先对条件表达式进行求值判断
- 语法
- if-else if-else语句
- 语法
if(条件表达式){ 语句... }else if(条件表达式){ 语句... }else if(条件表达式){ 语句... }else if(条件表达式){ 语句... }else{ 语句... }
- 执行流程
- 会自上向下依次对if后的条件表达式进行求值判断
- 如果条件表达式结果为true,则执行当前if后的语句,执行完毕语句结束
- 如果条件表达式结果为false,则继续向下判断,直到找到true为止
- 如果所有的条件表达式都是false,则执行else后的语句
- 会自上向下依次对if后的条件表达式进行求值判断
- 注意
- if-else if-else语句中只会有一个代码块被执行
- 一旦有执行的代码块,下边的条件都不会在继续判断了,所以一定要注意条件的编写顺序
- 语法
- switch语句
- 语法
switch(表达式){ case 表达式: 代码... break case 表达式: 代码... break case 表达式: 代码... break case 表达式: 代码... break default: 代码... break }
- 执行流程
- 会依次将switch后的表达式和case后的表达式进行全等比较
- 如果比较结果为true,则自当前case处开始执行代码
- 如果比较结果为false,则继续比较其他case后的表达式,直到找到true为止
- 如果所有的比较都是false,则执行default后的语句
- 会依次将switch后的表达式和case后的表达式进行全等比较
- 注意
- 当比较结果为true时,会从当前case处开始执行代码,只要是当前case后的代码,都会执行
- 可以使用break来避免执行其他的case
- 总结
- switch语句和if语句的功能是重复的,但在多个全等判断时,switch结构比较清晰
- 语法
-
2、循环语句
循环语句可以使指定的代码反复执行,通常编写一个循环,要有三个要件
-
- 初始化表达式(初始化变量)
- 条件表达式(设置循环运行的条件)
- 更新表达式(修改初始化变量)
-
- while循环
- 语法
while(条件表达式){ 语句... }
- 执行流程
- while语句在执行时,会先对条件表达式进行判断
- 如果结果为true,则执行循环体,执行完毕,继续判断,如此重复,直到条件表达式结果为false时,循环结束
- while语句在执行时,会先对条件表达式进行判断
- 注意
- 当一个循环的条件表达式恒为true时,这个循环就是一个死循环,会一直执行(慎用)
- 示例
//初始化表达式 let a = 0 //条件表达式 while(a < 5){ console.log(a) //更新表达式 a ++ }
- 语法
- do-while循环
- 语法
do{ 语句... }while(条件表达式)
- 执行流程
- do-while语句在执行时,会先执行do后的循环体,执行完毕后,会对while后的条件表达式进行判断
- 如果为false,则循环终止
- 如果为true,则继续执行循环体,以此类推
- do-while语句在执行时,会先执行do后的循环体,执行完毕后,会对while后的条件表达式进行判断
- 和while循环的区别
- while语句是先判断再执行
- do-while语句是先执行再判断,可以确保循环至少执行一次
- 语法
- for循环
- 语法
for(初始化表达式; 条件表达式; 更新表达式){ 语句... }
- 执行流程
- 执行初始化表达式,初始化变量
- 执行条件表达式,判断循环是否执行
- false则终止循环
- true则执行循环体,执行更新表达式,对初始化变量进行修改,如此循环,直到判断false为止
- 注意
- for循环中的三个表达式都可以省略,创建死循环
for(;;){ 语句... }
- 使用let在for循环的()中声明的变量是局部变量,只能在for循环内部访问
- 使用var在for循环的()中声明的变量,可以在for循环的外部访问
- for循环中的三个表达式都可以省略,创建死循环
- 语法
- break和continue
- break
- break用来终止switch和循环语句
- break执行后,当前的switch或循环会立刻停止
- break会终止离他最近的循环
- continue
- continue用来跳过当次循环
- break
- while循环
五、对象
对象是JS中的一种复合数据类型,它相当于一个容器,在对象中可以存储各种不同类型数据,使用typeof检查时会返回'object'
1、创建对象
-
- Object()函数
let obj = new Object() let obj2 = Object() // Object函数创建对象,可省略new
- 对象字面量
- 可以直接使用{} 来创建对象
- 使用{}所创建的对象,可以直接向对象中添加属性
- 语法
{ 属性名:属性值, [属性名]:属性值, }
- Object()函数
2、对象属性
对象中可以存储多个各种类型的数据,对象中存储的数据,我们称为属性
-
- 属性名
- 通常属性名就是一个字符串,没有什么特殊要求,但是如果你的属性名太特殊了,不能直接使用,需要使用[]来设置,虽然如此,但是我们还是强烈建议属性名也按照标识符的规范命名
- 也可以使用符号(symbol)作为属性名,来添加属性,使用symbol添加的属性,通常是那些不希望被外界访问的属性,获取这种属性时,也必须使用symbol
- 使用[]去操作属性时,可以使用变量
let mySymbol = Symbol() let obj = Object() obj[mySymbol] = '通过symbol添加的属性'
- 属性值
- 对象的属性值可以是任意的数据类型,也可以是一个对象
- 属性操作
- 向对象添加属性
- 对象.属性名 = 属性值
- 读取对象属性
- 对象.属性名
- 如果读取的是一个对象中没有的属性,不会报错而是返回undefined
- 修改对象属性
- 对象.属性名 = 新属性值
- 删除对象属性
- delete 对象.属性名
- 检查对象是否含有某个属性
- 使用in运算符
- 属性名 in 对象
- 如果有,返回true,没有则返回false
- 属性名 in 对象
- 使用in运算符
- 枚举对象属性
- 使用for-in语句
- 语法
for(let 变量 in 对象){ 语句 }
for-in的循环体会执行多次,每次执行时,都会将一个属性名赋值给我们所定义的变量
- 注意
- 并不是所有的属性都可以枚举,比如使用符号添加的属性
let obj = { name:'孙悟空', age:18, gender:'男', [Symbol()]:'特殊的属性' } for(let propName in obj){ console.log(propName, obj[propName]) }
- 并不是所有的属性都可以枚举,比如使用符号添加的属性
- 语法
- 使用for-in语句
- 向对象添加属性
- 属性名
3、对象方法
-
- 当一个对象的属性指向一个函数,那么我们就称这个函数是该对象的方法,调用函数就称为调用对象的方法
六、函数
函数也是一个对象,它具有其他对象所有的功能
函数中可以存储代码,且可以在需要时调用这些代码
使用typeof检查函数对象时会返回function
1、函数的创建
-
- 函数声明
function 函数名(){ 语句... }
- 函数表达式
const 变量 = function(){ 语句... }
- 箭头函数
() => { 语句... }
- 函数声明
2、参数
-
- 形式参数(形参)
- 在定义函数时,可以在函数中指定数量不等的形式参数(形参),相当于在函数内部声明了对应的变量,但是没有赋值
- 定义参数时,也可以指定默认值,会在没有对应实参时生效
- 实际参数(实参)
- 在调用函数时,可以在函数的()传递数量不等的实参
- 如果实参和形参数量相同,则对应的实参赋值给对应的形参
- 如果实参多于形参,则多余的实参不会使用
- 如果形参多于实参,则多余的形参为undefined
- 注意
- 当箭头函数中只有一个参数时,可以省略()
const fn = a => console.log('a=',a) // 箭头函数只有一条语句时,也可以省略{} fn(10)
- 当箭头函数中只有一个参数时,可以省略()
- 形式参数(形参)
3、返回值
-
- return
- 在函数中,可以通过return关键字来指定函数的返回值
- 返回值就是函数的执行结果,函数调用完毕返回值便会作为结果返回
- 任何值都可以作为返回值使用(包括对象和函数之类)
- 如果return后不跟任何值,则相当于返回undefined
- 如果不写return,那么函数的返回值依然是undefined
- return一执行函数立即结束
function sum(a, b) { // 计算完成后,将计算的结果返回而不是直接打印 return a + b console.log(a + b) //return后,无效代码,永远不会执行 } let result = fn() console.log(result)
- 箭头函数的返回值
- 函数的返回值可以直接写在箭头后,省去return关键字
- 如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来
const sum = (a, b) => a + b let result = sum(123, 456) console.log(result)
- return
4、作用域(scope)
作用域指的是一个变量的可见区域
-
- 作用域有两种
- 全局作用域
- 全局作用域在网页运行时创建,在网页关闭时消耗
- 所有直接编写到script标签中的代码都位于全局作用域中
- 全局作用域中的变量是全局变量,可以在任意位置访问
- 局部作用域
- 块作用域
- 块作用域是一种局部作用域
- 块作用域在代码块执行时创建,代码块执行完毕它就销毁
- 在块作用域中声明的变量是局部变量,只能在块内部访问,外部无法访问
let a = 10 { let b = 20 { let b = 30 let c = 40 console.log(a) //10 console.log(b) // 就近原则 30 console.log(c) } { console.log(a) // 10 console.log(b) // 20 console.log(c) // 无法访问c,会报错 } }
- 函数作用域
- 函数作用域也是一种局部作用域
- 函数作用域在函数调用时产生,调用结束后销毁
- 函数作用域,在函数创建时就已经确定(词法作用域),与定义的位置有关,和调用的位置无关
let a = "全局变量a" function fn(){ console.log(a) // a永远是'全局变量a',与调用位置无关 } function fn2(){ let a = "fn2中的a" fn() } fn2() // 打印'全局变量a'
- 函数每次调用都会产生一个全新的函数作用域
- 在函数中定义的变量是局部变量,只能在函数内部访问,外部无法访问
- 注意
- 在局部作用域中,如果没有使用var或let声明变量,则变量会自动成为window对象的属性 也就是全局变量
function fn(){ d = 10 // 没有使用var或let声明,成为全局变量,相当于window.a = 10 }
fn() console.log(d) // 可以访问
- 在局部作用域中,如果没有使用var或let声明变量,则变量会自动成为window对象的属性 也就是全局变量
- 块作用域
- 全局作用域
- 作用域链
- 当我们使用一个变量时,JS解释器会优先在当前作用域中寻找变量
- 如果找到了则直接使用
- 如果没找到,则去上一层作用域中寻找,找到了则使用,否则继续向上层查找,直到全局作用域都没找到,则报错 xxx is not defined
- 当我们使用一个变量时,JS解释器会优先在当前作用域中寻找变量
- 作用域有两种
5、window对象
-
- 浏览器为我们提供了一个window对象,代表的是浏览器窗口,通过该对象可以对浏览器窗口进行各种操作
- window对象还负责存储JS中的内置对象(ES标准中定义好的)和浏览器的宿主对象(由浏览器所提供的,比如document、console)
- window对象的属性可以通过window对象访问,也可以直接访问
- 在全局中使用var声明的变量,都会作为window对象的属性保存
- 使用let声明的变量不会存储在window对象中,而存在一个秘密的小地方(无法访问)
- 使用function声明的函数,都会作为window的方法保存
6、提升
-
- 变量的提升
- 使用var声明的变量,它会在所有代码执行前被声明(没有赋值),所以我们可以在变量声明前就访问变量(访问结果为undefined)
- let声明的变量实际也会提升,但是在赋值之前解释器禁止对该变量的访问
- 函数的提升
- 使用函数声明(function开头)创建的函数,会在其他代码执行前被创建,所以我们可以在函数声明前调用函数
- 变量的提升
7、debug
-
- 代码中使用debugger关键字打断点
- 在浏览器中操作
8、立即执行函数(IIFE)
-
- 立即执行函数是一个匿名的函数,并它只会调用一次
- 可以利用IIFE来创建一个一次性的函数作用域,避免变量冲突的问题
(function(){ let a = 10 console.log(a) })() (function(){ let a = 100 console.log(a) }()) //立即执行函数的括号也可以写在内部
9、this
-
- 函数在执行时,JS解析器每次都会传递进一个隐含的参数,这个参数就叫做 this
- this会指向一个对象,所指向的对象会根据函数调用方式的不同而不同
- 以函数形式调用时,this指向的是window
- 以方法的形式调用时,this指向的是调用方法的对象
- 注意
-
箭头函数没有自己的this,它的this由外层作用域决定(取决于在哪里定义的,全局定义的是window,对象方法里定义的,就是外层对象),与它的调用方式无关
function fn(){ console.log('fn=>',this) } const fn2 = () => { console.log('fn2=>',this) // 定义在全局作用域,是window } const obj = { name:'孙悟空', fn, // fn:fn,方法名和函数名相同时,可这样简写 fn2, //fn2:fn2 // sayName:function(){}简写,可省去:function sayName(){ const fn3 = () =>{ console.log('fn3=>',this) //定义在sayName方法中,this指向外层obj } fn3() } } obj.fn() //obj obj.fn2() //window obj.sayName() //obj
-
10、严格模式
-
- JS运行代码的模式有两种
- 正常模式
- 默认情况下代码都运行在正常模式中
- 这种处理方式导致代码的运行性能较差
- 在正常模式,语法检查并不严格,能不报错的地方尽量不报错
- 严格模式(推荐)
- 语法检查变得严格
- 禁止一些语法
- 更容易报错
- 提升了性能
- 语法检查变得严格
- 正常模式
- 严格模式开启
- 在代码开头使用字符串"use strict"
- 可以在全局开头使用(全局的严格模式)
- 也可以在函数开头使用(函数的严格模式)
- 在代码开头使用字符串"use strict"
- JS运行代码的模式有两种
11、高阶函数
-
- 如果一个函数的参数或返回值是函数,则这个函数就称为高阶函数
- 将函数作为参数,意味着可以对另一个函数动态的传递代码
class Person { constructor(name, age) { this.name = name this.age = age } } const personArr = [ new Person("孙悟空", 18), new Person("沙和尚", 38), new Person("红孩儿", 8), new Person("白骨精", 16), ] function filter(arr, cb) { const newArr = [] for (let i = 0; i < arr.length; i++) { if (cb(arr[i])) { newArr.push(arr[i]) } } return newArr } // 过滤年龄大于18岁的对象 result = filter(personArr, a => a.age >= 18) console.log(result)
- 将函数作为返回值,可以在不修改原函数的基础上,为其添加某些功能
function sayHello(){ console.log('hello') } function addLog(fn){ return () => { console.log('打印日志') fn() } } let newFun = addLog(sayHello) // 将函数作为参数,并返回一个新的函数 newFun() // 调用新函数
12、闭包
-
- 闭包就是能访问到外部函数作用域中变量的函数
- 使用场景
- 当我们需要隐藏一些不希望被别人访问的内容时就可以使用闭包
- 构成闭包的要件:
- 函数的嵌套
- 内部函数要引用外部函数中的变量
- 内部函数要作为返回值返回
- 闭包生命周期
- 闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新的闭包
- 在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)
- 注意事项
- 闭包主要用来隐藏一些不希望被外部访问的内容,这就意味着闭包需要占用一定的内存空间
- 相较于类来说,闭包比较浪费内存空间(类可以使用原型而闭包不能)
- 需要执行次数较少时,使用闭包
- 需要大量创建实例时,使用类
- 示例
function outer(){ let num = 0 return () => { num++ console.log(num) } } let fn1 = outer() // 创建独立闭包 let fn2 = outer() // 创建独立闭包 fn1() // 1 fn1() // 2 fn2() // 1 fn1 = null // 内部函数丢失,闭包销毁 fn2 = null // 内部函数丢失,闭包销毁
13、递归
-
- 调用自身的函数称为递归函数
- 递归的作用和循环是基本一致
- 递归的核心思想
- 将一个大的问题拆分为一个一个小的问题,小的问题解决了,大的问题也就解决了
- 编写递归函数,一定要包含两个要件:
- 基线条件 —— 递归的终止条件
- 递归条件 —— 如何对问题进行拆分
- 递归和循环的区别
- 递归思路的比较清晰简洁(使用循环解决比较麻烦的场景下,才会使用)
- 循环的执行性能比较好(能用循环,就避免使用递归)
- 示例:求斐波拉契数列中的第n个数
// 求斐波那契数列中的第n个数 function fib(n) { // 确定基线条件 if (n < 3) { return 1 } // 设置递归条件 // 第n个数 = 第n-1个数 + 第n-2个数 return fib(n - 1) + fib(n - 2) } let result = fib(10) console.log(result)
14、arguments
-
- arguments是函数中又一个隐含参数
- arguments是一个类数组对象(伪数组)
- 可以通过索引来读取元素,也可以通过for循环遍历,但是不能使用数组方法
- arguments用来存储函数的实参
- 无论用户是否定义形参,实参都会存储到arguments对象中
- 可以通过该对象直接访问实参
- 箭头函数不具有arguments参数
- 示例
//定义一个函数,可以求任意个数值的和 function sum(){ let result = 0 // 通过arguments,可以不受参数数量的限制更加灵活的创建函数 for(let value of arguments){ result += value } return result } let result = sum(10,30,50) console.log(result) // 90
15、可变参数
-
- 在定义函数时可以将参数指定为可变参数
- 参数名前使用扩展符(...)
- 可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回
- 可变参数的作用和arguments基本是一致,但是也具有一些不同点:
- 可变参数的名字可以自己指定
- 可变参数就是一个数组,可以直接使用数组的方法
- 可变参数可以配合其他参数一起使用,但是需要将可变参数写到最后
- 示例
function sum(...num){ console.log(num) // 打印数组[10,20,30] let result = 0 for(let value of num){ result += value } return result } result = sum(10,20,30) // 可传入任意长度的数字作为参数 console.log(result) // 60 // 和普通参数一起使用 function fn(a,b,...args){ console.log(a) // 10 console.log(b) // 20 console.log(args) //[30,40] } fn(10,20,30,40)
- 在定义函数时可以将参数指定为可变参数
16、函数的调用方式
-
- 函数()
- 以这种方式调用,this指向window
function fn() { console.log("函数执行了~", this) } fn()
- 以这种方式调用,this指向window
- 函数.call()
- 可以用来指定函数中的this
- 第一个参数将会成为函数的this,实参直接在第一个参数后一个一个的列出来
- 箭头函数没有call方法
function fn() { console.log("函数执行了~", this) } const obj = { name: "孙悟空", fn } function fn2(a, b) { console.log("a =", a, "b =", b, this) } fn2.call(obj,'hello',true) // a = 'hello', b = true, this = obj
- 函数.apply()
- 第一个参数将会成为函数的this,函数的实参需要通过一个数组传递
- 箭头函数没有apply方法
function fn() { console.log("函数执行了~", this) } const obj = { name: "孙悟空", fn } function fn2(a, b) { console.log("a =", a, "b =", b, this) } fn2.apply(obj,['hello',true]) //apply方法,实参需要传递数组形式
- 函数()
17、bind()方法
-
- 可以用来创建一个新的函数
- bind可以为新函数绑定this,this由bind第一个参数决定(无法修改)
- bind可以为新函数绑定参数(无法修改)
- 注意
- 箭头函数没有bind方法
- 示例
function fn(a, b, c) { console.log("fn执行了~~~~", this) console.log(a, b, c) } const obj = {name:"孙悟空"} const newFn = fn.bind(obj, 10, 20, 30) // 绑定了this,也绑定了实参,因此均无法修改 newFn(100,200,300) // 100、200、300不起作用,依旧会打印10,20,30 const newFn2 = fn.bind(obj,10) // 只绑定this和参数a newFn2(200,300) // 打印10,200,300
- 可以用来创建一个新的函数
七、面向对象编程
面向对象的编程:
-
- 面向对象的编程指,程序中的所有操作都是通过对象来完成
- 做任何事情之前都需要先找到它的对象,然后通过对象来完成各种操作
1、类
-
- 使用Object创建对象的问题:
- 无法区分出不同类型的对象
- 不方便批量创建对象
- 在JS中可以通过类(class)来解决这个问题:
- 类是对象模板,可以将对象中的属性和方法直接定义在类中,定义后,就可以直接通过类来创建对象
- 通过同一个类创建的对象,我们称为同类对象
- 可以使用instanceof来检查一个对象是否是由某个类创建
- 如果某个对象是由某个类所创建,则我们称该对象是这个类的实例
- 语法
- class 类名 {}
- 类名要使用大驼峰命名
- const 类名 = class {}
- class 类名 {}
- 通过类创建对象
- new 类()
- 示例
// Person类专门用来创建人的对象 class Person{ } // Dog类式专门用来创建狗的对象 class Dog{ } const p = new Person() // 调用构造函数创建对象 const d = new Dog() console.log(p instanceof Person) // true 检查p对象是否是Person类的实例 console.log(d instanceof Person) // false
- 属性
- 实例属性
- 属性名 = 值
- 实例属性,只能通过实例去访问
- 静态属性(类属性)
- 使用static关键字
- static 属性名 = 值
- 静态属性,只能通过类去访问
- 使用static关键字
- 私有属性
- 属性前加 #
- 私有属性只能在类内部使用
- 私有属性操作
- 获取属性
- get关键字
get 属性名(){ return this.#属性 }
- get关键字
- 设置属性
- set关键字
set 属性名(参数){ this.#属性 = 参数 }
- 获取属性
- 示例
class Person{ /* 类的代码块,默认就是严格模式,类的代码块是用来设置对象的属性的,不是什么代码都能写 */ name = "孙悟空" // Person的实例属性name,只能通过实例访问 static test = "test静态属性" // 使用static声明的属性,是静态属性(类属性),只能通过类去访问 #age // 声明私有属性 //设置私有属性 set age(num){ // 对参数进行验证 if(typeof num == 'number' && num > 0){ this.#age = num } } //获取私有属性 get age(){ return this.#age } }
- 实例属性
- 方法
- 实例方法
- 定义
- 方法名 = function(){语句...}
- 这种方式定义的方法,位于对象自身中
- 方法名(){语句...}
- 这种方式定义的方法,位于原型对象中(prototype),后续介绍
- 方法名 = function(){语句...}
- 实例方法中的this指向当前实例
- 定义
- 静态方法(类方法)
- 使用static关键字
- static 方法名(){语句...}
- 静态方法中的this指向当前类
- 使用static关键字
- 构造方法(constructor)
- 类中一个特殊的方法,方法名:constructor
- 构造方法会在我们调用类创建对象时执行
- 可以在构造函数中,为实例属性进行赋值
- 在构造函数中,this表示当前所创建的对象
- 示例
class Person{ // 构造方法,最先执行 constructor(name,age) { console.log('构造方法执行了') this.name = name //this指向当前创建的实例对象 this.age = age } // 实例方法(一种定义方式) sayName = function (){ console.log('姓名:',this.name) //this指向当前实例 } // 实例方法(另一种定义方式) sayAge(){ console.log('年龄:',this.age) } // 静态方法 static test(){ console.log('我是静态方法:',this) // this指向类 Person } } const p = new Person('孙悟空',18) // 创建实例,constructor方法执行,为实例属性赋值 p.sayName() //调用实例方法 p.sayAge()// 调用实例方法 Person.test() // 调用静态方法
- 实例方法
- 继承
- 使用extends关键字,被继承的类称为 父类(超类),继承的类称为 子类
- 语法
class 子类 extends 父类{ 语句... }
- 语法
- 当一个类继承另一个类时,就相当于将另一个类中的代码复制到了当前类中(简单理解)
- 通过继承可以减少重复的代码,并且可以在不修改一个类的前提对其进行扩展
- 继承的原则
- 开闭原则(OCP):在不修改一个类的情况下对其进行扩展
- 示例
class Animal{ constructor(name) { this.name = name } sayName(){ console.log('我的名字:',this.name) } } // Dog继承自Animal,并添加run方法 class Dog extends Animal{ // 添加run方法 run(){ console.log(this.name,'正在跑...') } } // Cat继承自Animal,并重写constructor和sayName方法 class Cat extends Animal{ constructor(name,age) { super(name) // 重写constructor方法时,第一行代码必须为super() this.age = age } sayName() { super.sayName() //使用super.方法名()来引用父类的方法 console.log('我是一只猫咪') } } dog = new Dog('旺财') dog.sayName() //我的名字: 旺财 dog.run() //旺财 正在跑... cat = new Cat('汤姆',3) cat.sayName() //我的名字: 汤姆 我是一只猫咪
- 使用extends关键字,被继承的类称为 父类(超类),继承的类称为 子类
- 使用Object创建对象的问题:
2、对象结构
-
- 对象中存储属性的区域实际有两个
- 对象自身
- 直接通过对象所添加的属性,位于对象自身中
- 在类中通过 x = y 的形式添加的属性,位于对象自身中
- 原型对象(prototype)
- 在对象中会有一个属性用来存储原型对象,这个属性叫做__proto__
- 同类型对象,它们的原型对象指向同一个
- 原型对象也负责为对象存储属性
- 当我们访问对象中的属性时,会优先访问对象自身的属性
- 对象自身不包含该属性时,才会去原型对象中寻找
- 会添加到原型对象中的情况
- 在类中通过xxx(){}方式添加的方法,位于原型中
- 主动向原型中添加的属性或方法
- 对象自身
- 对象中存储属性的区域实际有两个
3、原型对象
-
- 原型对象访问
- 对象.__proto__
- Object.getPrototypeOf(对象)
- 类.prototype
- 原型对象中的数据
- 对象中的数据(属性、方法等)
- constructor (对象的构造函数)
- 原型链
- 原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同
- 读取对象属性时,会优先读取对象自身属性,如果对象中有,则使用
- 没有则去对象的原型中寻找,再没有,继续去原型的原型中去找,直到Object对象的原型,如果依然没有,则返回undefined
- 原型的作用
- 原型就相当于是一个公共的区域,可以被所有该类实例访问
- 可以将该类实例中,所有的公共属性(方法)统一存储到原型中,这样我们只需要创建一个属性,即可被所有实例访问
- JS中继承就是通过原型来实现的
- 当继承时,子类的原型就是一个父类的实例(换种理解方式:当有继承关系时,原型对象就是父类的实例对象)
- 子类.prototype = new 父类()
- 子类实例.__proto__ = new 父类()
- 当继承时,子类的原型就是一个父类的实例(换种理解方式:当有继承关系时,原型对象就是父类的实例对象)
- 原型就相当于是一个公共的区域,可以被所有该类实例访问
- 示例
class Person { name = "孙悟空" age = 18 sayHello() { console.log("Hello,我是", this.name) } } const p = new Person() console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === Object.getPrototypeOf(p)) // true console.log(p.constructor === Person) //true console.log(p.__proto__.constructor === Person) //true console.log(p.__proto__) // 原型对象 console.log(p.__proto__.__proto__) // Object对象的原型 console.log(p.__proto__.__proto__ === Object.prototype) // true console.log(p.__proto__.__proto__.__proto__) //null p.sayHello()
- 原型对象访问
4、检查对象
-
- 检查实例
- instanceof
- 用来检查一个对象是否是一个类的实例
- 只要原型链上有该类实例,就会返回true
- Object是所有对象的原型,所以任何和对象和Object进行instanceof运算都会返回true
- 对象.__proto__.constructor
- 可以用来严格检验一个对象是否是一个类的实例
class Person{ } const p = new Person() console.log(p instanceof Object) // true console.log(p.__proto__.constructor === Object) // false console.log(p.__proto__.constructor === Person) // true
- 可以用来严格检验一个对象是否是一个类的实例
- instanceof
- 检查属性
- in
- 使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true
- 对象.hasOwnProperty(属性名)
- 用来检查一个对象的自身是否含有某个属性(官方不推荐使用)
- Object.hasOwn(对象, 属性名)
- 用来检查一个对象的自身是否含有某个属性
- 示例
class Person { name = "孙悟空" sayHello() { console.log("Hello,我是", this.name) } } const p = new Person() console.log(p instanceof Person) // 检查实例 true console.log(p instanceof Object) // true console.log('sayHello' in p) // true console.log(p.hasOwnProperty('sayHello')) //false console.log(Object.hasOwn(p,'sayHello')) //false
- in
- 检查实例
5、旧类
-
- 早期JS中,直接通过函数来定义类
- 一个函数如果直接调用,那么这个函数就是一个普通函数
- xxx()
- 一个函数如果通过new调用,那么这个函数就是一个构造函数
- new xxx()
- 一个函数如果直接调用,那么这个函数就是一个普通函数
- 旧的方式定义类时,通常将类的定义放在一个立即执行函数内
var Person = (function () { function Person(name, age) { // 在构造函数中,this表示新建的对象 this.name = name this.age = age } // 向原型中添加属性(方法) Person.prototype.sayHello = function () { console.log(this.name) } // 静态属性 Person.staticProperty = "xxx" // 静态方法 Person.staticMethod = function () {} return Person })() var Animal = (function(){ function Animal(){ } return Animal })() var Cat = (function(){ function Cat(){ } // 继承Animal Cat.prototype = new Animal() return Cat })() var p = new Person("孙悟空", 18) p.sayHello() var cat = new Cat() console.log(cat)
- 早期JS中,直接通过函数来定义类
6、new运算符
-
- new运算符是创建对象时要使用的运算符
- 当使用new去调用一个函数时,这个函数将会作为构造函数调用,并会进行如下操作:
- 创建一个普通的JS对象(Object对象 {}), 为了方便,称其为新对象
- 将构造函数的prototype属性设置为新对象的原型
- 使用实参来执行构造函数(constructor),并且将新对象设置为函数中的this
- 如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回(千万不要这么做)
- 如果构造函数的返回值是一个原始值或者没有指定,则返回新对象(通常不需要指定)
7、对象的复制
-
- 浅拷贝(shallow copy)
- 通常对对象的拷贝都是浅拷贝
- 浅拷贝顾名思义,只对对象的浅层进行复制(只复制一层)
- 实现方式
- Object.assign(目标对象, 被复制的对象)
- 将被复制对象中的属性复制到目标对象里,并将目标对象返回
- 目标对象中没有的属性,则添加,有则修改
- ... (展开运算符)
- 将属性在新对象中展开
- Object.assign(目标对象, 被复制的对象)
- 深拷贝(deep copy)
- 深拷贝指不仅复制对象本身,还复制对象中的属性和元素
- 因为性能问题,通常情况不太使用深拷贝
- 实现方式
- structuredClone()函数
- JSON工具类(后面单独介绍)
- 示例
// 浅拷贝 const obj2 = Object.assign({}, obj) console.log(obj) // { name: "孙悟空", age: 18 } console.log(obj === obj2) // false const obj3 = Object.assign({'gender': '男'}, obj) console.log(obj3) // {'gender':'男',name: "孙悟空", age: 18} const obj4 = {address: "高老庄", ...obj, age: 48} // 使用展开运算符将obj中的属性在新对象中展开,添加address属性,同时修改age属性 console.log(obj4) // {address: '高老庄', name: '孙悟空', age: 48} // 深拷贝 const obj5 = { name: '牛魔王', age: 38, son: { name: '红孩儿', age: 18 } } const obj6 = structuredClone(obj5) obj6.son.age = 20 // 修改红孩儿年龄 console.log(obj5) // 红孩儿年龄还是18 console.log(obj6) // 红孩儿年龄修改为20
- 浅拷贝(shallow copy)
8、总结
-
- 面向对象的编程的步骤
- 找对象
- 搞对象
- 学习对象
- 明确这个对象代表什么,有什么用
- 如何获取到这个对象
- 如何使用这个对象(对象中的属性和方法)
- 对象的分类
- 内建对象
- 由ES标准所定义的对象,比如 Object Function String Number ....
- 宿主对象
- 由浏览器提供的对象,比如BOM、DOM
- 自定义对象
- 由开发人员自己创建的对象
- 内建对象
- 面向对象的编程的步骤
八、数组
1、简介
-
- 数组也是一种复合数据类型,在数组可以存储多个不同类型的数据
- 数组中存储的是有序的数据,数组中的每个数据都有一个唯一的索引,可以通过索引来操作获取数据
- 数组中存储的数据叫做元素
- 索引(index)是一组大于0的整数
- 创建数组
- 通过Array()来创建数组
- 也可以通过[]来创建数组
- 向数组中添加元素
- 数组[索引] = 元素
- 读取数组中的元素
- 数组[索引]
- 如果读取了一个不存在的元素,不会报错,而是返回undefined
- 数组[索引]
- length属性
- 获取数组的长度
- 获取的实际值就是数组的最大索引 + 1
- 向数组最后添加元素
- 数组[数组.length] = 元素
- length是可以修改的
- 修改后的length大于原数组长度,则后续补相应个数的undefined
- 修改后的length小于原数组长度,则保留数组保留新的长度数据,后面部分截掉
2、遍历数组
-
- for循环
let a = [1, 4, 7, 2, 5, 8, 3, 6, 9] for (let i = 0; i < a.length; i++) { console.log(a[i]) }
- for-of语句
- 语法
- for(变量 of 可迭代的对象){语句...}
- 执行流程
- for-of的循环体会执行多次,数组中有几个元素就会执行几次
- 每次执行时都会将一个元素赋值给变量
const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"] for(let value of arr){ console.log(value) }
- 语法
- 数组的forEach()方法(后续方法部分会介绍)
- for循环
3、数组的复制
-
- 浅拷贝
- slice()方法,下面数组方法中会介绍
- ...(展开运算符)
- 可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递
- Array.from()
const originalArray = [1, 2, 3, 4, 5]; const shallowCopy_1 = originalArray.slice(); //方式1 const shallowCopy_2 = [...originalArray]; //方式2 const shallowCopy_3 = Array.from(originalArray); //方式3
- 深拷贝
- structuredClone()函数
- JSON.parse(JSON.stringify(数组))
- 浅拷贝
4、数组的方法
-
- Array.isArray()
- 静态方法
- 用来检查一个对象是否是数组
- Array.from()
- 静态方法
- 可以将其它对象转换为数组
- concat()
- 用来连接两个或多个数组
- 非破坏性方法,不会影响原数组,而是返回一个新的数组
- at()
- 可以根据索引获取数组中的指定元素
- 可以接收负索引作为参数
- includes()
- 可以检查数组中是否存在某个元素
- 存在返回true
- 否则返回false
- 可以检查数组中是否存在某个元素
- indexOf()
- 获取元素在数组中第一次出现的索引
- 找到了则返回元素的索引,否则返回-1
- 参数:
- 1. 要查询的元素
- 2. 查询的其实位置
- lastIndexOf()
- 获取元素在数组中最后一次出现的位置
- 找到了则返回元素的索引,否则返回-1
- join()
- 将一个数组中的元素连接为一个字符串
- 参数:
- 指定一个字符串作为连接符
- 不指定,默认使用','
- slice()
- 用来截取数组(非破坏性方法)
- 参数:
- 1. 截取的起始位置(包括该位置)
- 2. 截取的结束位置(不包括该位置)
- 第二个参数可以省略不写,如果省略则会一直截取到最后
- 索引可以是负值
- 如果将两个参数全都省略,则可以对数组进行浅拷贝(浅复制)
- push()
- 向数组的末尾添加一个或多个元素,并返回新的长度
- pop()
- 删除并返回数组的最后一个元素
- unshift()
- 向数组的开头添加一个或多个元素,并返回新的长度
- shift()
- 删除并返回数组的第一个元素
- splice()
- 可以删除、插入、替换数组中的元素
- 参数:
- 1. 删除的起始位置
- 2. 删除的数量
- 3. 要插入的元素
- 返回值:
- 返回被删除的元素
let arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"] arr.splice(2,1) // 从索引2的位置,删掉一个元素,即删掉沙和尚 console.log(arr) //["孙悟空", "猪八戒", "唐僧"] arr.splice(1,0,'红孩儿','白骨精') // 从索引为1的位置,删掉0个元素,插入红孩儿和白骨精,即插入操作 console.log(arr) // ['孙悟空', '红孩儿', '白骨精', '猪八戒', '唐僧'] arr.splice(1,1,'白龙马') // 将红孩儿替换为白龙马 console.log(arr) //['孙悟空', '白龙马', '白骨精', '猪八戒', '唐僧']
- 返回被删除的元素
- reverse()
- 反转数组(会改变原数组)
- sort()
- sort用来对数组进行排序(会对改变原数组)
- sort默认会将数组升序排列
- 注意:sort默认会按照Unicode编码进行排序,所以如果直接通过sort对数字进行排序,可能会得到一个不正确的结果
- 参数:
- 可以传递一个回调函数作为参数,通过回调函数来指定排序规则
- (a, b) => a - b (升序排列)
- (a, b) => b - a (降序排列)
let arr = [2, 3, 1, 9, 0, 4, 5, 7, 8, 6, 10] // 升序 arr.sort((a,b) => a-b) console.log(arr) // 降序 arr.sort((a,b) => b-a) console.log(arr)
- 可以传递一个回调函数作为参数,通过回调函数来指定排序规则
- forEach()
- 用来遍历数组
- 它需要一个回调函数作为参数,这个回调函数会被调用多次,每次调用,都会将数组中的数据作为参数传递
- 回调函数中有三个参数:
- element 当前的元素
- index 当前元素的索引
- array 被遍历的数组
let arr = [2, 3, 1, 9, 0, 4, 5, 7, 8, 6, 10] // 遍历数组中的元素 arr.forEach(ele =>{ console.log(ele) }) // arr.forEach((ele,index,array) =>{ // console.log(ele,index,array) // })
- filter()
- 将数组中符合条件的元素保存到一个新数组中返回
- 需要一个回调函数作为参数,会为每一个元素去调用回调函数,并根据返回值来决定是否将元素添加到新数组中
- 非破坏性方法,不会影响原数组
- 参数同forEach()方法
let arr = [2, 3, 1, 9, 0, 4, 5, 7, 8, 6, 10] // 过滤arr数组中的所有偶数 let newArr = arr.filter(ele => ele % 2 === 0) console.log(newArr) // [2, 0, 4, 8, 6, 10]
- map()
- 根据当前数组生成一个新数组
- 需要一个回调函数作为参数,回调函数的返回值会成为新数组中的元素
- 非破坏性方法不会影响原数组
- 参数同forEach()方法
let arr = [2, 3, 1, 9, 0, 4, 5, 7, 8, 6, 10] // arr数组中的每个元素进行平方 let newArr = arr.map(ele => ele ** 2) console.log(newArr) //[4, 9, 1, 81, 0, 16, 25, 49, 64, 36, 100]
- Array.isArray()
5、数组去重
-
- 方式一:原数组上去重
const arr = [1, 2, 1, 3, 2, 2, 4, 5, 5, 6, 7] for(let i=0; i<arr.length; i++){ const index = arr.indexOf(arr[i], i+1) if(index !== -1){ // 出现重复内容 arr.splice(index, 1) i-- } } console.log(arr)
- 方式二:去重后数据存入新数组
const arr = [1, 2, 1, 3, 2, 2, 4, 5, 5, 6, 7] const newArr = [] for(let ele of arr){ if(newArr.indexOf(ele) === -1){ newArr.push(ele) } } console.log(newArr)
- 方式一:原数组上去重
九、内建对象
1、解构赋值
-
- 数组解构
- 使用[]
- [变量1...] = 数组
- 变量可提前声明,也可以声明同时解构
- 如果变量提前声明,需要在解构前添加上分号(;),以免js解释器解析出错
let a, b, c ;[a,b,c] = ['孙悟空','唐僧','猪八戒'] // 前面添加; console.log(a,b,c) // a = '孙悟空' b = '唐僧' c = '猪八戒' let [d,e,f] = [1,2,3] // 声明同时解构 console.log(d,e,f) // 1 2 3
- 如果变量提前声明,需要在解构前添加上分号(;),以免js解释器解析出错
- 变量数少于数组元素个数,则按顺序将数组元素赋值给相应变量
let [a,b] = [1,2,3] console.log(a,b) // 1 2
- 变量数多于数组元素个数,则多余的变量赋值为undefined
let [c,d,e,f] = [1,2,3] console.log(c,d,e,f) // 1 2 3 undefined
- 可以给变量提供默认值
- 有则覆盖
- 没有则使用默认值
let [a,b,c=100] = [11,22] console.log(a,b,c) //使用默认值: 11 22 100 let [n1,n2,n3=100] = [33,44,55] console.log(n1,n2,n3) //覆盖默认值: 33 44 55
- 解构数组时,可以使用...来设置获取多余的元素
let [n1, n2, ...n3] = [4, 5, 6, 7] console.log(n1,n2) // 4 5 console.log(n3) // [6,7]
- 可以交换两个变量的值
// 交换两个变量 let a1 = 10 let a2 = 20 ;[a1,a2] = [a2,a1] console.log(a1,a2) // 20 10 // 交换数组中的元素 let arr = [100,200] ;[arr[1],arr[0]] = [arr[0],arr[1]] console.log(arr) // [200,100]
- 变量可提前声明,也可以声明同时解构
- 对象解构
- 使用{}
- {属性1,...} = 对象
- 属性必须是对象中存在的,对于没有的属性会赋值undefined
- 可以给属性取别名,同时也还可以设置默认值
- 变量可提前声明,也可以声明同时解构
- 如果变量声明提前,则需要使用小括号()将解构语句包裹起来,同时前面需要添加分号;
const obj = { name: "孙悟空", age: 18, gender: "男" } // let { name, age, gender } = obj // 声明变量同时解构对象 let name, age, gender ;({ name, age, gender } = obj) // 为了避免对象当作代码块,需要将整体套上小括号(),小括号前面添上; let { address } = obj // 没有的属性返回undefined console.log(address) // undefined console.log(name, age, gender) // 孙悟空 18 男 let {name:a, age:b, gender:c, address:d="花果山"} = obj // 可以给属性取别名,同时还可以设置默认值 console.log(a, b, c, d) // 孙悟空 18 男 花果山
- 如果变量声明提前,则需要使用小括号()将解构语句包裹起来,同时前面需要添加分号;
- {属性1,...} = 对象
- 使用{}
- 数组解构
2、JSON
-
- 对象序列化
- JS中的对象使用时都是存在于计算机的内存中的
- 序列化指将对象转换为一个可以存储的格式(JSON字符串)
- 序列化用途
- 作为数据交换的格式
- 用来编写配置文字
- 如何序列化
- 工具类 JSON (JavaScript Object Notation), 即:JS对象表示法
- 工具类,所以无法使用new运算符
- 编写JSON注意事项
- JSON字符串有两种类型
- JSON对象 { }
- JSON数组 [ ]
- JSON字符串的属性名必须使用双引号引起来
- JSON中可以使用的属性值(元素)
- 数字(Number)
- 字符串(String) 必须使用双引号
- 布尔值(Boolean)
- 空值(Null)
- 对象(Object {})
- 数组(Array [])
- JSON的格式和JS对象的格式基本上一致的,但是更严格,末尾不要加上逗号(,)
- JSON方法
- JSON.stringify()
- 将对象序列化成JSON字符串
- JSON.parse()
- 将JSON字符串反序列化成对象
- 结合stringify可实现对象的深拷贝
const arr = [1,2,3] const obj = { name: '孙悟空', age: 18 } let jsonArr = JSON.stringify(arr) console.log(jsonArr) // 序列化成JSON字符串 '[1,2,3]' let jsonObj = JSON.stringify(obj) console.log(jsonObj) // 序列化成JSON字符串 '{"name":"孙悟空","age":18}' console.log(JSON.parse(jsonArr)) // 反序列化成数组 console.log(JSON.parse(jsonObj)) // 反序列化成对象 // 深拷贝 const obj3 = { name: "孙悟空", friend: { name: "猪八戒", }, } const obj4 = JSON.parse(JSON.stringify(obj)) // 对obj3进行深拷贝 console.log(obj3.friend === obj4.friend) // false
- JSON.stringify()
- JSON字符串有两种类型
- 工具类 JSON (JavaScript Object Notation), 即:JS对象表示法
- 对象序列化
3、Map
-
- Map用来存储键值对结构的数据(key-value)
- Map和Object的主要区别:
- Object中的属性名只能是字符串或符号,如果传递了一个其他类型的属性名,JS解释器会自动将其转换为字符串
- Map中任何类型的值都可以称为数据的key
- 创建
- new Map()
- 属性
- size
- 获取map中键值对的数量
- size
- 方法
- set(key, value)
- 向map中添加键值对
- get(key)
- 根据key获取值
- delete(key)
- 删除指定数据
- has(key)
- 检查map中是否包含指定键
- clear()
- 删除全部的键值对
- set(key, value)
- 示例
const obj = {} // 创建一个Map const map = new Map() map.set("name", "孙悟空") map.set(obj, "呵呵") map.set(NaN, "哈哈哈") map.delete(NaN) // map.clear() console.log(map) console.log(map.get("name")) console.log(map.has("name"))
4、Set
-
- Set用来创建一个集合
- 它的功能和数组类似,不同点在于Set中不能存储重复的数据
- 创建
- new Set()
- new Set([...])
- 可以指定一个数组
- 属性
- size
- 获取元素个数
- size
- 方法
- add()
- 添加元素
- has()
- 检查元素
- delete()
- 删除元素
- add()
- 示例
// 创建一个Set const set = new Set() // 向set中添加数据 set.add(10) set.add("孙悟空") set.add(10) console.log([...set]) // [10, '孙悟空'] // 利用Set对数组去重 const arr = [1,2,3,3,4,1,2,3,4,5,5,6,7,7] const newSet = new Set(arr) console.log([...newSet]) // [1, 2, 3, 4, 5, 6, 7]
5、Math
-
- Math也是一个工具类
- Math中为我们提供了数学运算相关的一些常量和方法
- 常量
- Math.PI 圆周率
- 方法
- Math.abs()
- 求一个数的绝对值
- Math.min()
- 求多个值中的最小值
- Math.max()
- 求多个值中的最大值
- Math.pow()
- 求x的y次幂
- Math.sqrt()
- 求一个数的平方根
- Math.floor()
- 向下取整
- Math.ceil()
- 向上取整
- Math.round()
- 四舍五入取整
- Math.random()
- 生成一个0-1之间的随机数
- Math.abs()
- 生成x-y之间的随机数
- Math.round(Math.random() * (y-x) + x)
- 示例
let result = Math.abs(10) result = Math.abs(-10) result = Math.min(10, 20, 30, 44, 55, -1) result = Math.max(10, 20, 30, 44, 55, -1) result = Math.pow(4, 2) // 4 ** 2 result = Math.sqrt(4) // 4 ** .5 result = Math.floor(1.2) result = Math.ceil(1.2) result = Math.round(1.4) result = Math.trunc(1.5) /* 生成0-5之间的随机数 Math.random() --> 0 - 1 生成 0-x之间的随机数: Math.round(Math.random() * x) Math.floor(Math.random() * (x + 1)) 生成 x-y 之间的随机数 Math.round(Math.random() * (y-x) + x) */ // result = Math.round(Math.random() * 5) // result = Math.floor(Math.random() * 6) // 1-6 // result = Math.round(Math.random() * 5 + 1) // 11 - 20 result = Math.round(Math.random() * 9 + 11) console.log(result)
6、Date
-
- 在JS中所有的和时间相关的数据都由Date对象来表示
- 创建
- new Date()
- 不传参数,创建的是当前时间的对象
- 传递一个表示时间的字符串,两种样式:
- 月/日/年 时:分:秒
- 年-月-日T时:分:秒
- 注意日和时中间有个T
- 按 年份, 月, 日, 时, 分, 秒, 毫秒依次传入位置参数
- 注意月份从0开始,取值范围(0-11)
- new Date()
- 方法
- 类方法
- Date.now()
- 获取当前的时间戳
- Date.now()
- 对象方法
- getFullYear()
- 获取4位年份
- getMonth()
- 返当前日期的月份(0-11),0表示1月
- getDate()
- 返回当前是几日
- getDay()
- 返回当前日期是周几(0-6) 0表示周日
- toLocaleDateString()
- 将日期转换为本地的字符串(仅含日期)
- toLocaleTimeString()
- 将时间转换为本地的字符串(仅含时间)
- toLocaleString()
- 可以将一个日期转换为本地时间格式的字符串(包含日期和时间)
- 参数
- 1. 描述语言和国家信息的字符串
- zh-CN 中文中国
- zh-HK 中文香港
- en-US 英文美国
- 2. 需要一个对象作为参数,在对象中可以通过对象的属性来对日期的格式进行配置
- dateStyle 日期的风格
- full
- long
- medium
- short
- timeStyle 时间的风格
- full
- long
- medium
- short
- hour12 是否采用12小时值
- true
- false
- weekday 星期的显示方式
- long
- short
- narrow
- year
- numeric
- 2-digit
- dateStyle 日期的风格
- 1. 描述语言和国家信息的字符串
- getFullYear()
- 类方法
- 示例
let d = new Date() // 直接通过new Date()创建时间对象时,它创建的是当前的时间的对象 //字符串格式: 月/日/年 时:分:秒 d = new Date('07/30/2022 10:10:10') // 字符串格式: 年-月-日T时:分:秒 d = new Date("2022-12-23T23:34:35") // new Date(年份, 月, 日, 时, 分, 秒, 毫秒) d = new Date(2016, 0, 1, 13, 45, 33) // 2026-01-01 13:45:33 let result = d.getFullYear() //2016 result = d.getMonth() // 0 result = d.getDate() // 1 result = d.getDay() // 5 result = d.getTime() // 1451627133000 result = d.toLocaleDateString() // 将日期转换为本地的字符串 2016/1/1 result = d.toLocaleTimeString() // 将时间转换为本地的字符串 13:45:33 result = d. toLocaleString() // 将日期转换为本地日期+时间的字符串 2016/1/1 13:45:33 console.log(result)
7、正则表达式
-
- 正则表达式用来定义一个规则
- 通过这个规则计算机可以检查一个字符串是否符合规则,或者将字符串中符合规则的内容提取出来
- 正则表达式也是JS中的一个对象,所以要使用正则表达式,需要先创建正则表达式的对象
- 创建
- new RegExp()
- 可以接收两个参数(字符串)
- 正则表达式
- 匹配模式
- i :忽略大小写
- g :全局模式
- 可以接收两个参数(字符串)
- 使用字面量
- /正则/匹配模式
- 注意,正则和匹配模式都不需要写引号
- new RegExp()
- 方法
- test()
- 通过正则表达式检查一个字符串是否符合规则
// 检查一个字符串中是否有a let reg = new RegExp('a','i') // 字面量形式 reg = /a/i result = reg.test('abc') // true result = reg.test('bc') // false result = reg.test('ddac') // true
- 通过正则表达式检查一个字符串是否符合规则
- exec()
- 获取字符串中符合正则表达式的内容
- 如果匹配,则返回一个数组,否则返回null
let str = "hello123worldhello456worldyeah789" let re = /hello((\d+)world)/g let result = re.exec(str) while(result){ console.log(result[0], result[1], result[2]) // result[0] 表示整体匹配的内容,result[1]表示第一层分组的内容,result[2]表示第二层分组的内容 result = re.exec(str) }
- test()
8、字符串
-
- 字符串其本质就是一个字符数组
- 字符串的很多方法都和数组是非常类似的
- 属性和方法
- length
- 获取字符串的长度
- 字符串[索引]
- 获取指定位置的字符
- at()
- 根据索引获取字符,可以接受负索引
- charAt()
- 根据索引获取字符
- concat()
- 用来连接两个或多个字符串
- includes()
- 用来检查字符串中是否包含某个内容
- 有则返回true
- 否则返回false
- 用来检查字符串中是否包含某个内容
- indexOf()
- 查询某个内容首次出现的索引,没有返回-1
- lastIndexOf()
- 查询某个内容最后出现的索引,没有返回-1
- startsWith()
- 检查一个字符串是否以指定内容开头
- endsWith()
- 检查一个字符串是否以指定内容结尾
- padStart()
- 向字符串前面添加指定的内容,使字符串保持某个长度
- padEnd()
- 向字符串后面添加指定的内容,使字符串保持某个长度
- replace()
- 使用一个新字符串替换一个指定内容
- 也可以根据正则表达式替换字符串中的指定内容
- 全局模式(g)下,可以替换所有
- 非全局模式下,仅替换第一次匹配到的内容
- replaceAll()
- 使用一个新字符串替换所有指定内容
- slice()
- 对字符串进行切片
- substring()
- 截取字符串,同slice,但是可以自动判断传入的起止索引,做相应调整
- split()
- 用来将一个字符串拆分为一个数组
- 也可以根据正则表达式来对一个字符串进行拆分
- toLowerCase()
- 将字符串转换为小写
- toUpperCase()
- 将字符串转换为大写
- trim()
- 去除前后空格
- trimStart()
- 去除开始空格
- trimEnd()
- 去除结束空格
- search()
- 可以去搜索符合正则表达式的内容第一次在字符串中出现的位置
- match()
- 根据正则表达式去匹配字符串中符合要求的内容
- matchAll()
- 根据正则表达式去匹配字符串中符合要求的内容(必须设置g 全局匹配)
- 它返回的是一个迭代器
- length
- 示例
let str = "a@b@c@d" let result = str.split("@") str = "孙悟空abc猪八戒adc沙和尚" result = str.split(/a[bd]c/) str = "dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd" result = str.search("abc") result = str.search(/1[3-9]\d{9}/) result = str.replace(/1[3-9]\d{9}/g, "哈哈哈") result = str.match(/1[3-9]\d{9}/g) result = str.matchAll(/1[3-9](\d{9})/g) for(let item of result){ console.log(item) }
十、DOM
1、document
-
- document对象表示的是整个网页
- document对象的原型链
- HTMLDocument -> Document -> Node -> EventTarget -> Object.prototype -> null
- 凡是在原型链上存在的对象的属性和方法都可以通过Document去调用
- 部分属性:
- document.documentElement --> html根元素
- document.head --> head元素
- document.title --> title元素
- document.body --> body元素
- document.links --> 获取页面中所有的超链接
2、元素节点(element)
-
- 在网页中,每一个标签都是一个元素节点
- 元素的原型链
- 以div元素为例
- HTMLDivElement -> HTMLElement -> Element -> Node -> ...
- 以div元素为例
- 获取元素节点对象方式:
- 1、通过document对象获取已有元素节点
- document.getElementById()
- 根据id获取一个元素节点对象
- document.getElementsByClassName()
- 根据元素的class属性值获取一组元素节点对象
- 返回的是一个类数组对象(实时更新)
- document.getElementsByTagName()
- 根据标签名获取一组元素节点对象(实时更新)
- document.getElementsByTagName("*") 获取页面中所有的元素
- document.getElementsByName()
- 根据name属性获取一组元素节点对象(实时更新)
- 主要用于表单项
- document.querySelectorAll()
- 根据选择器去页面中查询元素
- 会返回一个类数组(不会实时更新)
- document.querySelector()
- 根据选择器去页面中查询第一个符合条件的元素
- document.getElementById()
- 2、通过document对象来创建元素节点
- document.createElement()
- 根据标签名创建一个元素节点对象
- document.createElement()
- 1、通过document对象获取已有元素节点
- 通过元素节点对象,获取其他节点
- element.children
- 获取当前元素的子元素
- element.childNodes
- 获取当前元素的子节点(会包含空白的子节点,所以很少使用)
- element.firstElementChild
- 获取当前元素的第一个子元素
- element.lastElementChild
- 获取当前元素的最后一个子元素
- element.nextElementSibling
- 获取当前元素的下一个兄弟元素
- element.previousElementSibling
- 获取当前元素的前一个兄弟元素
- element.parentNode
- 获取当前元素的父节点
- element.tagName
- 获取当前元素的标签名(大写)
- element.children
- 元素插入、替换、删除、复制操作
- 插入
- element.appendChild(元素)
- 作为最后一个子节点插入
- element.insertAdjacentElement()
- 可以向元素的任意位置添加元素
- 参数
- 1、要添加的位置
- beforeend
- 标签的最后,即作为最后一个子节点插入
- afterbegin
- 标签的开始位置后,即作为第一个
- beforebegin
- 作为上一个兄弟元素插入
- afterend
- 最为后一个兄弟元素插入
- beforeend
- 2、要添加的元素
- 1、要添加的位置
- element.insertAdjacentHTML()
- 可以向任何位置添加html代码
- 参数
- 1、要添加的位置,同insertAdjacentElement
- 2、要添加的html代码
- element.appendChild(元素)
- 替换
- element.replaceWith()
- 使用一个元素替换当前元素
- element.replaceWith()
- 删除
- element.remove()
- 删除当前元素
- elment.removeChild(元素)
- 删除指定子元素
- element.parentNode.removeChild(element)
- 删除当前元素
- element.parentNode.removeChild(element)
- 删除指定子元素
- element.remove()
- 复制
- element.cloneNode()
- 它会复制节点的所有特点包括各种属性,但不会复制节点的子节点(比如节点中的文本)
- 可以传递一个true作为参数,这样该方法也会将元素的子节点一起复制
- element.cloneNode()
- 插入
- 示例
<!DOCTYPE html> <html lang="zh"> <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 id="btn01">按钮1</button> <button id="btn02">按钮2</button> <hr /> <ul id="list"> <li id="swk">孙悟空</li> <li id="zbj">猪八戒</li> <li id="shs">沙和尚</li> </ul> <script> /* 点击按钮后,向ul中添加一个唐僧 */ // 获取ul const list = document.getElementById("list") // 获取按钮 const btn01 = document.getElementById("btn01") btn01.onclick = function () { // 创建一个li const li = document.createElement("li") // 向li中添加文本 li.textContent = "唐僧" // 给li添加id属性 li.id = "ts" // appendChild() 用于给一个节点添加子节点 // list.appendChild(li) //insertAdjacentElement()可以向元素的任意位置添加元素 //两个参数:1.要添加的位置 2.要添加的元素 // beforeend 标签的最后 afterbegin 标签的开始 // beforebegin 在元素的前边插入元素(兄弟元素) afterend 在元素的后边插入元素(兄弟元素) // list.insertAdjacentElement("afterend", li) list.insertAdjacentHTML("beforeend", "<li id='bgj'>白骨精</li>") } const btn02 = document.getElementById("btn02") btn02.onclick = function(){ // 创建一个蜘蛛精替换孙悟空 const li = document.createElement("li") li.textContent = "蜘蛛精" li.id = "zzj" // 获取swk const swk = document.getElementById("swk") // replaceWith() 使用一个元素替换当前元素 // swk.replaceWith(li) // remove()方法用来删除当前元素 swk.remove() } </script> </body> </html>
3、文本节点
-
- 在DOM中,网页中所有的文本内容都是文本节点对象,可以通过元素来获取其中的文本节点对象,但是我们通常不会这么做
- 直接通过元素去操作文本
- element.textContent
- 获取或修改元素中的文本内容
- 获取的是标签中的内容,不会考虑css样式
- element.innerText
- 获取或修改元素中的文本内容
- 获取内容时,会考虑css样式
- 通过innerText去读取CSS样式,会触发网页的重排(计算CSS样式)
- 当字符串中有标签时,会自动对标签进行转义
- <li> --> <li>
- element.innerHTML
- 获取或修改元素中的html代码
- 可以直接向元素中添加html代码
- innerHTML插入内容时,有被xss注入的风险
- element.textContent
4、属性节点
-
- 在DOM也是一个对象,通常不需要获取对象,而是直接通过元素即可完成对其的各种操作
- 属性节点操作
- 方式一
- 读取
- 元素.属性名
- 注意:class属性需要使用className来读取
- 读取一个布尔值时,会返回true或false,比如disabled属性
- 元素.属性名
- 修改
- 元素.属性名 = 属性值
- 读取
- 方式二
- 读取
- 元素.getAttribute(属性名)
- 修改
- 元素.setAttribute(属性名, 属性值)
- 删除
- 元素.removeAttribute(属性名)
- 读取
- 方式一
5、css样式操作
-
- 修改样式
- 元素.style.样式名 = 样式值
- 如果样式名中含有-,则需要将样式表修改为驼峰命名法
-
background-color --> backgroundColor
-
- 读取样式
- getComputedStyle()函数
- 它会返回一个对象,这个对象中包含了当前元素所有的生效的样式
- 参数
- 1. 要获取样式的对象
- 2. 要获取的伪元素
- 返回值:
- 返回的一个对象,对象中存储了当前元素的样式
- 注意
- 样式对象中返回的样式值,不一定能来拿来直接计算
- 所以使用时,一定要确保值是可以计算的才去计算
<!DOCTYPE html> <html lang="zh"> <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>css样式操作</title> <style> .box1 { height: 200px; background-color: #bfa; } .box1::before { content: "hello"; color: red; } </style> </head> <body> <button id="btn">点我一下</button> <hr /> <div class="box1"></div> <script> /* 点击按钮后,读取元素的css样式 */ const btn = document.getElementById("btn") const box1 = document.querySelector(".box1") btn.onclick = function () { const styleObj = getComputedStyle(box1) console.log(styleObj.width) console.log(styleObj.left) // console.log(parseInt(styleObj.width) + 100) // box1.style.width = parseInt(styleObj.width) + 100 + "px" // console.log(styleObj.backgroundColor) const beforeStyle = getComputedStyle(box1, "::before") // console.log(beforeStyle.color) console.log(box1.firstElementChild) } </script> </body> </html>
- getComputedStyle()函数
- 修改样式
6、事件(event)
-
- 事件就是用户和页面之间发生的交互行为
- 比如:点击按钮、鼠标移动、双击按钮、敲击键盘、松开按键...
- 可以通过为事件绑定响应函数(回调函数),来完成和用户之间的交互
- 绑定事件方式:
- 1.可以直接在元素的属性中设置
- 2.可以通过为元素的指定属性设置回调函数的形式来绑定事件
- 一个事件只能绑定一个响应函数,如果绑定多个,则后面的会覆盖掉前面的
- 常用的属性比如
- onclick:单击
- ondblclick:双击
- onmouseenter:鼠标进入
- 3.可以通过元素addEventListener()方法来绑定事件
- 可以给同一个元素对象绑定多个事件
- 参数
- 1、事件字符串(前面不需要on)
- click:单击
- dblclick:双击
- mouseenter:鼠标进入
- 2、回调函数
- 参数
- 1、事件字符串(前面不需要on)
- 4、元素.addEvent()方法
- 参数
- 1、事件字符串前面带有on,比如onclick
- 2、回调函数
- 参数
- 删除事件:
- removeEventListener(事件字符串, 回调函数)
- 参数同addEventListener()方法内保持一致,即事件名和回调函数都必须一致
- removeEventListener(事件字符串, 回调函数)
- 事件对象
- 事件对象是由浏览器在事件触发时所创建的对象
- 这个对象中封装了事件相关的各种信息
- 通过事件对象可以获取到事件的详细信息
- 比如:鼠标的坐标、键盘的按键...
- 不同鼠标点击位置坐标区分:
- ScreenX和ScreenY:参照电脑屏幕左上角
- ClientX和ClientY:参照浏览器内容可视区域左上角
- pageX和pageY:参照网页左上角(浏览器可视区域左上角+横纵滚动距离)
- offsetX和offsetY:参照触发事件的对象左上角
- 浏览器在创建事件对象后,会将事件对象作为响应函数的参数传递,所以我们可以在事件的回调函数中定义一个形参来接收事件对象
<!DOCTYPE html> <html lang="zh"> <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> #box1{ width: 300px; height: 300px; border: 10px greenyellow solid; } </style> </head> <body> <div id="box1"></div> <script> const box1 = document.getElementById("box1") //绑定鼠标移动事件 box1.addEventListener("mousemove", event => { console.log(event.clientX, event.clientY) // 鼠标横纵坐标 box1.textContent = event.clientX + "," + event.clientY }) </script> </body> </html>
- 在DOM中存在着多种不同类型的事件对象,多种事件对象有一个共同的祖先(Event)
- event.target
- 触发事件的对象
- event.currentTarget
- 绑定事件的对象
- event.stopPropagation()
- 停止事件的传导
- event.preventDefault()
- 取消默认行为
- event.target
- 事件的冒泡
- 事件的冒泡就是指事件的向上传导
- 当元素上的某个事件被触发后,其祖先元素上的相同事件也会同时被触发
- 事件的冒泡和元素的样式无关,只和结构相关
- 冒泡的存在大大的简化了代码的编写,但是在一些场景下我们并不希望冒泡存在,不希望事件冒泡时,可以通过事件对象来取消冒泡
- event.stopPropagation()
<!DOCTYPE html> <html lang="zh"> <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> #box1 { width: 300px; height: 300px; background-color: greenyellow; } #box2 { width: 250px; height: 250px; background-color: #ff0; } #box3 { width: 200px; height: 200px; background-color: orange; } </style> </head> <body> <div id="box1"> <div id="box2"> <div id="box3"></div> </div> </div> <a id="aaa" href="https://www.baidu.com">超链接</a> <script> const box1 = document.getElementById("box1") const box2 = document.getElementById("box2") const box3 = document.getElementById("box3") const aaa = document.getElementById("aaa") aaa.addEventListener("click", (event) => { event.preventDefault() // 取消默认行为,即取消跳转 alert("被点了~~~") }) box1.addEventListener("click", function (event) { // alert(event) /* 在事件的响应函数中: event.target 表示的是触发事件的对象 this 绑定事件的对象 */ // console.log(event.target) // console.log(this) console.log(event.currentTarget) // alert("Hello 我是box1") }) box2.addEventListener("click", function(event){ event.stopPropagation() alert("我是box2") }) box3.addEventListener("click", function(event){ event.stopPropagation() // 取消事件的传导 alert("我是box3") }) </script> </body> </html>
- event.stopPropagation()
- 事件的委派
- 委派就是将本该绑定给多个元素的事件,统一绑定给某个祖先元素(通常为document),这样可以降低代码复杂度方便维护
<!DOCTYPE html> <html lang="zh"> <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> </head> <body> <button id="btn">点我一下</button> <hr /> <ul id="list"> <li><a href="javascript:;">链接一</a></li> <li><a href="javascript:;">链接二</a></li> <li><a href="javascript:;">链接三</a></li> <li><a href="javascript:;">链接四</a></li> </ul> <script> /* 我一个希望: 只绑定一次事件,既可以让所有的超链接,包括当前的和未来新建的超链接都具有这些事件 思路: 可以将事件统一绑定给document,这样点击超链接时由于事件的冒泡, 会导致document上的点击事件被触发,这样只绑定一次,所有的超链接都会具有这些事件 */ const list = document.getElementById("list") const btn = document.getElementById("btn") // 获取list中的所有链接 const links = list.getElementsByTagName("a") document.addEventListener("click", (event) => { // 在执行代码前,先来判断一下事件是由谁触发 // 检查event.target 是否在 links 中存在 // console.log(Array.from(links)) if([...links].includes(event.target)){ alert(event.target.textContent) } }) // 点击按钮后,在ul中添加一个新的li btn.addEventListener("click", () => { list.insertAdjacentHTML( "beforeend", "<li><a href='javascript:;'>新超链接</a></li>" ) }) </script> </body> </html>
- 委派就是将本该绑定给多个元素的事件,统一绑定给某个祖先元素(通常为document),这样可以降低代码复杂度方便维护
- 事件的捕获
- 在DOM中,事件的传播可以分为三个阶段:
- 1.捕获阶段
- 由祖先元素向目标元素进行事件的捕获
- 默认情况下,事件不会在捕获阶段触发
- 2.目标阶段
- 触发事件的对象
- 3.冒泡阶段
- 由目标元素向祖先元素进行事件的冒泡
- 1.捕获阶段
- 事件的捕获,指事件从外向内的传导,当前元素触发事件以后,会先从当前元素最大的祖先元素开始向当前元素进行事件的捕获
- 如果希望在捕获阶段触发事件,可以将addEventListener的第三个参数设置为true
- 一般情况下我们不希望事件在捕获阶段触发,所有通常都不需要设置第三个参数
- 在DOM中,事件的传播可以分为三个阶段:
- 事件就是用户和页面之间发生的交互行为
7、文档加载
-
- 网页是自上向下加载的,如果将js代码编写到网页的上边,js代码在执行时,网页还没有加载完毕,这时会出现无法获取到DOM对象的情况
- 解决方案:
- 将script标签编写到body的最后(执行最早)
- 将代码编写到window.onload的回调函数中
- 将代码编写到document对象的DOMContentLoaded的回调函数中(执行时机早于window.onload)
- 将代码编写到外部的js文件中,然后以defer的形式进行引入(执行时机早于DOMContentLoaded)
<!DOCTYPE html> <html lang="zh"> <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> <script> // window.onload = function () { // const btn = document.getElementById("btn") // console.log(btn) // } // window.addEventListener("load", function () { // const btn = document.getElementById("btn") // alert(btn) // }) // document.addEventListener("DOMContentLoaded", function () { // const btn = document.getElementById("btn") // alert(btn) // }) </script> <!--以defer的形式引入外部js--> <script defer src="./script/script.js"></script> </head> <body> <button id="btn">点我一下</button> </body> </html>
十一、BOM
1、BOM
-
- 浏览器对象模型
- BOM为我们提供了一组对象,通过这组对象可以完成对浏览器的各种操作
- BOM对象:
- Window —— 代表浏览器窗口(全局对象)
- Navigator —— 浏览器的对象(可以用来识别浏览器)
- Location —— 浏览器的地址栏信息
- History —— 浏览器的历史记录(控制浏览器前进后退)
- Screen —— 屏幕的信息
- BOM对象都是作为window对象的属性保存的,所以可以直接在JS中访问这些对象
- 比如 console.log(history)
2、navigator
-
- 浏览器的对象(可以用来识别浏览器)
- userAgent
- 返回一个用来描述浏览器信息的字符串
<!DOCTYPE html> <html lang="zh"> <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>userAgent</title> </head> <body> <script> // console.log(navigator.userAgent) let sBrowser const sUsrAg = navigator.userAgent // The order matters here, and this may report false positives for unlisted browsers. if (sUsrAg.indexOf("Firefox") > -1) { sBrowser = "Mozilla Firefox" // "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0" } else if (sUsrAg.indexOf("SamsungBrowser") > -1) { sBrowser = "Samsung Internet" // "Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-G955F Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/9.4 Chrome/67.0.3396.87 Mobile Safari/537.36 } else if ( sUsrAg.indexOf("Opera") > -1 || sUsrAg.indexOf("OPR") > -1 ) { sBrowser = "Opera" // "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 OPR/57.0.3098.106" } else if (sUsrAg.indexOf("Trident") > -1) { sBrowser = "Microsoft Internet Explorer" // "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; Zoom 3.6.0; wbx 1.0.0; rv:11.0) like Gecko" } else if (sUsrAg.indexOf("Edge") > -1) { sBrowser = "Microsoft Edge (Legacy)" // "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299" } else if (sUsrAg.indexOf("Edg") > -1) { sBrowser = "Microsoft Edge (Chromium)" // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.64 } else if (sUsrAg.indexOf("Chrome") > -1) { sBrowser = "Google Chrome or Chromium" // "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/66.0.3359.181 Chrome/66.0.3359.181 Safari/537.36" } else if (sUsrAg.indexOf("Safari") > -1) { sBrowser = "Apple Safari" // "Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.0 Mobile/15E148 Safari/604.1 980x1306" } else { sBrowser = "unknown" } alert(`You are using: ${sBrowser}`) </script> </body> </html>
- 返回一个用来描述浏览器信息的字符串
3、location
-
- 表示的是浏览器地址栏的信息,等同于window.location
- location直接赋值
- 修改为一个新的地址,这样会使得网页发生跳转
- location.assign()
- 跳转到一个新的地址
- location.replace()
- 跳转到一个新的地址(无法通过回退按钮回退)
- location.reload()
- 刷新页面,可以传递一个true来强制清缓存刷新
- location.href
- 获取当前地址
- 如果赋值新的地址,则网页会发生跳转
- 示例
<!DOCTYPE html> <html lang="zh"> <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>location</title> </head> <body> <button id="btn">点我一下</button> <input type="text" name="username" /> <script> const btn = document.getElementById("btn") btn.addEventListener("click", () => { // console.log(location.href) // location = "https://www.baidu.com" // location.assign("https://www.baidu.com") // location.replace("https://www.baidu.com") location.reload(true) }) </script> </body> </html>
4、history
-
- 浏览器的历史记录(控制浏览器前进后退)
- history.back()
- 回退按钮
- history.forward()
- 前进按钮
- history.go()
- 可以向前跳转也可以向后跳转
- 传入正数,向前跳转
- 传入负数,向后跳转
- 示例
<!DOCTYPE html> <html lang="zh"> <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>history</title> </head> <body> <button id="btn">点我一下</button> <script> const btn = document.getElementById("btn") btn.onclick = () => { /* history.back() - 回退按钮 history.forward() - 前进按钮 history.go() - 可以向前跳转也可以向后跳转 */ // console.log(history.length) // history.back() // history.forward() history.go(-1) } </script> </body> </html>
5、事件循环(event loop)
-
- 函数在每次执行时,都会产生一个执行环境
-
- 执行环境负责存储函数执行时产生的一切数据
- 函数的执行环境要存储到哪里呢?
- 函数的执行环境存储到了一个叫做调用栈的地方
- 栈,是一种数据结构,特点 后进先出
-
队列,是一种数据结构,特点 先进先出
- 调用栈(call stack)
- 调用栈负责存储函数的执行环境
- 当一个函数被调用时,它的执行环境会作为一个栈帧,插入到调用栈的栈顶,函数执行完毕其栈帧会自动从栈中弹出
-
消息队列
- 消息队列负责存储将要执行的函数
- 当我们触发一个事件时,其响应函数并不是直接就添加到调用栈中的,因为调用栈中有可能会存在一些还没有执行完的代码
- 事件触发后,JS引擎是将事件响应函数插入到消息队列中排队
6、定时器
-
- 定时器的本质,就是在指定时间后将函数添加到消息队列中
- 设置定时器的方式有两种:
- setTimeout()
- 使代码在指定时间后执行(单次)
- 参数
- 1. 回调函数(要执行的代码)
- 2. 间隔的时间(毫秒)
- 关闭定时器
- clearTimeout()
- 参数
- 设置定时器的返回值
- 参数
- clearTimeout()
- 希望可以确保函数每次执行都有相同间隔
- 在setTimeout的回调函数的最后,在调用一个setTimeout
- setInterval()
- 每间隔一段时间代码就会执行一次
- 但是如果函数执行的速度比较慢,它是无法确保每次执行的间隔都是一样的
- 参数:
- 1. 回调函数(要执行的代码)
- 2. 间隔的时间(毫秒)
- 关闭定时器
- clearInterval()
- 参数
- 设置定时器的返回值
- 参数
- clearInterval()
- 每间隔一段时间代码就会执行一次
- setTimeout()
- 示例
<!DOCTYPE html> <html lang="zh"> <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> </head> <body> <script> /* 定时器的本质,就是在指定时间后将函数添加到消息队列中 */ console.time() setTimeout(function(){ console.timeEnd() console.log("定时器执行了~") }, 3000) // 使程序卡6s const begin = Date.now() while (Date.now() - begin < 6000) {} /* setInterval() 没间隔一段时间就将函数添加到消息队列中 但是如果函数执行的速度比较慢,它是无法确保每次执行的间隔都是一样的 */ // console.time("间隔") // setInterval(function(){ // console.timeEnd("间隔") // // console.log("定时器执行了~~") // alert("定时器执行~") // console.time("间隔") // }, 3000) /* 希望可以确保函数每次执行都有相同间隔 */ // console.time("间隔") // setTimeout(function fn() { // console.timeEnd("间隔") // alert("哈哈") // console.time("间隔") // // 在setTimeout的回调函数的最后,在调用一个setTimeout // setTimeout(fn, 3000) // }, 3000) </script> </body> </html>