跳到主要内容

JavaScript


1.入门


字面量和变量

        /*
字面量
- 字面量其实就是一个值,它所代表的含义就是它字面量的意思
- 比如:1 2 3 4 100 "hello" true null ...
- 在js中所有的字面量都可以直接使用,但是直接使用字面量并不方便

变量
- 变量可以用来存储字面量
- 并且变量中存储的字面量可以随意的修改
- 通过变量可以对字面量进行描述,并且变量比较方便修改

- 变量的使用
声明变量 -> let 变量名 / var 变量
变量赋值 -> a = xx
声明和赋值同时进行 -> let 变量 = 值
*/

变量的内存

变量中并不存储任何值,而是存储值的内存地址!


常量

        /*
在JS中,使用const声明常量,常量只能赋值一次,重复赋值会报错
在JS中除了常规的常量外,有一些对象类型的数据我们也会声明为常量
*/

const PI = 3.1415926;

标识符

        /*
在JS中,所有可以由我们自主命名的内容,都可以认为是一个标识符
像 变量名 函数名 类名...
使用标识符需要遵循如下的命名规范:
1.标识符只能含有字母、数值、下划线、$,且不能以数字开头
2.标识符不能是JS中的关键字和保留字,也不建议使用内置的函数或类名作为变量名
3.命名规范:
- 通常会使用驼峰命名法
- 首字母小写,每个单词开头大写
- maxlength -> maxLength
- borderleftwidth -> borderLeftWidth

- 类名会使用大驼峰命名法
- 首字母大写,每个单词开头大写
- maxlength -> MaxLength

- 常量的字母会全部大写
- PI MAX_LLENGTH
*/

2.数据类型


数值

        /*
数值(Number)
- 在JS中所有的整数和浮点数都是Number类型
- JS中的数值并不是无限大的,当数值超过一定范围后会显示近似值
- Infinity 是一个特殊的数值,表示无穷
- 所以在JS中进行一些精度较高的运算时要十分注意
- NaN 也是一个特殊的数值,表示非法的数值

大整数(BigInt)
- 大整数用来表示一些比较大的整数
- 大整数使用n结尾,它可以表示的数字范围是无限大
a = 9999999999999999999999999n

其他进制的数字:
二进制 0b
a = 0b1010
八进制 0o
a = 0o10
十六进制 0x
a = 0xff
*/

类型检查

        /* 
typeof 运算符
- typeof用来检查不同的值的类型
- 它会更具不同的值返回不同的结果
*/
let a = 10;
let b = 10n;

console.log(typeof a); // number
console.log(typeof b); // bigint

字符串

        /* 
字符串(String)
- 在JS中使用单引号或双引号来表示字符串
- 转义字符 \
\" -> "
\' -> '
\\ -> \
\t -> 制表符
\n -> 换行
- 模板字符串
- 使用反单引号 ` 来表示模板字符串
- 模板字符串中可以嵌入变量
- 使用typeof检查一个字符串时会返回 "string"
*/

其他数据类型

        /*
布尔值(Boolean)
- 布尔值主要用来进行逻辑判断
- 布尔值只有两个true 和 false
- 使用typeof检查一个布尔值时会返回 "boolean"

空值(Null)
- 空值用来表示空对象
- 空值只有一个 null
- 使用typeof检查一个空值时会返回 "object"
- 使用typeof无法检查空值

未定义(Undefined)
- 当声明一个变量而没有赋值时,它的值就是undefined
- undefined类型的值只有一个就是undefined
- 使用typeof检查一个undefined类型的值时,会返回 "undefined"

符号(Symbol)
- 用来创建一个唯一的标识
- 使用typeof检查符号时会返回 "symbol"

JS中原始值一共有七种
1.Number
2.BigInt
3.String
4.Boolean
5.Null
6.Undefined
7.Symbol
七种原始值是构成各种数据的基石
原始值在JS中是不可变类型,一旦创建就不能修改
*/

let a = Symbol(); // 调用Symbol()创建了一个符号
console.log(typeof a); // symbol

类型转换-字符串

        /* 
类型转换指将一种数据类型转换为其他类型
将其他类型转换为(字符串、数值和布尔值)

转换为字符串
1.调用toString()方法将其他类型转换为字符串
- 调用xxx的yyy方法
-> xxx.yyy()
- 由于null和undefined没有toString()方法
所以这两个东西调用toString()时会报错
2.调用String()函数将其他类型转换为字符串
- 调用xxx函数
-> xxx()
- 原理:
对于拥有toString()方法的值调用String()函数时,实际上就是在调用toString()方法
对于null,则直接转换为 "null"
对于undefined,直接转换为 "undefined"
*/
let a = 10;
console.log(typeof a, a); // number 10

a = a.toString();
console.log(typeof a, a); // string "10"

let b = 33;
b = undefined;
console.log(typeof b, b); // undefined undefined

b = String(b);
console.log(typeof b, b); // string undefined

类型转换-数值

        /* 
将其他类型的数据类型转换为数值
1.使用Number()函数来将其他类型转换为数值
转换的情况:
- 字符串:
- 如果字符串是一个合法的数字,则会自动转换为对应的数字
- 如果字符串不是合法数字,则转换为NaN
- 如果字符串是空串或纯空格的字符串,则转换为0
- 布尔值:
- true转换为1,false转换为0
- null 转换为 0
- undefined 转换为 NaN

2.专门来将字符串转换为数值的两个方法
parseInt() 将一个字符串转换为一个整数,解析一个字符串并返回指定基数的十进制整数
-如果参数不是一个字符串,则将其转换为字符串 (使用 ToString抽象操作)。
字符串开头的空白符将会被忽略。
- 解析时,会自左向右读取一个字符串,直到读取到字符串中所有的有效的整数
parseFloat() 将一个字符串转换为浮点数
- 解析时,会自左向右读取一个字符串,直到读取到字符串中所有的有效的小数
*/
let a = "123";
console.log(typeof a, a); // string "123"

a = Number(a);
console.log(typeof a, a); // number 123

let b = "12.45c456";
console.log(typeof b, b); // string "12.45c456"

c = parseInt(b);
d = parseFloat(b);
console.log(typeof c, c); // number 12
console.log(typeof d, d); // number 12.45

类型转换-布尔值

        /* 
1.使用Boolean()函数来将其他类型转换为布尔值
- 转换的情况:
数字:
- 0 和 NaN 转换为false
- 其余是true
字符串:
- 空串 转换为 false
- 其余是true
null和undefined 都转换为false
对象:对象都会转换为true

- 所有标识空性的没有的错误的都会转换为false:0、NaN、空串、null、undefined、false
*/

类型转换

		/*
类型转换
- 转换为字符串
显示转换
toString()
String()
隐式转换
+ ""
- 转换为数值
显示转换
Number()
parseInt()
parseFloat()
隐式转换
+
- 转换为布尔值
显示转换
Boolean()
隐式转换
!!
*/

3.运算符


算数运算符

        /* 
运算符(操作符)
- 运算符可以用来对一个或多个操作数进行运算
- 算数运算符:
+ - * / 加减乘除
** 幂运算
% 模运算,两个数相除取余数

- 注意:
- 算数运算时,除了字符串的加法,
其他运算的操作数是非数值时,都会转换为数值然后再运算


JS是一门弱类型语言,当进行运算时会通过自动的类型转换来完成运算
a = 10 - "5" // 10 - 5
a = 10 + true // 10 - 1
a = 5 + null // 5 + 0
a = 6 - undefined // 6 - NaN

当任意一个值和字符串做加法时,它会先将其他值转换为字符串,然后再做拼串的操作
可以利用这一特点来完成类型转换
可以通过为 任意类型 + 一个空串 的形式来将其转换为字符串
其原理和String()函数相同,但使用起来更加简洁
*/
a = "a" + null;
console.log(a); // "anull"

赋值运算符

        /*
赋值运算符用来将一个值赋给一个变量
= 将符号右侧的值赋值给左侧的变量
??=
- 空赋值
- 只有当变量的值为null或undefined时才会对变量进行赋值
+=
- a += n 等价于 a = a + n
-=
- a -= n 等价于 a = a - n
*=
- a *= n 等价于 a = a * n
/=
- a /= n 等价于 a = a / n
%=
- a %= n 等价于 a = a % n
**=
- a **= n 等价于 a = a ** n
*/

一元的加减

        /* 
一元的加减
+ 正号
- 不会改变数值的符号
- 负号
- 可以对数值进行符号位取反

当我们对非数值类型进行正负运算时,会先将其转换为数值然后再运算
*/
let a = "123";
a = +a; // a = Number(a)
console.log(typeof a, a); // number 123

自增和自减

		/* 
++ 自增运算符
- ++ 使用后会使得原来的变量立刻增加1
- 自增分为前自增(++a)和后自增(a++)
- 无论是++a还是a++都会使原变量立刻增加1
- 不同的是++a和a++所返回的值不同
a++ 是自增前的值 旧值
++a 是自增后的值 新值

-- 自减运算符
- 参考自增
*/
let a = 10;
a++;
console.log(a); // 11

let b = 10;
let c = b++;
console.log("b++ =", c); // b++ = 10

let d = 10;
let e = ++d;
console.log("++d =", e); // ++d = 11

let n = 5;
let result = n++ + ++n + n // 5 + 7 + 7
console.log(result); // 19

逻辑运算符

        /* 
! 逻辑非
- ! 可以用来对一个值进行非运算
- 它可以对一个布尔值进行取反操作
true -> false
false -> true
- 如果对一个非布尔值进行取反,它会先将其转换为布尔值然后再取反
可以利用这个特点将其他类型转换为布尔值

&& 逻辑与
- 可以对两个值进行与运算
- 当&&左右都为true时,返回true,否则返回false
- 与运算时短路的与,如果第一个值为false,则不看第二个值
- 与运算是找false,如果找到false则直接返回,没有false才会返回true
- 对非布尔值进行与运算,它会转换为布尔值然后运算,但是最终会返回原值
- 如果第一个值为false,则直接返回第一个值
- 如果第一个值为true,则返回第二个值

|| 逻辑或
- 可以对两个值进行或运算
- 当||左右有true时,返回true,否则返回false
- 或运算也是短路的或,如果第一个值为true,则不看第二个值
- 或运算是找true,如果找到true则直接返回,没有true才会返回false
- 对非布尔值进行或运算,它会转换为布尔值然后运算,但是最终会返回原值
- 如果第一个值为true,则直接返回第一个值
- 如果第一个值为false,则返回第二个值
*/
let a = 123;
a = !!a;
console.log(typeof a, a); // boolean true

