JavaScript学习笔记

203 阅读56分钟

JavaScript

计算机基础

1 编程语言

编程:就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码(编写程序的过程),并最终得到结果的过程叫编程

计算机程序:就是让计算机执行一系列的指令集合(命令) ,而程序全部都是用我们所掌握的语言来编写的,所以人们要控制计算机一定要通过计算机语言向计算机发出命令

上面所定义的计算机指:任何能够执行代码的设备,例如:智能手机、ATM机、服务器等

2 计算机语言

计算机语言指用于人与计算机之间通讯的语言,它是人于计算机传递信息的媒介

计算机语言的种类(三大类):

  • 机器语言
  • 汇编语言
  • 高级语言

实际上计算机最终所执行的都是机器语言,它是由”0“ 和 ”1“ 组成的二进制数,二进制是计算机语言的基础

如今通用的编程语言有两种形式:汇编语言高级语言

  • 汇编语言:实质和机器语言相同,都是直接对硬件操作,其指令采用英文缩写的标识符,容易识别记忆
  • 高级语言:相对低级语言来说,它不是特指某一种语言,其包含了很编程语言,比如:java、javascript、C++、C#等

JavaScript基础

1 初始JavaScript

1.1 介绍

  • JavaScript是世界上最流行的语言之一,是一种运行在客户端的脚本语言
  • 脚本语言:不需要编译,运行过程中由js解释器(js引擎)逐行进行解释并执行
  • 现在也可以基于Node.js技术进行服务器端编程

1.2 浏览器执行JS过程

浏览器分成两部分:渲染引擎、JS引擎

  • 渲染引擎:用来解析HTML和CSS,俗称内核。比如,chrome浏览器的blink,老版本的webkit
  • JS引擎:也称为JS解析器。用来读取网页中的JavaScript代码,对其处理后运行。比如,chrome的V8

浏览器本身并不会执行JS代码,而是通过内置JavaScript引擎(解释器)来执行JS代码。JS引擎执行代码时,逐行解释每一句源码(转换为机器语言),然后由计算机去执行。所以,JavaScript语言归为脚本语言,会逐行进行解释执行。

1.3 JS的组成

  • ECMAScript :JavaScript语法
  • DOM:页面文档对象模型
  • BOM:浏览器对象模型

2 变量

2.1 变量的概述

本质:变量是程序在内存中申请的一块用来存放数据的空间

2.2 变量的使用

变量在使用时分两步:1、声明变量 ,2、赋值

// 声明变量
// 声明一个age的变量
var age;
// 赋值
age = 14;
// 同时声明多个变量,并赋值
var age = 18, gz = 2000
   

2.3 声明变量的特殊情况

情况说明结果
var age; console.log(age);只声明,不赋值undefined
console.log(age)不声明,不赋值,直接使用报错
age = 10; console.log(age)不声明,只赋值10

2.4 变量命名的规范

  • 由字母(A-Z,a-z)、数字(0-9)、下划线(_)、美元符号($)组成。比如:user、num_01等
  • 严格区分大小写。比如:var app 和var APP是两个变量
  • 不能以数字开头
  • 不能是关键字、保留字。比如:var 、for 、while
  • 变量名应该有意义
  • 遵守驼峰命名法,首字母小写,后面单词的首字母大写,比如:myName

2.5 交换两个变量的值

var temp; // 声明一个临时变量为空
var age = 18;
var text = 20;
temp = age;
age = text
text = temp

3 数据类型

在计算机中,不同的数据所占用的存储空间不同,于是定义了不同数据类型

JS把数据类型分为两类:

  • 简单数据类型(Number、String、Boolean、Undefined、Null)
  • 复杂数据类型(Object)

3.1 简单数据类型

简单数据类型说明默认值
Number数字型(数值型),包含整型值和浮点值,比如:21、0.210
Boolean布尔值类型,比如:true、false,等价于1 和 0false
String字符串类型,比如:“张三”(在js中字符串都带双引号)“”(空)
Undefinedvar a; 声明了变量a,但是没有赋值,则a = undefinedundefined
Nullvar a = null;声明了变量a为null (空值)null

3.2 Number 数字型

JavaScript数字型既可以用来保存整数值,也可用来保存小数(浮点数)

// 示例
var num = 10; // num 数字型
var PI = 3.14; // PI 小数(浮点数)

javascript中数值的最大和最小值(了解 ):

console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);

数字型三个特殊值(了解):

alert(Infinity); // 无穷大,大于任何数值
alert(-Infinity); // 无穷小,小于任何数值
alert(NaN); // Not a number 代表一个非数值

isNaN(): 用来判断非数字,并且返回一个值。如果是数字,返回false;如果不是数字,返回true

console.log(isNaN(12)); // false
console.log(isNaN("王权")); // true

3.3 String 字符串型

字符串型可以是引号中的任意文本,其语法为''单引号""双引号

var str = '王权'; // 单引号 字符串 
var str = "王权"; // 双引号 字符串 

因为HTML标签里面的属性使用的是双引号,JS这里更推荐使用单引号

3.3.1 字符串转义字符

类似HTML里面的特殊字符,字符串中也有特殊字符,称为转义字符

转义符都是\开头的,常用转义如下:

