基础开篇

书写格式

和css一样,有三种书写格式,行内式、内嵌式、外链式

1
2
3
4
5
6
7
8
9
10
11
<div onclick="alert('hello world');">我是div</div>

<head>
<script>
window.onload = function () { // 必须添加这句
alert("hello world");
}
</script>
</head>

<script type="text/javascript" src="01-js书写格式.js"></script>

通常将js代码放在body的最后, 因为HTML是从上至下加载, 而js代码通常是给标签添加交互(操作元素), 所以需要先加载HTML, 否则如果执行js代码时HTML还未被加载, 那么js代码将无法添加交互(操作元素);HTML页面中出现<script>标签后,就会让页面暂停等待脚本的解析和执行。无论当前脚本是内嵌式还是外链式,页面的下载和渲染都必须停下来等待脚本的执行完成才能继续, 所以如果把js代码写在head中, 那么js代码没有执行完毕之前后续网页无法查看

注释代码

1
2
3
4
5
6
7
// 我是被注释的内容-单行

/*
我是被注释的
内容
多行
*/

常见的输出方式

浏览器弹窗输出

1
2
3
alert("hello world");
prompt("请输入内容:");
confirm("你好吗?");

image-20220510133029902

在页面中显示内容

1
document.write("hello world2");

在控制台中显示内容

1
2
3
console.log("hello world3");
console.error("错误信息");
console.warn("警告信息");

img

常量和变量

常量

常量: 固定不能改变的数据

  • 整型常量6660b1001等各种进制整数
  • 实型常量3.146.66等所有小数
  • 布尔常量truefalse
  • 字符串常量a、”lnj”等使用单引号(‘)或双引号(“)括起来的一个或几个字符

变量

可以以变化的数据, 需要先定义后使用

定义变量
  • let 变量名称;
  • let num;

ES6之后都用let,新的方式;var num是老的定义方式。

初始化变量

第一次给变量赋值,称之为变量的初始化。

1
2
3
let num;
num = 666; //变量的初始化
num = 888; //不是变量的初始化

先定义后初始化

1
2
let num;
num = 666;

定义的同时初始化

1
let num = 666;
变量默认值

JavaScript中变量没有初始化保存的是undefined

1
2
3
let num;
console.log(num); // undefined
// 如果变量没有初始化, 里面存储的是undefined
let 和 var 的区别
  • var 变量可以重复定义同名,后面的会覆盖前面的;let 不能,会报错

  • var 变量可以先使用后定义(预解析);let 不可以,不会预解析

  • 无论是 var 还是 let 定义在 {} 外面都是全局变量;

关键字和保留字

关键字

  • 指被赋予特殊含义的单词
  • 关键字在开发工具中会显示特殊颜色
  • 关键字不能用作变量名、函数名等
  • 关键字严格区分大小写, var和Var前者是关键字, 后者不是
关键字
break do instanceof typeof case
else new var catch finally
return void continue for switch
while default if throw delete
in try function this with
debugger false true null

保留字

JavaScript预留的关键字,他们虽然现在没有作为关键字,但在以后的升级版本中有可能作为关键字

保留字
class enum extends super const export
import implements let private public yield
interface package protected static

标识符

指程序员在程序中自己起的名称,诸如: 变量名称,函数名称等

  • 只能由26个英文字母的大小写、10个阿拉伯数字0~9、下划线_、美元符号$组成
  • 不能以数字开头
  • 严格区分大小写,比如test和Test是2个不同的标识符
  • 不可以使用关键字、保留字作为标识符
  • JS底层保存标识符时实际上是采用的Unicode编码,所以理论上讲,所有的utf-8中含有的内容都可以作为标识符
  • 变量的命名建议遵守***驼峰命名法***,首字母小写,第二个单词的首字母大写,例如: userName、myFirstName

数据类型

基本数据类型

  • String 字符串
  • Number 数值
  • Boolean 布尔值
  • Null 空值
  • Undefined 未定义

引用数据类型

  • Object 对象

数据类型和常量之间的关系,就像猫科动物和狮子老虎豹子之间的关系。

查看数据类型

格式:typeof 数据

1
2
3
console.log(typeof 123); // number
var num = 10;
console.log(typeof num); // number
1
2
3
4
5
var value= 10;
// 此时将value的数据类型number以字符串返回给我们, 存入到res变量中
var res = typeof value;
// 此时检查res的数据类型为string, 证明typeof返回给我们的是一个字符串
console.log(typeof res); // string

运算符

算数运算符

加减乘除余运算符 + - * / %

1
2
3
4
5
6
7
8
9
10
11
let res1 = 1 + 1;
let res2 = 1 - 1;
let res3 = 2 * 2;
let res4 = 10 / 3;
let res5 = 10 % 3;
console.log(res1); // 2
console.log(res2); // 0
console.log(res3); // 4
console.log(res4); // 3.3333
console.log(res5); // 1
console.log(res5+res1*res1-1); // 4

😃任何值和NaN做运算都得NaN

1
2
var result = 2 + NaN;
console.log(result); //NaN

非Number类型的值进行运算时,会将这些值转换为Number然后在运算

1
2
3
4
5
6
var result = true + 1; // + - * /  %
console.log(result); // 1+1=2
result = true + false;
console.log(result); // 1+0=1
result = 2 + null;
console.log(result);// 2+0=0

任何的值和字符串做加法运算,都会先转换为字符串,然后再和字符串做拼接的操作

1
2
3
4
var result = 1 + "123";
console.log(result); // 1123
result = 2 + "true";
console.log(result); // 2true

任何的值和字符串做- * / %法运算, 都会先转换为字符串转换为Number

1
2
3
4
var result = 2 - "1"; // - * /  %
console.log(result); // 1
result = "2" - "1";
console.log(result); // 1

😃取余运算m%n, n等于0 返回NaN;m为正负结果为正负,和n正负无关

1
2
3
4
var result = 10 % 0;
console.log(result); // NaN

console.log(-5%-2); // -1

正号+,对于非Number类型的值,会将先转换为Number,然后再运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var bool = true;
var res = +bool;
console.log(res); // 1

var str = "123";
res = +str;
console.log(res); // 123

var str2 = "123abc";
res = +str2;
console.log(res); // NaN, 所以内部不是调用parseInt, 而是Number()函数

var temp = null;
res = +temp;
console.log(res); // 0

负号-,可以对数字进行负号的取反

1
2
3
var num = 123;
num = -num;
console.log(num); // -123

赋值运算符

就是将等号右边的值存储到等号左边的变量中。优先级低于算数运算符,为右结合性(从右至左计算)

简单赋值运算符=,复杂赋值运算符 += -= *= /= %=

1
2
3
4
5
6
//由于算数运算符的优先级高于赋值运算符,所以会先计算1+1,然后再赋值给定义的res变量
let res = 1+1;

//由于赋值运算符的结合性是右结合性,所以会先将3赋值给num2,然后再将num2中的值赋值给num1
let num1, num2;
num1 num2 = 3;
1
2
3
4
5
let res = 5;
//会将等号左边的值取出来和右边进行指定的运算,运算完毕之后再将运算的结果存储到左边
res += 3 //相当于 res = res + 3;
res -= 3 //相当于 res = res - 3;
console.log(res); // 8 2

自增自减运算符

自增运算符 ++ :可以快速的对一个变量中保存的数据进行+1操作

自增运算符 -- :可以快速的对一个变量中保存的数据进行-1操作

1
2
3
let num = 5;
num++;
console.log(num); //6

自增或者自减写在变量后面:变量先参与其它的运算,然后再自增或者自减

自增或者自减写在变量前面:变量先自增或者自减,然后再参与其它的运算

1
2
3
4
let num = 1;
let res1 = num++ +1 ; //相当于let res1 = num+1; num++;
let res2 = ++num +1 ; //相当于num++; let res2 = num+1;
console.log(res1+res2); //2+3=5

注意1:自增自减运算符只能出现在变量的前后面,不能出现在常量或者表达式的前后面

注意2:企业开发中,自增自减运算符最好单独出现,不要出现在表达式中

1
2
3
4
5
6
7
8
9
let a, b;
a = 10;
b = 5;
let res = a++ + b; //不推荐的写法
//推荐的写法
let res = a + b;
a++;
console.log(res); //15
console.log(a); //11

关系运算符

> < >= <= == != === !==

😃关系运算符只会返回true或false

😃优先级:前四个优先级高于后四个。

😃等等于 == 和 恒等于=== 的区别

等等于 == 只判断取值是否相等,不判断数据类型是否相等;恒等于=== 判断取值和数据类型是否相等。

不等于 != !== 的区别一样,前者只判断取值,后者判断取值和数据类型

1
2
let res = 123 == "123";  //true
let res = 123 ==="123"; //false

😃对于非数值进行比较时,会将其转换为数字然后在比较

1
2
3
4
console.log(1 > true); //false
console.log(1 > false); //true
console.log(1 > "0"); //true
console.log(1 > null); //true

如果符号两侧的值都是字符串时,不会将其转换为数字进行比较, 而会分别比较字符串中字符的Unicode编码

Unicode编码转换地址

1
2
3
4
5
6
7
8
9
// a的Unicode编码是:0061
// b的Unicode编码是:0062
console.log("a" < "b");//true

// 比较多位时则是从前往后一位一位比较
// 第一位相同比较第二位, 直到比较到不一样或者结束为止
// c的Unicode编码是:0063
// 类似于C语言strcmp函数, 只不过JavaScript中比较的是Unicode编码
console.log("ab" < "ac");//true

null、undefined 、NaN比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
console.log(null == 0); // false
console.log(undefined == 0); // false
// 永远不要判断两个NaN是否相等
console.log(NaN == NaN); // false

/*
* 可以通过isNaN()函数来判断一个值是否是NaN
* 如果该值是NaN则返回true,否则返回false
*/
var num = NaN;
console.log(isNaN(num)); // true

// undefined 衍生自 null, 所以返回true
console.log(null == undefined); // true;
console.log(null === undefined); // false;

// == 判断值是否相等
// == 会进行数据类型转换
console.log("123" == 123); // true
// === 判断值和类型时候同时相等
// === 不会进行数据类型转换
console.log("123" === 123); // false

逻辑运算符

逻辑与 && 逻辑或 || 逻辑非 !

一假则假 一真则真 真变假,假变真

😃逻辑运算符的返回值只有 true 和 false

😃优先级:逻辑与 && 高于 逻辑或 ||