true && alert(123); // 第一个值为true,alert会执行
false && alert(456); // 第一个值为false,alert不会执行

let result = 1 && 2;
console.log(result); // 2
let result2 = 1 && 0;
console.log(result2); // 0
let result3 = 0 && NaN;
console.log(result3); // 0

true || alert(78); // 第一个值为true,alert不会执行
false || alert(90); // 第一个值为false,alert会执行

关系运算符

        /* 
关系运算符
- 关系运算符用来检查两个值之间的关系是否成立
成立返回true,不成立返回false
- >
- 用来检查左值是否大于右值
- >=
- 用来检查左值是否大于或等于右值
- <
- 用来检查左值是否小于右值
- <=
- 用来检查左值是否小于或等于右值

注意:
当对非数值进行关系运算时,它会先将其转换为数值然后再比较
当关系运算符的两端是两个字符串,它不会将字符串转换为数值,
而是逐位的比较字符的Unicode编码
利用这个特点可以对字符串按照字母排序
注意比较两个字符串格式的数字时一定要进行类型转换
*/

let result = 5 < "10";
console.log(result); // true

let result2 = "1" > false;
console.log(result2); // true

let result3 = "a" < "b";
console.log(result3); // true
let result4 = "abc" < "b";
console.log(result3); // true

let result5 = "12" < "2";
console.log(result3); // true

相等运算符

        /* 
==
- 相等运算符,用来比较两个值是否相等
- 使用相等运算符比较两个不同类型的值时,
它会将其转换为相同的类型(通常转换为数值)然后再比较
类型转换后值相同也会返回true
- null和undefined进行比较时会返回true
- NaN不和任何值相等,包括它自身
- 不能使用 == 或 === 来检查一个值是否NaN,可以使用isNaN来检查
===
- 全等运算符,用来比较两个值是否全等
- 它不会进行自动的类型转换,如果两个值的类型不同直接返回false

!=
- 不等,用来检查两个值是否不相等
- 会自动的进行类型转换
!==
- 不全等,比较两个值是否不全等
- 不会自动的类型转换
*/
let result = 1 == 1;
console.log(result); // true
let result2 = 1 == "1";
console.log(result2); // true
let result3 = null == undefined;
console.log(result3); // true

条件运算符

        /* 
条件运算符
条件运算符 ? 表达式1 : 表达式2
- 执行顺序:
条件运算符在执行时,会先对条件表达式进行求值判断,
如果结果为true,则执行表达式1
如果结果为false,则执行表达式2
*/
false ? alert(1) : alert(2); // 2

let a = 100;
let b = 10;
a > b ? console.log("a大") : console.log("b大"); // "a大"
let max = a > b ? a : b;
console.log(max); // 100

运算符优先级

        /*
和数学一样,JS中的运算符也有优先级,比如先乘除和加减

可以通过优先级的表格来查询运算符的优先级
- 在表格中位置靠上的优先级越高,优先级越高越先执行,优先级一样自左向右执行
优先级我们不需要记忆
因为()拥有最高的优先级,使用运算符时,如果遇到拿不准的,可以直接通过()来改变优先级即可
*/

4.流程控制语句


代码块

        /*
使用 {} 来创建代码块,代码块可以用来对代码进行分组,
同一个代码块中的代码,就是同一组代码,一个代码块中的代码要么都执行要么都不执行

let 和 var
- 在JS中,使用let声明的变量具有块作用域
在代码块中声明的变量无法在代码块的外部访问

- 使用var声明的变量,不具有块级作用域
*/

if语句

        /*
流程控制语句可以用来改变程序执行的顺序
1.条件判断语句
2.条件分支语句
3.循环语句

if语句
- 语法:
if(条件表达式){语句}
- 执行流程
if语句在执行会先对if后的条件表达式进行求值判断
如果结果为true,则执行if后的语句
如果为false则不执行

if语句只会控制紧随其后的那一行代码,如果希望可以控制多行代码,可以使用{}将语句括起来
最佳实践,即使if后只有1行代码,我们也应该编写代码块,这样结构会更加的清晰

如果if后的添加表达式不是布尔值,会转换为布尔值然后再运算

prompt() 可以用来获取用户输入的内容,它会将用户输入的内容以字符串的形式以字符串的形式返回,
可以通过变量来接收
*/

if-else语句

        /*
if-else语句
- 语法:
if(条件表达式){
语句...
}else{
语句...
}
- 执行流程:
if-else执行时,先对条件表达式进行求值判断,
如果结果为true,则执行if后的语句
如果结果为false,则执行else后的语句

if-else else-if语句:
- 语法:
if(条件表达式){
语句...
}else if(条件表达式){
语句...
}else if(条件表达式){
语句...
}else if(条件表达式){
语句...
}else{
语句...
}
- 执行流程:
会自上向下依次对if后的条件表达式进行求值判断,
如果条件表达式结果为true,则执行当前if后的语句,执行完毕语句结束
如果条件表达式结果为false,则继续向下判断,直到找到true为止
如果所有的条件表达式都是false,则执行else后的语句

注意语句中只会有一个代码块被执行,
一旦有执行的代码块,下边的条件都不会再继续判断了
所以一定要注意,条件的编写顺序
*/

switch语句

        /*
switch语句
- 语法:
switch(表达式){
case 表达式:
代码...
break;
case 表达式:
代码...
break;
case 表达式:
代码...
break;
default:
代码...
break;
}

- 执行的流程
switch语句在执行时,会依次将switch后的表达式和和case后的表达式进行全等比较
如果比较结果为true,则自当前case处开始执行代码
如果比较结果为false,则继续比较其他case后的表达式,直到找到true为止

- 注意:
当比较结果为true时,会从当前case处开始执行代码
也就是说case是代码执行的起始位置
这就意味着只要是当前case后的代码,都会执行
可以使用break来避免执行其他的case
都没有的话,会执行default的语句

- 总结
switch语句和if语句的功能是重复,switch能做的事if也能做,反之亦然,
它们最大的不同在于,switch在多个全等判断时,结构比较清晰

*/

循环语句

        /* 
循环语句
- 通过循环语句可以使指定的代码反复执行
- JS中一共有三种循环语句
while语句
do-while语句
for语句
- while语句
- 语法:
while(条件表达式){
语句...
}

- 执行流程:
while语句在执行时,会先对条件表达式进行判断,
如果结果为true,则执行循环体,执行完毕,继续判断
如果为true,则再次执行循环体,执行完毕,继续判断,如此反复
如果知道条件表达式结果为false时,循环结束

- 通常编写一个循环,要有三个条件
1.初始化表达式(初始化变量)
2.条件表达式(设置循环运行的条件)
3.更新表达式(修改初始化变量)
*/
let i = 0;
while (1) {
console.log(i);
i++;
if (i >= 5) {
break;
}
}

do-while循环

        /*
do-while循环
- 语法:
do{
语句...
}while(条件表达式)

- 执行顺序:
do-while语句在执行时,会先执行do后的循环体
执行完毕后,会对while后的条件表达式进行判断
如果为false,则循环终止
如果为true,则继续执行循环体,以此类推

- 和while的区别:
while语句是先判断再执行
do-while语句是先执行再判断

实质的区别:
do-while语句可以确保循环至少执行一次
*/

for循环

        /* 
for循环
- for循环和while没有本质区别,都是用来反复执行代码
- 不同点就是语法结构,for循环更加清晰
- 语法:
for(初始化表达式; 条件表达式; 更新表达式){
语句...
}
- 执行流程:
1.执行初始化表达式,初始化变量
2.执行条件表达式,判断循环是否执行(true执行,false终止)
3.判断结果为true,则执行循环体
4.执行更新表达式,对初始化变量进行修改
5.重复第二部,直到判断为false为止
- 初始化表达式,在循环的整个的生命周期中只会执行1次
- for循环中的三个表达式都可以省略,省略后就变为死循环了
- 使用let在for循环的()中声明的变量是局部变量,只能在for循环内部访问
使用var在for循环()中声明的变量可以在for循环的外部访问
- 创建死循环的方式:
while(1){}
for(;;){}
*/

break和continue

        /*
break和continue
- break
- break用来终止switch和循环语句
- break执行后,当前的switch或循环会立刻停止
- break会终止离他最近的循环

- continue
- continue用来跳过当次循环
*/

5.对象


对象

        /* 
数据类型:
原始值
1.数值 Number
2.大整数 BigInt
3.字符串 String
4.布尔值 Boolean
5.空值 Null
6.未定义 Undefined
7.符号 Symbol

对象
- 对象是JS中的一种复合数据类型,它相当于一个容器,在对象中可以存储各种不同类型数据
- 对象中可以存储多个各种类型的数据,对象中存储的数据,我们称为属性
- 向对象中添加属性:
对象.属性名 = 属性值
- 读取对象中的属性
对象.属性名
- 如果读取的是一个对象中没有的属性,不会报错而是undefined
- 修改属性
对象.属性名 = 新的属性值
- 删除属性
delete 对象.属性名

原始值只能用来表示一些简单的数据,不能表示复杂数据
比如:现在需要在程序中表示一个人的信息
*/
// 创建对象
let obj = new Object();
let obj2 = Object();

obj.name = "孙悟空";
obj.age = 18;
obj.gender = "男";
console.log(obj); // {name: '孙悟空', age: 18, gender: '男'}
console.log(obj.name); // 孙悟空

对象的属性

        /* 
属性名
- 通常属性名就是一个字符串,所以属性名可以是任何值,没有什么特殊要求
但是如果你的属性名实在太特殊了,不能直接输入,需要使用[]来设置
如 obj["21432sdaf"],虽然如此,但是我们还是强烈建议属性名也按照标识符的规范命名

- 也可以使用符号(symbol)作为属性名,来添加属性
获取这种属性时,也必须使用symbol
使用symbol添加的属性,通常是那些不希望被外界访问的属性

- 使用[]去操作属性时,可以使用变量

属性值
- 对象的属性值可以是任意的数据类型,也可以是一个对象

使用typeof检查一个对象时,会返回一个object

in 运算符
- 用来检查对象中是否含有某个属性
- 语法 属性名 in obj
- 如果有返回true,没有返回false
*/
let obj = Object();
obj.name = "孙悟空";
obj["21432sdaf"] = "呵呵"; // 不建议