转义符说明
\n换行符。n是newline的意思
\斜杠\
''' 单引号
*"" 双引号
\ttab缩进
\b空格,b是blank的意思
console.log("我是\王权"); // 我是\王权
console.log("我是问*王权"); // 我是“”王权
console.log("我是问\b王权"); // 我是 王权
3.3.2 字符串的长度

字符串是由若干字符组成的,这些字符的数量就是字符串的长度,通过字符串的length属性,可以获取整个字符串的长度。

var str1 = 'my name is andy';
var str2 = '我是王权!'
console.log(str1.length) // 15  空格也算字符
console.log(str2.length) // 5  符号也算字符
3.3.3 字符串的拼接

多个字符串之间可以使用 + (加号) 进行拼接,其拼接方式为:字符串 + 任何类型 = 拼接之后的字符串

拼接前会把字符串相加的任何类型转换成字符串,再拼接成一个新的字符串

console.log('沙漠' + '骆驼'); // 沙漠骆驼
cconsole.log('王权' + 18); // 王权18
cconsole.log('王权' + true); // 王权true
cconsole.log('12' + 12); // '1212'

var age = 18;
cconsole.log('王权' + age + '岁'); // 王权18岁 

3.4 Boolean 布尔型

布尔类型由两个值:true(真、对)、false(假、错)

布尔型和数字相加的时候,true的值为1,false的值为0

var flag = true // flag 布尔型
var flag2 = false //  flag2 布尔型

console.log(flag + 1); // 2  true参与加法运算当1来看
console.log(flag1 + 1); // 1 false参与加法运算当0来看

3.5 Undefined 未定义数据类型

如果一个变量声明未赋值,则就是undefined(未定义数据类型)

未定义数据类型 + 字符串 = 字符串

undefined + 数字 = NaN

var str;
console.log(str) // undefined 声明未赋值
var variable = undefined;
console.log(variable + '王权'); // undefined王权 未定义数据类型 + 字符串 = 字符串
console.log(variable + 1); // NaN undefined + 数字 = NaN

3.6 Null 空值

null 空值,声明了也赋值了,只是赋的值是null

null + 数字 = 该数字

var space = null;
console.log(space + 1); // 1

3.7 typeof 检测数据类型

typeof可用来检测变量的数据类型

var num = 10;
console.log(typeof num); // unmber
var str = "王权";
console.log(typeof str); // string
var vari = undefined;
console.log(typeof undefined); // undefined

var timer = null;
console.log(typeof timer); // object

3.8 数据类型的转换

把一种数据类型的变量转换成另外一种数据类型,数据类型的转换

通常会实现3种方式转换:

  • 转换为字符串类型
  • 转换为数字型
  • 转换为布尔型
3.8.1 转换成字符串类型
方式说明示例
toString()转换成字符串var num=1; alert(num.toString());
String()转换成字符串(强制转换)var num=1; alert(String(num));
加号拼接字符串任意类型和字符串拼接结果都是字符串var num=1; alert(num + '字符串')

注意:

  • toString() 和String()使用的方式不一样
  • 三种转换方式,第三种更常用,这种方式也成为 ”隐式转换“
3.8.2 转换成数字型(重要)
方式说明示例
parseInt(string)函数将string类型转换成整型数值型parseInt('28')
parseFloat(string)函数将string类型转换成浮点数数值型parseFloat('28.12')
Number()强制转换函数将string类型转换成数值型Number('12')
js隐式转换 (- * /)利用算术运算隐式转换为数值型'12' - 0

注意:

  • 隐式转换:在进行算术运算的时候,JS自动转换了数据类型,称为隐式转换
3.8.3 转换成布尔型
方式说明示例
Boolean()函数其他类型转换成布尔值Boolean('true')

注意:

  • 代表否定的值会被转换成false,比如:''、0、NaN、null、undefined
  • 其余值都会转换为true

4 标识符、关键字、保留字

标识符:指开发人员为变量、属性、函数、参数取的名字,标识符不能是关键字和保留字

关键字:指JS本身已经使用了的字,不能再用它们充当变量名、方法名

保留字:实际上是预留的”关键字“,意思是虽然现在还不是关键字,但未来可能会成为关键字

5 运算符

运算符也被称为操作符,是用于实现赋值、比较、执行运算等功能的符号

JavaScript中常用的运算符:

  • 算术运算符
  • 递增和递减运算符
  • 比较运算符
  • 逻辑运算符
  • 赋值运算符

5.1 算术运算符

概念:算术运算符使用的符号,用于执行两个变量或数值的算术运算

运算符说明示例
+1+1 = 2
-1-1 = 0
*1*1 = 1
/1/1 = 1
%取余数(取模)返回除法的余数 9%2=1

如何判断一个数能够被整除?

它的余数是0,则这个数能被整除,这是取余运算符的主要用途。 比如:4 % 2 = 0

5.2 递增递减运算符

如果需要反复给数字变量添加或减去1,则可使用递增(++)和递减(--)运算符

JavaScript中递增(++)和递减(--)可放在变量的前面或后面,放在变量前面称为前置递增(递增)运算符,放在变量后面称为后置递增(递减)运算符

注意:递增和递减运算符必须和变量配合使用

++num,前置递增,自加1,类似 num = num +1(先自增,后运算)

num++,后置递增,自加1 ,类似num = num +1(先运算,再自增)

// 后置自增,先表达式返回原值,后面变量再自加1
var age = 10;
console.log(age++ + 10); // 20 
console.log(age); // 10

var c = 10;
c++; // c++ 11   c = 11
var d = c++ + 2; // c++ 11 c=12
console.log(d); // 13

var e = 10;
var f = e++ + ++e; // 1.e++ = 10 e = 11 2.e = 12 ++e = 12
console.log(f); // 22

5.3 比较运算符

比较运算符(关系运算符)是两个数据进行比较时所使用的运算符,会返回一个布尔值(true/false)作为比较的结果

运算符名称说明示例结果
<小于哈1 < 2true
大于号1 > 2false
>=大于等于号2 >= 2true
<=小于等于号2 <= 2true
==等号(会转型)37 = 37true
!=不等号37 != 37false
===(全等)、!==(不全等于)全等 (要求值和数据类型都一样)

注意:

  • 等于符号 == :默认转换数据类型,会把字符串型的数据转换成数字型
  • 一个等号=:赋值,把右边赋值给左边
  • 两个等号==:判断,判断两边的值是否相等(此时由隐式转换,类型不同会转换成相同类型进行比较)
  • 三个等号===:全等,判断两边的值和数据类型是否完全相等

5.4 逻辑运算符

逻辑运算符是用来进行布尔值运算的运算符,其返回值也是布尔值,开发中常用来进行条件判断

逻辑运算符说明示例
&&逻辑与,简称与 andtrue && false
逻辑或,简称或 ortruefalse
!逻辑非,简称非 not!true

注意:

  • 逻辑与:两边都为true,结果才是true,只要有一边为false,结果就为fasle
  • 逻辑或:两边都为false,结果才是fasle,只要有一边是true,结果就是true
  • 逻辑非:取反,用来去一个布尔值相反的值

5.5 赋值运算符

用来把数据赋值给变量的运算符

赋值运算符说明示例
=直接赋值var a=4 ;
+=、-=加、减一个数后再赋值var age = 10; age+=5; //15
*=、/=、%=乘、除、取模后再赋值var age = 2; age*=5; //10

5.6 运算符的优先级(重要)

优先级运算符顺序
1小括号()
2一元运算符++、--、!
3算术运算符先* 、/、 %, 后+、 -
4关系运算符>、>=、<、<=
5相等运算符== 、!=、===、!==
6逻辑运算符先&&,后
7赋值运算符=
8逗号运算符

注意:

  • 一元运算符号里面的逻辑非优先级很高
  • 逻辑与比逻辑或优先级高

6 流程控制

在程序执行的过程中,各条代码的执行顺序对程序的结果是有直接影响的,很多时候需要通过控制代码的执行顺序来实现各种功能

简述:流程控制就是来控制代码按照什么结构顺序来执行的

流程控制主要有三种结构:

  • 顺序结构:程序中最简单、最基本的流程控制,程序会按照代码的先后顺序,依次执行
  • 分支结构
  • 循环结构

6.1 分支流程控制

由上到下执行代码的过程中,根据不同的条件,执行不同的路径代码,从而得到不同的结果

js语言提供了两种分支结构语句:

  • if 语句
  • switch 语句
6.1.1 if 语句
// 为真则执行
if(条件表达式){
    // 执行语句
}

if(条件表达式){
    // 为真执行此处代码
} else{
    // 反之,为假,执行此处代码
}

三元运算符:

语法:条件表达式 ? 表达式1 : 表达式2

如果条件表达式为真,则返回表达式1的值,反之,返回表达式2的值

6.1.2 switch 语句

switch 语句也是多分支语句,实现多选一,根据不同条件执行不同的代码

switch(表达式){
    case value1:
        执行语句;
        break; // 如果条件满足,执行完之后退出switch
    case value2:
        执行语句;
        break;
     ……   
     default:
        执行默认的语句;// 前面都不执行时执行
}

执行思路:用表达式的值和casr后面的选项值做匹配,如果匹配上,就执行case里面的语句,如果都没匹配上,则执行default默认的语句

注意:

  • 在开发中,表达式通常写成变量,且变量的值case里面的值必须全等于(值和类型一致)才能匹配上
var num = 3;
switch(num){
    case 1:
        console.log(1);
        break;
}
  • break:如果当前的case里面没有break,则不会退出switch,会继续执行下面的代码,直到有break为止
var num = 1;
switch(num){
    case 1:
        console.log(1);
    case 2:
        console.log(2);
    case 3:
        console.log(3);
        break;
}

// 输出结果:
1  2  3
6.1.3 switch 和 if else 的区别
  • 一般情况下,两个语句可相互替换
  • switch...case语句通常处理case是比较确定的值的情况;if...else更加灵活,常用于判断范围(大于、小于或者等于某个范围)
  • 当分支比较少,if...else语句执行效率比switch语句高
  • 当分支比较多时,switch语句执行效率高,且结构清晰

6.2 循环

在js中,主要有三种类型的循环语句:

  • for 循环
  • while 循环
  • do...while 循环
6.2.1 for 循环(重要)

语法结构:

for(初始化变量; 条件表达式; 操作表达式){
    // 循环体
}
for(var i = 1; i <= 100; i++){
    // 循环体
}
6.2.2 for循环打印n行n列的星星
var rows = prompt('请输入行数:'); // 用户输入行数
var cols = prompt('请输入列数:'); // 用户输入列数
var str = '';
// 里层for循环控制一行打印多少个星星,打印完毕换行,外层for循环控制打印多少行的星星
for(var i = 1; i <= rows ; i++){
    for(var j = 1; j <= cols; j++){
        str = str + '*'
    }
    str += '\n'; // 换行
}
console.log(str)
6.2.3 for循环打印倒三角
// 打印倒三角
var str = '';
for(var i = 1; i <= 10; i++){ // 外层控制行数
    for(var j = i; j <= 10; j++){ // 里层控制打印的个数,如何实现个数逐渐减少  j = i
        str = str + '*';
    }
    str += '\n';
}
console.log(str)
// 打印正三角
var str = '';
for(var i = 1 ; i <= 10; i++){
    for(var j = i; j >= 1; j--){
        str = str + '*';
    }
    str += '\n'
}
console.log(str)
6.2.4 for循环九九乘法表
var str = '';
for (var i = 1; i <= 9; i++) {
    for (var j = 1; j <= i; j++) {
        str += j + '*' + i + '=' + i * j + '\t'
    }
    str += '\n'
}
console.log(str)
6.2.5 while 循环

while 语句可以在条件表达式为真的前提下,循环执行指定的代码,直到表达式不为真时结束

// 语法结构
while(条件表达式){
   // 循环体代码
}

// 示例1
var num = 1;
while(num <= 100){
    console.log('你好');
    num++;
}
// 示例2
var msg = prompt('今天星期几?')
while(msg !== '星期一'){
    msg = prompt('答错了,重来,今天星期几?')
}

代码执行思路:

1、先执行条件表达式,如果结果为true,则执行循环体代码;如果为false,则退出循环,执行后面的代码

2、执行循环体代码

3、循环体代码执行完毕之后,程序会继续判断执行条件表达式,如条件任然为true,则会继续执行循环体,直到条件表达式为false时,整个循环过程才会结束

6.2.6 do...while 循环

do...while语句和while语句类似,该循环会先执行一次代码块,然后对条件表达式进行判断,如果条件为真,就会重复执行代码,否则退出循环

// 语法结构
do {
    // 循环体
} while(条件表达式)
   
// 示例
var i = 1;
do{
    console.log(i);// 10
    i++;
} while(i <= 100)
console.log(i); // 11 

执行思路:

1、先执行一次循环体,再判断条件,如果条件表达式为真,则继续执行循环体,否则退出循环

注意:do...while 循环体至少执行一次代码

6.2.7 continue break

常和循环结合使用:www.bilibili.com/video/BV1Sy…

continue :立即跳出本次循环,继续下一次循环(本次循环体中)

break :立即跳出整个循环(循环结束)

7 数组(重要)

数组指一组数据的集合,其中每个数据称为元素,数组中可存放任意类型的元素

7.1 创建数组的方式

js中创建数组有两种方式:

  • new 创建数组
  • 数组字面量创建数组
7.1.1 new 创建数组
var 数组名 = new Array();
var arr = new Array(); // 创建一个新的空数组
7.1.2 数组字面量创建数组
var arr = []
var arr = ['红','黄','蓝']

7.2 访问数组元素

数组的索引:索引(下标)用来访问数组元素的序号(数组下标从0开始)

访问数组元素:数组名[索引]

var arr = ['红','黄','蓝']
console.log(arr[0]); // 红
console.log(arr[1]); // 黄
console.log(arr[2]); // 蓝
console.log(arr[3]); // undefined

7.3 遍历数组(重要)

遍历:把数组的每个元素从头到尾访问一遍(此处只有常用的几个)

7.3.1 indexOf()

遍历数组,返回元素在数组中第一次出现的下标

接收2个参数:

  • 参数1:要查找的元素
  • 参数2:开始查找的下标
let arr = ['a','b','c','a']
// 遍历数组,查找a元素,从下标为1的元素开始往后查找
arr.indexOf('a', 1) // 3
7.3.2 forEach()

遍历数组,获取每一个元素,没有返回值

参数为一个函数,函数的形参1代表数组元素形参2代表元素的下标

let arr = ['a','b','c','a'];
arr.forEach(function(item, index){
    console.log(item, index)
})
7.3.3 map()

遍历数组,返回一个新数组,数组由参数里的返回值组成,必须使用return

参数为一个函数,函数的形参1代表数组元素形参2代表元素的下标

let arr = ['a','b','c','a'];
// 示例1 
let res = arr.map(function(item){
   return item == 'a' 
});
console.log(res); // [true, false, false, true]
// 示例2
let res = arr.map(function(item, index){
   return index
});
console.log(res); // [0, 1, 2, 3]
7.3.4 filter()

遍历数组,返回一个新数组:新数组由参数里面,条件为true的元素组成

参数为一个函数,函数的形参1代表数组元素形参2代表元素下标

let arr = ['a','b','c','a'];
let res = arr.filter(function (item, index) {
    return index >= 2
});
console.log(res); // [ 'c' ,'a' ]
7.3.5 for循环
let arr = ['a','b','c','a'];
for(var i = 0; i < arr.length; i++){
    console.log(arr[i]);
}

7.4 数组求和、平均值

var arr = [1, 2, 3, 4, 5, 9];
var sum = 0;
var avg = 0;
for (var i = 0; i < arr.length; i++) {
    sum += arr[i];
}
avg = sum / arr.length
console.log(sum, avg);

7.5 数组求最大值

7.6 数组转换为字符串

var arr = ['red', 'blue', 'pink'];
var str = '';
for(var i = 0; i < arr.length; i++){
    str += arr[i] + ''
}
console.log('str');

8 函数

函数:指封装了一段可被重复调用执行的代码块,通过此代码块可实现大量代码的重复使用

8.1 函数的使用

函数的使用分为两步:声明函数、调用函数

// 声明一个sum的函数
function sum(val1, val2){
   return val1 + val2;
}
// 调用声明的函数,且打印结果
let res = sum(1, 2)
console.log(res)

8.2 函数求1-100的和

// 声明函数
function getSum(){
    var sum = 0;
    for(var i = 0; i <= 100; i++){
        sum += i
    }
    console.log(sum)
}
// 调用函数
getSum();

8.3 函数的参数(形参和实参)

function 函数名(形参1, 形参2,...){
    
}
函数名(实参1, 实参2,...);

参数的作用:在函数内部某些值不固定,可通过参数在调用函数时传递不同的值进去

8.4 函数的返回值 return

函数只是实现某种功能,最终的结果需要返回给函数的调用者:函数名()

只要函数遇到return,就把return后面的结果返回给函数的调用者

function 函数名(){
    return 需要返回的结果;
}
函数名();

function colors(color1){
    return color1
}
let res = colors('red');
console.log(res)

注意:

  • return 返回程序结果后,后面的代码将不会执行(终止函数)
  • return 只能返回一个值,如果出现return num1,num2;的情况,只会返回最后一个值
  • 如果需要return多个值,可用数组保存,然后return数组
  • 如果函数由return,则返回return后面的值;如果没有return,则函数返回的值是undefined

8.5 break 、continue、return区别

  • break:结束循环(终止循环),比如:for、while
  • continue:跳出本次循环,继续执行下一次循环,比如:for、while
  • return:可退出循环,还可返回return语句中的值,还可结束当前的函数体内的代码(结束函数)

8.6 arguments

当不确定多少个参数会传递时,可用arguments来获取

在JavaScript中,arguments实际上是当前函数的内置对象,所有的函数都内置了一个arguments对象,里面存储了传递的所有实参

只有函数才有arguments对象

function fn(){
    console.log(arguments); // [1,2,3] 是个伪数组
    console.log(arguments.length); // 3 
    console.log(arguments[0]); // 1
    for(var i = 0; i < arguments.length; i++){
        console.log(arguments[i]);
    }
}
fn(1,2,3)

arguments是一个伪数组,可进行遍历

伪数组的特点:

  • 具有length属性
  • 按索引方式存储数据
  • 不具有数组的push、pop等方法
  • 可以按照数组遍历的方式去遍历伪数组

8.7 函数相互调用

function fn1(){
    console.log('fn1');
    fn2(); // 在fn1里面调用fn2
}
fn1();
function fn2(){
     console.log('fn2');
}

8.8 函数的两种声明方式

  • 利用函数关键字自定义函数(命名函数)
function fn(){

}
fn();// 调用:函数名()
  • 函数表达式(匿名函数)
var 变量名 = function(){};

var fun = function(){
    // 函数体
}
fun(); // 调用函数时,采用 变量名() 的方式

注意:

  • fun是变量名,不是函数名
  • 函数表达式声明方式跟变量差不多,只不过一个变量里面存的是值,一个存的是函数

9 作用域

作用域:变量在某个范围内起到的作用和效果

目的:为了提高程序的可靠性减少命名冲突

js的作用域(ES6之前):全局作用域局部作用域

全局作用域

  • 整个script标签
  • 或是整个js文件

局部作用域(函数作用域):

  • 在函数内部就是局部作用域,只在函数内部起效果或作用

9.1 变量的作用域

在JavaScript中,根据作用域的不同,变量可分为两种:全局变量局部变量

  • 全局变量

    • 在全局作用域下声明的变量(函数外声明),在任何位置都可使用
    • 在全局作用域下var声明的变量是全局变量
    • 特殊情况:在函数内部不适用var声明的变量,直接赋值,也是全局变量(不建议使用)
  • 局部变量

    • 在函数内部定义声明的变量,只能在函数内部使用
    • 在函数内部var声明的变量是局部变量
    • 函数的形参实际上是局部变量

注意:

  • 如果在函数内部,没有声明直接赋值的变量也属于全局变量

  • 函数的形参也可看做局部变量

  • 从执行效率来看全局变量和局部变量(二者区别):

    • 全局变量:在任何地方都可使用,只有浏览器关闭时才会销毁,比较占内存资源
    • 局部变量:只在函数内部使用,当其所在的代码块被执行时,会被初始化,当程序执行完毕就会销毁,节省内存资源

9.2 作用域链

作用域链:内部函数访问外部函数的变量,采取的是链式查找的方式来决定取哪个值,这种结构称为作用域链

10 预解析(重要)

JavaScript代码是由浏览器中的JavaScript解析器来执行的。

解析器在运行代码的过程分为两步:预解析代码执行(先预解析,再代码执行)

预解析:js引擎会把js里面所有的 var、function 提升到当前作用域的最前面

代码执行:按照代码书写的顺序从上往下执行

预解析分为:变量预解析(变量提升)函数预解析(函数提升)

  • 变量提升:把所有的变量声明提升到当前作用域的最前面,不提升赋值操作
console.log(num); // undefined
var num = 10;

// 相当于执行了以下代码:
var num;
console.log(num); //  undefined  声明了未赋值,则是undefined
num = 10;
// 函数表达式(匿名函数),var fun = funciton{}, fun是变量名
// 函数表达式调用,必须写在函数表达式的下面才不会报错
fun(); // 报错
var fun = function(){
    console.log(22);
}

// 相当于执行了以下代码:
var fun;
fun(); // 报错  声明了一个fun的变量名,直接调用报错
fun = function(){
    console.log(22)
}

var fun = function(){
    console.log(22); 
}
fun(); // 函数表达式调用,必须写在函数表达式的下面才不会报错   正常输出:22
  • 函数提升:把所有的函数声明提升到当前作用域的最前面,不调用函数
fu();  // 正常输出 11
function fn(){
    console.log(11);
}

// 相当于执行了以下代码: 先声明的函数,再调用
function fn(){
    console.log(11);
}
fu();  // 正常输出 11

11 对象

在JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象。比如:字符串、数组、函数等

对象是由属性方法组成的:

  • 属性:事物的特征,在对象中用属性来表示
  • 方法:事物的行为,在对象中用方法来表示

11.1 创建对象的方式

在JavaScript中,三种创建对象的方式:

  • 字面量创建对象
  • new Object 创建对象
  • 构造函数创建对象
11.1.1 字面量 创建对象

对象字面量:花括号{} 里面包含了表达这个具体事物(对象)的属性和方法

var obj = {}

var obj = {
    name:'王权',
    age:18,
    sayHi:function(){
        console.log('999');
    }
}

对象:

  • 里面的属性或方法采取键值对的形式
  • 多个属性或方法中间用逗号隔开
  • 方法冒号后面跟的是一个匿名函数

使用对象:

  • 调用对象的属性:对象名.属性名
// 调用对象obj里面的name属性
console.log(obj.name)
  • 第二种调用属性的方法:对象名['属性名']
console.log(obj['name'])
  • 调用对象的方法:对象名.方法名()
obj.seyHi()
11.1.2 new Object创建对象
// new Object 创建一个对象
var obj = new Object()

// 添加属性
obj.name = jay;
obj.age = 18;
obj.sayHi = function(){
    console.log(999);
}

注意:

  • 利用 = 等号赋值的方法,给对象添加属性和方法
  • 每个属性和方法之间用分号结束
11.1.3 构造函数 创建对象

字面量和new Object的方式一次只能创建一个对象,里面的属性和方法大量相同时,只能复制

可应用函数的方式,封装相同的代码,这个函数称为:构造函数

构造函数:把对象里面一些相同的属性和方法抽象出来封装到函数里

构造函数与普通函数的区别:里面封装的不是普通代码,而是对象,所以和普通函数不一样

// 构造函数语法格式:
function 构造函数名(){
    this.属性 = 值;
    this.方法 = function(){};
}
// 使用构造函数
new 构造函数名();
// 构造函数的示例
function Star(name, age, sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.sing = function(sang){
        console.log(sang)
    }
}
var ldh =  new Star('刘德华', 18, '男'); // 调用函数返回的是一个对象
console.log(typeof ldh) // Object
console.log(ldh.name) // 刘德华
console.log(ldh['age']) // 18
ldh.sing('冰雨') // 调用构造函数里面的方法,并传值

// 如果需要创建多个对象,只需要调用构造函数即可创建:
var zxy = new Star('张学友', 18 , '男')

注意:

  • 构造函数首字母要大写
  • 构造函数不需要return,就可以返回结果
  • 调用构造函数 ,必须使用new
  • 构造函数里面的属性和方法必须添加this

11.2 变量、属性、函数、方法的区别

www.bilibili.com/video/BV1Sy…

11.2.1 变量和属性

相同点:都是用来存储数据的

var num = 10;
var obj = {
	age:18
}

不同点:

  • 变量:单独声明并赋值,使用时直接写变量名(单独存在)
  • 属性:在对象里面不需要声明的,使用时必须是 对象.属性名 obj.age
12.2.2 函数和方法

相同点:都是实现某种功能,做某件事的

不同点:

  • 函数是单独声明,调用时 函数名() (单独存在)
  • 方法:在对象里面,调用时 对象.方法()

11.3 构造函数和对象的区别

构造函数:泛指某一大类

function Star(name, age, sex){
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.sing = function(sang){
        console.log(sang)
    }
}

对象:特指具体的事物

var ldh =  new Star('刘德华', 18, '男'); 

用构造函数创建对象的过程也成为对象的实例化

11.4 new关键字在构造函数中执行的过程

new关键字执行过程:

1、在内存中创建一个新的空对象

2、this 指向创建的空对象

3、执行构造函数里面的代码,给这个空对象添加属性和方法

4、 返回这个新对象(所以构造函数里面不需要return)

11.5 遍历对象

for...in语句用于对数组对象的属性进行循环操作

var obj = {
    name:'jay',
    age:18,
    sex:'男'
}

// for...in遍历对象
for(变量 in 对象){
    
}

// 示例
for(var k in obj){
    console.log(k); // 变量K,输出的是所有的  属性名
    console.log(obj[k]); // 输出对象中所有的  属性值
}

注意:

  • for...in 里面的变量,一般写为 k 或 key

11.5 对象小结

  • 对象可让代码结构更清晰
  • 对象是复杂数据类型Object
  • 对象的本质:对象就是一组无序的相关属性和方法的集合
  • 构造函数:泛指某一大类,可用于封装对象。比如:不管是红苹果或绿苹果,统称苹果
  • 对象:特指某一个事物。比如:这个红苹果
  • for...in 语句用于对对象的属性进行循环操作

12 简单数据类型和复杂数据类型

12.1 数据类型内存分配

JavaScript内置对象

JavaScript中对象分为三种:

  • 自定义对象
  • 内置对象
  • 浏览器对象

自定义对象和内置对象属于ECMAScript,浏览器对象属于js独有的(js API)

内置对象就是js语言自带的一些对象,这些对象供开发者使用,并提供了一些常用的或是最基本而必要的功能(属性和方法)

内置对象最大的优点是帮助程序员快速开发

JavaScript提供的内置对象:Math、Date、Array、String等

1 Math 数学对象

Math数学对象,不是一个构造函数,不需要new来调用,可直接使用里面的属性和方法

// 示例
console.log(Math.PI); // PI 一个属性  圆周率

1.1 Math 最大值、最小值

Math.max() 函数返回一组数中的最大值

Math.min()为一组数中的最小值,使用方法和Math.max()一样

如果没有参数,则结果为:-Infinity(负无穷大)

如果有任意一参数不能转成数值,则会返回NaN

console.log(Math.max(12, 16, 0)) // 16
console.log(Math.max(12, 0, 'jay')) // NaN
console.log(Math.max()) // -Infinity

1.2 Math 绝对值

绝对值:Math.abs()

console.log(Math.abs(1)); // 1
console.log(Math.abs(-1)); // 1
console.log(Math.abs('-1')); // 1 隐式转换 会把字符串型转换成数字型
console.log(Math.abs('red')); // NaN

1.3 Math 取整的方法

三个取整的方法:

  • Math.floor():向下取整
// 向下取整  往最小了取值
console.log(Math.floor(1.1)); // 1
console.log(Math.floor(1.9)); // 1
  • Math.ceil():向上取整
// 向上取整  往大了取值
console.log(Math.ceil(1.1)); // 2
console.log(Math.ceil(1.9)); // 2
  • Math.round():四舍五入 就近取整 注意 -3.5 结果是 3
// 四舍五入
console.log(Math.round(1.1)); // 1
console.log(Math.round(1.5)); // 2
console.log(Math.round(1.9)); // 2
console.log(Math.round(-1.1)); // -1
console.log(Math.round(-1.5)); // -1 

1.4 Math 随机数

random() :返回一个随机的小数 0 <= x < 1(大于等于0,小于1的小数)

Math.random() 方法里面没有参数

console.log(Math.random()); // 产生一个随机数

得到两个数之间的随机数,并且包含这两个数:

公式:Math.floor(Math.random() * (max - min + 1)) + min

// 得到两个数之间的随机数,并且包含这两个数
function getRandom(min, max){
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(getRandom(1, 100))

1.5 随机数-猜数字案例

猜数字游戏:

1、随机生成一个1-10的整数,需要用到Math.random()方法

2、如果猜大了,提示大了,反之,提示小了

3、需要一直循环,直到猜到为止

function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
var random = getRandom(1, 10);
console.log(random);
while (true) {
    var num = prompt('1-10之间的数字,你猜猜:');
    if (num > random) {
        alert('大了');
    } else if (num < random) {
        alert('小了');
    } else {
        alert('猜对了');
        break; // 猜对之后,终止循环
    }
}

2 Date日期对象

Date() 日期对象是一个构造函数,必须使用new来调用创建日期对象

var date = new Date()
console.log(date);
  • 如果没有参数,则返回当前系统的当前时间

2.1 日期格式化

获取年、月、日、时、分、秒、星期的方法如下:

方法说明代码
getFullYear()获取当前年dObj.getFullYear()
getMonth()获取当前月份(0-11)dObj.getMonth()
getDate()获取当天日期dObj.getDate()
getDay获取星期几(周日0 到 周六 6)dObj.getDay
getHours()获取当前小时dObj.getHours()
getMinutes()获取当前分钟dObj.getMinutes()
getSeconds()获取当前秒钟dObj.getSeconds()
var date = new Date();
console.log(date.getFullYear()); // 返回当前日期的年  2022
console.log(date.getMonth() + 1); // 返回当前日期的月份 月份从0开始,0-11,所以需要+1 
console.log(date.getDate()); // 返回当天日期
console.log(date.getDay()); // 周一返回:1,周六返回:6,周日返回:0
// 当前年月日星期
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth();
var dates = date.getDate();
var day = date.getDay()
var arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
console.log('今天是:' + year + '年' + month + '月' + dates + '日 ' + arr[day]);
输出:今天是:2022年4月28日 星期六

2.2 时分秒格式化

格式化日期 时分秒:

// 封装一个函数返回当前的 时分秒 格式
function getTimer() {
    var time = new Date();
    var h = time.getHours();
    var m = time.getMinutes();
    var s = time.getSeconds();
    h = h < 10 ? '0' + h : h;
    m = m < 10 ? '0' + m : m;
    s = s < 10 ? '0' + s : s;
    return h + ':' + m + ':' + s;
}
console.log(getTimer()); // 19:51:02

2.3 Date总的毫秒数(时间戳)

Date对象是基于1970年1月1日(世界标准时间)起的毫秒数

获得Date总的毫秒数,不是当前时间的毫秒数,而是距离1970年1月1日过了多少毫秒数

获取总毫秒数的方式如下:

  • valueOf() 、getTime():
// 通过valueOf() getTime()查看
var date = new Date();
console.log(date.valueOf()); // 距离1970年1月1日的总毫秒数
console.log(date.getTime());// 距离1970年1月1日的总毫秒数
  • 简单的写法(最常用的写法):
var date = +new Date(); // +new Date() 括号内为空,则返回的就是总的毫秒数
console.log(date)
  • H5 新增的 获取总的毫秒数
console.log(Date.now())

2.4 倒计时(案例)

核心算法:输入的时间减去现在的时间,就是剩余的时间,即倒计时。但是不能拿时分秒相减。

思路:

1、可用时间戳来做,用用户输入时间的总毫秒数减去现在时间的总的毫秒数,得到的就是剩余时间的毫秒数

2、把剩余的时间总的毫秒数转换为:天、时、分、秒(时间戳转换为时分秒

转换公式如下:

  • d = parseInt(总秒数/60/60/24) ; // 计算天数
  • h = parseInt(总秒数/60/60%24); // 计算小时
  • m = parseInt(总秒数/60%60);// 计算分钟
  • s = parseInt(总秒数%60); // 计算当前秒数
function countDown(time){
    var nowTime = +new Date(); // 括号内为空,则返回的是当前时间的总毫秒数
    var inputTime= +new Date(time); // 括号内有参数,则返回括号内参数的总毫秒数
    /* 输入的时间 - 现在时间的总毫秒,再除以1000将毫秒转换为秒 */
    var times = (inputTime - nowTime) / 1000; // times 是剩余时间的总秒数,1s = 1000ms
    
    var d = parseInt(times/60/60/24) ; // 天
    var h = parseInt(times/60/60%24); // 小时
    var m = parseInt(times/60%60);// 分钟
    var s = parseInt(times%60); // 秒数
    
    d = d < 10 ? '0' + d : d;
    h = h < 10 ? '0' + h : h;
    m = m < 10 ? '0' + m : m;
    s = s < 10 ? '0' + s : s;
    
    return d + '天' + h +'时' + m + '分' + s + '秒'
}
console.log(countDown('2022-06-01 00:00:00'))

3 Array 数组对象

3.1 创建数组的两种方式

  • 数组字面量
var arr = [1,2,3];
console.log(arr[0]);
  • new Array()
var arr = new Array(); // 创建了一个空的数组

var arr1 = new Array(2); // 这个括号里面的2表示:数组的长度为2,里面有两个空的数组元素

var arr2 = new Array(2, 3); // 相当于var arr2 = [2,3]  这时括号内表示:有两个数组元素是 2 和 3

3.2 检测是否为数组

  • instanceof 运算符:可检测是否为数组,结果为布尔值(true/false)
var arr = [], obj = {};
console.log(arr instanceof Array); // true
console.log(obj instanceof Array); // false
  • Array.isArray():如果对象是Array,则为true,否则为false
var arr = [], obj = {};
console.log(Array.isArray(arr)); // true
console.log(Array.isArray(obj)); // false

Array.isArray() 和 instanceof的区别:

  • 当检测Array实例时,Array.isArray 优于 instanceof,因为Array.isArray 能检测 iframes
  • Array.isArray 是H5新增的方法,IE9以上版本支持

3.3 添加删除数组元素

方法名说明返回值
push(参数1 ...)在数组末尾添加一个或多个元素,注意修改原数组并返回新的数组
pop()删除数组的最后一个元素,把原数组长度减1,无参数,修改原数组返回它删除的元素的值
unshift(参数1...)在数组的开头添加一个或多个元素,修改原数组返回新的长度
shift()删除数组的第一个元素,数组长度减1,无参数,修改原数组返回第一个元素的值
3.3.1 push()
  • 在数组末尾添加一个或多个元素,并返回新的数组
  • push() 参数直接写数组元素即可
  • push 完毕之后,返回的结果是 新数组的长度
  • 原来的数组会改变
var arr = [1,2,3];
 // arr.push(4, 'jay'); // 给数组增加元素
// console.log(arr); // [1,2,3,4,'jay']
console.log(arr.push(4, 'jay')) // 5 返回新数组的长度
3.3.2 unshift()
  • 在数组开头,添加一个或多个数组元素
  • unshift() 参数直接写数组元素即可
  • unshift 完毕之后,返回的结果是 新数组的长度
  • 原来数组会改变
var arr = [1,2,3];
arr.unshift('red', 'blue'); // ['red', 'blue', 1, 2, 3]
console.log(arr.unshift('red', 'blue')); // 7 返回新数组的长度
3.3.3 pop()
  • 删除数组的最后一个元素,一次只能删除一个元素
  • pop() 没有参数
  • pop完毕之后,返回的结果是 删除的元素
  • 原数组会改变
var arr = [1,2,3];
arr.pop(); // [1,2]
console.log(arr.pop());// 3 返回被删除的元素
3.3.4 shift()
  • 删除数组的第一个元素,一次只能删一个
  • shift() 没有参数
  • shift()完毕之后,返回的结果是 删除的那个元素
  • 原数组会变化
var arr = [1,2,3];
arr.shift(); // [2,3]
console.log(arr.shift());

3.4 数组排序

方法名说明是否修改原来的数组
reverse()颠倒数组中元素的顺序,无参数会改变原来的数组,返回新数组
sort()对数组的元素进行排序(升序降序)会改变原来的数组,返回新数组
3.4.1 reverse() 翻转数组
var arr = [1, 2, 3, 4]
arr.reverse();
console.log(arr); // [4,3,2,1]
3.4.2 sort() 数组排序
var arr = [5, 6, 4, 2, 1];
arr.sort(function(a, b){
    // return a - b; // 升序排序
    return b - a; // 降序排序
})
console.log(arr); // [1, 2, 4, 5, 6]

3.5 获取数组元素索引(indexOf())

3.5.1 indexOf()、lastIndexOf()

数组索引的方法:

方法名说明返回值
indexOf()数组中查找给定元素的第一个索引如果存在,返回索引号,如果不存在,则返回-1
lastIndexOf()在数组中的最后一个的索引如果存在,返回索引号,如果不存在,则返回-1

indexOf():

  • indexOf(数组元素) :返回该数组元素的索引号
  • 只返回一个满足条件的索引号
  • 如果在该数组中找不到该数组元素,则返回-1
  • indexOf(数组元素,起始位置):indexOf('春',3) 表示从索引号为 3 的位置开始往后查找 春 字
// 示例  indexOf(数组元素)
var arr = ['red', 'blue', 'pink'];
console.log(arr.indexOf('blue')); // 1
3.5.1 数组去重

目的:把旧数组里面的不重复的元素选出来放到新数组中,重复的元素只保留一个,放到新数组中去重

核心算法:遍历旧数组,用旧数组元素取查询新数组。如果该元素在新数组里面没有出现过,则添加到新数组,否则不添加

怎么知道该元素存不存在? 利用 新数组.indexOf(数组元素) 如果没有 返回-1

// 核心:新数组.indexOf(旧数组数组元素) == -1 
function unique(arr) {
    var newArr = [];
    for (var i = 0; i < arr.length; i++) {
        if (newArr.indexOf(arr[i]) === -1) {
            newArr.push(arr[i])
        }
    }
    return newArr
}

var result = unique(arr = [1, 2, 1, 5, 3, 1, 3])
console.log(result); // [1,2,5,3]

3.6 数组转换成字符串

方法名说明返回值
toString()把数组转换成字符串,逗号分隔每一项返回一个字符串
join('分隔符')用于把数组中的所有元素转换为一个字符串返回一个字符串
var arr = [1,2,3];
// toString() 数组转换成字符串
console.log(arr.toString()); // 1,2,3
// join('分隔符')  括号内如果不写,默认用逗号进行分隔
console.log(arr.join('-')); // 1-2-3
console.log(arr.join()); // 1,2,3
console.log(arr.join('&')); // 1&2&3

4 字符串对象

4.1 基本包装类型

为了方便操作基本数据类型,JavaScript提供了三个特殊的引用类型:String、Number、Boolean。

基本包装类型:把简单的数据类型包装称为复杂的数据类型,基本数据类型就有了属性和方法

var str = 'andy';
console.log(andy.length);

基本数据类型没有属性和方法,对象才有,但是上面的代码可以执行,这是因为js会把基本数据类型包装成复杂数据类型,其执行过程如下:

// 1.生成临时变量,把简单类型包装成复杂数据类型
var temp - mew String('andy');
// 2.赋值给声明的字符变量
str = temp;
// 3.销毁临时变量
temp = null;

4.2 根据字符串返回位置(indexOf())

字符串对象,根据字符返回位置

// str.indexOf('要查找的字符', [起始位置]) 起始位置不写,默认从第一个字符开始查找
var str = '改革春风吹满地, 春天来了'
console.log(str.indexOf('春', 3)) // 从索引号是3的位置,往后查找春字

4.3 求字符出现的位置及次数(案例)

示例:查找字母 o ,"haeobcosndosoaoaoaoaxovfodo"

核心算法:

1、先查找第一个o出现的位置

2、indexOf()返回的结果不是-1,就继续往后查找

3、因为indexOf()只能查找到第一个就停止,所以后面的查找,需要用第二个参数的索引加1,以此类推,直到indexOf() ==-1结束

var str = 'haeobcosndosoaoaoaoaxovfodo'
var index = str.indexOf('o');
var num = 0
while (index !== -1) {
    console.log(index);
    num++;
    index = str.indexOf('o', index + 1);
}
console.log('o出现的次数:', num);

4.4 根据位置返回字符

方法名说明示例
charAt(index)返回指定位置的字符(index 字符串的索引号)str.charAt(0)
charCodeAt(index)获取指定位置处字符的ASCLL码(index 字符串的索引号)str.charCodeAt(0)
str[index]获取指定位置处字符HTML5,IE8支持 和 chartAt()等效
// 根据位置返回字符 charAt(index)
var str = 'blue';
console.log(str.charAt(3)); // e

// 遍历所有字符
for (var i = 0; i < str.length; i++) {
    console.log(str.charAt(i)); // 所有的字符 
    console.log(str.charCodeAt(i)); // 所有字符的ASCLL码
}

4.5 统计出现次数最多的字符(案例)

www.bilibili.com/video/BV1Sy…

步骤:

1、核心算法:利用chartAt()遍历这个字符串

2、把每个字符都存储给对象,如果对象没有该属性,就为1,如果存在了就+1

3、遍历对象,得到最大值和该字符

var str = 'dnkfjejowdsmfjowhndfbjwhwomc';
var obj = {};

// 把每个字符存入对象,
for (var i = 0; i < str.length; i++) {
    var chars = str.charAt(i); // 得到每一个字符
    // obj[chars] 得到的是属性值,判断它是否存在,存在则里面的次数+1
     obj[chars] ? obj[chars]++ : obj[chars] = 1
}
console.log(obj);

// 遍历对象
var max = 0;
var maxChar = '';
for (k in obj) {
    // k 是属性名
    // obj[k] 是属性值
    if (obj[k] > max) {
        max = obj[k];
        maxChar = k
    }
}
console.log('出现次数最多的是:' + maxChar + ' 次数:' + max); 

4.6 替换字符串 replace

replace('被替换的字符', '替换的字符'):只会替换第一个字符

var str = 'andyandy'
console.log(str.replace('a', 'b')) // bndyandy

全部替换:

var str = 'ndawoed;oewdjooooodwenakcn';
while (str.indexOf('o') !== -1) {
    str = str.replace('o', '*')
}
console.log(str);

4.7 字符转换成数组 split

字符转换为数组:split('分隔符')

var str = 'red,blue,pink'
console.log(str.split(',')) //  ['red', 'blue', 'pink']

var str = 'red&blue&pink'
console.log(str.split('&')) //  ['red', 'blue', 'pink']

4.8 大小写转换

toUpperCase():小写转大写

toLowerCase():大写转小写

var str = 'red,pink'
console.log(str.toUpperCase()); // RED,PINK
console.log(str.toLowerCase()); // red,pink

4.9 截取字符串 substr

substr('截取的起始位置', '截取的几个字符')

substr()方法如果不写截取几个字符,默认后面的全部截取

var str = '改革春风吹满地';
console.log(str.substr(2, 2)); // 春风
console.log(str.substr(2)); // 春风吹满地

Web APIs

JavaScript基础和Web APIs两个阶段的关联性:JS基础学习ECMAScript基础语法为后面做铺垫,Web APIs是JS的应用,大量使用JS基础语法做交互效果

API 和Web API:

API (应用程序编程接口),是一些预先定义的函数,目的是提供应用程序与开发人员基于某种软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节(API是给程序员提供的一种工具,以便能更轻松的实现想要完成的功能)

Web API是浏览器提供的一套操作浏览器功能页面元素API(BOM和DOM)

1 DOM

DOM:文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标记语言(HTML或XML)的标准编程接口

W3C定义了一系列的DOM接口,通过这些DOM接口可改变网页的内容、结构、样式

DOM中的专有名词:

  • 文档:一个页面就是一个文档,DOM中使用document表示
  • 元素:页面中的所有标签都是元素,DOM中使用element表示
  • 节点:网页中的所有内容都是节点(标签、属性、文本、注释等),DOM中使用node表示

关于DOM操作,主要针对于元素的操作,主要有创建、增、删、改、查、属性操作、事件操作

1.1 获取元素

DOM中获取页面中元素的几种方式:

  • 根据ID获取
  • 根据标签名获取
  • 通过HTML5新增的方法获取
  • 特殊元素获取
1.1.1 根据ID获取 getElementById()

getElementById():匹配特定ID的元素

// 语法
var element = document.getElementById(id);

// 示例
<div id='time'>2022-6-1</div>
var timeId = document.getElementById('time');
console.log(timeId) // <div id="time">2022-6-1</div>
console.dir(timeId)

注意:

  • 返回一个元素对象
  • 返回值:返回一个匹配到ID的DOM Element对象,若在当前Document下没有找到,则返回null
  • console.dir(timeId):打印返回的元素对象,可更好的查里面的属性和方法
1.1.2 根据标签名获取 getElementsByTagName()

getElementsByTagName():返回带有指定标签名的对象的集合(以伪数组的形式存储的)

<ul>
    <li>2022-01-01 1</li>
    <li>2022-01-01 2</li>
    <li>2022-01-01 3</li>
    <li>2022-01-01 4</li>
    <li>2022-01-01 5</li>
</ul>
var lis = document.getElementsByTagName('li'); // 选中所有相同的标签
console.log(lis); // 指定标签名的对象合集  以伪数组的方式存储
console.log(lis[0]); // <li>2022-01-01 1</li>

// 遍历
for (var i = 0; i < lis.length; i++) {
    console.log(lis[i]);
}

注意:

  • getElementsByTagName():获取指定标签名的对象合集,以伪数组的方式存储(可使用数组的部分方法)
  • 可用for遍历元素对象
  • 如果页面中只有一个li,返回的依然是伪数组的形式
  • 如果页面中没有该元素,返回的是一个空的伪数组

获取某个元素(父元素)内部所有指定标签名的子元素:

<ol id='ol'>
    <li>2022-01-01 1</li>
    <li>2022-01-01 2</li>
    <li>2022-01-01 3</li>
    <li>2022-01-01 4</li>
    <li>2022-01-01 5</li>
</ol>
// 先获取父元素,再获取父元素中的指定标签名
var ol = document.getElementById('ol')
var ollis = ol.getElementsByTagName('li')

注意:父元素必须是单个对象(必须指明是哪一个元素对象) ,获取的时候不包括父元素自己

1.1.3 H5新增的方法

H5新增的方法不兼容IE9以下的浏览器,不考虑兼容性和移动端可放心食用

getElementsByClassName():根据类名获取某些元素集合

<div class="box">盒子</div>
<div class="box">盒子</div>
var boxs = document.getElementsByClassName('box');
console.log(boxs); // 伪数组的方式存储

querySelector('选择器'):根据指定选择器,返回第一个元素对象

<div class="box">盒子 1</div>
<div class="box">盒子 2</div>
<div id="nav">
    <ul>
        <li>首页</li>
        <li>产品</li>
    </ul>
</div>
// 类选择器
var box = document.querySelector('.box')
console.log(box); // <div class="box">盒子 1</div>
// ID选择器
var nav = document.querySelector('#nav')
console.log(nav); // 
// 标签选择器
var li = document.querySelector('li')
console.log(li); // <li>首页</li>

querySelectorAll('选择器'):根据指定选择器返回所有元素对象的集合

<div class="box">盒子 1</div>
<div class="box">盒子 2</div>
<div id="nav">
    <ul>
        <li>首页</li>
        <li>产品</li>
    </ul>
</div>

// 选择所有的.box (类选择器)
var allBox = document.querySelectorAll('.box');  // 以伪数组的方式存储
var allLi = document.querySelectorAll('li')
1.1.4 获取body和HTML元素

获取body元素:

var body = document.body;
console.log(body);
console.dir(body); // console.dir() 打印返回的元素对象,可更好的查里面的属性和方法

获取HTML元素:

var html = document.documentElement;
console.log(html)                    

1.2 事件基础

JavaScript可创建动态页面,事件是可被JavaScript侦测到的行为

事件三要素:

  • 事件源:被触发的对象(谁触发的事件)
  • 事件类型:如何触发。比如:鼠标点击,鼠标经过、键盘按下
  • 事件处理程序:通过函数赋值的方式 完成
// 示例
<button id="btn">按钮</button>

var btn = document.getElementById('btn')
btn.onclick = function () {
    alert('点击')
}
1.2.1 常见的鼠标事件
鼠标事件触发条件
onclick鼠标点击左键触发
onmouseover鼠标经过触发
onmouseout鼠标离开触发
onfocus获取鼠标焦点触发
onblur失去鼠标焦点触发
onmousemove鼠标移动触发
onmouseup鼠标弹起触发
onmousedown鼠标按下触发

1.3 操作元素

JavaScript的DOM操作可改变网页内容、结构、样式,可利用DOM操作元素来改变元素里面的内容、属性等

1.3.1 改变元素内容(innerText、innerHTML)

innerText:修改元素的内容

// 示例:点击获取当前系统时间
<button id="btn">点击获取当前系统时间</button>
<p></p>
<div></div>

var btn = document.getElementById('btn')
var p = document.querySelector('p')
btn.onclick = function () {
    p.innerText = getNowDay()
}

function getNowDay() {
    var date = new Date();
    var year = date.getFullYear();
    var month = date.getMonth();
    var dates = date.getDate();
    var day = date.getDay();
    var arr = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
    return '今天是:' + year + '年' + month + '月' + dates + '日 ' + arr[day]
}

// 页面加载就显示时间(不依赖事件)
var div = document.querySelector('div');
div.innerText = getNowDay()

共同点:

  • innerText和innerHTML都可修改页面内容

区别:

  • innerText 不识别html标签 , innerHTML识别html标签
var div = document.querySelector('div');
div.innerText = '<strong>今天是:<strong>2020年'; // 不识别html标签,页面会显示<strong>
div.innerHTML = '<strong>今天是:<strong>2020年';// 识别html标签,今天是 加粗显示
  • innerText 非标准(老版本火狐不支持),innerHTML W3C标准(推荐)
  • 这两个属性可读写(可获取元素里面的内容)
<div>
    王权
	<span>陈斌</span>
</div>
var div = document.querySelector('div');
console.log(div.innerText); // 王权 陈斌
console.log(div.innerHTML); //  王权  <span>陈斌</span>
  • innerText 去除html标签,同时空格和换行也会去掉
  • innerHTML 保留html标签,同时保留空格和换行
1.3.2 修改元素属性

www.bilibili.com/video/BV1Sy…

1.3.3 分时问候(案例)

www.bilibili.com/video/BV1Sy…

1.3.4 修改表单属性

利用DON可用操作表单元素的属性:

type、value、checked、selected、disabled

<button>按钮</button>
<input type="text" value="输入。。。">

var btn = document.querySelector('button');
var input = document.querySelector('input')
btn.onclick = function () {
    input.value = '999';
    // this指向事件函数的调用者 btn
    // disabled 被点击之后,不能再使用
    this.disabled = true
}
1.3.5 样式属性操作

可通过JavaScript修改元素的大小、颜色、位置等样式

element.style行内样式操作

element.className类名样式操作

// element.style:行内样式操作
<div></div>

var div = document.querySelector('div')
// 鼠标经过时的样式
div.onmouseover = function () {
    // div.style.backgroundColor = '#f90' // 可用this this指向事件函数的调用者
    this.style.backgroundColor = '#f90'
    this.style.width = '500px'
}
// 鼠标离开时的样式
div.onmouseout = function () {
    this.style.backgroundColor = 'red'
}

注意:

  • JS里面的样式采用驼峰命名法。比如:backgroundColor、fontSize
  • js修改style样式操作,产生的是行内样式,权重比较高
  • 如果样式少,或者简单的情况下使用(多的情况下,使用className)
 // element.className:类名样式操作
.change{
    background-color:#f90;
    width:500px;
    height: 500px;
}

<div class='div-style'></div>

var div = document.querySelector('div')
// 鼠标经过时的样式
div.onmouseover = function () {
   this.className = 'change'; // 可以理解为:给他添加个类名
}

注意:

  • 可以理解为:提前写好样式,给他添加个这个类名

  • 适合样式多或功能负责的情况

  • className 会直接更改元素的类名,会覆盖原先的类名,如果想要保留原先的类名,做法:

    this.className = '原先的类名  改变后的类名'
    this.className = 'div-style  change'
    
1.3.6 操作元素总结

操作元素:

  • 操作元素内容

    • innerText
    • innerHTML
  • 操作常见元素属性

    • src、href、title、alt等
  • 操作表单元素属性

    • type、value、disabled等
  • 操作元素样式属性

    • element.style
    • className
1.3.7 全选与取消全选(案例)

案例分析:

1、全选取消全选的做法:让下面的复选框的checked属性 跟随 全选按钮即可(循环下面的复选框,将其checked属性改为和全选按钮一样)

2、下面的复选框需要全部选中,上面的全选才能选中:给下面的所有复选框绑定点击事件,每次点击,都要循环一下查看下面所有的复选框是否都选中,如果存在一个没选选中,则全选按钮不选中。设置一个变量,来控制是否选中

全选:<input type="checkbox">
<div id="checkboxContent">
    篮球<input type="checkbox">
    足球<input type="checkbox">
    排球<input type="checkbox">
</div>

var allCheckbox = document.querySelector('input')
// 选择ID为checkboxContent下所有的input标签的元素集合
var inputs = document.getElementById('checkboxContent').getElementsByTagName('input')

// 当全选框选中时,其他的复选框全部选中
allCheckbox.onclick = function () {
    // this.checked 复选框是选中状态为true,未选中为fasle
    // this指向调用事件函数的allCheckbox
    // console.log(this.checked);
    for (var i = 0; i < inputs.length; i++) {
        // 循环其他的复选框,把其它的状态改为和全选的状态一样
        inputs[i].checked = this.checked; 
    }
}

// 给下面的所有复选框绑定点击事件,每次点击,都要循环一下查看下面所有的复选框是否都选中,如果存在一个没选中,则全选按钮不选中
// 变量 flag 控制全选按钮是否选中
for (var i = 0; i < inputs.length; i++) {
    inputs[i].onclick = function () {
        // flag 控制全选按钮是否选中
        var flag = true;
        // 每次点击下面的复选框,都要循环检查其他的按钮是否全被选中
        for (var i = 0; i < inputs.length; i++) {
            if (!inputs[i].checked) {
                flag = false;
            }
        }
        allCheckbox.checked = flag;
    }
}
1.3.8 获取自定义属性值 getAttribute()

自定义属性:程序员自己添加的属性,称为自定义属性

获取元素的属性值:

  • element.属性
  • element.getAttribute('属性')
// 示例
<div id='demo'><div>
var div = document.querySelector('div')
console.log(div.id); // demo 
console.log(div.getAttribute('id')); // demo

区别:

  • element.属性:获取内置属性值(元素本身自带的属性)
  • element.getAttribute('属性'):主要获取自定义的属性(标准的),程序员自定义的属性
1.3.9 自定义属性的操作(设置、移除属性)

设置属性的值

  • element.属性 = '值' 设置内置属性值

    // 示例
    <div id='demo' class='div1' index='1' ><div>
    var div = document.querySelector('div')
    div.id = 'test';
    div.className = 'test1'
    
  • element.setAttribute('属性', '值') :主要针对自定义属性

    // 示例
    <div id='demo' class='div1' index='1' ><div>
    var div = document.querySelector('div')
    div.setAttribute('index', 2);
    div.setAttribute('class', 'footer')
    

移除属性 removeAttribute(属性):

div.removeAttribute('index')
// 移除后
<div id='demo' class='div1' ><div>
1.3.10 tab栏切换(案例)

案例分析:

1、tab切换有两个大模块

2、上面的模块选项卡,点击某一个,当前这个的背景颜色变成红色,其余的不变,可用修改类名的方式

3、下面的模块内容,会随着上面的选项卡变化而变化,所以下面的模块变化写在点击事件里面

4、规律:下面的模块显示内容和上面的选项卡一一对应,相匹配

5、核心思路:给上面的tab_list里面的所有小li添加自定义属性(setAttribute),属性值从0开始编号

6、当点击tab_list里面的某个小li,让tab_con里面对应序号的内容显示,其余隐藏

<div class="tab_list">
    <ul>
        <li class="current">1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
    </ul>
</div>
<div class="tab_con">
    <div style="display: block;">111</div>
    <div>222</div>
    <div>333</div>
    <div>444</div>
    <div>555</div>
</div>

var tab_list = document.querySelector('.tab_list');
var lis = tab_list.querySelectorAll('li');
var items = document.querySelector('.tab_con').querySelectorAll('div')
// 循环绑定事件
for (var i = 0; i < lis.length; i++) {
    // 给五个li设置索引号 
    lis[i].setAttribute('data-index', i)
    lis[i].onclick = function () {
        // 上面的模块选项卡,点击某一个,当前这个的背景颜色变成红色,其余的不变,可用修改类名的方式
        for (var i = 0; i < lis.length; i++) {
            // 清除所有li的class 这个类
            lis[i].className = '';
        }
        // 只留下当前点击的那个,给他添加上类(样式)
        this.className = 'current';

        // 点击切换时,显示的内容模块
        var index = this.getAttribute('data-index')
        // 先把所有的隐藏
        for (var i = 0; i < items.length; i++) {
            items[i].style.display = 'none'
        }
        // 只留下当前的要显示的内容(和上对应的内容)
        items[index].style.display = 'block'
    }
}
1.3.11 H5自定义属性

自定义属性的目的:为了保存并使用数据,有些数据可保存到页面中,不必保存到数据库中

自定义属性获取属性是通过:getAttribute('属性')

但是有些自定义属性很容易引起歧义,不容易判断元素是内置属性还是自定义属性

H5新增的自定义属性(不考虑兼容性问题):

  • H5规定自定义属性data-开头作为属性名,并且赋值。比如:<div data-index = '1'></div>

  • 通过setAttribute('属性')自定义属性。比如:div.setAttribute('data-time', 20)

  • 获取自定义属性的值getAttribute。比如:div.getAttribute('data-time')

  • dataset 是一个集合,里面存放了所有以data开头的自定义属性。

    • 示例:console.log(div.dataset.index) 打印data-开头 index的自定义属性,console.log(div.dataset)打印所有以data开头的自定义属性
    • div.dataset['index'] 打印data开头 index的自定义属性
    • dataset 兼容性差 IE11才兼容
  • 如果自定义属性里面有多个-连接的单词,获取自定义属性时,需要采取驼峰命名法。

    // 自定义属性里面有多个-连接的单词,获取自定义属性时,需要采取驼峰命名法
    <div data-list-name='andy'></div>
    console.log(div.dataset.listName); // andy
    console.log(div.dataset['listName']); // andy
    

1.4 节点操作

页面中的所有内容都是节点(标签、属性、文本、注释等),在DOM中,节点使用node来表示

HTML DOM树中的所有节点均可以通过JavaScript进行访问,所有HTML元素(节点)均可被修改、创建、删除

一般的节点至少拥有:nodeType(节点类型)nodeName(节点名称)nodeValue(节点值) 三个基本属性

nodeType(节点类型):

  • 元素节点 nodeType 为 1
  • 属性节点 nodeType 为 2
  • 文本节点 nodeType 为 3(文本节点包括:文字、空格、换行)

在实际开发中,节点操作主要操作的是元素节点

1.4.1 父节点 parentNode

语法:node.parentNode 返回指定子节点的父节点

// 示例
<div class='box'>
    <span class='font'>文字文字</span>
</div>
// 父节点
var font = document.querySelector('.font');
// var box = document.querySelector('.box') // 以前的写法
console.log(font.parentNode); // font.parentNode 可以直接获取子节点font的父节点

注意:

  • node.parentNode 得到的是离子节点最近的父节点
  • 如果指定的节点找不到父节点,则返回为null
1.4.2 子节点 children

语法:parentNode.childNodes 返回包含指定节点的子节点(包含:元素节点、文本节点等)的集合,该集合为即使更新的集合

如果只想要获得里面的元素节点,则需要专门处理,一般不提倡用childNodes

推荐使用:parentNode.children

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>
var ul = document.querySelector('ul');
// var li = document.querySelector('li'); // 以前的写法
console.log(ul.children); // ul.children 可以直接获取父节点ul下的所有子节点
1.4.3 第一个和最后一个子节点

parentNode.firstElementChild :返回第一个子元素节点,找不到返回null

parentNode.lastElementChild:返回最后一个子元素节点,找不到返回null

<ul>
    <li>11</li>
    <li>22</li>
    <li>33</li>
</ul>

var ul = document.querySelector('ul')
console.log(ul.firstElementChild); // 第一个子元素节点: <li>11</li>
console.log(ul.lastElementChild); // 最后一个子元素节点: <li>33</li>

注意:这两个方法有兼容性问题,IE9以上才支持

实际开发的写法,既没有兼容性问题,又能返回第一个和最后一个子元素:

ul.children[ol.children.length - 1 ] :获取最后一个子元素(父节点所有子节点的长度减1,则是元素的下标),不管子元素增加减少,都可以

// ul.children 返回ul下面所有的子节点,再用children[0]数组的方式取第一个子节点
console.log(ul.children[0]); // 第一个子元素节点: <li>11</li>
console.log(ul.children[2]); // 最后一个子元素节点: <li>33</li>
console.log( ul.children[ol.children.length - 1 ]); // 最后一个子元素节点: <li>33</li>
1.4.4 导航栏(父子节点案例)

案例分析:

1、导航栏里面的li都要鼠标经过的效果,所以需要循环绑定鼠标事件

2、核心元素:当鼠标经过li里面的第二个孩子ul显示,当鼠标离开,ul隐藏

<ul class="nav">
    <li>
        <a href="">首 页</a>
        <ul>
            <li><a href="">首页内容</a></li>
            <li><a href="">首页内容</a></li>
            <li><a href="">首页内容</a></li>
        </ul>
    </li>
    <li>
        <a href="">微 博</a>
        <ul>
            <li><a href="">微博内容</a></li>
            <li><a href="">微博内容</a></li>
            <li><a href="">微博内容</a></li>
        </ul>
    </li>
</ul>

var nav = document.querySelector('.nav')
var lis = nav.children; // 得到nav下面的子li
for (var i = 0; i < lis.length; i++) {
    lis[i].onmouseover = function () {
        this.children[1].style.display = 'block';
    }
    lis[i].onmouseout = function () {
        this.children[1].style.display = 'none';
    }
}
1.4.5 兄弟节点

语法:

  • node.nextElementSibling 返回下一个兄弟元素节点,找不到返回null
  • node.previousElementSibling 返回下一个兄弟元素节点,找不到返回null
<div>div</div>
<span>span</span>
var div = document.querySelector('div');
console.log(div.nextElementSibling); // <span>span</span>
console.log(div.previousElementSibling); // null

注意:这两个方法有兼容性问题,IE9以上才支持

1.4.6 创建/添加节点

语法:

  • 创建节点:document.createElement('tagName') 创建由tagName指定的HTML元素。因为这些元素原先是不存在的,是根据需要动态生成的,所以也称为动态创建元素节点
  • 添加节点:node.appendChild(child) 将一个节点添加到指定父节点的子节点列表的末尾(node:父级,child子级)
  • 添加节点:node.insertBefore(child, 指定元素) 将一个节点添加到父节点的指定子节点前面
// 示例
<ul>
    <li>99</li>
</ul>

// 1.创建节点 元素节点
var li = document.createElement('li');

// 2.添加节点 node.appendChild(child) node 父级  child 子级
var ul = document.querySelector('ul');
ul.appendChild(li)

// 3.添加节点 node.insertBefore(child, 指定元素)
var lili = document.createElement('li');
ul.insertBefore(lili, ul.children[0]);

如果需要在页面添加一个新的元素(步骤):

1.创建元素

2.添加元素

1.4.7 删除节点

语法:node.removeChild(child) 从父节点中删除一个子节点,返回删除的节点

// 示例
<button>删除</button>
<ul>
    <li>11</li>	
	<li>22</li>
</ul>
// 1.获取元素
var ul = document.querySelector('ul');
var btn = document.querySelector('button');

// 2.删除元素  node.removeChild(child)
// ul.removeChild(ul.children[0])

// 3.点击按钮依次删除子节点
btn.onclick = function () {
     // 如果父节点下没有子节点了,按钮禁用,还有子节点则可以继续删除
    ul.children.length == 0 ?  this.disabled = true :  ul.removeChild(ul.children[0])
}
1.4.8 留言板(创建/添加/删除节点案例)

核心思路:点击按钮之后,就动态创建一个li,添加到ul里面

创建li的同时,把文本域里面的值通过li.innerHTML赋值给li

<textarea name="" id="" cols="30" rows="10"></textarea>
<button>添加</button>
<ul></ul>

var btn = document.querySelector('button')
var ul = document.querySelector('ul')
var text = document.querySelector('textarea')
btn.onclick = function () {
    if (text.value == '') {
        alert('您还没有输入留言~');
        return false; // 终止操作
    } else {
        // 创建元素节点
        var li = document.createElement('li')
        // 现有li 才能赋值
        // console.log(text.value); // 留言的内容
        li.innerHTML = text.value + "<a href='javascript:;'>删除</a>"

        // 添加元素节点 insertBefore 添加的元素永远在一个行
        ul.insertBefore(li, ul.children[0])
        // 添加留言完成之后,清空原来输入框的内容
        text.value = ''

        // 删除元素节点 删除的是当前链接的父节点
        var as = document.querySelectorAll('a');
        for (var i = 0; i < as.length; i++) {
            as[i].onclick = function () {
                // node.removeChild(child) 删除的是li  当前a所在的li  this.parentNode
                // li 的父节点是ul ul.removeChild()
                ul.removeChild(this.parentNode);
            }
        }
    }
}
1.4.9 复制节点(克隆节点)

语法:node.cloneNode() 返回调用该方法的节点的一个副本,也成为克隆节点/拷贝节点

<ul>
    <li>22</li>
    <li>33</li>
    <li>44</li>
</ul>
var ul = document.querySelector('ul');
// 复制ul下面第一个孩子li的节点
var lili = ul.children[0].cloneNode(true)
// 添加到ul下的li列表的末尾
ul.appendChild(lili)
// 添加到ul下的li列表的第一个li的前面
ul.insertBefore(lili, ul.children[0])

注意:

  • 如果括号内的参数为或者为false,则是浅拷贝,只克隆复制节点本身,只复制标签不复制里面的内容
  • 如果括号内为true,则是深拷贝复制里面的标签和内容
1.4.10 动态生成表格(综合案例)

案例分析:

1、因为里面的数据是动态的,需要js动态生成。先模拟数据,定义好相应的数据,数据采取数组对象的形式存储

2、所有的数据都是放在tbody里面的行里面

3、因为行很多,需要循环创建多个行(多少人创建多少行)

4、每一个行里面有多个单元格(对应里面的数据),需要使用循环创建多个单元格,并且把数据存入里面(双重for循环)

5、最后一列单元格是删除,需要单独创建单元格

6、点击a删除当前a所在的行(链接的父级的父级) node.removeChild(child),当前a的父级是列,列的父级是行,删除一整行: this.parentNode.parentNode

for(k in obj){
    // k 得到的是属性名
    // obj[k] 得到的是属性值
}
// 动态生成表格案例
<table cellspacing="0">
    <thead>
        <tr>
            <th>姓名</th>
            <th>科目</th>
            <th>成绩</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody></tbody>
</table>
// 1.先准备数据
var datas = [    {        name: '王权',        subject: 'JavaScript',        score: 100    },    {        name: '沉稳五',        subject: 'JavaScript',        score: 90    },    {        name: '灵梦',        subject: 'JavaScript',        score: 80    }]
// 2.往tbody里面创建行:多少人创建多少行,通过数组的长度创建几行
var tbody = document.querySelector('tbody');
for (var i = 0; i < datas.length; i++) { // 外面的for循环决定多少行
    // 创建trvar tr = document.createElement('tr');
    // 在父节点tbody下添加子节点tr
    tbody.appendChild(tr)

    // 3.行里面创建单元格 td,单元格的数量取决于对象里面的属性个数 ,得到数组对象属性个数,也就是单元格列的个数
    // for循环遍历对象(每个datas数组里面的元素对象都要遍历一遍,得到每一个元素对象属性的个数)
    for (var k in datas[i]) { // 里面的for循环决定多少列 td
        // 创建单元格列
        var td = document.createElement('td');
        // 把对象里面的属性值给td  
        td.innerHTML = datas[i][k];
        // 单元格放在行里面
        tr.appendChild(td);
    }
    // 4.创建删除的单元格
    var td = document.createElement('td');
    // 给删除的单元格添加删除的文字
    td.innerHTML = "<a href='#'>删 除</a>"
    // 将删除的单元格放入行中
    tr.appendChild(td)
}
// 5.删除单元格操作
var as = document.querySelectorAll('a')
for (var i = 0; i < as.length; i++) {
    as[i].onclick = function () {
        // 点击a删除当前a所在的行(链接的父级的父级) node.removeChild(child)
        // 当前a的父级是列,列的父级是行,删除一整行: this.parentNode.parentNode
        tbody.removeChild(this.parentNode.parentNode)
    }
}
1.4.11 三种动态创建元素的区别

www.bilibili.com/video/BV1Sy…

  • document.write()(了解):直接将内容写入页面的内容流。
// 例如:点击按钮在文档中写入一个内容是123div,按钮点击之后,文档重绘,其他的内容都没有了,只有这个123div
document.write('<div>123</div>')
  • element.innerHTML:创建元素
  • document.createElement():创建元素

区别:

  • document.write() 正常下是没有问题的,但是当文档流执行完毕,则它会导致页面全部重绘(原来的内容就没有了)
  • innerHTML 是将内容写入某个DOM节点,不会导致页面全部重绘
  • innerHTML 创建多个元素效率更高(不要拼接字符串,采取数组push形式拼接,再转换成字符),结构稍微复杂
  • createElement() 创建多个元素效率稍微低一点,但是结构更清晰

总结:不同浏览器下,innerHTML效率比createElement高

2 事件高级

2.1 注册(绑定)事件的方式

给元素添加事件,称为注册事件或者绑定事件

注册事件的两种方式:

  • 传统的方式
  • 方法监听注册方式
2.1.1 传统注册方式

传统注册方式:

  • 利用on开头的事件 onclick
  • <button onclick='alert('99')'></button>
  • btn.onclick = function(){}
  • 特点:注册事件的唯一性
  • 同一个元素同一个事件只能设置一个处理函数,后面注册的处理函数会覆盖前面注册的处理函数
2.1.2 方法监听注册方式

方法监听注册方式:

  • W3C标准 推荐方式
  • addEventListener() 方法
  • IE9之前的IE不支持此方法,可用attachEvent() (不提倡用此方法)代替
  • 特点:同一个元素同一个事件可注册(绑定)多个监听器(处理函数function(){})
  • 按照绑定的顺序依次执行
2.1.3 addEventListener 事件监听方式

语法:eventTarget.addEventListener(type, listener[, useCapture]) 将指定的监听器绑定到eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数。

该方法接收三个参数:

  • type:事件类型字符串(要加引号)。比如:click、mouseover等,注意这里不需要带on
  • listener:事件处理函数,事件发生时,会调用该函数
  • useCapture:是否阻止冒泡,可选参数,布尔值,默认是false
// 示例
<button>按钮</button>

var btn = document.querySelector('button');
btn.addEventListener('click', function () {
    console.log('按钮');
})

2.2 删除事件(事件解绑)

2.2.1 传统注册事件的事件解绑

传统注册事件的事件解绑:eventTarget.onclick = null;

// 示例 只能点击一次,点完第一次 btn.onclick = null  事件解绑
<button>按钮</button>
btn.onclick = function () {
    console.log('按钮');
    btn.onclick = null
}
2.2.2 方法监听注册方式

方法监听注册方式:eventTarget.removeEventListener(type, listener[, useCapture])

// 示例
<button>按钮</button>
var btn = document.querySelector('button');
btn.addEventListener('click', fn) // 里面的fn  调用不需要加 小括号
function fn() {
    console.log('按钮');
    btn.removeEventListener('click', fn)
}

注意:removeEventListener 方法解除事件绑定,必须告诉他解除哪个处理函数,前面绑定的函数可单独写出来

2.3 DOM事件流(重要)

www.bilibili.com/video/BV1Sy…

事件流:描述的是从页面中接收事件的顺序

事件发生时会在元素节点之间按照特定的顺序传播,这个传播的过程称为DOM事件流DOM事件流:事件传播的过程

DOM事件流分为三个阶段:

  • 捕获阶段:网景最早提出,由DOM最顶层节点开始,然后逐级向下传播到最具体的元素接收的过程
  • 当前目标阶段
  • 冒泡阶段:IE最早提出,事件开始时由具体的元素接收,然后逐级向上传播到DOM最顶层节点的过程

注意:

  • JS代码中,只能执行捕获或者冒泡其中的一个阶段
  • onclick、attachEvent只能得到冒泡阶段
  • addEventListener(type, listener[, useCapture]) 第三个参数如果是true,表示事件捕获阶段调用事件处理程序;如果是false(不写默认是false),表示事件冒泡阶段调用事件处理程序
  • 在实际开发中,很少使用事件捕获,更关注事件冒泡
  • 有些事件是没有冒泡的。比如:onblur、onfocus、onmouseenter、onmouseleave

2.4 事件对象

<button>按钮</button>
var btn = document.querySelector('button');
btn.onclick = function (event) {
    console.log(event);
}
btn.addEventListener('click', function(event){
    console.log(event);
})
  • event 就是一个事件对象,写在侦听函数的小括号里面,当形参来看
  • 事件对象只有有了事件才会存在,它是系统自动创建的,不需要我们传递参数
  • 事件对象:是事件的一系列相关数据的集合,跟事件相关的。比如:鼠标点击,里面包含了鼠标的相关信息(鼠标坐标等);如果是键盘事件,里面则包含的是键盘事件相关的信息(比如判断用户按下了哪个键)
  • 事件对象简单理解:事件发生后,跟事件相关的一系列信息数据的集合都放在这个对象里面,这个对象就是事件对象event,它有很多属性和方法
  • 事件对象可自己命名。比如:event、evt、e(常用)
  • 事件对象由兼容性问题。IE6/7/8通过window.event

事件对象兼容性写法:

btn.onclick = function (e) {
    e = e || window.event;
    console.log(e);
}

2.5 e.target 和 this 的区别

e.target 返回的是触发事件的对象(元素)

<div>123</div>
var div = document.querySelector('div')
div.addEventListener('click', function (e) {
    console.log(e.target); // <div>123</div>
    console.log(this);// <div>123</div>
})

区别:

  • e.target 返回的是触发事件的对象(元素),this返回的是绑定事件的对象(元素)
  • e.target 点击了哪个元素,就返回哪个元素;this 哪个元素绑定了,就返回哪个元素

2.6 阻止默认行为

组织默认行为,比如:让链接不跳转,让提交按钮不提交

www.bilibili.com/video/BV1Sy…

2.7 阻止事件冒泡

事件冒泡:事件开始时由具体的元素接收,然后逐级向上传播到DOM最顶层节点的过程

阻止事件冒泡:

  • 标准写法(推荐):利用事件对象里面stopPropagation()方法
  • 非标准写法:IE6-8 利用事件对象cancelBubble属性
// 示例
btn.addEventListener('click', function (e) {
    e.stopPropagation(); // 阻止事件冒泡
    e.cancelBubble = true // 阻止事件冒泡
})

2.8 事件委托(代理、委派)

事件委托的原理:不是每个子节点单独设置事件监听器,而是事件监听器设置在父节点上,然后利用冒泡原理影响每个子节点

事件委托的作用:只操作一次DOM,提高了程序的性能

下面案例:给ul绑定点击事件,然后利用事件对象的target来找到当前所点击的li,因为点击li,事件会冒泡到ul上,ul有注册事件,就会触发事件监听器

 <ul>
    <li>111</li>
    <li>222</li>
    <li>333</li>
</ul>
var ul = document.querySelector('ul');
ul.addEventListener('click', function (e) {
    // alert('弹框~~');
    // e.target 可得到点击的对象
    console.log(e.target);
})

2.9 鼠标事件对象

获取鼠标在页面中的坐标

event对象代表事件的状态,跟事件相关的一系列信息的集合。

鼠标事件:MouseEvent

2.9.1 获取鼠标的坐标
鼠标事件对象说明
e.clientX返回鼠标相对于浏览器窗口可视区的x坐标
e.clientY返回鼠标相对于浏览器窗口可视区的y坐标
e.pageX返回鼠标相对于文档页面的x坐标 IE9+支持
e.pageY返回鼠标相对于文档页面的y坐标 IE9+支持
e.screenX返回鼠标相对于电脑屏幕的X坐标
e.screenY返回鼠标相对于电脑屏幕的y坐标
// 示例
document.addEventListener('click', function(e){
    // 1.client 鼠标在可视区的x和y坐标
    console.log(e.clientX);
    console.log(e.clientY);
    // 2.page 鼠标在页面文档的x和y坐标
    console.log(e.pageX);
    console.log(e.pageY);
    // 3.screen 鼠标在电脑屏幕的x和y坐标
    console.log(e.screenX);
    console.log(e.screenY);
})
2.9.2 跟随鼠标移动的div(案例)

案例分析:

1、鼠标不断的移动,使用鼠标移动事件:mousemove

2、在页面中移动,给document注册事件

3、div要移动距离,而且不占位置,使用绝对定位即可

4、核心原理:每次鼠标移动,都会获得最新的鼠标坐标,把这个x和y坐标作为图片的top和left值就可移动图片

div {
    width: 100px;
    height: 100px;
    background-color: red;
    position: absolute;
}
<div></div>
var div = document.querySelector('div')
// mousemove 只要鼠标移动1px,就会触发这个事件
document.addEventListener('mousemove', function (e) {
    // e.pageX e.pageY 鼠标在页面文档的x和y坐标
    var x = e.pageX;
    var y = e.pageY;
    // console.log(x, y);
    // 拼接单位px
    div.style.left = x - 50 + 'px';
    div.style.top = y - 50 + 'px';

})

2.10 键盘事件

2.10.1 keyup、keydown、keypress
键盘事件说明
onkeyup某个键盘按键被松开时触发
onkeydown某个键盘按键被按下时触发
onkeypress某个键盘按键被按下时触发,但是不识别功能键,比如ctrl、shift、箭头等
// 示例
document.addEventListener('keyup', function () {
    console.log('弹起~');
})
document.addEventListener('keydown', function () {
    console.log('按下~');
})
document.addEventListener('keypress', function () {
    console.log('按下keypress~');
})

注意:

  • 传统方式需要加on,addEventListener不需要加on
  • onkeypress和前两个的区别是:不识别功能键。比如ctrl、左右箭头、shift等
  • 三个执行顺序:keydown -> keypress -> keyup
2.10.2 keyCode判断用户按下哪个键

键盘事件对象中的keyCode属性可用得到相应键的ASCII码值

document.addEventListener('keyup', function (e) {
 	console.log(e.keyCode);
})
document.addEventListener('keydown', function (e) {
    console.log(e.keyCode);
})
document.addEventListener('keypress', function (e) {
   console.log(e.keyCode);
})

注意:

  • keyup 和 keydown事件不区分大小写,a 和 A 得到的ASCII码值都是65
  • keypress 事件区分大小写,a 97 ,A 65

3 DOM

3.1 DOM概述

DOM:浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象,其核心对象时window

DOM和BOM的区别:

DOMBOM
文档对象模型浏览器对象模型
DOM就是把文档当作一个对象来看待BOM把浏览器当作一个对象来看待
DOM的顶级对象是documentBOM的顶级对象是window
DOM主要学习的是操作页面元素BOM学习的是浏览器窗口交互的一些对象
DOM是W3C标准规范BOM是浏览器厂商在各自浏览器上定义的,兼容性较差

BOM比DOM更大,BOM包含DOM

window对象是浏览器的顶级对象:

  • 它是JS访问浏览器窗口的一个接口
  • 它是全局对象,定义在全局作用域中的变量、函数都会变成window对象的属性和方法

3.2 页面加载事件

语法(两种):

  • 传统:window.onload = function(){}
  • 方法:window.addEventListener('load', function(){})

window.onload 是窗口(页面)加载事件,当文档内容完全加载完成之后(包括图像、文字

css等),会触发该事件,就调用处理函数。

<script>
    window.onload = function (){
		 // js代码
    }
</script>

注意:

  • 有了window.onload就可以把JS代码写在页面元素的任何地方,因为onload是等页面内容全部加载完毕再去执行处理函数的
  • window.onload传统注册事件方式只能写一个,如果有多个,后面的会覆盖前面的,以最后一个为准
  • 如果使用addEventListener,则没有限制

3.3 调整浏览器窗口大小事件

语法:window.onresize = function(){}或者 window.addEventListener('resize', function(){})

window.onresize 是调整浏览器窗口大小时加载的事件,当触发时就调用处理函数

window.addEventListener('resize', function () {
    console.log('窗口尺寸变化了');
     console.log(window.innerWidth); // 当前屏幕的宽度
})

注意:

  • 只要窗口大小发生变化,就会触发这个事件
  • 可利用这个事件完成响应式布局,配合 window.innerWidth获取当前屏幕的宽度

3.4 定时器

两种定时器:

  • setTimeout()
  • setInterval()
3.4.1 setTimeout() 定时器

语法:window.setTimeout(调用的函数, [延迟的毫秒数])

setTimeout() 方法用于设置一个定时器,该定时器会在定时器到期后执行调用函数,只调用一次就结束了这个定时器

// 示例一
setTimeout(function () {
    console.log('2秒到了');
}, 2000)

// 示例二
setTimeout(callback, 2000)
function callback() {
    console.log('2秒到了');
}

注意:

  • 这个window在调用时可省略
  • 延时的事件单位时毫秒(1000ms = 1s),可省略,如果省略为0,则立即调用
3.4.2 clearTimeout() 清除定时器

语法:window.clearTimeout(timeout ID)

clearTimeout()方法可取消setTimeout()建立的定时器

// 示例
<button> 按钮</button>
var timer = setTimeout(function () {
	console.log('2秒到了');
}, 2000)
var btn = document.querySelector('button');
btn.onclick = function(){
    // 清除定时器
    clearTimeout(timer);
}
3.4.3 setInterval() 定时器

语法:window.setInterval(回调函数,[间隔的毫秒数])

setInterval() 方法重复调用一个函数,每隔一段时间(设置的间隔时间),就去调用一次

setInterval(function () {
    console.log('111');
}, 1000)

注意:

  • 这个window在调用时可省略
  • 延时的事件单位时毫秒(1000ms = 1s),可省略,如果省略为0,则立即调用
3.4.4 clearInterval 清除定时器

语法:window.clearInterval(interval ID)

clearInterval() 方法清除通过setInterval创建的定时器

// 示例
<button> 按钮</button>
var timer = setInterval(function () {
	console.log('2秒到了');
}, 2000)
var btn = document.querySelector('button');
btn.onclick = function(){
    // 清除定时器
    clearInterval(timer);
}
3.4.5 倒计时(案例)

案例分析:

1、因为时间是不断变化的,因此需要定时器来自动变化(setInterval)

2、三个盒子里面分别存放时、分、秒

3、三个盒子利用innerHTML放入计算出来的时、分、秒

<div class="hour"></div>
<div class="minute"></div>
<div class="second"></div>

var hour = document.querySelector('.hour');
var minute = document.querySelector('.minute');
var second = document.querySelector('.second');
var inputTime = +new Date('2022-6-5 18:00:00')
countDown(); // 先调用一次时间函数,防止页面刷新出现空白的情况
// 开启定时器,每隔一秒调用一次
setInterval(countDown, 1000)
// 计算 倒计时 时间的函数
function countDown() {
    var nowTime = +new Date(); // 括号内为空,则返回的是当前时间的总毫秒数

    /* 输入的时间 - 现在时间的总毫秒,再除以1000将毫秒转换为秒 */
    var times = (inputTime - nowTime) / 1000; // times 是剩余时间的总秒数,1s = 1000ms

    var h = parseInt(times / 60 / 60 % 24); // 小时
    var m = parseInt(times / 60 % 60);// 分钟
    var s = parseInt(times % 60); // 秒数

    h = h < 10 ? '0' + h : h;
    m = m < 10 ? '0' + m : m;
    s = s < 10 ? '0' + s : s;

    hour.innerHTML = h;
    minute.innerHTML = m;
    second.innerHTML = s;
}

3.5 this 指向问题

  • 全局作用域或者普通函数中的this指向全局对象window,定时器里面的this也指向window
  • 方法调用中谁调用this指向谁
  • 构造函数中this指向构造函数的实例

3.6 JS执行队列(重要)

3.6.1 JS是单线程

JavaScript语言的一大特点就是单线程,也就是说:同一个时间只能做一件事

这是因为JavaScript这门脚本语言诞生的使命所致--JavaScript是为处理页面中用户的交互,以及操作DOM而诞生的。

比如:对某个DOM元素进行添加和删除操作,不能同时进行,要先添加后删除

3.6.2 同步和异步

为了解决代码一行一行执行,可能会导致后面的代码堵塞的问题,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程。

于是,JavaScript出现了同步异步

同步:前一个任务结束后再执行后面的任务,程序的执行顺序与任务的排列顺序是一致的、同步的,会出现代码阻塞的情况。

比如:做饭的同步做法:烧水煮饭,等水开了(10分钟之后),再去切菜、炒菜。

异步:同时做多个任务,多个任务一块进行

比如:做饭的异步做法:烧水煮饭,烧水的同时,去切菜、炒菜。

3.6.3 同步异步任务执行过程
同步任务异步任务
同步任务都在主线程上执行,形成一个执行栈js异步是通过回调函数实现的
一般而言,异步任务有以下三种类型:
1、普通事件。如:click、resize等
2、资源加载。如:load、error等
3、定时器,包括setInterval、setTimeout等
异步任务相关回调函数添加到任务队列中(任务队列也叫消息队列

执行机制:

1、把JS任务分为同步任务和异步任务,同步任务放在执行栈中,异步任务放在任务队列中(消息队列)

1、先执行执行栈中的同步任务

2、再去任务队列里面去看有没有异步任务,如果有则把任务队列里面的异步任务放到执行栈中执行

3、一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行

console.log(1);
setTimeout(() => {
    console.log(2);
}, 0)
console.log(3);
// 输出顺序:1 3 2

js执行机制

3.6.4 异步进程处理

js执行机制(事件循环)

3.7 location 对象

window对象提供了一个location属性,用于获取或设置窗体的URL,并且可以用于解析URL

因为这个属性返回的是一个对象,所以这个属性也称为location对象

3.7.1 URL

URL:统一资源定位符,是互联网上标准资源的地址

互联网上每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它

URL的一般语法格式为:

protocol://host[:port]/path/[?query]#fragment
https://www.bilibili.com/video/BV1Sy4y1C7ha?p=281
组成说明
protocol通信协议。常用:http、ftp、maito等
host主机(域名) www.bilibili.com
port端口号,可选,省略时使用方案的默认端口 。如:http默认端口号为80
path路径。由零个或多个'/'符号隔开的字符串,一般用啦表示主机上的一个目录或文件地址
query参数。以键值对的形式,通过&符号分隔开
fragment片段 #后面内容,常见于链接 锚点
3.7.2 location 对象常见的属性
location对象返回值
location.href获取或设置 整个URL
location.host返回主机(域名) www.bilibili.com
location.port返回端口号。如果没有端口号,返回空字符串
location.pathname返回路径
location.search返回参数
location.hash返回片段 #后面内容 常见于链接 锚点

记住:location.href 获取或设置 整个URL 、 location.search 返回参数

3.7.3 5秒后跳转页面(案例)

获取当前URL:

<button>按钮</button>
<script>
    var btn = document.querySelector('button');
    btn.addEventListener('click', function () {
        console.log(location.href); //  file:///E:/practice/css/img/location.html
        // 修改url地址,当按钮点击时,则会跳转到修改过后的地址
        location.href = 'https://www.baidu.com/'
    })
</script>

5秒后跳转页面:

案例分析:

1、利用定时器做倒计时效果

2、时间到了,就跳转页面,使用location.href

<p></p>
<script>
    var p = document.querySelector('p');
    var timer = 5;
    setInterval(() => {
        if (timer == 0) {
            location.href = 'https://www.baidu.com/';
        } else {
            p.innerHTML = timer + '秒后跳转到百度页面'
            timer--;
        }
    }, 1000)
</script>
3.7.4 获取URL参数(案例)

案例分析:

1、第一个登录页面,里面由提交表单,action提交到index.html页面

2、第二个页面,可以使用第一个页面的参数,这样实现了一个数据不同页面之间的传递效果

3、第二个页面之所有可以使用第一个页面的数据,是利用了URL里面的location.search参数

4、第二个页面中,需要把这个参数提取

5、第一步去掉问好? 利用substr

6、第二步利用=等号分隔键和值 split('=')

// --login.html
<form action="index.html">
    用户名:<input type="text" name="username">
    <input type="submit" value="登录">
</form>

// --index.html
<div></div>
script>
    console.log(location.search); // ?username=andy
    // 1.去掉?问号 substr('起始位置', 截取几个字符)
    // 从字符串的第二个元素开始截取,substr()方法如果不写截取几个字符,默认后面的全部截取
    var params = location.search.substr(1);  // params = username=andy
	soncole.log(params); //  username=andy

    // 2.利用=等号 把字符串分割为数组  split('=')
    var arr = params.split('=')
    console.log(arr); //  ['username', 'andy']

    var div = document.querySelector('div')
    div.innerHTML = arr[1] + ' 欢迎您!'
</script>
3.7.5 location 常见方法
location方法返回值
location.assign()跟href一样,可以跳转页面(也称为重定向页面),记录浏览历史,可以后退页面
location.replace()替换当前页面,因为不记录历史,所以不能后退页面
location.reload()重新加载页面,相当于刷新按钮,或者F5。如果参数为true,强制刷新你ctrl+F5

3.8 navigator 对象

navigator对象:包含有关浏览器的信息

它有很多属性,最常用的是userAgent,该属性可以返回由客户机发送服务器的user-agent头部的值

前端代码可以判断用户在哪个终端(PC端、移动端等)打开页面,实现跳转

www.bilibili.com/video/BV1Sy…

3.9 history 对象

www.bilibili.com/video/BV1Sy…

window 对象提供了一个history对象,与浏览器历史记录进行交互

history对象包含用户(在浏览器窗口中)访问过的URL

history对象方法说明
back()可以后退页面功能
forward()前进页面功能
go(参数)前进后退功能。参数如果是1,则前进1个页面;如果参数是-1,则后退1个页面
// 示例
<a href='index.html'>点击去首页</a>
<button>前进</button>
<script>
    var btn = document.querySelectot('button');
	btn.addEventListener('click', function(){
        history.forward(); // 
    })
</script>

4 PC端网页特效

4.1 元素偏移量 offset系列

offset:偏移量,使用offset系列相关属性可动态的得到该元素的位置(偏移量)、大小等

  • 获得元素距离带有定位的父元素的位置
  • 获得元素自身的大小(宽高)
  • 注意:但会的数值都不带单位
4.1.1 offset系列常用属性
offset系列属性说明
element.offsetParent返回该元素带有定位的父元素,如果父元素没有定位,则返回body
element.offsetTop返回元素相对带有定位的父元素上方的偏移
element.offsetLeft返回元素相对带有定位的父元素左边框的偏移量
element.offsetWidth返回自身包括padding、边框、内容的宽度,返回数值不带单位
element.offsetHeight返回自身包括padding、边框、内容的高度,返回数值不带单位
4.1.2 offsetLeft 、offsetTop

www.bilibili.com/video/BV1Sy…

offsetLeft 、offsetTop返回元素相对于带有定位的父元素上方/左边的偏移,如果父级没有定位,则以body为准距离上方/左方的距离是多少

<style>
    * {
        padding: 0;
        margin: 0;
    }

    .father {
        width: 100px;
        height: 100px;
        background-color: red;
        margin-left: 100px;
    }

    .son {
        width: 50px;
        height: 50px;
        background-color: rgb(51, 61, 197);
        margin-left: 10px;
    }
</style>

<div class="father">
    <div class="son"></div>
</div>

<script>
    var father = document.querySelector('.father');
    var son = document.querySelector('.son')
    console.log(father.offsetTop); // 0
    console.log(father.offsetLeft); // 100 因为father  margin-left: 100px; 距离左侧100的距离
	// 如果父级没有加定位 是 0 110 ,加了定位是 0 10
    console.log(son.offsetTop); 
    console.log(son.offsetLeft);
</script>
4.1.3 offsetWidth、offsetHeight

offsetWidth、offsetHeight 返回自身包括padding、边框、内容的宽度/高度,返回数值不带单位

当盒子没有宽度时,盒子随之浏览器窗口大小变化而变化,offsetWidth、offsetHeight可以动态的获取盒子的大小

.test {
    width: 100px;
    height: 100px;
    padding: 10px;
    border: 10px solid pink;
    background-color: red;
}
<div class="test"></div>
<script>
    var test = document.querySelector('.test')
    console.log(test.offsetWidth); // 140 offsetWidth = width + padding + border 
    console.log(test.offsetHeight); // 140
</script>
4.1.4 offsetParent

offsetParent 返回该元素带有定位的父元素,如果父元素没有定位,则返回body

div class="father">
    <div class="son"></div>
</div>
<script>
    console.log(son.offsetParent); // div class="father"><div class="son"></div></div>
	console.log(son.parentNode);// 
</script>

offsetParent 和 parentNode的区别:

  • offsetParent:返回带有定位的父级,没有没有一直往上找,直到body
  • parentNode:不管有没有定位,返回其父级
4.1.5 offset 和 style 的区别

www.bilibili.com/video/BV1Sy…

offsetstyle
offset可得到任意样式表中的样式值style只能得到行内样式表中的样式值
offset系列获得的数值没有单位style.width 获得的时带单位的字符串
offsetWidth包含 padding + border + widthstyle.width 不包含padding + border
offsetWidth 等属性是只读属性,只能获取不能赋值(不能修改)style.width 是可读写属性,可获取也可赋值修改
所以,想要获取元素大小位置:offset所以:想要修改元素的值:style
4.1.6 鼠标在盒子内的坐标(案例)

案例分析:

1、在盒子内移动,得到鼠标距离盒子左右的距离(盒子添加mousemove鼠标移动事件)

2、首先,得到鼠标在页面中的坐标(e.pageX e.pageY)

3、其次,盒子在页面中的距离(box.offsetLeft box.offsetTop)

<style>
    * {
        margin: 0;
        padding: 0;
    }

    .box {
        width: 500px;
        height: 500px;
        margin: 0 auto;
        background-color: red;
    }
</style>

<div class="box"></div>

<script>
    var box = document.querySelector('.box')
    box.addEventListener('mousemove', function (e) {
        var x = e.pageX - this.offsetLeft;
        var y = e.pageY - this.offsetTop;
        console.log(box.offsetLeft, box.offsetTop);
        box.innerHTML = 'x坐标是:' + x + 'y坐标是:' + y
    })
</script>
4.1.7 拖拉拽(案例)

www.bilibili.com/video/BV1Sy…

拖拉拽的原理:鼠标按下(mousedown) + 鼠标移动(mousemove) + 松开鼠标(mouseup)

案例分析:

1、拖拉拽过程:鼠标移动过程中,获得最新的值,再赋值给盒子的left和top值,div就可以跟着鼠标移动

2、鼠标在页面的坐标 - 鼠标在盒子内的坐标 = 盒子移动时的坐标

3、鼠标按下:得到鼠标在盒子中的坐标

4、鼠标移动:盒子的坐标 = 鼠标坐标 - 盒子坐标 。注意移动事件要写在鼠标按下的事件里面

5、鼠标松开:停止拖拽,解除鼠标移动事件

<style>
    * {
        margin: 0;
        padding: 0;
    }

    .box {
        width: 200px;
        height: 200px;
        background-color: red;
        position: absolute;
    }
</style>

<div class="box"></div>

<script>
    var box = document.querySelector('.box')
    // 1.当鼠标按下,获取鼠标再盒子内的坐标
    box.addEventListener('mousedown', (e) => {
        // 鼠标在盒子内的坐标 = 鼠标在页面中的坐标 - 盒子在页面中的坐标
        var x = e.pageX - box.offsetLeft;
        var y = e.pageY - box.offsetTop;
        console.log(e.pageX, e.pageY, box.offsetLeft, box.offsetTop);
        // 2.鼠标移动的时候,把鼠标在页面中的坐标,减去鼠标在盒子内的坐标,得到盒子的left和top值
        document.addEventListener('mousemove', move)
        function move(e) {
            box.style.left = e.pageX - x + 'px';
            box.style.top = e.pageY - y + 'px';
        }
        // 3.鼠标松开,移除鼠标移动事件
        document.addEventListener('mouseup', () => {
            document.removeEventListener('mousemove', move)
        })
    })
</script>
4.1.8 放大镜效果(案例)

www.bilibili.com/video/BV1Sy…

  • 整个案例分为三个模块

  • 鼠标经过小图片盒子,黄色遮挡层和大图片盒子显示;鼠标离开两个盒子隐藏

    • 鼠标经过:mouseover
    • 鼠标离开:mouseout
    • 显示隐藏:display:block display:none
  • 黄色的遮挡层跟随鼠标移动

    • 把鼠标坐标给遮挡曾不合适,因为遮挡层的坐标以父盒子为准
    • 首先,获得鼠标在盒子的坐标
    • 其次,把数值给遮挡层的 left 和 top 值
    • 此时,用到鼠标移动事件,但是还是在小图片盒子内移动
    • 为了使鼠标在遮罩层的中间,需要减去遮罩层宽高的一半 offsetWidth、offsetHeight获取盒子的宽高
    • 遮挡层不能超过小盒子的范围(如果小于0,就把坐标设置为0)
    • 遮挡层最大的移动距离:小图片盒子宽度 减去 遮挡层盒子宽度
  • 移动黄色遮挡层,大图片跟随移动

    • 求大图片移动的公式:大图片移动距离 = 遮挡层移动的距离 * 大图片最大移动距离 / 遮挡层最大移动距离
    • www.bilibili.com/video/BV1Sy…
<style>
    .box {
        position: relative;
        width: 502px;
        height: 285px;
        border: 1px solid red;

    }

    .mask {
        display: none;
        position: absolute;
        top: 0;
        left: 0;
        width: 150px;
        height: 150px;
        background-color: rgb(235, 238, 82, 0.4);
        /* 鼠标经过变成移动的样式 */
        cursor: move;

    }

    .enlarge {
        display: none;
        position: absolute;
        top: 0;
        left: 510px;
        width: 300px;
        height: 285px;
        border: 1px solid red;
        overflow: hidden;
    }

    .enlarge img {
        position: absolute;
        top: 0;
        left: 0;
    }
</style>

<div class="box">
    <img src="./img/大数据热点图.jpg" alt="" class="img1">
    <div class="mask"></div>
    <div class="enlarge">
        <img src="./img/大数据热点图.jpg" alt="" class="img2">
    </div>
</div>
<script>
    var box = document.querySelector('.box')
    var mask = document.querySelector('.mask');
    var enlarge = document.querySelector('.enlarge');
    var img1 = document.querySelector('.img1');

    // 鼠标经过显示遮罩层和大图片盒子
    box.addEventListener('mouseover', () => {
        mask.style.display = 'block';
        enlarge.style.display = 'block';
    })
    // 鼠标经过隐藏遮罩层和大图片盒子
    box.addEventListener('mouseout', () => {
        mask.style.display = 'none';
        enlarge.style.display = 'none';
    })
    // 
    box.addEventListener('mousemove', (e) => {
        // 先计算出鼠标在盒子内的坐标
        var x = e.pageX - box.offsetLeft;
        var y = e.pageY - box.offsetTop;
        // console.log(x,y);

        // 把鼠标在盒子内的坐标赋值给遮罩层的left和top值
        // 为了使鼠标在遮罩层的中间,需要减去遮罩层宽高的一半 offsetWidth、offsetHeight获取盒子的宽高
        var maskX = x - mask.offsetWidth / 2;
        var maskY = y - mask.offsetHeight / 2

        // 遮挡层最大的移动距离:小图片盒子宽度 减去 遮挡层盒子宽度
        var maskXMax = box.offsetWidth - mask.offsetWidth;
        var maskYMax = box.offsetHeight - mask.offsetHeight;
        // console.log(maskXMax, maskYMax);
        if (maskX < 0) {
            maskX = 0
        } else if (maskX >= maskXMax) {
            maskX = maskXMax
        }
        if (maskY < 0) {
            maskY = 0
        } else if (maskY >= maskYMax) {
            maskY = maskYMax
        }

        mask.style.left = maskX + 'px';
        mask.style.top = maskY + 'px';

        // 大图片移动距离 = 遮挡层移动的距离 * 大图片最大移动距离 / 遮挡层最大移动距离
        // 大图
        var img2 = document.querySelector('.img2');
        // 大图片最大移动的距离
        var enlargeMax = img2.offsetWidth - enlarge.offsetWidth;
        // 大突破的移动距离
        var enlargeX = maskX * enlargeMax / maskXMax;
        var enlargeY = maskY * enlargeMax / maskYMax;
        img2.style.left = -enlargeX + 'px';
        img2.style.top = -enlargeY + 'px';
    })
</script>

4.2 元素可视区 client 系列

client 就是客户端,client 系列的相关属性来获取元素可视区的相关信息。

通过client系列的相关属性可动态的得到该元素的边框大小、元素大小等

client系列说明
element.clientTop返回元素上边框的大小
element.clientLeft返回元素左边框的大小
element.clientWidth返回自身包括padding、内容的宽度,不含边框,返回的数值不带单位
element.clientHeight返回自身包括padding、内容的高度,不含边框,返回的数值不带单位
4.2.1 立即执行函数

立即执行函数:不需要调用,立马能够自己执行的函数,也可以传递参数

立即执行函数最大的作用:独立创建了一个作用域,里面所有的变量都是局部变量,不会有命名冲突的情况

两种写法语法:

  • (function(){})()
(function(){
   console.log(2) // 2
})(); // 第二个小括号可看做是调用该函数

(function(a){
   console.log(a); // 1
})(1); // 也可以传参
  • (function(){}())
(function () {
    console.log(3); // 3
}());

(function sum(a) { // 函数有名字也不影响
    console.log(a); // 4
}(3));

4.3 元素滚动 scroll 系列

scroll:滚动事件,使用scroll系列的相关属性可动态的得到该元素的大小、滚动的距离等

scroll系列属性说明(返回数值都不带单位)
element.scrollTop返回被卷去的上册距离
element.scrollLeft返回被卷去的左侧距离
element.scrollWidth返回自身实际的宽度,不含边框
element.scrollHeight返回自身实际的高度,不含边框
// 示例
<style>
    div {
        width: 200px;
        height: 100px;
        border: 1px solid red;
        overflow: auto;
    }
</style>

<div>
    文字文字文字文字文字文字
    文字文字文字文字文字文字
    文字文字文字文字文字文字
    文字文字文字文字文字文字
    文字文字文字文字文字文字
    文字文字文字文字文字文字
    文字文字文字文字文字文字
</div>
<script>
    var div = document.querySelector('div');
	// scroll 滚动事件,当滚动条发生变化时,会触发该事件
    div.addEventListener('scroll', () => {
        console.log(div.scrollTop);
    })
</script>

4.4 offset、client、scroll总结

三个系列都可返回元素的大小,返回值都不带单位

三个系列大小对比作用
element.offsetWidth返回自身包括padding、边框、内容区的宽度
element.clientWidth返回自身包括padding、内容区的库纳杜
element.scrollWidth返回自身实际的宽度,不含边框(包含了文字超出的部分)

区别:

  • offsetWidth 包含了边框
  • clientWidth 值返回元素的高度,但是如果元素内容超过了盒子的大小,则用scrollWidth可得到超出

三个系列的主要用法:

  • offset系列常用于获取元素位置(offsetLeft、offsetTop
  • client系列常用于获取元素大小(clientWidth、clientHeight
  • scroll系列常用于获取滚动距离(scrollTop、scrollLeft

注意:页面滚动距离通过window.pageXOffset获得

4.5 mouseenter 和 mouseover区别(面试题)

mouseenter 和 mouseover都是鼠标移动到元素上会触发的事件

区别:

  • mouseenter 鼠标经过只有自身盒子触发,不会冒泡
  • mousrover 鼠标经过自身盒子会触发,经过子盒子也会触发
  • 跟mouseenter搭配鼠标离开mouseleave同样不会冒泡

4.6 动画函数封装

核心原理:通过定时器setInterval()不断移动盒子位置

4.6.1 盒子移动动画(案例)

案例分析:

  • 获取盒子当前位置
  • 让盒子在当前位置上加上一个移动距离
  • 利用定时器不断重复这个操作
  • 加一个结束定时器的条件
  • 注意此元素需要添加定位,才能使用element.style.left
<style>
    * {
        margin: 0;
        padding: 0;
    }

    .box {
        position: absolute;
        left: 0;
        width: 100px;
        height: 100px;
        background-color: red;
    }
</style>

<div class="box"></div>
<script>
    var box = document.querySelector('.box');
    var timer = setInterval(() => {
        // window.innerWidth 当前设备的宽度
        if (box.offsetLeft >= window.innerWidth) {
            // 如果需要停止动画:停止动画的本质是  移除定时器
            // clearInterval(timer);

            // 如果想让动画移动到设备宽度就从头开始移动,则将left设置为0
            box.style.left = 0
        }
        box.style.left = box.offsetLeft + 5 + 'px';
    }, 30)
</script>
4.6.2 动画效果封装

此方法需要传递两个参数:动画的对象、目标位置

// 动画的对象obj, 目标位置target
function animate(obj, target) {
    var timer = setInterval(() => {
        if (obj.offsetLeft >= target) {
            // 如果需要停止动画:停止动画的本质是  移除定时器
            // clearInterval(timer);

            // 如果想让动画移动到设备宽度就从头开始移动,则将left设置为0
            obj.style.left = 0
        }
        obj.style.left = obj.offsetLeft + 5 + 'px';
    }, 30)
}
animate(box, window.innerWidth)

优化方案:www.bilibili.com/video/BV1Sy…

// 优化
function animate(obj, target, callback) {
    // 如果需要某个条件才动画,比如点击按钮动画执行,需要先清除一次定时器,
    // 以防按钮不断点击,元素移动的速度越来越快,因为开启了太多的定时器
    // clearInterval(obj.timer); // 先清除以前的定时器,只保留当前的一个定时器
    obj.timer = setInterval(() => {
        // 步长写在定时器里面
        // 把步长值改为整数,不要出现小数的问题
        // var step = Math.ceil((target - obj.offsetLeft) / 10);
        var step = (target - obj.offsetLeft) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step)

        if (obj.offsetLeft == target) {
            // 如果需要停止动画:停止动画的本质是  移除定时器
            clearInterval(obj.timer);

            // 如果想让动画移动到设备宽度就从头开始移动,则将left设置为0
            // obj.style.left = 0

            // 回调函数写在定时器里面
            if (callback) {
                // 调用传递进来的回调函数
                callback();
            }
        }
        // 把每次加1 这个步长值改为一个慢慢变小的值,步长公式:(目标值 - 现在的位置) / 10
        obj.style.left = obj.offsetLeft + step + 'px';
    }, 30)
}
animate(box, 500, function () {
    // 动画执行完毕后,执行回调函数, 此处代码调用
    box.style.backgroundColor = 'blue'
})

4.7 常见网页特效案例

4.7.1 banner轮播

案例分析:

  • 鼠标经过(mouseover、mouseenter)轮播模块,左右按钮显示,鼠标离开(mouseout、mouseleave)左右按钮隐藏

    • mouseover 鼠标经过自身盒子会触发,经过子盒子也会触发 ,对应mouseout
    • mouseenter 鼠标经过只有自身盒子触发,不会冒泡,对应mouseleave
    • 显示隐藏按钮display
  • 动态生成导航小圆点

    • 核心思路:导航小圆点要跟图片张数一致
    • 先得到ul里面图片的张数(图片放在li里面,li的个数也就是图片的个数)ul.children.length
    • 循环动态生成小圆圈(小圆圈放在ol里面)
    • 创建节点createElemeny('li')
    • 插入节点 ol.appendChild(li)
    • 把ol里面的第一个li设置类名为current:表示选中的效果以及表示展示的是第一张图片 ol.children[0].className = 'current'
    • 给导航小圆点添加点击事件:排他思想 干掉其他的li,留下当前点击的li
  • 点击右侧按钮播放下一张图片原理: 点击右侧按钮一次,图片往左播放一张,以此类推。左侧按钮同理

    • 声明一个变量num,点击一次,自增1,让这个变量 乘以 图片宽度,就是ul的滚动距离
    • 图片无缝滚动原理(滚动到最后一张,循环滚动):把ul第一个li复制一份放到ul的最后面,当图片滚动到复制的最后一张时,让ul快速的,不做动画的跳到最左侧:left为0
    • 同时num赋值为0,重新开始滚动图片
  • 图片播放的同时,导航小圆点跟随一起变化

    • 简单的做法:声明一个变量cricle,每次点击自增1。注意:左侧按钮也需要这个变量,因为要声明全局变量
    • 但是因为前面克隆了一张图片,现在图片多了一张,而小圆点的个数是之前图片的数量,所以必须加一个判断条件
    • 如果cricle == 之前图片的数量,就重新复原为cricle = 0
  • 点击导航小圆点,播放相应的图片

    • 点击导航小圆点是ul在移动,而不是ul里面的li(要提前给ul加定位)
    • 滚动图片的核心算法:点击某个小圆点,就让图片滚动,导航小圆点的索引号乘以图片的宽度作为ul移动的距离
  • 鼠标不经过轮播图,轮播图也会自动轮播图片

    • 定时器
    • 自动播放轮播图,实际上类似点击了右侧按钮
    • 此时使用手动调用右侧按钮点击事件 next.click()
    • 鼠标经过banner,停止定时器
    • 鼠标离开banner,开启定时器
4.7.2 带有动画的返回顶部

滚动窗口至文档中的特定位置:window.scroll(x,y)

页面滚动了多少:window.pageYOffset

btn.addEventlistener('click', function(){
    // 里面的x,y不跟单位,直接写数字即可
	// window.scroll(0, 0)
    animate(window, 0); // 因为是窗口滚动,所以对象是window
})

function animate(obj, target, callback) {
    // 如果需要某个条件才动画,比如点击按钮动画执行,需要先清除一次定时器,
    // 以防按钮不断点击,元素移动的速度越来越快,因为开启了太多的定时器
    clearInterval(obj.timer); // 先清除以前的定时器,只保留当前的一个定时器
    obj.timer = setInterval(() => {
        // 步长写在定时器里面
        // 把步长值改为整数,不要出现小数的问题
        // var step = Math.ceil((target - obj.offsetLeft) / 10);
        var step = (target - window.pageYOffset) / 10;
        step = step > 0 ? Math.ceil(step) : Math.floor(step)

        if (window.pageYOffset == target) {
            // 如果需要停止动画:停止动画的本质是  移除定时器
            clearInterval(obj.timer);

            // 如果想让动画移动到设备宽度就从头开始移动,则将left设置为0
            // obj.style.left = 0

            // 回调函数写在定时器里面
            // if (callback) {
            //     // 调用传递进来的回调函数
            //     callback();
            // }
            callback && callback();
        }
        // 把每次加1 这个步长值改为一个慢慢变小的值,步长公式:(目标值 - 现在的位置) / 10
        // obj.style.left = obj.offsetLeft + step + 'px';
        window.scroll(0,window.pageYOffset + step)
    }, 30)
}

5 本地存储

5.1 本地存储特性

特性:

  • 数据存储在用户浏览器中
  • 设置、读取方便、页面刷新不会丢失数据
  • 容量较大,sessionStorage约5M、localStorage约20M
  • 只能存储字符串,可以将对象JSON.stringify()编码后存储

5.2 sessionStorage

sessionStorage:

  • 生命周期为关闭浏览器窗口(关闭浏览器窗口消失)
  • 在同一个窗口(页面)下数据可共享
  • 以键值对的形式存储使用

存储数据:sessionStorage.setItem(key, value)

set.addEventListener('click', function () {
    var val = inputBox.value
    sessionStorage.setItem('userName', val)
    sessionStorage.setItem('password', val)
})

获取数据:sessionStorage.getItem(key)

get.addEventListener('click', function () {
    console.log(sessionStorage.getItem('userName'));
})

删除数据:sessionStorage.removeItem(key)

remove.addEventListener('click', function () {
    sessionStorage.removeItem('userName')
})

删除所有数据:sessionStorage.clear()

del.addEventListener('click', function () {
    sessionStorage.clear()
})

2.3 localStorage

localStorage:

  • 生命周期永久生效,除非手动删除,否则关闭页面也会存在
  • 可以多窗口(页面)共享(同一浏览器)

存储数据:localStorage.setItem(key, value)

set.addEventListener('click', function () {
    var val = inputBox.value
    localStorage.setItem('userName', val)
    localStorage.setItem('password', val)
})

获取数据:localStorage.getItem(key)

get.addEventListener('click', function () {
    console.log(localStorage.getItem('userName'));
})

删除数据:localStorage.removeItem(key)

remove.addEventListener('click', function () {
    localStorage.removeItem('userName')
})

删除所有数据:localStorage.clear()

del.addEventListener('click', function () {
    localStorage.clear()
})

6 移动端特效

www.bilibili.com/video/BV1Sy…

6.1 触屏事件

移动端浏览器兼容性较好,不需要考虑JS的兼容性问题,可放心食用原生js

移动端也独特的地方,比如:触屏事件touch(触屏事件) ,Android和IOS都有

touch对象代表一个触摸点。触摸点可能是手指,也可能是触摸笔。触屏事件可响应用户对屏幕或触控板的操作

常见的触屏事件:

触屏touch事件说明
touchstart触摸到DOM元素时触发
touchmove在DOM元素上滑动时触发
touchend在DOM元素上移开时触发

\