1
2
let res = true || false && false;
console.log(res); // 先判断 false && false 结果为假false;再判断 true||false 结果为真true,最终输出true

对于非Boolean类型的数值, 逻辑运算符会将其悄悄咪咪转换为Boolean类型来判断

1
2
3
4
let res1 = !0;  //0转换为布尔类型是false,相当于 let res = !false;
let res2 = !123; //123转换为布尔类型是true,相当于 let res = !true;
console.log(res1); //true
console.log(res2); //faalse

在逻辑与运算中,如果参与运算的不是布尔类型

  • 如果条件A不成立, 则返回条件A的数值本身
  • 如果条件A成立, 不管条件B成不成立都返回条件B数值本身
1
2
3
4
5
6
7
8
let result1 =  null && 0;  		//null转换为布尔类型是 false,不成立
console.log(result1); // null

let result2 = "123" && "abc"; //123转换为布尔类型是 true,成立
console.log(result2); // "abc"

let result3 = "123" && 0;
console.log(result3); // 0

在逻辑或运算中,如果参与运算的不是布尔类型

  • 如果条件A不成立, 则不管条件B成不成立都返回条件B数值本身
  • 如果条件A成立, 则返回条件A的数值本身
1
2
3
4
5
6
7
8
let result =  null || 0;   		 // num转换为布尔类型是 false,不成立
console.log(result); // 0

let result = "123" || "abc"; //123转换为布尔类型是 true,成立
console.log(result); // "123"

let result = "123" || 0;
console.log(result); // "123"

😃短路现象

格式:条件A && 条件B 格式:条件A || 条件B

在逻辑与运算中,由于一假则假,所以只要条件A是假,那么条件B就不会运算(即代码失效)

在逻辑或运算中,由于一真则真,所以只要条件A是真,那么条件B就不会运算(即代码失效)

1
2
3
4
5
6
7
8
9
let num = 1;
let res = (10 > 20) && (++num > 0);
console.log(num); // 1
console.log(res); // false

let num = 5;
let res = (10 < 20) && (++num > 0);
console.log(num); // 5
console.log(res); // true

逗号运算符

逗号运算符一般用于简化代码;左结合性(从左至右运算);优先级是所有运算符中最低的

😃逗号运算符会从左至右依次取出每个表达式的值, 最后整个逗号表达式的值等于最后一个表达式的值

格式: 表达式1,表达式2,… …,表达式n;

1
2
3
4
5
6
7
8
9
10
let a, b, c, d; //利用逗号运算符同时定义多个变量

d = (a = 1 + 1,b = 3 * 4, c = 10 / 2);
console.log(d); // 5
/*
1.先计算表达式1, a = 2
2.再计算表达式2, b = 12
3.再计算表达式3, c = 5
4.将表达式3的结果返回给d
*/

三目运算符-条件运算符

格式: 条件表达式 ? 结果A : 结果B;

当条件为真的时候,返回结果A;条件为假的时候,返回结果B

1
2
let res = (10 > 5) ? alert("语句1") : alert("语句2");  //弹语句1
let res = (10 < 5) ? alert("语句1") : alert("语句2"); //弹语句2

流程控制

选择结构:if 和 switch ;循环结构:

if结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if(条件表达式){
执行代码;
}

if(条件表达式){
条件成立执行的代码;
}else{
条件不成立执行的代码;
}

if(条件表达式A){
语句块A;
}else if(条件表达式B){
语句块B;
}else if(条件表达式C){
语句块C;
}else
语句块D;
}

switch结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
switch(条件表达式){
case 表达式A:
语句A;
break;
case 表达式B:
语句B;
break;
case 表达式C:
语句C;
break;
.......
default:
前面所有case都不匹配后执行的代码;
break;
}


var num = 120;
switch (120){
case num: // 这里可以是变量
console.log("120"); // 输出120
break;
case 110: // 这里可以是常量
console.log("110");
break;
default:
console.log("default");
break;
}

😃能用if就用if。对区间进行判断,建议用if;对几个固定的值进行判断,建议用switch

1
2
3
4
5
6
7
8
9
10
//需求:判断一个数是否大于100

let num = 110;
//if结构
if(num>100){
alert("这是大于100的数");
}else{
alert("这是不大于100的数");
}
//switch结构做不了

while循环

条件为真时,循环执行代码,直至不满足条件

1
2
3
while(条件表达式){
条件满足执行的代码;
}
1
2
3
4
5
6
7
8
9
//不管三七二十一先写上循环结构的代码
//将需要执行的代码写道{}中
//再()中写上循环的条件

let num = 1;
while(num<=10){
alert("发射导弹"+num);
++num;
}

dowhile循环

1
2
3
do{
需要重复执行的代码;
}while(条件表达式)

😃while和dowhile循环是可以互换的;如果循环体中的代码无论如何都需要先执行一次,那么建议使用dowhile;其他的情况都建议使用while循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//需求:要求用户输入密码,判断密码是否正确(假设正确密码123456),不正确继续输入;正确欢迎回来

let pwd = prompt("请输入密码");
while (pwd !== "123456"){
pwd = prompt("请输入密码");
}
alert("密码正确,欢迎回来");
//while循环先接收一次用户输入的密码,再判断,再执行

let pwd = -1 ;
do{
pwd = prompt("请输入密码");
}while (pwd !== "123456");
alert("密码正确,欢迎回来");
//dowhile循环直接接收用户输入的密码并判断,再执行

for循环

1
2
3
4
5
6
7
8
for(A初始化表达式;B条件表达式;C循环后增量表达式){
D重复执行的代码;
}
//循环过程:先执行A,再执行B,B满足后执行D,然后执行D;接着B\D\C....

for(let num = 1;num <= 10;num++){
consloe.log("发射子弹" + num);
}

😃for循环和while循环如何选择?for循环结束之后的变量可以让外界使用也可以不让外界使用,更灵活;while循环结束之后,变量还可以被外界使用。所以能用for就用for。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let num = 1;
while(num<=10){
alert("发射子弹"+num);
num++;
}
console.log(num); //11

//for不给外界使用变量
for(let num = 1;num <= 10;num++){
consloe.log("发射子弹" + num);
}
console.log(num); //报错

//for给外界使用变量
let num = 1
for(;num <= 10;num++){
consloe.log("发射子弹" + num);
}
console.log(num); //11

三大跳转

break 关键字:可以用于switch语句和循环结构中,作用是立即结束当前的switch语句/循环结构

continue 关键字:只能用于循环结构,作用是跳过本次循环,进入下一次循环

1
2
3
4
5
6
7
let num = 1;
while (num<10){
console.log("发射子弹" + num);
num++;
break;
}
//发射子弹1

如果是在循环嵌套结构中

break结束的是当前所在的循环结构;continue是跳过本次循环进入下一次循环

2022-05-12-2986f9277d09dda34e5b4f34ec8e5aca

数组

数组就是专门用于存储一组数据的

  • Number/String/Boolean/Null/undefined 是基本数据类型
  • 数组(Array)是引用数据类型(对象类型)
1
2
3
4
5
6
7
8
9
//创建一个数组
let 变量名称 = new Array(size);
let bianliang = new Array(3);
//往数组中存储数据
变量名称[索引号] = 需要存储的数据 ;
bianliang[0] = "1" ;
//获取数组中的数据
变量名称[索引号];
console.log(bianliang[0]);

数组注意点

  • js中数组对应的索引中没有存储数据,默认存储的是undefined
  • js中数组访问了数组不存在的索引,会返回 undefined
  • js中数组的存储空间不够时数组会自动扩容
  • js中数组可以存储不同类型的数据

😃创建数组的其它方式

通过构造函数创建数组

1
2
3
let 变量名称 = new Array(size);//创建一个指定大小的数组
let 变量名称 = new Array(); //创建一个空数组
let 变量名称 = new Array(data1,data2,...); //创建一个带数据的数组

通过字面量创建数组-使用这个更方便

1
2
let 变量名称 = [] ; //创建一个空数组
let bianliang = [data1,data2,....] ;//创建一个带数据的数组

数组的遍历

变量.length 可以获取数组的长度

1
2
3
4
5
6
7
let bl = ["a","b","c"] ;

console.log(bl.length); //输出数组的长度

for(let i=0; i<bl.length;i++){
console.log(bl[i]);
}//循环输出每个索引号中保存的数据

数组的解构赋值

解构赋值是es6中新增的一种赋值方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let bl = [1,3,5];

//传统的比较复杂麻烦
let a = bl[0]; //变量a被赋值为1
let b = bl[1]; //变量b被赋值为3
let c = bl[2]; //变量c被赋值为5

//解构赋值
let [a,b,c] = bl;
let [a,b,c] = [1,3,5]; //完全解构
let [a,b] = [1]; //b=undefiend
let [a,b,c] = [1,3,5,7]; //c=5
let [a,b,c] = [1,3,[5,7]]; //c = [5,7]
let [a,b=999,c=777] = [1,3]; //a=1 b=3 c=7777

//es6中新增的扩展运算符【...变量】打包剩余的数据,只能写在最后一个变量的前面
let [a,...b] = [1,3,5]; //a=1 b=[3,5]

数组中的增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//改 splice方法:从什么(索引)位置开始,替换多少个元素,新的内容
变量.splice(1,2,"data1","data2")

//增 unshift方法不仅可以在数组的最前面新增数据,并且会将新增内容之后的数组长度返回给我们,同 push
变量.unshift("data1","data2","data3");

//增 punsh方法不仅可以在数组的最后新增数据
变量.push("data1","data2","data3");
//并且会将新增内容之后数组的长度返回给我们
let res = bl.push("x","y");
console.log(res); //输出 6

//删 shift方法不仅可以删除数组中最前面一条数据,别切会将删除的数据返回给我们,同 pop
变量.shift();

//删 pop方法不仅可以删除数组最后一条数据
变量.pop();
//并且会将删除的数据返回给我们
let res = bl.pop();
console.log(res); //输出 d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let bl = ["a","b","c","d"];

//查:获取数组中索引为1的那个数据
console.log(bl[1]);

//改:将索引为1的数据改为m
bl[1] = "m";

//改:将索引为1的数据改为m,将索引为2的数据改为n
bl.splice(1,2,"m","n");

//增:在数组的最后新增N条数据
bl.push("x","y","z");

//增:在数组的最前面新增N条数据
bl.unshift("o","p");

//删:删除数组中最后一条数据
bl.pop();