let mySymbol = Symbol();
obj[mySymbol] = "通过symbol添加的属性";

let str = "address";
obj[str] = "花果山"; // 等价于 obj["address"] = "花果山"

console.log("name" in obj); // true
console.log("haha" in obj); // false

对象的字面量

        /* 
对象字面量
- 可以直接用来 {} 来创建对象
- 使用 {} 所创建的对象,可以直接向对象中添加属性
- 语法:
{
属性名:属性值,
["属性名"]:属性值
}
*/
let mySymbol = Symbol();
let str = "address";
let obj = {
name: "孙悟空",
age: 18,
["gender"]: "男",
[mySymbol]: "特殊的属性",
[str]: "花果山"
};
console.log(typeof obj, obj); // object {name: '孙悟空', age: 18, gender: '男', address: '花果山', Symbol(): '特殊的属性'}

对象的枚举

        /* 
枚举属性,指将对象中的所有的属性全部获取
for-in语句
- 语法:
for(let propName in 对象){
语句...
}

- for-in的循环体会执行多次,有几次就会执行几次,
每次执行时,都会将一个属性名赋值给我们所定义的变量

- 注意:并不是所有的属性都可以枚举,比如 使用符号添加的属性
*/
let obj = {
name: "孙悟空",
age: 18,
gender: "男",
[Symbol()]: "测试属性" // 符号添加的属性是不能枚举
};

for (let propName in obj) {
console.log(typeof propName); // string
console.log(propName, obj[propName]);
}

可变类型

        /* 
- 原始值都属于不可变类型,一旦创建就无法修改
- 在内存中不会创建重复的原始值
- 当我们为一个变量重新赋值时,绝对不会影响其他属性

- 对象属于可变类型
- 对象创建完成后,可以任意的添加删除修改对象的属性
- 注意:
- 当对两个对象进行相等或全等比较时,比较的是对象的内存地址
- 如果有两个变量同时指向一个对象,
通过一个变量修改对象时,对另一个变量也会产生影响
当修改一个对象时,所有指向该对象的变量都会受到影响
*/

改变量和改对象

        /*
修改对象
- 修改对象时,如果有其他变量指向改对象
则所有指向该对象的变量都会受到影响

修改变量
- 修改变量时,只会影响当前的变量

在使用变量存储对象时,很容易因为改变变量指向的对象,提高代码的复杂度
所以通常情况下,声明存储对象的变量时会使用const

注意:
const只是禁止变量被重新赋值,对对象的修改没有任何影响
*/

方法

        /* 
方法(method)
- 当一个对象的属性指向一个函数时,那么我们就称这个函数是该对象的方法,
调用函数就称为调用对象的方法
*/

6.函数


函数

        /* 
函数(Function)
- 函数也是一个对象
- 它具有其他对象所有的功能
- 函数中可以存储代码,且可以在需要时调用这些代码

- 语法:
function 函数名(){
语句...
}

- 调用函数:
- 调用函数就是执行函数中存储的代码
- 语法:
函数对象()

使用typeof检查函数对象时会返回function
*/

函数的创建方式

        /* 
函数的定义方式:
1.函数的声明
function 函数名(){
语句...
}

2.函数表达式
const 变量 = function(){
语句...
}

3.箭头函数
const 变量 = () => {
语句...
}
*/

function fn() {
console.log("函数声明所定义的函数");
}

const fn2 = function () {
console.log("函数表达式");
}

const fn3 = () => {
console.log("箭头函数");
}

参数

        /*
形式参数
- 在定义函数时,可以在函数中指定数量不等的形式参数(形参)
- 在函数中定义形参,就相当于在函数内部声明了对应的变量但是没有赋值

实际参数
- 在调用函数时,可以在函数的()传递数量不等的实参
- 实际参数赋值给对应的形参
- 参数:
1.如果实参和形参数量相同,则对应的实参赋值给对应的形参
2.如果实参多余形参,则多余的实参不会使用
3.如果形参多余形参,则多余的形参为undefined

- 参数的类型
- JS中不会检查参数的类型,可以传递任何类型的值作为参数

1.函数的声明
function 函数名(参数){
语句...
}

2.函数表达式
const 变量 = function(参数){
语句...
}

3.箭头函数
const 变量 = (参数) => {
语句...
}

*/

箭头函数的参数

        /* 
当箭头函数中只有一个参数时,可以省略()
定义参数时,可以为参数指定默认值
默认值,会在没有对应实参时生效
*/

const fn = a => {
console.log(a);
}
fn(123); // 123

const fn2 = (a = 10, b = 20, c = 30) => {
console.log(a, b, c);
}
fn2(1, 2); // 1 2 30

对象和函数作为参数

        /* 
对象可以作为参数传递
传递实参时,传递并不是变量本身,而是变量中存储的值

函数每次调用,都会重新创建默认值

在JS中,函数也是一个对象(一等函数)
别的对象能做的事情,函数也可以
*/

let obj = { name: "孙悟空" };

function fn(a) {
console.log("我是fn");
a();
}

function fn2() {
console.log("我是fn2");
}

fn(fn2); // 我是fn 我是fn2

函数的返回值

        /*
在函数中,可以通过return关键字来指定函数的返回值
返回值就是函数的执行结果,函数调用完毕返回值便会作为结果返回

任何值都可以作为返回值使用(包括对象和函数之类)
如果return后不跟任何值,则相当于返回undefined
如果不写return,那么函数的返回值依然是undefined

return一执行函数立即结束
*/

箭头函数的返回值

        /* 
箭头函数的返回值可以直接写在箭头后
如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来
*/
const sum = a => a + 1;
console.log(sum(5)); // 6

const fn = () => ({ name: "孙悟空" });

作用域

        /*
作用域(scope)
- 作用域指的是一个变量的可见区域
- 作用域有两种:
全局作用域
- 全局作用域在网页运行时创建,在网页关闭时销毁
- 所有直接编写到script标签中的代码都位于全局作用域中
- 全局作用域中的变量是全局变量,可以在任意位置访问

局部作用域
- 块作用域
- 块作用域是一种局部作用域
- 块作用域在代码块执行时创建,代码块执行完毕它就销毁
- 在块级作用域中声明的变量时局部变量,只能在块内部访问,外部无法访问

- 函数作用域
- 函数作用域也是一种局部作用域
- 函数作用域在函数调用时产生,调用结束后销毁
- 函数每次调用都会产生一个全新的函数作用域
- 在函数中定义的变量时局部变量,只能在函数内部访问,外部无法访问
*/

作用域链

        /* 
作用域链
- 当我们使用一个变量时,JS解释器会优先在当前作用域中寻找变量,
如果找到了则直接使用
如果没找到,则去上一层作用域中寻找,找到了则使用
如果没找到,则继续去上一层寻找,以此类推
如果一种到全局作用域都没找到,则报错 xxx is not defined
*/
let a = 10;
{
let a = "第一代码块中的a";
{
let a = "第二代码块中的a";
console.log(a); // "第二代码块中的a"
}
}

window对象

        /* 
window对象
- 在浏览器中,浏览器为我们提供了一个window对象,可以直接访问
- window对象代表的是浏览器窗口,通过该对象可以对窗口进行各种操作
除此之外window对象还负责存储JS中的内置对象和浏览器的宿主对象
- window对象的属性可以通过window对象访问,也可以直接访问
- 函数就可以认为是window对象的方法

向window对象中添加的属性会自动称为全局变量


var 用来声明变量,作用和let相同,但是var不具有块作用域
- 在全局中使用var声明的变量,都会作为window对象的属性保存
- 使用function声明的函数,都会作为window的方法保存
- 使用let声明的变量不会存储在window,而存在一个秘密的小地方
- var虽然没有块作用域,但有函数作用域
- 在局部作用域中,如果没有使用var或let声明变量,则变量会自动成为window对象的属性,也就是全局变量
*/
{
function fn() {
console.log(1);
d = 10; // window.d
}

}
fn();
window.fn();
console.log(d); // 10

提升

        /* 
变量的提升
- 使用var声明的变量,它会在所有代码执行前被声明
所以我们可以在变量声明前就访问变量

函数的提升
- 使用函数声明创建的函数,它会在其他代码执行前被创建
所以我们可以在函数声明前就可以调用函数

let声明的变量实际也会提升,但是在赋值之前解释器禁止对该变量的访问
*/
console.log(a); // undefined
var a = 10;

fn1(); // "我是fn"
function fn1() {
console.log("我是fn");
}

// 变量和函数的提升同样适用于函数作用域
var b = 1;
function fn2() {
console.log(b); // undefined
var b = 2;
console.log(b); // 2
}
fn2();
console.log(b); // 1

立即执行函数

        /* 
在开发中应该尽量减少直接在全局作用域中编写代码

所以我们的代码要尽量编写到局部作用域中

如果使用let声明的变量,可以使用{}来创建块作用域

立即执行函数(IIFE)
- 立即执行函数是一个匿名函数,并且它只会调用一次
- 可以利用立即执行函数来创建一个一次性的函数作用域,避免变量的冲突问题
*/
(function () {
console.log("立即执行函数");
}());

this

        /* 
this
- 函数在执行时,JS解释器每次都会传递一个隐含的参数
- 这个参数就叫做this
- this会指向一个对象
- this所指向的对象会根据函数调用方式的不同而不同
1.以函数的形式调用时,this指向的是window
2.以方法的形式调用时,this指向的是调用方法的对象

- 通过this可以在方法中引用调用方法的对象
*/

function fn() {
console.log(this === window);
}
fn(); // true

const obj = { name: "孙悟空" };
obj.test = fn;
obj.test(); // false

箭头函数的this

        /* 
箭头函数:
(参数) => 返回值
例子:
无参箭头函数:() => 返回值
一个参数的:a => 返回值
多个参数的:(a, b) => 返回值

只有一个语句的函数:() => 返回值
只返回一个对象的函数:() => ({...})
有多行语句的函数:() => {
...
return 返回值
}

箭头函数没有自己的this,它的this由它的外层作用域决定
箭头函数没有自己的this指向,它的this指向上一级作用域的this
箭头函数的this和它的调用方式无关
*/
function fn() {
console.log(this);
}
const fn2 = () => {
console.log(this);
}
const obj = {
name: "孙悟空",
fn,
fn2,
sayHello() {
console.log(this.name);
function t1() {
console.log("t1-->", this);
const t4 = () => {
console.log("t4-->", this);
}
t4(); // t4--> Window
}
const t2 = () => {
console.log("t2-->", this);
const t3 = () => {
console.log("t3-->", this);
}
t3(); // obj
}
t1(); // t1--> Window
t2(); // t2--> obj
}
}
obj.fn(); // obj
obj.fn2(); // window
obj.sayHello();