//删:删除数组种最前面一条数据
bl.shift();

//删:删除数组中索引为2的数据
bl.splice(2,1);

数组常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
let bl = [1,2,3,4,5];

//【清空数组】
bl=[]; //方法1
bl.length = 0; //方法2
bl.splice(0,bl.length); //方法3


//【将数组转换为字符串】
bl.toString();


//【将数组转换成指定格式字符串】
//join方法默认情况下没有传递参数,就是调用toString();
bl.join();
//如果传递了参数,就会将传递的参数作为元素和元素的连接符号
let str = bl.join("+");
console.log(str); //输出 1+2+3+4+5
console.log(typeof str); //输出 string


//【将两个数组拼接成一个数组-contact方法】
变量1.contact(变量2);
//不会修改原有的数组,而是生成一个新的数组返回给我们
let bl1 = [1,2,3];
let bl2 = [4,5,6];
let res = bl1.contact(bl2);
console.log(res); //输出 [1,2,3,4,5,6]
console.log(typeof res); //输出 object
console.log(bl1); //输出[1,2,3]
console.log(bl2); //输出[4,5,6]


//【将两个数组拼接成一个数组-扩展运算符】推荐
let res = [...bl1,...bl2] ;
//扩展运算符在解构赋值中(等号的左边)标识将剩余的数据打包成一个新的数组
//扩展运算符在等号右边,那么表示将数组中的所有数据解开,放到所在的位置


//【对数组中的内容进行反转】
bl.reverse(); //会修改原有的数组


//【截取数组中指定范围的呢日用】
let bl = ["a","b","c","d"];
bl.slice(1,3); //从什么索引开始,到什么索引结束,包头不包尾;截取b,c


//【查找元素在数组中的位置】
let bl = ["a","b","c","d","c"];
bl.indexOf(c); //查找数据 c 在什么位置;如果没有找到就会返回-1;从左到右,一旦找到就会立即停止查找
bl.lastIndexOf(c); //从右至左查找
bl.indexOf(c,3) //从索引3的位置开始查找数据c,参数二可以省略,默认从0开始


//【判断数组中是否包含某个元素】
let bl = ["a","b","c","d"];
//方法一:使用indexOf或者lastIndexOf查找,判断是否为-1即可
//方法二:es6中新增的 includes 方法
bl.includes(a); // true
bl.includes(x); // false

//【将一串数据切割成数组返回给我们】
let bl = "a,b,c" ;
let res = bl.split(","); //按照参数,来切割字符串,转换成一个数组之后返回给我们
console.log(res); //输出 数组 ["a","b","c"]

二维数组

二维数组就是数组的每一个元素又是一个数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//获取数据
let bl = [[666,777],[888,999]];
let bl1 = bl[0]; //得到一个一维数组
let bl2 = bl1[1] //得到一位数组中的元素
//简写
let bl3 = bl[0][1];


//存储数据
//在定义二维数组的时候,将来需要存储多少个一维数组,就写上多少个[]即可
let bl = [[],[]];
bl[0] = [666,777]; //bl=[[666,777],[]]
bl[0][1] = 888; //bl=[[,888],[]]


//遍历数据
let bl = [[666,777],[888,999]];
for(let i=0; i<bl.length; i++){
let bl1 = bl[i];
//console.log(bl1); 输出[666,777] [888,999]
for(let j =0; j<bl1.length; j++){
console.log(bl1[j]);
}
}

函数

函数是专门用于封装代码的,函数是一段可以随时被反复执行的代码块

1
2
3
4
5
6
7
8
9
10
11
12
13
function 函数名称(形参列表){
被封装的代码;
}

//创建函数
function toLeft(){
console.log("打左转灯");
console.log("向左打方向盘");
console.log("回正方向盘");
}

//调用函数
toLeft(); //含义:找到名称叫做toLeft的函数,执行这个函数中封装的代码

注意点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
1】一个函数可以有形参也可以没有形参(0个或多个);形参就是定义函数时函数()中的变量
function say(name){
console.log("hello " + name);
}
say("zhangsan"); //输出 hello zhangsan


2】一个函数可以有返回值也可以没有返回值
function getSum(a,b){
return a+b;
}
console.log(getSum(1,2)) ; //输出3


3】函数没有通过return明确返回值,默认返回undefiend
function say(){
console.log("hello");
//return; 即便这里写了,但是没有在return后面明确返回值是什么
}
let res = say(); //输出 hello
console.log(res); //undefined


4return的作用和break相似,所以return后面不能编写任何语句,永远执行不到
break作用立即结束switch语句或者循环语句
return作用理解结束当前所在函数


5】调用函数时 实参的个数和形参的个数可以不相同;实参就是调用函数时传入的数据
function getSum(a,b){
console.log(a,b)
return a+b;
}
let res = getSum(10); //输出 10 undefined
let res = getSum(10,20,30); //输出 10 20


6】JS中的函数和数组一样,都是引用数据类型;既然是一种数据类型,所以也可以保存到一个变量中
//将一个函数保存到一个变量中,将来可以通过变量名称找到函数并执行函数
let say = function(){
console.log("hello");
}
say();

函数中的扩展运算符作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//扩展运算符在等号左边,将剩余的数据打包到一个新的数组中;并且只能写在最后
let [a,...b] = [1,3,5]

//扩展运算符在等号右边,将数组中的数据解开
let arr1 = [1,3,5];
let arr2 = [2,4,6];
let arr = [...arr1,...arr2]; //[1,3,5,2,4,6]

//扩展运算符在函数的形参列表中,将传递给函数的所有实参打包到一个数组中
//注意点:和在等号左边一样,也只能写在形参列表的最后
function getSum(a,...bl){
console.log(a); //输出 10
console.log(bl);
}
getSum(10,20,30,40); //输出数组 [20,30,40]

案例

1
2
3
4
5
6
7
8
9
10
function getSum(...bl){
let sum = 0;
for (let i = 0; i<bl.length; i++){
let num = bl[i]
sum += num ;
}
return sum ;
}
let res = getSum(10,20,30);
console.log(res); //输出 60

函数形参默认值

从ES6开始,可以直接在形参后面通过 = 指定默认值

1
2
3
4
function getSum(a = "hello" , b = "world"){
console.log(a,b);
}
getSum(); //输出 a的hello b的world

从ES6开始,默认值还可以从其他的函数中获取

1
2
3
4
5
6
7
8
function getSum(a = "hello" , b = getDefault()){
console.log(a,b);
}
getSum();
function getDefault{
return("世界")
}
//输出 a的hello b的世界

函数作为参数和返回值

函数作为其他函数的参数

1
2
3
4
5
6
7
8
9
10
11
//将函数保存到一个变量中
let say = function(){
console.log("hello");
}
say(); //输出 hello
//将函数作为其他函数的参数
function test(fn){ //相当于 let fn = say;
fn();
}
test(say); //输出 hello
//函数test把实参say(say又是函数,自带代码块)传入fn,fn

函数作为其他函数的返回值

1
2
3
4
5
6
7
8
9
function test(){  //相当于 let fn = say;
//注意点:在其它编程语言中函数是不可以嵌套定义的,但是在js中函数是可以嵌套定义的
let say = function(){
console.log("hello");
}
return say;
}
let fn = test(); //相当于 let fn = say
fn();

匿名函数

匿名函数:就是没有名称的函数

注意点:不能够只定义不使用,否则会报错,使用场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//作为其他函数的参数
function test(fn){
fn();
}
test(function(){
console.log("hello");
});

//作为其他函数的返回值
function test(){
return function(){
console.log("hello");
};
}
let fn = test();
fn();

//作为一个立即执行的函数
//注意点:匿名函数立即执行,那么必须使用()将函数的定义包裹起来才可以
( function (){
console.log("hello");
}) ();

箭头函数

箭头函数时es6中新增的一种定义函数的格式;目的就是为了简化定义函数的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//es6之前如何定义函数:
//第一种格式
function 函数名称(形参列表){
需要封装的代码;
}
//第二种格式
let 函数名称 = function(形参列表){
需要封装的代码;
}

//es6之后使用 箭头函数 定义函数
let 函数名称 = (形参列表) => {
需要封装的代码;
}
//箭头函数中如果只有一个形参,那么 () 可以省略
//箭头函数中如果 {} 中只有一句代码,那么{} 也可以省略
let 函数名称 = 形参列表 => 执行一句代码;

代码演示

1
2
3
4
5
6
7
8
9
10
11
function say(name){
console.log("hello " + name);
}

//使用箭头函数转换一下:
let say = (name) => {
console.log("hello " + name);
}

//只有一个形参, {}中只有一句代码,省略后:
let say = name => console.log("hello " + name);

普通函数/方法中的this,谁调用就是谁;而箭头函数中的this,是父作用域中的this,不是调用者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function demo(){
console.log(this);
}
demo(); //window

let p = {
name:"zs",
say:function(){
console.log(this);
},
//因为没有将箭头函数放到其它函数中,所以箭头函数属于全局作用域
//在js中只有定义一个新的函数才会开启一个新的作用域
hi:() => {
console.log(this);
}
}
p.say(); //{name: 'zs', say: ƒ, hi: ƒ}
p.hi(); //window
console.log(this); //window


function Person(){
this.name = "zs";
this.say = function(){
console.log(this);
}
//因为将箭头函数放到其它函数中,所以箭头函数属于其它函数(当前的其它函数就是构造函数)
//既然箭头函数属于构造函数,所以箭头函数中的this就是构造函数的this
this.hi = () => {
console.log(this);
}
}
let p = new Person();
p.say(); //Person
p.hi(); //Person


function Person(){
this.name = "zs";
this.say = function(){
console.log(this);
}
//箭头函数中的this永远都只看它所属的作用域的this
//无法通过 call/bind/apply 来修改
this.hi = () => {
console.log(this);
}
}
let p = new Person();
p.say.call({name:"ww"}); //{name: 'ww'}
p.hi.call({name:"ww"}); //Person

递归函数

递归函数就是在函数中自己调用自己;递归函数在一定程度上可以实现循环功能

能看懂就行,企业开发中用的不多

1
2
3
4
5
6
7
8
function login{
let pwd = prompt("请输入密码");
if(pwd !== "123456"){
login();
}
alert("密码正确,欢迎回来")
}
login();//输错几次密码就会弹几次密码正确

login函数调用执行代码会分配新的存储空间执行代码,执行过程如下图:

image-20221103160954030

闭包

闭包是一种特殊的函数。

如何生成一个闭包?

  • 当一个内部函数引用了外部函数的数据(变量/函数)时,那么内部的函数就是闭包
  • 所以只要满足 “是函数嵌套” 、“内部函数引用外部函数数据”

闭包特点

  • 只要闭包还在使用外部函数的数据,那么外部的数据就一直不会被释放
  • 也就是说可以延长外部函数数据的声明周期

闭包注意点

  • 当后续不需要使用闭包的时候,一定要手动将闭包设置为null,否则会出现内存泄露
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function test(){
var i = 666; //局部变量
}//只要代码执行到了大括号结束,i这个变量就会自动释放
console.log(i); //not defined


function test(){
var i = 777;
//由于demo函数满足闭包的两个条件,所以demo函数是闭包
function demo(){
console.log(i);
}
}
let fn = test();
fn(); //777

函数中变量作用域

在js中 {} 外面的作用域,称之为全局作用域

在js中函数后面{}的作用域,称之为局部作用域

在ES6中只要 {} 没有和函数结合在一起,那么应该叫块级作用域

块级作用域和局部作用域区别

  • 在块级作用域中通过 var 定义的变量是全局变量
  • 在局部作用域中通过 var 定义的变量是局部变量

😃简单理解

{}内的let全部都是局部变量;外的let都是全局变量

var 只有在函数后面的{}内才是局部变量,其它都是全局变量

面向对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
###面向对象思想
面向对象(Object Oriented,OO)是软件开发方法
面向对象是一种对现实世界抽象的理解,是计算机编程技术发展到一定阶段后的产物
Object Oriented Programming-OOP ——面向对象编程


###面向对象和面向过程区别
【面向过程】
强调的是功能行为
关注的是解决问题需要哪些步骤

回想下前面我们完成一个需求的步骤:
首先搞清楚我们要做什么
然后分析怎么做
最后我用代码体现
一步一步去实现,而具体的每一步都需要我们去实现和操作

在上面每一个具体步骤中我们都是参与者, 并且需要面对具体的每一个步骤和过程, 这就是面向过程最直接的体现

面向对象是基于面向过程而言;面向对象和面向过程都是一种思想

【面向对象】
将功能封装进对象,强调具备了功能的对象
关注的是解决问题需要哪些对象

当需求单一, 或者简单时, 我们一步一步去操作没问题, 并且效率也挺高。 可随着需求的更改, 功能的增加, 发现需要面对每一个步骤非常麻烦, 这时就开始思索, 能不能把这些步骤和功能再进行封装, 封装时根据不同的功能,进行不同的封装,功能类似的封装在一起。这样结构就清晰多了, 用的时候, 找到对应的类就可以了, 这就是面向对象思想


###示例
【面向过程】
了解电脑
了解自己的需求
对比参数
去电脑城
砍价,付钱
买回电脑
被坑

【面向对象】
找班长
描述需求
班长把电脑买回来

默认类 创建对象

面向对象的核心就是对象,那怎么创建对象?

  • 现实生活中可以根据模板创建对象,编程语言也一样,也必须先有一个模板,在这个模板中说清楚将来创建出来的对象有哪些属性行为
  • JavaScript中的类相当于图纸,用来描述一类事物。
  • JavaScript中可以自定义类, 但是也提供了一个默认的类叫做Object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
【通过 new Object() 创建对象】
// 1.使用默认类创建一个空对象
var obj = new Object()
// 2.动态给空对象新增属性
obj.name = "lnj";
obj.age = 33;
// 3.动态给空对象新增方法
obj.say = function () {
console.log("hello");
}
// 4.使用对象的属性和方法
console.log(obj.name);
console.log(obj.age);
obj.say();


【通过字面量创建对象】
// 1.使用字面量创建对象
var obj = {}; // 相当于var obj = new Object()
// 2.动态给空对象新增 属性
obj.name = "lnj";
obj.age = 33;
// 3.动态给空对象新增 方法
obj.say = function () {
console.log("hello");
}


// 1.使用字面量创建对象
var obj = {
name : 'lnj',
age: 33,
say : function () {
console.log("hello");
}
}
// 2.使用对象的属性和方法
console.log(obj.name);
console.log(obj.age);
obj.say();

//上面的创建方式, 每多创建一个人都需要将代码再写一遍, 冗余代码太多, 所以我们可以创建 创建对象的代码封装到一个函数中

工厂函数 创建对象

专门用于创建对象的函数我们称之为工厂函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createPerson(myname, myage) { 

var obj = new Object();
obj.name = myname;
obj.age = myage;
obj.say = function () {
console.log("hello");
}
//简写
var obj = {
name: myname,
age: myage,
say: function () {
console.log("hello");
}
}

return obj;
}
var obj1 = createPerson("lnj", 33);
var obj2 = createPerson("zq", 18);
console.log(obj1);
console.log(obj2);

构造函数 创建对象

构造函数和工厂函数一样,都是专门用于创建对象的;构造函数本质上是工厂函数的简写;推荐使用,更专业

构造函数的函数名称首字母必须大写;并且只能通过new来调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(myName,myAge){
// let obj = new Object(); //系统自动添加的1
// let this = obj; //系统自动添加的2
this.name = myName;
this.age = myAge;
this.say = function(){
console.log("hello");
}
//return this; //系统自动添加的3
}
let obj1 = new Person("zs",34);
let obj2 = new Person("ls",25);
console.log(obj1);
console.log(obj2);

//new Person("zs",34); 系统做了什么事情:
//系统会在构造函数中自动创建一个对象 1
//会自动将刚才创建的对象复制给this 2
//会在构造函数的最后自动添加 return this; 3

image-20221104173436985

由于两个对象中的say方法实现的都是一样的,但是保存到了不同的存储空间中,所以有性能问题,需要优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function mySay(){
console.log("hello");
}

function Person(myName,myAge){
// let obj = new Object(); //系统自动添加的1
// let this = obj; //系统自动添加的2
this.name = myName;
this.age = myAge;
this.say = mySay;
//return this; //系统自动添加的3
}
let obj1 = new Person("zs",34);
let obj2 = new Person("ls",25);
console.log(obj1);
console.log(obj2);

image-20221104173325253