严格模式

        /*
JS运行代码的模式有两种:
- 正常模式
- 默认情况下代码都运行在正常模式中,
在正常模式,语法检查并不严格
它的原则是能不报错的地方尽量不报错
- 这种处理方式导致代码的运行性能较差

- 严格模式
- 在严格模式下,语法检查变得严格
1.禁止一些语法
2.更容易报错
3.提升了性能

- 在开发中,应该尽量使用严格模式
这样可以将一些隐藏的问题消灭在萌芽阶段,同时也能提升代码的运行性能
*/
"use strict"

call和apply

        /* 
根据函数调用的方式的不同,this的值也不同
1.以函数形式调用,this是window
2.以方法形式调用,this是调用方法的对象
3.构造函数中,this是新建的对象
4.箭头函数没有自己的this,由外层作用域决定
5.通过call和apply调用的函数,它们的第一个参数就是函数的this
6.通过bind返回的函数,this由bind第一个参数决定(无法修改)

调用函数除了通过 函数() 这种形式外,还可以通过其他的方式来调用函数
比如,我们可以通过调用函数的call()和apply()来两个方法来调用函数
函数.call()
函数.apply()
- call 和 apply 除了可以调用函数,还可以用来指定函数中的this
- call 和 apply 的第一个参数,将会成为函数的this
- 通过call方法调用函数,函数的实参直接在第一个参数后一个一个的列出来
- 通过apply方法调用函数,函数的实参需要通过一个数组传递
*/

function fn(a, b) {
console.log("函数执行了", this);
console.log(a, b);
}

const obj = { name: "孙悟空" }

fn(); // 函数执行了 Window
fn.call(obj, 1, 2); // 函数执行了 Object
fn.apply(obj, [3, 4]);

bind

        /*
bind() 是函数的方法,可以用来创建一个新的函数
- bind可以为新函数绑定this
- bind可以为新函数绑定参数

箭头函数没有自身的this,它的this由外层作用域决定,也无法通过call apply 和 bind修改它的this
箭头函数没有arguments
*/

function fn(a, b, c) {
console.log("fn执行了", this); // fn执行了 {name: '孙悟空'}
console.log(a, b, c); // 10 20 30
}

const obj = { name: "孙悟空" }

const newFn = fn.bind(obj, 10);
newFn(20, 30);

7.面向对象


面向对象

        /*
面向对象编程(OOP)
1.程序是干嘛的?
- 程序就是对现实世界的抽象
2.对象是干嘛的?
- 一个事物抽象到程序中后就变成了对象
- 在程序的世界中,一切皆对象
3.面向对象的编程
- 面向对象的编程指,程序中的所有操作都是通过对象来完成
- 做任何事情之前都需要先找到它的对象,然后通过对象来完成各种操作
*/

        /* 
使用Object创建对象的问题
1.无法区分出不同类型的对象
2.不方便批量创建对象

在JS中可以通过类(class)来解决这个问题:
1.类是对象模板,可以将对象中的属性和方法直接定义在类中
定义后,就可以直接通过类来创建对象
2.通过同一个类创建的对象,我们称为同类对象
可以使用instanceof来检查一个对象是否是由某一个创建
如果某个对象是某个类所创建,则我们称该对象是这个类的实例

语法:
class 类名{} 类名要使用大驼峰命名
const 类名 = class{}

通过类创建对象
new 类()
*/
class Person { }
const p1 = new Person();
console.log(p1 instanceof Person); // true

属性

        /* 
类是创建对象的模板,要创建第一件事就是定义类
*/
class Person {
/*
类的代码块,默认就是严格模式
类的代码块是用来设置对象的属性的,不是什么代码都能写
*/
name = "孙悟空"; // Person的实例属性,实例属性只能通过实例访问
age = 18;
static test = "test静态属性"; // 使用static声明的属性,是静态属性(类属性) Person.test
static hh = "静态属性"; // 静态属性只能通过类去访问 Person.hh
}
const p1 = new Person();
console.log(p1); // Person {name: '孙悟空', age: 18}
console.log(p1.name); // 孙悟空
console.log(Person.test); // test静态属性

方法

        class Person {
name = "孙悟空";

test1 = function () {
// 添加方法的一种
}

test2() {
// 添加方法(实例方法) 实例方法中的this就是当前实例
console.log(this);
}

static test3() {
// 静态方法(类方法) 通过类来调用,静态方法中的this指向的是当前类
console.log(this);
}
}

const p1 = new Person();
p1.test2();
Person.test3();

构造函数

        /* 
当我们在类中直接指定实例属性的值时,意味着我们创建的所有对象的属性都是这个值
*/
class Person {
/*
在类中可以添加一个特殊的方法constructor,该方法我们称为构造函数(构造方法)
构造函数会在我们调用类创建对象时执行
可以在构造函数中,为实例属性进行赋值
在构造函数中,this表示当前所创建的对象
*/
name; // 可以不写
age;
gender;
constructor(name, age, gender) {
console.log("构造函数执行了", name, age, gender);
this.name = name;
this.age = age;
this.gender = gender;
}
}

const p1 = new Person("孙悟空", 18, "男"); // 构造函数执行了 孙悟空 18 男

封装

        /* 
面向对象的特点:
封装、继承和多态

1.封装
- 对象就是一个用来存储不同属性的容器
- 对象不仅负责属性,还要负责数据的安全
- 直接添加到对象的属性,并不安全,因为它们可以被任意的修改
- 如何确保数据的安全:
1.私有化数据
- 将需要保护的数据设置为私有,只能在类内部使用
2.提供setter和getter方法来开放对数据的操作
- 属性设置私有,通过getter setter方法操作带来的好处
1.可以控制属性的读写权限
2.可以在方法中对属性的值进行验证

- 封装主要用来保证数据的安全
- 实现封装的方式:
1.属性私有化 加#
2.通过getter和setter方法来操作属性
get 属性名(){
return this.#属性
}

set 属性名(参数){
this.#属性 = 参数
}

*/

class Person {
/*
实例化使用#开头就变成了私有属性,私有属性只能在类内部访问
*/
#name;
#age;
#gender;

constructor(name, age, gender) {
this.#name = name;
this.#age = age;
this.#gender = gender;
}

sayHello() {
console.log(this.#name);
}

// getter方法,用来读取属性
getName() {
return this.#name;
}

// setter方法,用来设置属性
setName(name) {
// 可以写逻辑
this.#name = name;
}

// 新写法
get age() {
return this.#age;
}

set age(age) {
this.#age = age;
}
}

const p1 = new Person("孙悟空", 18, "男");
p1.setName("猪八戒");
p1.age = 28;
console.log(p1.age); // 28

多态

		/*
多态
- 在JS中不会检查参数的类型,所以这就意味着任何数据都可以作为参数传递
- 要调用某个函数,无需指定类型,只要对象满足某些条件即可
*/

继承

        /* 
继承
- 可以通过extends关键字来完成继承
- 当一个类继承另一个类时,就相当于将另一个类中的代码复制到当前类中
- 继承发生时,被继承的类称为 父类(超类),继承的类称为 子类
- 通过继承可以减少重复的代码,并且可以在不修改一个类的前提对其进行扩展

封装 --- 安全性
继承 --- 扩展性
多态 --- 灵活性

- 通过继承可以在不修改一个类的情况下对其进行扩展
- OCP 开闭原则
- 程序应该对修改关闭,对扩展开放
*/

class Animal {
constructor(name) {
this.name = name;
}

sayHello() {
console.log("动物在叫");
}
}

class Dog extends Animal {
/*
在子类中,可以通过创建同名方法来重写父类的方法
*/
sayHello() {
console.log("汪汪汪");
}
}

class Cat extends Animal {
/*
重写构造函数
重写构造函数时,构造函数的第一行代码必须为super()
*/
constructor(name, age) {
super(name); // 调用父类的构造函数
this.age = age;
}

sayHello() {
// 在方法中可以使用super来引用父类的方法
super.sayHello();
console.log("喵喵喵");
}
}

const dog = new Dog("旺财");
const cat = new Cat("汤姆", 3);

dog.sayHello(); // 汪汪汪
cat.sayHello(); // 动物在叫 喵喵喵
console.log(dog.name); // 旺财
console.log(cat.name, cat.age); // 汤姆 3

对象的结构

        /* 
对象中存储属性的区域实际上有两个:
1.对象自身
- 直接通过对象所添加的属性,位于对象自身中 p1.address = "花果山"
- 在类中通过 x = y 的形式添加的属性,位于对象自身中 name = "孙悟空"

2.神秘位置
- 对象中还有一些内容,会存储到其他的对象里(原型对象)
- 在对象中会有一个属性用来存储原型对象,这个属性叫做__proto__
- 原型对象也负责为对象存储属性
当我们访问对象中的属性时,会优先访问对象自身的属性,
对象自身不包含该属性时,才会去原型对象中寻找
- 会添加到原型对象中的情况:
1.在类中通过xxx(){}方法添加的方法,位于原型中
2.主动向原型中添加的属性或方法
*/
class Person {
name = "孙悟空";
age = 18;
sayHello = "hello";

sayHello() {
console.log("hello");
}
}

const p1 = new Person();

console.log(p1.sayHello); // "hello"

原型对象

        /* 
访问一个对象的原型对象
1.对象.__proto__
2.Object.getPrototypeOf(对象)

原型对象中的数据:
1.对象中的数据(属性、方法等)
2.constructor(对象的构造函数)

注意:
原型对象也有原型,这样就构成了一条原型链,根据对象的复杂程度不同,原型链的长度也不同
p对象的原型链:p对象 -> 原型 -> 原型 -> null
obj对象的原型链:obj对象 -> 原型 -> null

原型链:
- 读取对象属性时,会优先读取对象自身属性,
如果对象中有,则使用,没有则去对象的原型中寻找
如果原型中有,则使用,没有则去原型的原型中寻找
直到找到Object对象的原型(Object的原型没有原型(为null))
如果依然没有找到,则返回undefined

- 作用域链,是找变量的链,找不到会报错
- 原型链,是找属性的链,找不到会返回undefined
*/
class Person {
name = "孙悟空";
age = 18;

sayHello() {
console.log(this.name);
}
}

const p = new Person();

console.log(p); // Person {name: '孙悟空', age: 18}
console.log(p.__proto__); // {constructor: ƒ, sayHello: ƒ}
console.log(Object.getPrototypeOf(p) === p.__proto__); // true
console.log(p.__proto__.__proto__);
console.log(p.__proto__.__proto__.__proto__); // null
        /* 
所有的同类型对象它们的原型对象都是同一个,
也就意味着,同类型对象的原型链是一样的

原型的作用:
原型就相当于是一个公共的区域,可以被所有该类实例访问,
可以将该类实例中,所有的公共属性(方法)统一存储到原型中
这样我们只需要创建一个属性,即可被所有实例访问

JS中继承就是通过原型来实现的
当我们继承时,子类的原型就是父类的实例

在对象中有些值是对象独有的,像属性(name,age,gender)每个对象都应该有自己的值,
但是有些值对于每个对象来说都是一样的,像各种方法,对于一样的值没必要重复创建
*/

class Person {
name = "孙悟空"

sayHello() {

}

sayHello2 = () => {

}
}

const p1 = new Person();
const p2 = new Person();

console.log(p1.__proto__ === p2.__proto__); // true
console.log(p1.sayHello === p2.sayHello); // true
console.log(p1.sayHello2 === p2.sayHello2); // false

class Animal { }
class Cat extends Animal { }
const cat = new Cat();
// cat --> Animal实例 --> object --> Object原型 --> null

修改原型对象

        /* 
大部分情况下,我们是不需要修改原型
注意:
千万不要通过类的实例去修改原型
1.通过一个对象影响所有同类对象,这么做不合适
2.修改原型先创建实例,麻烦
3.危险

处理通过__proto__能访问对象的原型外,
还可以通过类的prototype属性,来访问实例的原型
修改原型时,最好通过类去修改
好处:
1.一修改就是修改所有实例的原型
2.无需创建实例即可完成对类的修改

原则:
1.原型尽量不要手动改
2.要改也不要通过实例对象去改
3.通过 类.prototype 属性去修改
4.最好不要直接给prototype去赋值
*/
class Person {

}

const p = new Person();
console.log(Person.prototype === p.__proto__); // true

Person.prototype.fly = () => {
console.log("我在飞");
}
console.log(p.fly()); // 我在飞

instanceof和hasOwn

        /* 
instanceof 用来检查一个对象是否是一个类的实例
- instanceof检查的是对象的原型链上是否有该类实例
只要原型链上有该类实例,就会返回true
- dog -> Animal的实例 -> Object实例 -> Object原型
- Object是所有对象的原型,所以任何对象和Object进行instanceof运算都会返回true

in
- 使用in运算符检查属性时,无论属性在对象自身还是在原型中,都会返回true

对象.hasOwnProperty(属性名)(不推荐使用)
- 用来检查一个对象的自身是否含有某个属性

Object.hasOwn(对象, 属性名)
- 用来检查一个对象的自身是否含有某个属性
*/
class Animal { }

class Dog extends Animal { }

const dog = new Dog();

console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true

class Person {
name = "孙悟空";
age = 18;

sayHello() {
console.log(this.name);
}
}

const p = new Person();
console.log(p.hasOwnProperty("sayHello")); // false
console.log(Object.hasOwn(p, "name")); // true

new运算符

        /*
new运算符是创建对象时要使用的运算符
- 使用new时,到底发生了哪些事情
- 当使用new去调用一个函数时,这个函数将会作为构造函数调用,
使用new调用函数时,将会发生这些事:
1.创建一个普通的JS对象(Object对象 {}),为了方便,称其为新对象
2.将构造函数的prototype属性设置为新对象的原型
3.使用实参来执行构造函数,并且将新对象设置为函数中的this
4.如果构造函数返回的是一个非原始值,则该值作为new运算的返回值返回(千万不要这么做)
如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回
通常不会为构造函数指定返回值
*/

总结

        /*
面向对象本质就是,编写代码时所有的操作都是通过对象来进行的。
面向对象的编程的步骤:
1.找对象
2.搞对象

学习对象:
1.明确这个对象代表什么,有什么用
2.如何获取到这个对象
3.如何使用这个对象(对象中的属性和方法)

对象的分类:
内建对象
- 由ES标准所定义的对象
- 比如 Object Function String Number ...

宿主对象
- 由浏览器提供的对象
- BOM、DOM

自定义对象
- 由开发人员自己创建的对象
*/

8.数组


简介

        /* 
数组(Array)
- 数组也是一种复合数据类型,在数组中可以存储多个不同类型的数据
- 数组中存储的是有序的数据,数组中的每个数据都有一个唯一的索引
可以通过索引来操作数据
- 数据中存储的数据叫做元素
- 索引(index)是一组大于0的整数
- 创建数组
通过Array()来创建数组,也可以通过[]来创建数组

- 向数组中添加元素
语法:
数组[索引] = 元素

- 读取数组中的元素
语法:
数组[索引]
- 如果读取了一个不存在的元素,不好报错而是返回undefined

- length
- 获取数组的长度
- 获取的实际值就是数组的最大索引+1
- 向数组最后添加元素:
数组[数组.length] = 元素
- length是可以修改的
*/

const arr = new Array();
const arr2 = [1, 2, 3, 4, 5];

arr2[100] = 99; // 使用数组时,应该避免非连续数组,因为它性能不好
console.log(arr2); // (101) [1, 2, 3, 4, 5, 空 ×95, 99]

arr[arr.length] = 1;
arr[arr.length] = 2;
arr[arr.length] = 3;
console.log(arr); // [1, 2, 3]

console.log(typeof arr); // object

遍历数组

        /* 
任何类型的值都可以成为数组中的元素
创建数组时尽量要确保数组中存储的数据类型是相同

遍历数组
- 遍历数组简单理解,就是获取到数组中的每一个元素
*/

let arr = [1, "hello", true, null, { name: "孙悟空" }, () => { }];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}

for-of

        /* 
for-of语句可以用来遍历可迭代对象

语法:
for(变量 of 可迭代的对象){
语句...
}

执行流程:
for-of的循环体会执行多次,数组中由几个元素就会执行几次
每次执行时都会将一个元素赋值给变量
*/

const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧"];

for (const value of arr) {
console.log(value);
}

数组的方法

        /* 
非破坏性方法
Array.isArray()
- 用来检查一个对象是否时数组

at()
- 可以根据索引获取数组中的指定元素
- at可以接收负索引作为参数

concat()
- 用来连接两个或多个数组
- 非破坏性方法,不会影响数组,而是返回一个新的数组

indexOf()
- 获取元素在数组中第一次出现的索引
- 参数:
1.要查询的元素
2.查询的起始位置
- 返回值:
找到了则返回元素的索引
没找到返回-1

lastIndexOf()
- 获取元素在数组中最后一次出现的位置
- 参数:
1.要查询的元素
2.查询的结束位置
- 返回值:
找到了则返回元素的索引
没找到返回-1

join()
- 将一个数组中的元素连接为一个字符串
- 参数:
指定一个字符串作为连接符

slice()
- 用来截取数组
- 参数:
1.截取的起始位置(包括该位置)
2.截取的结束位置(不包括该位置)
- 第二个参数可以省略不写,如果省略则会一直截取到最后
- 索引可以是负值

如果将两个参数全部省略,则可以对数组进行浅拷贝(浅复制)
*/

const arr = ["孙悟空", "猪八戒", "沙和尚", "唐僧", "猪八戒"];
const arr2 = ["白骨精", "蜘蛛精", "玉兔精"];

console.log(Array.isArray(arr)); // true

console.log(arr.at(-2)); // '唐僧'

console.log(arr.concat(arr2)); // ['孙悟空', '猪八戒', '沙和尚', '唐僧', '猪八戒', '白骨精', '蜘蛛精', '玉兔精']

console.log(arr.indexOf("猪八戒")); // 1
console.log(arr.indexOf("猪八戒", 3)); // 4

console.log(arr.lastIndexOf("猪八戒", 3)); // 1
console.log(arr.lastIndexOf("猪八")); // -1

console.log(arr.join("@")); // "孙悟空@猪八戒@沙和尚@唐僧@猪八戒"

console.log(arr.slice(0, 2)); // ['孙悟空', '猪八戒']
        /* 
破坏性方法
push()
- 向数组的末尾添加一个或多个元素,并返回新的长度

pop()
- 删除并返回数组的最后一个元素

unshift()
- 向数组的开头添加一个或多个元素,并返回新的长度

shift()
- 删除并返回数组的第一个元素

splice()
- 可以删除、插入、替换数组中的元素
- 参数:
1.删除的起始位置
2.删除的数量
3.要插入的元素

- 返回值:
- 返回被删除的元素

reverse()
- 反转数组,返回值是反转后的数组
*/

const arr = [1, 2, 3, 4];

console.log(arr.push(5, "haha")); // 6
console.log(arr); // [1, 2, 3, 4, 5, 'haha']

console.log(arr.pop()); // "haha"
console.log(arr); // [1, 2, 3, 4, 5]

console.log(arr.unshift("xixi")); // 6
console.log(arr); // ["xixi", 1, 2, 3, 4, 5]

console.log(arr.shift()); // "xixi"
console.log(arr); // [ 1, 2, 3, 4, 5]

const arr2 = [1, 2, 3, 4];

console.log(arr2.splice(1, 2, "haha", "xixi")); // [2, 3]
console.log(arr2); // [1, "haha","xixi", 4]
console.log(arr2.splice(1, 0, "gugu")); // []
console.log(arr2); // [1,"gugu", "haha","xixi", 4]