当前代码仍然存在弊端:阅读性降低;函数mySay污染了全局的命名空间(在相同的作用域内,后定义的会覆盖先定义的,为了避免覆盖,这个名字以后就不能用了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//创建一个对象,在对象里面添加一个方法,既解决了性能问题,又解决了全局变量匮乏的问题
let fns ={
mySay:function(){
console.log("hello");
}
}

function Person(myName,myAge){
// let obj = new Object(); //系统自动添加的1
// let this = obj; //系统自动添加的2
this.name = myName;
this.age = myAge;
this.say = fns.mySay;
//return this; //系统自动添加的3
}
let obj1 = new Person("zs",34);
let obj2 = new Person("ls",25);
console.log(obj1);
console.log(obj2);

但是这个写法还不够专业

推荐的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(myName,myAge){
this.name = myName;
this.age = myAge;
}
Person.prototype={
//注意点:为了不破坏原有的对象关系,在给prototype赋值的时候,
//需要在自定义的对象中手动添加 constructor属性,手动的指定需要指向谁
constructor:Person,
say:function(){
console.log("hello");
}
}
let obj1 = new Person("zs",34);
let obj2 = new Person("ls",25);
console.log(obj1);
console.log(obj2);

prototype属性

特点:

  • 存储在prototype中的方法可以被对应构造函数创建出来的所有对象共享
  • prototype除了可以存储方法以外,还可以存储属性
  • prototype如果出现了和构造函数中同名的属性或者方法,对象在访问的时候,访问到的是构造函数中的数据

应用场景

  • prototype中一般情况下用于存储所有对象都相同的一些属性以及方法;如果是对象特有的属性或者方法,我们会存储到构造函数中。
  • 因为prototype中保存的只会占用一份存储空间

对象三角恋关系

每个构造函数中都有一个默认的属性,叫做prototype; prototype属性保存着一个对象,这个对象我们称之为原形对象

每个原型对象中都有一个默认的属性,叫做 constructor ;constructor指向当前原型对象对应的那个构造函数

通过构造函数创建出来的对象我们称之为实例对象 ;每个实例对象中都有一个默认的属性,叫做 __proto__ ; __proto__指向创建它的那个构造函数的原型对象

image-20221104181229036

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(myName,myAge){
this.name = myName;
this.age = myAge;
}
Person.prototype={
say:function(){
console.log("hello");
}
}
let obj1 = new Person("zs",34);
console.log(Person.prototype);
console.log(Person.prototype);
console.log(Person.prototype.constructor);
//构造函数 Person
//原型对象 Person.prototype
//实例对象 obj1

函数对象完整关系

function关系图

image-20221105134719866

object函数关系图

image-20221105135310008

😃完整关系图

image-20221105135802444

属性注意点

在给一个对象不存在的属性设置值得时候,不会去原型对象中查找,如果当前对象没有就会给当前对象新增一个不存在的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(myName,myAge){
this.name = myName;
this.age = myAge;
}
Person.prototype={
constructor:Person,
currentType:"人"
say:function(){
console.log("hello");
}
}
let obj = new Person("zs",34);
obj.currentType = "新设置的值"
console.log(obj.currentType); //新设置的值
console,log(obj.__proto__.currentType); //人

JS三大特性

回顾1

局部变量和函数:无论是ES6之前还是ES6,只要定义一个函数就会开启一个新的作用域;只要在这个新的作用域中,通过 let/var 定义的变量就是局部变量;只要在这个新的作用域中,定义的函数就是局部函数。

回顾2

对象的私有变量和函数:默认情况修改对象中的属性和方法都是公开/共有的,只要拿到对象就能操作对象的属性和方法;外界不能直接访问的变量和函数就是私有变量和私有函数;构造函数的本质也是一个函数,所以也会开启一个新的作用域,所以构造函数中定义的变量和函数就是私有变量、私有函数。

封装

封装性就是隐藏实现希捷,仅对外公开接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person{
this.name = "zs";
//this.age = 18;
let age = 34; //私有变量,别人无法读取和设置
this.setAge = function(myAge){
if(myAge>=0){
age = myAge ;
}//私有函数
}
this.getAge = function(){
return age;
}
}
console.log(obj.name);//对象的属性
console.log(age); //报错 私有变量

let obj = new Person();
obj.setAge(99);
console.log(obj.getAge); //对象的方法 输出55

继承

回顾:this是什么?

谁调用当前函数或者方法,this就是谁。函数中默认的this是window,方法中的this是调用这个方法的那个对象

😃新知识: bind call apply 方法

bind方法作用:修改函数或者方法中的this为指定的对象,并且会返回一个修改之后的新函数给我们

call方法的作用:修改函数或者方法中的this为指定的对象,并且会立即调用修改之后的函数

apply方法的作用:修改函数或者方法中的this为指定的对象,并且会立即调用修改之后的函数

这三种方法除了修改this以外,还可以传递参数,只不过参数必须卸载this对象的后面;并且只有apply必须通过数组的方式传递

1
2
3
js中继承的终极方法
在子类的构造函数中通过call借助弗雷的构造函数
将子类的原型对象修改为父类的实例对象

多态

多态是指事物的多种状态。父类型变量保存子类型对象,父类型变量当前保存的对象不同,产生的结果也不同。

强类型语言:一般编译型语言都是强类型语言,要求变量的使用严格符合定义。类如定义 int num; 那么num中将来就只能狗存储整型数据;

弱类型语言:一般解释性语言都是弱类型语言,不会要求变量的使用严格符合定义。例如let num; num中既可以存储整形,也可以存储布尔类型等。

由于js是弱类型的语言,所以我们不用关注多态。

类和对象-ES6

es6之前如何定义一个类?通过构造函数来定义一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(myName,myAge){
this.name = myName; //实例属性
this.age = myAge;
this.say = function(){ //实例方法
console.log(this.name,this.age);
}

//类也是一个对象
Person.num= 666; //静态属性
Person.run = function(){ //静态方法
console.log("run")
}
}
let p = new Person("zs",18);
p.say(); //输出 zs 18
console.log(person.num);
person.run();

😃从es6开始系统提供了一个名称叫做 class 的关键字,这个关键字就是专门用于定义类的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person{
//name1 = "zs"; 这种定义实例属性的方式并不是es6正式版标准写法,大部分浏览器不支持。必须写在constructor中

//当我们通过new创建对象的时候,系统会自动调用constructor
constructor(name2,age2){
this.name2 = myName;
this.age2 = myAge;
this.name1 = "zs";
this.age1 = 17;
}
//实例方法
say(){
consloe.log(this.name2,this.age2);
}

// static num =666; 这种定义静态属性的方式也不是es6正式版的标准写法,大部分浏览器不支持。只能用es6之前的方式添加静态属性
Person.num= 666; //静态属性
static run(){ //静态方法
consloe.log("run");
}
let p = new Person("ls",66);
p.say();
console.log(person.num);
person.run();

es6中的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person{
constructor(myName,myAge){
this.name = myName;
this.age = myAge;
}
say(){
console.log(this.name,this.age);
}
}
//含义:告诉浏览器将来Student这个类需要继承于Person这个类
class Student extends Person{
constructor(myName,myAge,myFenshu){
super(myName,myAge);
this.fenshu = myFenshu;
}
study(){
console.log("day day up");
}
}
let stu = new Student("zs",18,99);
stu.say();
console.log(stu.fenshu);
stu.study();

获取对象类型

想知道一个对象是通过哪个函数传出来的,一个对象真实的类型,使用对象.constructor.name

1
2
3
4
5
6
7
8
9
10
11
12
let obj = new Object();
console.log(obj.constructor.name); //Object


let arr = new Array();
console.log(arr.constructor.name); //Array

function Person(){
this.name = "zs";
}
let p = new Person();
console.log(p.constructor.name); //Person

instanceof关键字:用于判断对象是否是指定构造函数创建出来的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person{
constructor(){
name = 18;
}
}
let p = new Person();
//判断p实例对象是否是Person这个构造函数创建出来的
console.log(p instanceof Person); //true

class Cat{
}
let c = new Cat();
console.log(c instanceof Person); //false

判断对象属性

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person{
name = null;
}
Person.prototype.height = 0;
let p = new Person();
//in 特点:只要类中或者原型对象中有,就会返回true
console.log("name" in p); //true
console.log("age" in p); //false
console.log("height" in p); //true
//只会判断类中有没有
console.log(p.hasOwnProperty("name")); //true
console.log(p.hasOwnProperty("age")); //false
console.log(p.hasOwnProperty("height"));//false

对象增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Person{}
let p = new Person();

//增(C)
p.name = "zs";
p.say = function(){
console.log("hello");
}
p["name"] = "zs";
p["say"] = function(){
console.log("hello");
}

//删除(R)
delete p.name;
delete ["name"];
delete p.say;
delete ["say"];

//改(U)
p.name = "WW";
p.say = function(){
console.log("HI");
}
p["name"] = "WW";
p["say"] = function(){
console.log("HI");
}

//查(D)
console.log(p.name);
console.log(p["name"]);
p.say();

对象遍历

依次取出对象中所有的属性和方法。在js中通过高级for循环来遍历对象

for(let key in 对象名称){}

含义:将指定对象中所有的属性和方法的名称取出来依次的复制给key这个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Person(myName,myAge){
this.name = myName;
this.age = myAge;
this.say = function(){
console.log(this.name,this.age);
}
}
let p = new Person("zs",18);

for(let key in p){
console.log(key); //输出对象的属性和方法的名称
//输出对象的属性和方法的名称以及取值;代码含义:取出P对象中名称叫做当前遍历到的名称的属性或者方法的取值
console.log(p[key]);
//console.log(p.key); undefined,这样写不对。含义:取出p对象中名称叫做key的属性的取值
}


//只输出属性
for(let key in p){
if(p[key] instanceof Function){ //判断它是不是函数
continue;
}
console.log(key);
console.log(p[key]);
}

对象解构赋值

对象的结构赋值和数组的解构赋值,除了符号不一样,其它的一摸一样。

对象结构使用{} ; 数组结构使用 []

1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {
name:"zs",
age:18
}

let {name,age} = {name:"ww",age:99};
let {name} = {name:"ww",age:99};
let {name,age,height} = {name:"ww",age:99}; //height:undefined
let {name,age,height=66} = {name:"ww",age:99};
let {a,b} = {name:"ww",age:99};//undefined undefined 左边的变量名称必须和对象的属性名称一致,才能解构出数据

let {age} = {name:"ww",age:99};
console.log(age); //99

应用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//以前的写法
let obj = {
name:"zs",
age:34
}
function say(name,age){
console.log(name,age)
}
say(obj.name,obj.age);

//结构赋值的写法
let obj = {
name:"zs",
age:34
}
function say({name,age}){
console.log(name,age)
}
say(obj);

深拷贝和浅拷贝

浅拷贝:

  • 对于基本类型属性无论是深浅拷贝,都会复制一份
  • 对于引用类型属性浅拷贝拷贝的是引用类型的地址
  • 简而言之, 浅拷贝修改引用类型属性, 拷贝前拷贝后的对象都会受到影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var obj1 = {
name: "lnj", // 基本类型属性
age: 33, // 基本类型属性
dog: { // 引用类型属性
name: "wc",
age: 1,
}
};

var obj2 = {};
/*
for(var key in obj1){
obj2[key] = obj1[key];
}
*/
function copy(o1, o2){
for(var key in o1){
o2[key] = o1[key];
}
}
copy(obj1, obj2);
console.log(obj1);
console.log(obj2);

// 修改基本类型属性, 不会影响原有对象
obj2.name = "zs";
console.log(obj1.name);
console.log(obj2.name);

// 修改引用类型属性, 会影响原有对象
obj2.dog.name = "xq";
console.log(obj1.dog.name);
console.log(obj2.dog.name);

深拷贝

  • 对于基本类型属性无论是深浅拷贝,都会复制一份
  • 对于引用类型属性深拷贝会将引用类型复制一份
  • 简而言之, 深拷贝修改引用类型属性, 只会影响当前修改对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
var obj1 = {
name: "lnj", // 基本类型属性
age: 33, // 基本类型属性
dog: { // 引用类型属性
name: "wc",
age: 1,
}
};
var obj2 = {};

function copy(o1, o2){
for(var key in o1){
var item = o1[key];
// 判断当前属性是否是引用类型
if(item instanceof Object){
// 创建一个新引用类型属性
var o = {};
o2[key] = o;
// 进一步拷贝引用类型属性
copy(item, o);
}
// 判断当前属性是否数组
else if(item instanceof Array){
// 创建一个数组属性
var arr = [];
o2[key] = arr;
// 进一步拷贝数组属性
copy(item, arr);
}
// 如果不是引用类型, 直接拷贝即可
else{
o2[key] = item;
}
}
}
copy(obj1, obj2);
console.log(obj1);
console.log(obj2);

// 修改基本类型属性, 不会影响原有对象
obj2.name = "zs";
console.log(obj1.name);
console.log(obj2.name);

// 修改引用类型属性, 会影响原有对象
obj2.dog.name = "xq";
console.log(obj1.dog.name);
console.log(obj2.dog.name);

数组高级API

数组遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let bl = [55,66,77,88,99];

//传统for循环
for (let i=0; i<bl.length; i++){
console.log(bl[i]);
}

//for of循环 - ES6
for(let value of bl){
console.log(value);
}

//利用Array对象的forEach方法来遍历
//forEach方法会自动调用传入的函数;每次调用都会将当前遍历到的xx传递给这个函数
bl.forEach(function(currentValue,currentIndex,currentArray){
console.log(currentValue,currentIndex,currentArray);
console.log(currentValue);//遍历
});
//currentValue元素的取值,currentIndex元素的索引,currentArray元素的数组

数组的查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let bl = [55,66,77,88,99];

//indexOf 从左往右查找,找到返回索引,找不到返回-1
let index = bl.indexOf(3);
//lastIndexOf 从右往左查找,找到返回索引,找不到返回-1
let index = bl.lastIndexOf(3);
//includes 从左往右查找,找到返回true,找不到false
let index = bl.includes(3);


//findIndex 定制版的indexOf,找到返回索引,找不到返回-1
let index = bl.findIndex(function(currentValue,currentIndex,currentArray){
if(currentValue === 66){
return true;
}
})
console.log(index);//2

//find 找到返回找到的元素,找不到返回undefined
bl.find(function(currentValue,currentIndex,currentArray){
if(currentValue === 66){
return true;
}
})
console.log(index);//66

数组的映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let bl = [55,66,77,88,99];

//filter方法:将满足条件的元素添加到一个新的数组中
let newBl = bl.filter(function(currentValue,currentIndex,currentArray){
if(currentValue % 2 ===0){
return true;
}
})
console.log(newBl); //新的数组 [66,88]

//map方法:将满足条件的元素映射到一个新的数组中
let newBl = bl.map(function(currentValue,currentIndex,currentArray){
if(currentValue % 2 ===0){
return true;
}
})
console.log(newBl); //新的数组 [undefined,66,undefined,88,undefined,]

数组的删除

数组的排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
let bl = ['March', 'Jan', 'Feb', 'Dec'];
//【字母升序】
bl.sort();
console.log(bl); // ["Dec", "Feb", "Jan", "March"]

//【字母降序】
//如果 compareFn(a, b) 大于 0,b 会被排列到 a 之前
//如果 compareFn(a, b) 小于 0,那么 a 会被排列到 b 之前
//如果 compareFn(a, b) 等于 0,a 和 b 的相对位置不变
bl.sort(function(a,b){
if(a>b){
reture -1;
}else if(a<b){
return 1;
}else{
return 0;
}
})
console.log(bl); // ['March', 'Jan', 'Feb', 'Dec']


let bl = ['4', '1', '3', '2'];
//【数字升序】
bl.sort();
console.log(bl); //['1', '2', '3', '4']
//【数字降序】
//如果数组中的元素是数值类型,需要升序就返回 a- b ;需要降序就返回 b-a
bl.sort(function(a,b){
return b-a;
})
console.log(bl); //['4', '3', '2', '1']


let bl = [
{name:"zs",age:18},
{name:"ls",age:66},
{name:"ww",age:17},
{name:"mz",age:42},
];
bl.sort(function(o1,o2){
return o1.age - o2.age; //升序
return o2.age - o1.age; //降序
})

字符串常用方法

在js中字符串可以看作一个特殊的数组,所以大部分数组的属性/方法 字符串都可以使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
let bl = "abcad";

//获取字符串长度 .lenth
console.log(bl.length); //5

//获取某个字符索引 charAt
let bl1 = bl.charAt(1);
console.log(bl1); //b

//字符串查找 indexOf lastIndexOf includes
let bl1 = bl.indexOf("a"); //0
let bl2 = bl.lastIndexOf("a"); //3
let bl3 = bl.includes("a"); //true



//字符串切割 split
let bl = "1-3-5";
let arr = bl.split("-");


//ES6 是否以指定字串串开头和结尾 startWith endWith
let bl = "http://www.baidu.com";
let str1 = bl.startWith("http");
let str2 = bl.endtWith(".com");


//ES6 字符串模板 -ES6
let bl = `...`;

//示例1
let bl = "<ul>\n"+
"<li>我是2</li>\n"+
"<li>我是3</li>\n"+
"<li>我是4</li>\n"+
"<li>我是5</li>\n"+
"</ul>";
let bl = ` <ul>
<li>我是2</li>
<li>我是3</li>
<li>我是4</li>
<li>我是5</li>
</ul> `;

//示例2
let name = "zs";
let age = 18;
var str = "我的名字是"+ name+ ",我的年龄是"+ age+ "岁。";
var str = `我的名字是${name},我的年龄是${age}岁。`;

正则表达式

正则表达式就是对字符串操作的一种逻辑公式.JavaScript RegExp 参考手册

1
2
3
4
//通过构造函数创建正则表达式对象
let reg = new RegExp("正则表达式","修饰符i/g/m");
//通过字面量来创建正则表达式对象
let reg = /正则表达式/修饰符igm ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let str = "123abc4562022-12-1abc2022-1-11";

//【是否包含字符abc(查找)】
//匹配规则.test(字符串)
let reg1 = new RegExp("abc"); //区分大小写
let reg2 = new RegExp("abc","i"); //修饰符i 表示不区分大小写
reg1.test(str); //指定匹配的规则
//console.log(reg1.test(str)) 包含就返回true,不包含就返回false


//【是否包含日期(查找)】
//匹配规则.test(字符串)
let reg = RegExp("\\d{4}-\\d{1,2}-\\d{1,2}"); //默认只找到第一个就返回,找所有需要加修饰符g
let dpp = /\d{4}-\d{1,2}-\d{1,2}/g ;
reg.test(str);
// 元字符 \d 表示查找数字,但因为\在字符串中有特殊的含义,所以要多写一个\,表示把后面的\转换为没有特殊含义的\
// 量词 {4}表示匹配4个
// 量词 {1,2}表示至少匹配1个至多匹配2个


//【提取日期】
//字符串.match(匹配规则)
let dpp = /\d{4}-\d{1,2}-\d{1,2}/g ; // 修饰符g表示执行全局匹配,即找到所有
str.match(dpp); //返回的是一个数组 ['2022-12-1', '2022-1-11']


//【替换】
//字符串.replace(匹配规则,要替换的字符串)
let dpp = /\d{4}-\d{1,2}-\d{1,2}/g ;
str.replace(dpp,"www.baidu.com")

常用正则表达式合集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
验证数字:^[0-9]*$
验证n位的数字:^\d{n}$
验证至少n位数字:^\d{n,}$
验证m-n位的数字:^\d{m,n}$
验证零和非零开头的数字:^(0|[1-9][0-9]*)$
验证有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
验证有1-3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
验证非零的正整数:^\+?[1-9][0-9]*$
验证非零的负整数:^\-[1-9][0-9]*$
验证非负整数(正整数 + 0) ^\d+$
验证非正整数(负整数 + 0) ^((-\d+)|(0+))$
验证长度为3的字符:^.{3}$
验证由26个英文字母组成的字符串:^[A-Za-z]+$
验证由26个大写英文字母组成的字符串:^[A-Z]+$
验证由26个小写英文字母组成的字符串:^[a-z]+$
验证由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
验证由数字、26个英文字母或者下划线组成的字符串:^\w+$

验证用户密码:^[a-zA-Z]\w{5,17}$ 正确格式为:以字母开头,长度在6-18之间,只能包含字符、数字和下划线。
验证是否含有 ^%&',;=?$\" 等字符:[^%&',;=?$\x22]+
验证汉字:^[\u4e00-\u9fa5],{0,}$
验证Email地址:^\w+[-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
验证InternetURL:^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$ ;^[a-zA-z]+://(w+(-w+)*)(.(w+(-w+)*))*(?S*)?$
验证电话号码:^(\d3,4|\d{3,4}-)?\d{7,8}$:--正确格式为:XXXX-XXXXXXX,XXXX-XXXXXXXX,XXX-XXXXXXX,XXX-XXXXXXXX,XXXXXXX,XXXXXXXX。
验证身份证号(15位或18位数字):^\d{15}|\d{}18$
验证一年的12个月:^(0?[1-9]|1[0-2])$ 正确格式为:“01”-“09”和“1”“12”
验证一个月的31天:^((0?[1-9])|((1|2)[0-9])|30|31)$ 正确格式为:01、09和1、31。
整数:^-?\d+$
非负浮点数(正浮点数 + 0):^\d+(\.\d+)?$
正浮点数 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
非正浮点数(负浮点数 + 0) ^((-\d+(\.\d+)?)|(0+(\.0+)?))$
负浮点数 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数 ^(-?\d+)(\.\d+)?$

JavaScrtpt三大对象

JavaScript中提供三种自带的对象,分别是“本地对象” / “内置对象” / “宿主对象”

1.本地对象

与宿主无关,无论在浏览器还是服务器中都有的对象,就是ECMAScript标准中定义的类(构造函数)。
在使用过程中需要我们手动new创建,例如:Boolean、Number、String、Array、Function、Object、Date、RegExp等。

2.内置对象

与宿主无关,无论在浏览器还是服务器中都有的对象,ECMAScript为我们创建好的对象。
在使用过程中无需我们手动new创建,例如:Global、Math、JSON。

3.宿主对象

对于嵌入到网页中的JS来说,其宿主对象就是浏览器,所有宿主对象就是浏览器提供的对象。
包含:Window和Document等。
所有的DOM和BOM对象都属于宿主对象。

内置对象Math

Math 是一个内置对象,它拥有一些数学常数属性和数学函数方法。Math 不是一个函数对象。Math MDN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Math.floor()	向下取整
let num = 3.9;
let num1 = Math.floor(num); //3

//Math.ceil() 向上取整
let num = 3.1;
let num1 = Math.ceil(num); //4

//Math.round() 四舍五入
let num= 3.45;
let num1 = Math.round(num); //3

//Math.abs() 绝对值
let num = -3;
let num1 = Math.abs(num); //3

//Math.random() 生成随机数
let num = Math.random(); //0-1之间的随机浮点数0.5236874,不包括1

//示例:生成1-100的随机数
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
}
let num = getRandomIntInclusive(1,100);
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/random

宿主对象 DOM

什么是window

  • window:是一个全局对象,代表浏览器中一个打开的窗口,每个窗口都是一个window对象

什么是document

  • document 是window的一个属性,这个属性是一个对象

  • document:代表当前窗口中的整个网页

  • 通过document对象我们就可以操作整个网页上的所有内容

什么是DOM?

  • DOM定义了访问和操作 HTML文档的标准方法
  • DOM全称:Document Object Model,即文档模型对象
  • 所以学习DOM就是学习如何通过Document对象操作网页上的内容
1
2
3
console.log(window.document);
console.log(typeof window.document); //object
console.log(window.document.title); //输出 <head>中<title>标签的内容

获取DOM

在JS中HTML标签也称之为DOM元素;使用document的时候前面不用加window

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<div class="father">
<form>
<input type="text" name="test">
<input type="password" name="test">
</form>
</div>
<div class="father" id="box">我是div</div>

<script>
// !!通过选择器获取 querySelector 只返回第一个
document.querySelector(".father");

// !!通过选择器获取 querySelectorAll 返回所有找到的
document.querySelector(".father");


// 通过ID获取指定元素
document.getElementById("box");

// 通过class名称获取
document.getElementsByClassName("father");

// 通过name名称获取
document.getElementsByName("test");

// 通过标签名称获取
document.getElementsByTagName("div");

</script>

获取指定元素的子元素

  • children属性获取到的是指定元素中所有的子元素

  • childrenNotes属性获取到的是指定元素中所有的节点

image-20221110145838657

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div>
<h1>1</h1>
<h2>2</h2>
<p>3</p>
<p>4</p>
<span>5</span>
<div><div>6</div></div>
</div>


<script>
let oDiv = document.querySelector("div");
//获取指定节点中的第一个
console.log(oDiv.firstChild); //节点 text
console.log(oDiv.firstElementChild);//元素 h1
//获取指定节点中的最后一个
console.log(oDiv.lastChild);
console.log(oDiv.lastElementChild);


let item = document.querySelector("h2")
//通过子元素获取父元素/父节点
console.log(item.parentElement); //元素 div
console.log(item.parentNode); //节点 div

//获取相邻的上一个
console.log(item.previousSibling); //节点 text
console.log(item.previousElementSibling); //元素 h1
//获取相邻的下一个
console.log(item.nextSibling); //节点 text
console.log(item.nextElementSibling);//元素 p
</script>

节点增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div>
<h1>标题</h1>
<p>我是段落</p>
</div>

<script>

let oDiv = document.querySelector("div");//选中父节点
let oH1 = document.querySelector("h1");//选中子节点h1
let oP = document.querySelector("p");//选中子节点p

//创建节点
let oSpan = document.createElement("span");

//添加节点
//appendChild 方法会将指定的元素添加到最后
oDiv.appendChild(oSpan);

//插入节点
oDiv.insertBefore(oSpan,oP);

//删除节点
//在js中想要删除某一个元素,只能通过对应的父元素来删除,元素不能自杀
oSpan.parentNode.removeChild(oSpan);
oDiv.parentNode.removeChild(oDiv);

//克隆节点
oDiv.cloneNode(); //只克隆该节点
oDiv.cloneNode(true);//克隆节点及其子节点
</script>

属性增删改查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<img src="/xx.img" alt="我是alt" title="我是title" zdy="by66">

<script>
let oImg = document.querySelector("img");//选中img节点

//获取元素属性
oImg.src; //获取不到自定义的属性值
oImg.getAttribute("zdy"); //都能获取到

//修改元素属性
oImg.title = "新的Title"
oImg.setAttribute("zdy","by999");

//新增元素属性
oImg.setAttribute("xinzeng","9898");

//给class追加值
oImg.className += " zhuijia";

//删除元素属性
oImg.alt = "";
oImg.removeAttribute("zdy");
</script>

操作元素内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div>
我是div
<h1>我是标题h1</h1>
<p>我是段落p</p>
</div>

<script>

//获取元素内容
let oDiv = document.querySelector("div");

oDiv.innerHTML;//包含标签,不去除内容两端的空格
oDiv.innerText;//不包含标签,去除内容两端的空格
oDiv.textContent;//不包含标签,不去除内容两端的空格


//设置元素内容
oDiv.innerHTML = "<span>123</span>";//包含标签,会转换成标签之后再添加
oDiv.innerText = "<span>456</span>";//不转换
oDiv.textContent = "<span>789</span>";//不转换
//整个div都被替换,包括子元素,即div内只有设置的内容了

//由于兼容性问题,需要结合使用innerText和textContent
function setText(obj,text){
if("textContent" in obj){
obj.textContent = text;
}else{
obj.innerText = text;
}
}
setText(oDiv,"www.baidu.com");
</script>

操作元素样式

1
2
3
4
5
6
7
8
9
10
11
12
13
<div></div>

<script>
let oDiv = document.querySelector("div");

//【设置元素属性】
oDiv.className = "box"; //新增 class="box"

//【设置元素样式】js添加的表现为 行内式
oDiv.style.width = "50px";
oDiv.style.height = "50px";
oDiv.style.backgroundColor = "#555";
</script>

【获取元素样式】

style属性 getComputedStyle() 方法 currentStyle属性 offsetWidth / offsetHeight属性 clientWidth / clientHeight属性
获取行内样式 全部 全部 全部 全部
纯宽高 纯宽高 纯宽高 宽/高+内边距+边框 宽/高+内边距
能获取,能设置 只能获取 只能获取 只能获取
都支持 IE9以下不支持 IE都支持(谷歌火狐不支持) 都支持

scrollWidth / scrollHeight

  • 当内容没有超出元素范围时,宽/高+内边距 ,即和clientWidth / clientHeight 一样
  • 当内容超出元素范围时,宽/高+内边距+超出的宽/高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div style = "width: 2px;"></div>

<style>
div{
height: 100px;
width: 100px;
padding: 50px;
border: 50px solid #000;
background-color: red;
background-clip: content-box;
}
</style>

<script>
let oDiv = document.querySelector("div");

//【style】
oDiv.style.height = "4px";
console.log(oDiv.style.width); //2px 行内样式的

//【getComputedStyle】
let gestyle = window.getComputedStyle(oDiv);
console.log(gestyle);
console.log(gestyle.width); //2px 行内样式的
console.log(gestyle.padding); //50px css中的

//【currentStyle】
console.log(oDiv.currentStyle);

//【offsetWidth/offsetHeight】
console.log(oDiv.offsetWidth); //202(不带单位)
console.log(oDiv.offsetHeight); //204(不带单位)

</script>

获取网页可视宽高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//兼容写法

//【方法1】
var browserWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
var browserHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;


//【方法2】
function getScreen(){
let width,height ;
if(window.innerWidth){
width = window.innerWidth;
height = window.innerHeight;
}else if(document.compatMode === "BackConpat"){
width = document.body.clientWidth;
height = document.body.clientHeight;
}else{
width = document.documentElement.clientWidth;
height = document.documentElement.clientHeight;
}
return {
width:width,
height:height
}
}
//使用
let {width,height} = getScreen();
console.log(width);
console.log(height);



//可以在Internet Explorer9、Chrome、Firefox、Opera 以及 Safari
window.innerHeight
window.innerWidth

//可以用于IE9以下的浏览器的标准模式中获取
document.documentElement.clientWidth //documentElement --> HTML --> 整个网页
document.documentElement.clientHeight
//可以用于IE9以下的浏览器的混杂模式中获取
document.body.clientWidth
document.body.clientHeight
//可以用于获取当前浏览器的渲染模式
document.compatMode //CSS1Compat表示标准模式;BackConpat 表示混杂模式

获取网页滚动距离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//兼容写法

//【方法1】
var scrollX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
var scrollY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;

//【方法2】
function getScroll() {
return {
x: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,
y: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
};
}

//【方法3】
let {scrollX,scrollY} = getScroll();
function getScroll(){
let scrollX,scrollY ;
if(window.pageXOffset){
scrollX = window.pageXOffset;
scrollY = window.pageYOffset;
}else if(document.compatMode === "BackConpat"){
scrollX = document.documentElement.scrollLeft;
scrollY = document.documentElement.scrollTop;
}else{
scrollX = document.body.scrollLeft;
scrollY = document.body.scrollTop;
}
return {
scrollX:scrollX,
scrollY:scrollY
}
}


//IE9及以上的浏览器
window.pageXOffset
window.pageYOffset
//标准模式下的浏览器
document.documentElement.scrollLeft
document.documentElement.scrollTop
//标准、混杂(怪异)模式下的浏览器
document.body.scrollLeft
document.body.scrollTop

回到顶部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//回到顶部
function getScroll() {
return {
x: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,
y: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
};
}

window.onscroll = function () {
console.log(getScroll().y);
if (getScroll().y > 10) {
xxx.onclick = function () {
window.scrollTo(0, 0);
}
}
}

JS三大家族

offset client scroll 三大属性并称为js三大家族

offsetLeft / offsetTop

  • 获取元素 到第一个定位祖先元素之间的偏移位;如果没有祖先元素是定位的,那么就是获取到body的偏移位

offsetParent

  • 获取元素的第一个定位的祖先元素,如果没有祖先元素是定位的,那么获取到的就是body
  • 即爷爷定位了,获取的是爷爷;爸爸也定位了,获取到的就是爸爸

clientLeft / clientTop

  • 获取该元素的左边框/顶部边框 大小

scrollLeft / scrollTop

  • 内容发生滚动,滚动的距离(距离顶部)就是scrollLeft / scrollTop

添加事件的三种方式

用户和浏览器之间的行为我们就称之为事件,比如 点击/移入/移出

完整事件见W3school

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<button>我是按钮</button>
<a href="http://baidu.com">链接</a>

<script>

let oBtn = document.querySelector("button");
oBtn.onclick = function() {
alert("我被点击了");
}

//如果给元素加了和系统同名的事件,我们添加的事件不会覆盖系统添加的事件
let oA = document.querySelector("a");
oA.onclick = function() {
alert("我也被点击了");
//以下代码含义:用我们添加的事件覆盖掉系统同名的事件
return false;
}

</script>

添加事件的三种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<button id="btn">我是按钮</button>

<script>

let myBtn = document.querySelector("#btn");

//第一种方式 onxxx
//由于是给属性赋值,所以后赋值的会覆盖前一个赋值
myBtn.onclick = function(){
alert("onxxx属性");
}

//第二种 addEventListener() 方法
//同一个事件不会覆盖,而是依次执行
//IE9以下不支持
myBtn.addEventListener("click",function(){
alert("至少传递两个参数")
alert("第一个参数是绑定的事件的名称");
alert("第二个参数是触发之后的回调函数");
})

//第三种 attachEvent() 方法
//同一个事件不会覆盖,而是依次执行
//只支持IE9以下的浏览器
myBtn.attachEvent("onclick",function(){
alert("和addEventListener差不多,只是兼容性的区别")
})

//封装 addEventListener和attachEvent
function addEvent(ele,name,fn){
if(ele.attachEvent){
ele.attachEvent("on"+name,fn)
}else{
ele.addEventListener(name,fn);
}
}

//使用
addEvent(myBtn,"click",function(){
alert("这里是使用封装的方法")
});

</script>

事件对象

事件对象就是一个系统自动创建的对象;当注册的事件被触发的时候,系统会自动创建事件对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<button id="btn">我是按钮</button>

<script>

let myBtn = document.querySelector("#btn");


myBtn.onclick = function(){
alert("事件触发");
}
//事件触发,执行回调函数的时候,会传递一个参数 event, 传递的就是事件对象
myBtn.onclick = function(event){
event = event || window.event; //window.event 兼容低级浏览器
alert("事件触发");
console.log(event); //....事件对象里面有很多属性
console.log(typeof event) //object
}

</script>

事件对象常用属性

1
2
3
4
//事件位置相关属性
event.offsetX / event.offsetY :事件触发相对于当前元素自身的位置
event.clientX / event.clientY :事件触发相对于浏览器可视区域的位置
event.pageX / event.pageX :事件触发相对于整个网页的位置

事件执行的三个阶段

三个阶段只有两个会被同时执行,要么捕获和当前,要么当前和冒泡。(历史遗留问题,W3C为了兼容)

  • 捕获阶段(从外向内的传递事件)
  • 当前目标阶段(执行回调函数)
  • 冒泡的阶段(从内向外的传递事件)

Snipaste_2022-11-14_13-58-53

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<div id="father">
<div id="son"></div>
</div>

<script>

let fatherDiv = document.querySelector("#father");
let sonDiv = document.querySelector("#son");

//传递true这个参数,点击sonDiv, 捕获+当前阶段
//第一行输出father(先执行),第二行输出son
fatherDiv.addEventListener("click",function(){
console.log("father");
}, true);
sonDiv.addEventListener("click",function(){
console.log("son");
}, true);

//传递false这个参数,点击sonDiv,当前阶段+冒泡
//第一行输出son(先执行),第二行输出father
fatherDiv.addEventListener("click",function(){
console.log("father");
}, false);
sonDiv.addEventListener("click",function(){
console.log("son");
}, false);

//阻止事件冒泡 event.stopPropagation();
//event事件对象是执行回调函数的时候系统默认传递的参数
sonDiv.addEventListener("click",function(){
event.stopPropagation();
console.log("son");
}, false);


//注意点:
//addEventListener 不添加true/false,默认是冒泡,即为false
//onXxxx的属性,不接受任何参数,所以默认是冒泡
//attachEvent方法,只能接受两个参数,所以默认就是冒泡

</script>

移入移出事件区别

onmouseover 移入到子元素,父元素的移入事件也会被触发

onmouseenter 移入到子元素,父元素的移入事件不会被触发

onmouseout 移出到子元素,父元素的移出事件也会被触发

onmouseleave 移出到子元素,父元素的移出事件不会被触发

定时器

1
2
3
4
5
6
7
8
9
10
//【重复执行的定时器】
//setInterval方法属于window,使用的时候可以省略
window.setInterval(function(){
console.log("每隔1000毫秒执行这段函数");
}, 1000);

//【只执行一次的定时器】
window.setTimeout(function(){
console.log("2000毫秒之后执行这段函数一次");
}, 2000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<button id="start">开始</button>
<button id="close">结束</button>

<script>

let startBtn = document.querySelector("#start");
startBtn.onclick = function(){
id = setInterval(function(){ //创建定时器的时候会返回给我们一个唯一的标识
console.log("随便写点");
}, 1000);
}

let closeBtn = document.querySelector("#close");
closeBtn.onclick = function(){
clearInterval(id); //把id传给它,他就知道要关闭哪一个了
}
</script>

函数防抖

函数防抖是优化高频率执行js代码的一种手段,可以让被调用的函数在一次连续的高频操作过程中只被调用一次

  • 作用:减少代码执行次数,提升网页性能
  • 应用场景 oninput onmousemove onscroll onresize 等事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<input type="text">

<script>

let myInput = document.querySelector("input");

//输入多少个文字就执行多少次代码,有性能问题
myInput.oninput = function(){
console.log("发送网络请求代码");
}

//函数防抖:连续操作只执行一次,解决了性能问题
let timeId = null ;
myInput.oninput = function(){
timeId && clearTimeout(timeId);
timeId = setTimeout(function(){
console.log("发送网络请求代码");
},1000);

}

</script>

函数防抖封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//封装
function debounce(fn,delay){
return function(){
let self = this ;
let args = arguments;
let timeId = null ;
timeId = setTimeout(function(){
//fn(); 无法准确拿到this和事件对象
fn.apply(self,args);
}, delay || 1000);
}
}

//使用
function test(){ //写好要执行的业务代码
console.log("发送网络请求代码");
console.log(this);
}
myInput.oninput = debounce(test,3000);//传入实参:业务代码和延时

函数节流

函数节流也是优化 高频率执行js代码的一种手段;可以减少高频调用函数的执行次数;作用和场景等同于函数防抖

  • 函数节流是减少连续的高频操作函数执行次数(例如调用10次,可能只执行3-4次)
  • 函数防抖是让连续的高频操作时函数只执行一次(例如连续调用10次,但只执行1次)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<script>

//初始化 元素的宽高为可视区域的一半
let myDiv = document.querySelector("div");
function resetSize(){
let {width,height} = getScreen(); //使用自己封装的函数获取可视区域宽高
myDiv.style.width = width / 2 + "px";
myDiv.style.height = height / 2 +"px";
}
resetSize();

//监听可视区域尺寸变化;

//问题:调用次数太多了
window.onresize = function(){
resetSize();
console.log("尺寸变化了A");
}

//函数防抖 问题:连续操作只执行一次,无法实时预览元素宽高变化,得停止操作才能看到结果
var timerId = null ;
window.onresize = function(){
timerId && clearTimeout(timerId);
timerId = setTimeout(function(){
resetSize();
console.log("尺寸变化了B");
}, 500)

}

//函数节流
var timerId = null ;
let flag = true;//1
window.onresize = function(){
if(!flag){//2
return;
}
flag = false;//3
timerId && clearTimeout(timerId);
timerId = setTimeout(function(){
flag = true;//4
resetSize();
console.log("尺寸变化了C");
}, 500)
}

</script>

函数节流封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//封装
function throttle(fn,delay){
let timerId = null;
let flag = true;//1
return function(){
if(!flag) {return}//2
flag = false;//3
let self = this ;
let args = arguments;
timerId = setTimeout(function(){
flag = true;//4
fn.apply(self,args);
}, delay || 1000);
}
}

//使用1
window.onresize = throttle(keshiquyu,500);
function keshiquyu(){
resetSize();
console.log("尺寸变化了D");
}

//使用2
window.onresize = throttle(function(){
resetSize();
console.log("尺寸变化了D");
},500)

宿主对象 BOM

什么是BOM?

  • BOM就是一套操作浏览器的API(接口/方法/属性)
  • DOM就是一套操作HTML标签的API(接口/方法/属性)

BOM中常见的对象

  • window:代表整个浏览器窗口;window是BOM中的一个对象,并且是一个顶级的对象(全局)
  • Navigator:代表浏览器的信息,通过Navigator我们就能判断用户当前是什么浏览器
  • Location:代表浏览器地址栏的信息,通过Location我们就能设置或者获取当前地址信息
  • History:代表浏览器的历史信息,通过History来实现刷新、上一步、下一步
    • 注意点:出于安全考虑,我们并不能拿到用户所有的历史记录,只能拿到当前历史记录
  • Screen:代表用户的屏幕信息

代表浏览器的信息,通过Navigator我们就能判断用户当前是什么浏览器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log(typeof window.navigator); //object

window.navigator
//里面有一个非常重要的属性userAgent,通过这个属性的取值我们可以判断用户当前的浏览器
//userAgent
:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"


//
let agent = window.navigator.userAgent;
if(/chrome/i.test(agent)){
alert("当前是chrome浏览器");
}else if(/firefox/i.test(agent)){
alert("当前是火狐浏览器");
}else if(/msie/i.test(agent)){
alert("当前是低级IE浏览器");
}else if("ActiveXObject" in window){
alert("当前是高级IE浏览器");
}

Location

代表浏览器地址栏的信息,通过Location我们就能设置或者获取当前地址信息

1
2
3
4
5
6
7
8
9
10
11
//当前地址栏的链接
window.location.href

//设置当前地址栏的链接
window.location.href = "www.baidu.com"

//重新加载页面
window.location.reload();

//强制刷新加载页面
window.location.reload(true);

History

代表浏览器的历史信息,通过History来实现刷新、上一步、下一步(浏览器地址栏左侧的 左右按钮即使前进 后退)

1
2
3
4
5
6
7
8
9
10
//前进
window.history.forward();
window.history.go(3);//前进3个页面

//后退
window.history.back();
window.history.go(-2);//后退1个页面

//刷新
window.history.go(0);//0代表刷新

自己封装工具类

就是把兼容写法封装到工具类当中,方便以后直接调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
(function (){
//获取网页可视宽高 -为了兼容
function getScreen(){
let width,height ;
if(window.innerWidth){
width = window.innerWidth;
height = window.innerHeight;
}else if(document.compatMode === "BackConpat"){
width = document.body.clientWidth;
height = document.body.clientHeight;
}else{
width = document.documentElement.clientWidth;
height = document.documentElement.clientHeight;
}
return {
width:width,
height:height
}
}

//获取网页滚动距离 -为了兼容
function getScroll(){
let x, y ;
if(window.pageXOffset){
x = window.pageXOffset;
y = window.pageYOffset;
}else if(document.compatMode === "BackConpat"){
x = document.documentElement.scrollLeft;
y = document.documentElement.scrollTop;
}else{
x = document.body.scrollLeft;
y = document.body.scrollTop;
}
return {
x:x,
y:y
}
}

//添加事件 -为了兼容
function addEvent(ele,name,fn){
if(ele.attachEvent){
ele.attachEvent("on"+name,fn)
}else{
ele.addEventListener(name,fn);
}
}

//获取元素样式 -为了兼容
function getStyleAttr(obj,name){
if(obj.currentStyle){
return obj.cerrentStyle[name];
}else {
return getComputedStyle(obj)[name];
}
}

//函数防抖
function debounce(fn,delay){
let timerId = null;
return function(){
let self = this ;
let args = arguments;
timerId = setTimeout(function(){
//fn(); 无法准确拿到this和事件对象
fn.apply(self,args);
}, delay || 1000);
}
}

//函数节流
function throttle(fn,delay){
let timerId = null;
let flag = true;//1
return function(){
if(!flag) {return}//2
flag = false;//3
let self = this ;
let args = arguments;
timerId = setTimeout(function(){
flag = true;//4
fn.apply(self,args);
}, delay || 1000);
}
}

window.getScreen = getScreen;
window.getScroll = getScroll;
window.getScreen = getScreen;
window.getStyleAttr = getStyleAttr;
window.debounce = debounce;
window.throttle = throttle;

})();

封装完成如何使用?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<input type="text">

<script>
let myInput = document.querySelector("input");

//网页可视宽高
let screenHeight = getScreen().height;
console.log(screenHeight);

//添加事件
addEvent(myBtn,"click",function(){
alert("这里写触发事件的代码");
});

//滚动距离
let scrollY = getScroll().y;
console.log(scrollY);

// 函数防抖
myInput.oninput = debounce(function(){
//这里随便写代码,你想干什么就干什么
console.log("发送网络请求代码");
}, 5000)

</script>