const arr3 = [5, 4, 3, 2, 1];
console.log(arr3.reverse()); // [1, 2, 3, 4, 5]
console.log(arr3); // [1, 2, 3, 4, 5]
        /* 
sort()
- sort用来对数组进行排序(会改变原数组)
- sort默认会将数组升序排列
注意:sort默认会按照unicode编码进行排序,所以如果直接通过sort对数字进行排序
可能会得到一个不正确的结果
- 参数:
- 可以传递一个回调函数作为参数,通过回调函数来指定排序规则
(a, b) => a - b 升序排列
(a, b) => b - a 降序排序

forEach()
- 用来遍历数组
- 它需要一个回调函数作为参数,这个回调函数会被调用多次
数组中有几个元素,回调函数就会调用几次
每次调用,都会将数组中的数据作为参数传递
- 回调函数中有三个参数:
element 当前的元素
index 当前元素的索引
array 被遍历的数组

filter()
- 将数组中符合条件的元素保存到一个新数组返回
- 需要一个回调函数作为参数,会为每一个元素去调用回调函数,并根据返回值来决定是否将元素添加到新数组中
- 非破坏性方法,不会影响原数组

map()
- 根据当前数组生成一个新的数组
- 需要一个回调函数作为参数,回调函数的返回值会成为新数组中的元素
- 非破坏性方法

reduce()
- 可以用来将一个数组中的所有元素整合为一个值
- 参数:
1.回调函数,通过回调函数来指定合并的规则
2.可选参数,初始值
*/

const arr = [1, 3, 4, 10, 22, 45, 2, 4, 8, 5, 8];
console.log(arr.sort()); // [1, 10, 2, 22, 3, 4, 4, 45, 5, 8, 8]
console.log(arr.sort((a, b) => a - b)); // [1, 2, 3, 4, 4, 5, 8, 8, 10, 22, 45]
console.log(arr.sort((a, b) => b - a)); // [45, 22, 10, 8, 8, 5, 4, 4, 3, 2, 1]

const arr2 = ["a", "b", "c", "d", "e", "f"];
arr2.forEach((element, index, array) => {
console.log(element, index, array);
})

console.log(arr.filter(ele => ele % 2 === 0)); // [22, 10, 8, 8, 4, 4, 2]

console.log(arr2.map(ele => ele + "hello")); // ['ahello', 'bhello', 'chello', 'dhello', 'ehello', 'fhello']

const arr3 = [1, 2, 3, 4, 5, 6];
console.log(arr3.reduce((a, b) => a + b, 10)); // 31

对象的复制

        /* 
数组的复制
如何去复制一个对象,复制必须要产生新的对象
当调用slice时,会产生一个新的数组对象,从而完成对数组的复制

...(展开运算符)
- 可以将一个数组中的元素展开到另一个数组中或者作为函数的参数传递
- 通过它也可以对数组进行浅复制
*/

const arr = [1, 2, 3, 4];
const arr2 = arr.slice();
const arr3 = [...arr];

console.log(arr2); // [1, 2, 3, 4]
console.log(arr3); // [1, 2, 3, 4]
console.log(arr === arr2); // false
console.log(arr === arr3); // false

/*
对象的复制
- Object.assign(目标对象, 被复制的对象)
- 将被复制对象中的属性复制到目标对象里,并将目标对象返回

- 也可以使用展开运算符对对象进行复制
*/

const obj = { name: "孙悟空", age: 18 }
const obj2 = Object.assign({}, obj);
const obj3 = { ...obj }
console.log(obj2); // {name: '孙悟空', age: 18}
console.log(obj === obj2); // false
console.log(obj3); // {name: '孙悟空', age: 18}
console.log(obj === obj3); // false

浅拷贝和深拷贝

        /* 
浅拷贝(shallow copy)
- 通常对对象的拷贝都是浅拷贝
- 浅拷贝顾名思义,只对对象的浅层进行复制(只复制一层)
- 如果对象中存储的数据是原始值,那么拷贝的深浅是不重要
- 浅拷贝只会对对象本身进行复制,不会复制对象中的属性(或元素)

深拷贝(deep copy)
- 深拷贝指不仅复制对象本身,还复制对象中的属性和元素
- 因为性能问题,通常情况下不太使用深拷贝
*/

const arr = [{ name: "孙悟空" }, { name: "猪八戒" }];
const arr2 = arr.slice(); // 浅拷贝
const arr3 = structuredClone(arr); // 深拷贝

console.log(arr === arr2); // false
console.log(arr[0] === arr2[0]); // true
console.log(arr === arr3); // false
console.log(arr[0] === arr3[0]); // false


/*
对象的拷贝
*/
const obj = {
name: "孙悟空",
friend: {
name: "猪八戒"
}
}

// 浅复制
const obj2 = Object.assign({}, obj);

// 深复制
const obj3 = structuredClone(obj);

// JSON深复制
const obj4 = JSON.parse(JSON.stringify(obj));

排序

        /* 
这种排序方式,被称为冒泡排序,冒泡排序是最慢的排序方式,数字少还可以凑合用,不适用于数据量较大的排序
*/
const arr = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4];
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
console.log(arr); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

/*
选择排序
*/
const arr2 = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4];
for (let i = 0; i < arr2.length; i++) {
for (let j = i + 1; j < arr2.length; j++) {
if (arr2[i] > arr2[j]) {
let temp = arr2[i];
arr2[i] = arr2[j];
arr2[j] = temp;
}
}
}
console.log(arr2); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

高阶函数

        /* 
一个函数的参数也可以是函数,
如果将函数作为参数传递,那么我们就称这个函数为回调函数(callback)

高阶函数
- 如果一个函数的参数或返回值是函数,则这个函数称为高阶函数
- 为什么要将函数作为参数传递?(回调函数有什么作用?)
- 将函数作为参数,意味着可以对另一个函数动态传递参数
*/
class Person {
#name;
#age;

constructor(name, age) {
this.#name = name;
this.#age = age;
}

get name() {
return this.#name;
}

get age() {
return this.#age;
}
}

const personArr = [
new Person("孙悟空", 18),
new Person("猪八戒", 28),
new Person("沙和尚", 38),
new Person("唐僧", 48)
];

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

function filter(arr, cb) {
const newArr = [];

for (let i = 0; i < arr.length; i++) {
if (cb(arr[i])) {
newArr.push(arr[i]);
}
}

return newArr;
}

console.log(filter(personArr, a => a.age > 20));
console.log(filter(arr, a => a % 2 === 0)); // [2, 4, 6, 8]

/*
希望在someFn()函数执行时,可以记录一条日志
在不修改原函数的基础上,为其增加记录日志的功能
可以通过高阶函数,来动态的生成一个新函数
*/
function someFn() {
return "hello";
}

function outer(cb) {
return () => {
console.log("记录日志");
const result = cb();
return result;
}
}

let fn = outer(someFn);
console.log(fn());
console.log(outer(someFn)());

闭包

        /* 
可以利用函数,来隐藏不希望被外部访问到的变量

闭包:
闭包就是能访问到外部函数作用域中变量的函数
什么时候使用:
当我们需要隐藏一些不希望别人访问的内容时就可以使用闭包
构成闭包的条件:
1.函数的嵌套
2.内部函数要引用外部函数中的变量
3.内部函数要作为返回值返回


函数的作用域,在函数创建时就已经确定了(词法作用域)
和调用的位置无关

闭包利用的就是词法作用域

闭包的生命周期:
1.闭包在外部函数调用时产生,外部函数每次调用都会产生一个全新的闭包
2.在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)

注意事项:
闭包主要用来隐藏一些不希望被外部访问的内容,这就意味着闭包需要占用一定的内存空间
相较于类来说,闭包比较浪费内存空间(类可以使用原型而闭包不能)
需要执行次数较少时,使用闭包
需要大量创建实例时,使用类
*/
function outer() {
let num = 0;
return () => {
num++;
console.log(num);
}
}

const newFn = outer();
console.log(newFn());
console.log(outer()());

递归

        /* 
递归
- 调用自身的函数称为递归函数
- 递归的作用和循环是基本一致的
- 递归的核心思想就是将一个大的问题拆分为一个一个的小的问题,小的问题解决了,大的问题也就解决了
- 编写递归函数,一定要包含两个要件:
1.基线条件 -- 递归的终止条件
2.递归条件 -- 如何对问题进行拆分

递归的作用和循环时一致的,不同点在于,递归的思路比较简洁,循环的执行性能比较好
在开发中,一般的问题都可以通过循环解决,也是尽量去使用循环,少用递归
只在一些使用循环解决比较麻烦的场景下,才使用递归
*/

function jieCheng(num) {
// 基线条件
if (num === 1) {
return 1;
}

// 递归条件
return jieCheng(num - 1) * num;
}

console.log(jieCheng(5)); // 120

可变参数

        /*
arguments
- arguments是函数中又一个隐含参数
- arguments是一个类数组对象(伪数组)
和数组类似,可以通过索引来读取元素,也可以通过for循环遍历,但是它不是一个数组对象,不能调用数组方法
- arguments用来存储函数的实参,
无论用户是否定义形参,实参都会存储到arguments对象中
可以通过该对象直接访问实参

... 可变参数,在定义函数时可以将参数指定为可变参数
- 可变参数可以接收任意数量实参,并将他们统一存储到一个数组中返回
- 可变参数的作用和arguments基本是一致,但是也具有一些不同点
1.可变参数的名字可以自己指定
2.可变参数就是一个数组,可以直接使用数组的方法
3.可变参数可以配合其他参数一起使用
*/

function sum() {
let result = 0;

for (const num of arguments) {
result += num;
}

return result;
}

function sum2(...num) {
return num.reduce((a, b => a + b, 0));
}

function fn(a, b, ...args) {
console.log(args); // ['hello', true, 2342]
}
fn(123, 456, "hello", true, 2342);

9.内建对象


结构赋值

        /* 
解构赋值
解构数组时,可以使用...来设置获取多余的元素

数组中可以存储任意类型的数据,也可以存数组
如果一个数组中的元素还是数组,则这个数组我们就成为二维数组
*/

const arr = ["孙悟空", "猪八戒", "沙和尚"];

let [a, b, c = 7, d = 10] = arr;
console.log(a, b, c, d); // 孙悟空 猪八戒 沙和尚 10

let [n1, n2, ...n3] = [1, 2, 3, 4, 5, 6];
console.log(n1, n2, n3); // 1 2 [3, 4, 5, 6]

let a1 = 10;
let a2 = 20;
[a1, a2] = [a2, a1];
console.log(a1, a2); // 20 10



/*
对象的解构
*/

const obj = { name: "孙悟空", age: 18, gender: "男" }

let { name, age, gender } = obj;
console.log(name, age, gender); // 孙悟空 18 男

let { address } = obj;
console.log(address); // undefined 没有的属性返回undefined

let { name: a, age: b, gender: c } = obj;
console.log(a, b, c); // 孙悟空 18 男

对象的序列化

        /* 
对象的序列化
- JS中的对象使用时都是存在于计算机的内存中的
- 序列化指将对象转换为一个可以存储的格式
在JS中对象的序列化通常是将一个对象转换为字符串(JSON字符串)
- 序列化的用途(对象转换为字符串有什么用):
- 对象转换为字符串后,可以将字符串在不同的语言之间进行传递
甚至人可以直接对字符串进行读写操作,使得JS对象可以不同的语言之间传递
- 用途:
1.作为数据交换的格式
2.用来编写配置文件
- 如何进行序列化:
- 在JS中有一个工具类 JSON(JavaScript Object Notation) JS对象表示法
- JS对象序列化后会转换为一个字符串,这个字符串我们称为JSON字符串

JSON.stringify() 可以将一个对象转换为JSON字符串
JSON.parse() 可以将一个JSON字符串转换为一个对象

- 也可以手动的编写JSON字符串,在很多程序的配置文件就是使用JSON编写的
- 编写JSON的注意事项:
1.JSON字符串有两种类型:
JSON对象 {}
JSON数组 []
2.JSON字符串的属性名必须使用双引号
3.JSON中可以使用的属性值(元素)
- 数字(Number)
- 字符串(String) 必须使用双引号
- 布尔值(Boolean)
- 空值(Null)
- 对象(Object {})
- 数组(Array [])
4.JSON的格式和JS对象的格式基本上一致的,
注意:JSON字符串如果属性是最后一个,则不要加,
*/

const obj = {
name: "孙悟空",
age: 18
}

const str = JSON.stringify(obj);
console.log(str); // {"name":"孙悟空","age":18}

const obj2 = JSON.parse(str);
console.log(obj2);

Map

        /* 
Map
- Map用来存储键值对结构的数据(key-value)
- Object中存储的数据就可以认为是一种键值对结构
- Map和Object的主要区别
- Object中的属性名只能是字符串或符号,如果传递了一个其他类型的属性名,
JS解释器会自动将其转换为字符串
- Map中任何类型的值都可以称为数据的key

创建:
new Map()

属性和方法:
map.size() 获取map中键值对的数量
map.set(key, value) 向map中添加键值对
map.get(key) 根据key获取值
map.delete(key) 删除指定数据
map.has(key) 检查map中是否包含指定键
map.clear() 删除全部的键值对
map.keys() 获取map的所有的key
map.value() 获取map的所有的value
*/

const map = new Map();
const obj = { name: "孙悟空" }

map.set(obj, "haha");
map.set(NaN, "xixi");
console.log(map.get(NaN)); // "xixi"
map.delete(NaN);
console.log(map.has(obj)); // true
map.clear();

const map2 = new Map();
map2.set("name", "孙悟空");
map2.set({}, "hehe");
// 将map转换为数组
const arr = Array.from(map2);
const arr2 = [...map2];
console.log(arr, arr2);

Set

        /* 
Set
- Set用来创建一个集合
- 它的功能和数组类似,不同点在于set中不能存储重复的数据
- 可以用Set来去重

- 使用方式:
创建
- new Set()
- new Set([...])

方法
- size 获取数量
- add() 添加元素
- has() 检查元素
- delete() 删除元素
*/

// 创建一个Set
const set = new Set();

// 向set中添加数据
set.add(10);
set.add("孙悟空");
set.add(10);
console.log(set); // {10, '孙悟空'}

Math

        /* 
Math
- Math是一个工具类
- Math中为我们提供了数学运算相关的常量和方法
- 常量:
Math.PI
- 方法;
Math.abs() 求一个数的绝对值
Math.min() 求多个值中的最小值
Math.max() 求多个值中的最大值
Math.pow() 求x的y次幂 等价于**
Math.sqrt() 求一个数的平方根

Math.floor() 向下取整
Math.ceil() 向上取整
Math.round() 四舍五入取整
Math.trunc() 直接去除小数位

Math.random() 生成随机数[0,1)
*/

Date

        /* 
Date
- 在JS中所有的和时间相关的数据都由Date对象来表示
- 对象的方法:
getFullYear() 获取4位年份
getMonth() 返回当前日期的月份(0-11)
getDate() 返回当前今天是几日
getDay() 返回当前日期是周几(0-6) 0 表示周日
getTime() 返回当前日期对象的时间戳
时间戳:自1970年1月1日0时0分0秒到当前时间所经历的毫秒数
计算机底层存储时间时,使用的是时间戳
Date.now() 获取当前的时间戳
*/

let d = new Date(); // 直接通过new Date()创建时间对象时,它创建的是当前的时间的对象
console.log(d);
console.log(d.getFullYear());
console.log(d.getMonth());
console.log(d.getDate());
console.log(d.getDay());
console.log(d.getTime());

日期的格式化

        /*
Date
- 在JS中所有的和时间相关的数据都由Date对象来表示
- 对象的方法:
getFullYear() 获取4位年份
getMonth() 返回当前日期的月份(0-11)
getDate() 返回当前是几日
getDay() 返回当前日期是周几(0-6) 0表示周日
.....

getTime() 返回当前日期对象的时间戳
时间戳:自1970年1月1日0时0分0秒到当前时间所经历的毫秒数
计算机底层存储时间时,使用都是时间戳
Data.now() 获取当前的时间戳

toLocaleString()
- 可以将一个日期转换为本地时间格式的字符串
- 参数:
1.描述语言和国家信息的字符串
zh-CN 中文中国
zh-HK 中文香港
en-US 英文美国
2.需要一个对象作为参数,在对象中可以通过对象的属性来对日期的格式进行配置
dateStyle 日期的风格
timeStyle 时间的风格
full
long
medium
short
hour12 是否采用12小时制
true
false
weekday 星期的显示方式
long
short
narrow
year
numeric
2-dagit
*/

const d = new Date();
let result = d.toLocaleDateString();
let result2 = d.toLocaleTimeString();
let result3 = d.toLocaleString()
console.log(result); // 2022/12/19
console.log(result2); // 10:25:29
console.log(result3); // 2022/12/19 10:25:29
let result4 = d.toLocaleString("zh-CN", {
year: "numeric",
month: "long",
day: "2-digit",
weekday: "short"
});
console.log(result4); // 2022年12月19日周一

包装类

        /* 
在JS中,除了直接创建原始值外,也可以创建原始值的对象
通过 new String() 可以创建String类型的对象
通过 new Number() 可以创建Number类型的对象
通过 new Boolean() 可以创建Boolean类型的对象
- 但是千万不要这么用

包装类:
JS中一共有5个包装类
String -- 字符串包装为String对象
Number -- 数值包装为Number对象
Boolean -- 布尔值包装为Boolean对象
BigInt -- 大整数包装为BigInt对象
Symbol -- 符号包装为Symbol对象
- 通过包装类可以将一个原始值包装为一个对象
当我们对一个原始值调用方法或属性时,JS解释器会临时将原始值包装为对应的对象
然后调用这个对象的属性或方法

- 由于原始值会被临时转换为对应的对象,这就意味着对象中的方法都可以直接通过原始值来调用
*/

let num = 11;
num = num.toString();
console.log(typeof num); // string

字符串方法

        /* 
字符串:
- 字符串其本质就是一个字符数组
- "hello" -- ["h","e","l","l","o"]
- 字符串的很多方法都和数组是非常类似的
- 属性和方法:
length 获取字符串的长度
字符串[索引] 获取指定位置的字符
str.at() (实验方法)
- 根据索引获取字符,可以接受负索引
str.chatAt()
- 根据索引获取字符
str.concat()
- 用来连接两个或多个字符串
str.includes("内容",查询起始位置)
- 用来检查字符串中是否含某个内容
有返回true
没有返回false
str.indexOf()
str.lastIndexOf()
- 查询字符串中是否包含某个类容
str.startsWith()
- 检查一个字符串是否以指定内容开头
str.endsWith()
- 检查一个字符串是否以指定内容结尾
str.padStart()
str.padEnd()
- 通过添加指定的内容,使字符串保持某个长度
str.replace()
- 使用一个新的字符串替换一个指定内容
str.replaceAll()
- 使用一个新字符串替换所有指定内容
str.slice()
- 对字符串进行切片
str.substring()
- 截取字符串
str.split()
- 用来将一个字符串拆分为一个数组
str.toLowerCase()
- 将字符串转换为小写
str.toUpperCase()
- 将字符串转换为大写
str.trim()
- 去除前后空格
str.trimStart()
- 去除开始空格
str.trimEnd()
- 去除结束空格
*/

let str = "hello";

// length
console.log(str.length); // 5

// at()
console.log(str.at(-1)); // o

// includes()
let str2 = "hello how are you";
console.log(str2.includes("how", 2)); // true

// padStart()
let str3 = "1";
console.log(str3.padStart(5, "0")); // 00001

// replace()
let str4 = "hello hello how are you";
console.log(str4.replace("hello", "happy")); // happy hello how are you

// slice()
console.log(str4.slice(0, 5)); // hello

// split()
let str5 = "sadf@kjh@hkj@hkefj@hfd";
console.log(str5.split("@")); // ['sadf', 'kjh', 'hkj', 'hkefj', 'hfd']

正则表达式

        /* 
正则表达式
- 正则表达式用来定义一个规制
- 通过这个规制计算机可以检查一个字符串是否符合规制
或者字符串中符合规制的内容提取出来
- 正则表达式也是JS中的一个对象,所以要使用正则表达式,需要先创建正则表达式的对象

通过构造函数来创建一个正则表达式的对象
new RegExp() 可以接收两个参数(字符串) 1.正则表达式 2.匹配模式
使用字面量来创建正则表达式: /正则/匹配模式

/a/ 表示,检查一个字符串是否有a
*/

let reg = new RegExp("a", "i"); // 通过构造函数来创建一个正则表达式的对象
let reg2 = /a/i;
console.log(reg, reg2);
        /* 
1.在正则表达式中大部分字符串都可以直接写
2. | 在正则表达式中表示或
3.[] 表示或(字符集)
[a-z] 任意的小写字母
[A-Z] 任意的大写字母
[a-zA-Z] 任意的字母
[0-9] 任意数字
4.[^] 表示除了
[^x] 除了x
5. . 表示除了换行符外的任意字符
6.在正则表达式中使用\作为转义字符
7.其他的字符集
\w 任意单词字符 [A-Za-z0-9_]
\W 除了单词字符 [^A-Za-z0-9_]
\d 任意数字 [0-9]
\D 除了数字 [^0-9]
\s 空格
\S 除了空格
\b 单词边界
\B 除了单词边界
8.开头和结尾
^ 表示字符串的开头
$ 表示字符串的结尾

i 匹配模式i表示忽略大小写

量词
{m} 正好m个
{m,} 至少m个
{m,n} m-n个
+ 一个以上,相当于{1,}
* 任意数量,有没有都行
? 0-1次,相当于{0,1}
*/

let reg = /ab/;
console.log(reg.test("abc")); // true

let reg2 = /a|b/;
console.log(reg2.test("ac")); // true

let reg3 = /[a-z]/;
console.log(reg3.test("ac")); // true

let reg4 = /[^a-z]/;
console.log(reg4.test("ac")); // false

let reg5 = /\w/;
console.log(reg5.test("ac")); // true

let reg6 = /^a/;
console.log(reg6.test("ba")); // false

let reg7 = /a$/;
console.log(reg7.test("ba")); // true

let reg8 = /a{3}/;
console.log(reg8.test("aaa")); // true

let reg9 = /^[a-z]{1,4}$/;
console.log(reg9.test("aaajjj")); // false

/*
exec()
- 获取字符串中符合正则表达式的内容

g 表示全局匹配
*/

let str = "abcaecafcacc";
let re = /a[a-z]c/ig;
console.log(re.exec(str));
console.log(re.exec(str));


/*
asgia15345678911ogdsjgoegri18745632111ndsothh14158296311aegrjesg

用自己的语言来描述出来
1 [3-9] 任意数字*9
*/

let str2 = "asgia15345678911ogdsjgoegri18745632111ndsothh14158296311aegrjesg";
// let re2 = /1[3-9]\d{9}/g;
let re2 = /(1[3-9]\d)\d{4}(\d{4})/g;

let result;
while (result = re2.exec(str2)) {
console.log(result[0], result[1], result[2]);
console.log(result[1] + "****" + result[2]);
}

字符串的正则表达式

        /* 
split()
- 可以根据正则表达式来对一个字符串进行拆分
search()
- 可以去搜索符合正则表达式的内容第一次在字符串出现的位置
replace()
- 根据正则表达式替换字符串中指定内容
match()
- 根据正则表达式去匹配字符串中符合要求的内容
matchAll()
- 根据正则表达式去匹配字符串中符合要求的内容(必须设置g 全局配置)
- 它返回的是一个迭代器
- 和exec() 一样

*/

let str = "孙悟空abc猪八戒adc沙和尚"
console.log(str.split(/a[bd]c/)); // ['孙悟空', '猪八戒', '沙和尚']

let str2 = "asgia15345678911ogdsjgoegri18745632111ndsothh14158296311aegrjesg";
console.log(str2.search(/1[3-9]\d{9}/)); // 5
console.log(str2.replace(/1[3-9]\d{9}/g, "哈哈哈")); // asgia哈哈哈ogdsjgoegri哈哈哈ndsothh哈哈哈aegrjesg
console.log(str2.match(/1[3-9]\d{9}/g)); // ['15345678911', '18745632111', '14158296311']

垃圾回收

        /*
垃圾回收(Garbage collection)
- 和生活一样,生活时间长了以后会产生生活垃圾
程序运行一段时间后也会产生垃圾
- 在程序的世界中,什么是垃圾?
- 如果一个对象没有任何的变量对其进行引用,那么这个对象就是一个垃圾
- 垃圾对象的存在,会严重的影响程序的性能
- 在JS中有自动的垃圾回收机制,这些垃圾对象会被解释器自动回收,我们无需手动处理
- 对于垃圾回收来说,我们唯一能做的事情就是将不再使用的变量设置为null
*/

10.DOM


简介

        /* 
要使用DOM来操作网页,我们需要浏览器至少得先给我一个对象
才能完成各种操作

所以浏览器已经为我们提供了一个document对象,它是一个全局变量可以直接使用
document代表得是整个的网页
*/

document

        /*
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 -> 获取页面中所有的超链接
...
*/

元素节点

        /* 
元素节点对象(element)
- 在网页中,每一个标签都是一个元素节点
- 如何获取元素节点对象?
1.通过document对象来获取元素节点
2.通过document对象来创建元素节点
- 通过document来获取已有的元素节点:
document.getElementById()
- 根据id获取一个元素节点对象
document.getElementsByClassName()
- 根据元素的class属性值获取一组元素节点对象
- 返回的是一个类数组对象
- 该方法返回的结果是一个实时更新的集合
document.getElementsByTagName()
- 根据标签名获取一组元素节点对象
- document.getElementByTagName("*") 获取页面中所有的元素
document.getElementsByName()
- 根据name属性获取一组元素节点对象
- 返回一个实时更新的集合
- 主要用于表单项
document.querySelectorAll()
- 根据选择器去页面中查询元素
- 会返回一个类数组(不会实时更新)
document.querySelector()
- 根据选择器去页面中查询第一个符合条件的元素

- 创建一个元素节点
document.createElement()
- 根据标签名创建一个元素节点对象
*/
        /* 
div元素的原型链
HTMLDivElement -> HTMLElement -> ELement -> Node -> ...

通过元素节点对象获取其他节点的方法
element.childNodes 获取当前元素的子节点(会包含空白的子节点)
element.children 获取当前元素的子元素
element.firstElementChild 获取当前元素的第一个子元素
element.lastElementChild 获取当前元素的最后一个子元素
element.nextElementSibling 获取当前元素的下一个兄弟元素
element.previousElementSibling 获取当前元素的前一个兄弟元素
element.parentNode 获取当前元素的父节点
element.tagName 获取当前元素的标签名
*/
const box1 = document.getElementById("box1");

const spans = box1.getElementsByTagName("span"); // 范围不同

文本节点

        /*
在DOM中,网页中所有的文本内容都是文本节点对象
可以通过元素来获取其中的文本节点对象,但是我们通常不会这么做

我们可以直接通过元素去修改其中的文本
修改文本的三个属性
element.textContent 获取或修改元素中的文本内容
- 获取的是标签中的内容,不会考虑css样式

element.innerText 获取或修改元素中的文本内容
- innerText获取内容时,会考虑css样式
- 通过innerText去读取CSS样式,会触发网页的重排(计算CSS样式)
- 当字符串中有标签时,会自动对标签进行转义
- <li> -> &lt;li&gt

element.innerHTML 获取或修改元素中的html代码
- 可以直接向元素中添加html代码
- innerHTML插入内容时,有被xss注入的风险
*/

const box1 = document.getElementById("box1");

console.log(1, box1.textContent); // 1 '\n 我是box1\n
console.log(2, box1.innerText); // 2 ''

属性节点

        /* 
属性节点(Attr)
- 在DOM中也是一个对象,通常不需要获取对象而直接通过元素即可完成对其的各种操作
- 如何操作属性节点:
方式一:
读取:元素.属性名(注意:class属性需要使用className来读取)
读取一个布尔值时,会返回true或false
修改:元素.元素名 = 属性值

方式二:
读取:元素.getAttribute(属性名)

修改:元素.setAttribute(属性名, 属性值)

删除:元素.removeAttribute(属性名)
*/

// <input type="text" name="username" value="admin" class="a" disabled>
const input = document.getElementsByName("username")[0];
const input2 = document.querySelector("[name=username]");
console.log(input.disabled); // true
console.log(input2.className); // a

事件

    <!-- <button id="btn" onclick="alert('点我')">点我一下</button> -->
<button id="btn">点我一下</button>
<button id="btn2">点我一下2</button>
<script>
/*
事件(event)
- 事件就是用户和页面之间发生的交互行为
比如:点击按钮、鼠标移动、双击按钮、敲击键盘、松开按键...
- 可以通过为事件绑定响应函数(回调函数),来完成和用户之间的交互
- 绑定响应函数的方式:
1.可以直接在元素的属性中设置
2.可以通过元素的指定函数设置回调函数的形式来绑定事件(一个事件只能绑定一个响应函数)
3.可以通过元素addEventListener()方法来绑定事件
*/

// 获取到按钮对象
const btn = document.getElementById("btn");
// 为按钮对象的事件属性设置响应函数
btn.onclick = function () {
alert("点我了");
}

const btn2 = document.getElementById("btn2");
btn2.addEventListener("click", function () {
alert("点我了了");
});
</script>

文档的加载

        /* 
网页是自上向下加载的,如果将js代码编写到网页的上边,
js代码在执行时,网页还没有加载完毕,这时会出现无法获取到DOM对象的情况

window.onload 事件会在窗口中的内容加载完毕后才触发
document的DOMContentLoaded事件会在当前文档加载完毕之后触发

如何解决这个问题:
1.将script标签编写到body的最后
2.将代码编写到window.onload的回调函数中
3.将代码编写到document对象的DOMContentLoaded的回调函数中(执行时机更早)
4.将代码编写到外部的js文件,然后以defer的形式引入(执行时机更早,早于DOMContentLoaded)
*/

window.onload = function () {

}

window.addEventListener("load", function () {

});

document.addEventListener("DOMContentLoaded", function () {

});

DOM的修改

            // 创建一个li
const li = document.createElement("li");
// 向li中添加文本
li.textContent = "唐僧";
// 给li添加属性
li.id = "ts"

// appendChild() 用于给一个节点添加子节点
list.appendChild(li);

/*
insertAdjacentElement() 可以向元素的任意位置添加元素
两个参数:
1.要添加的位置
beforeend 标签的最后
afterbegin 标签的开始
beforebegin 在元素的前边插入元素(兄弟元素)
afterend 在元素的后边插入元素(兄弟元素)
2.要添加的元素
*/
// list.insertAdjacentElement("beforeend", li);
list.insertAdjacentHTML("beforeend", "<li id='bgj'>白骨精</li>");



// 获取孙悟空
const swk = document.getElementById("swk");

/*
replaceWith() 使用一个元素替换当前元素
remove() 方法用来删除当前元素
*/
swk.replaceWith(li);


/*
只要点击超链接就会触发页面的跳转,事件中可以通过取消默认行为来阻止超链接的跳转
return false来取消默认行为,只在xxx.xxx = function(){} 这种形式绑定的事件才适
*/

节点的复制

            const newL1 = l1.cloneNode(true); // 用来对节点的复制

/*
使用cloneNode() 方法对节点进行复制时,它会复制节点的所有特点包括各种属性
这个方法默认只会复制当前节点,而不会复制节点的子节点
可以传递一个true作为参数,这样该方法也会将元素的子节点一起复制
*/