JavaScript 基础

JavaScript 介绍

JavaScript 定义

一种运行在客户端(浏览器)的编程语言,实现人机交互效果。

JavaScript 作用

  • 网页特效(监听用户的一些行为,让网页做出对应的反馈)
  • 表单验证(针对表单数据的合法性进行判断)
  • 数据交互(获取后台的数据,渲染到前端)
  • 服务端编程(node.js)

JavaScript 组成

  1. ECMAScript : 规定了js基础语法核心知识。比如:变量、分支语句、循环语句、对象等等

  2. Web APIs :

  • DOM 操作文档,比如对页面元素进行移动、大小、添加删除等操作
  • BOM 操作浏览器,比如页面弹窗,检测窗口宽度、存储数据到浏览器等等

JavaScript权威网站:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript

JavaScript 书写位置

内部 JavaScript

直接写在html文件里,用Script标签包住

规范:Script标签写在</body>上面

扩展:alert(“你好,js”)页面弹出警告对话框

注意事项:

我们将<script>放在HTML文件的底部附近的原因是浏览器会按照代码在文件中的顺序加载HTML。

如果先加载的JavaScript期望修改其下方的HTML,那么它可能由于HTML尚未被加载而失效。

因此,将JavaScript代码放在HTML页面的底部附近通常是最好的策略。

外部 JavaScript

代码写在以.js结尾的文件里

语法:通过script标签,引入到html页面中

JavaScript 注释

  • 单行注释

符号://

作用://右边这一行的代码会被忽略

快捷键:ctrl+/

  • 块注释

符号:/* */

作用:在/_和_/之间的所有内容都会被忽略

快捷键:shift+alt+a

JavaScript 结束符

作用:使用英文的;代表语句结束

实际情况:实际开发中,可写可不写,浏览器(JavaScript引擎)可以自动推断语句的结束位置

现状:在实际开发中,越来越多的人主张,书写JavaScript代码时省略结束符

约定:为了风格统一,结束符要么每句都写,要么每句都不写(按照团队要求)

输入输出语法

输出和输入也可理解为人和计算机的交互,用户通过键盘、鼠标等向计算机输入信息,计算机处理后再展示结果给用户,这便是依次输入和输出的过程。

输出语法:

1
document.write("要输出的内容");

作用:向body内输出内容

注意:如果输出的内容写的是标签,也会被解析成网页元素

1
alert("要输出的内容");

作用:页面弹出警告对话框

1
console.log("控制台打印");

作用:控制台输出语法,程序员调试使用

输出语法:

1
prompt("请输入您的姓名:");

作用:显示一个对话框,对话框中包含一条文字信息,用来提示用户输入文字

JavaScript 代码执行顺序:

  • 按HTML文档流顺序执行JavaScript代码
  • alert()和prompt()两者都会跳过页面渲染先执行

变量

变量的定义

变量是计算机中用来存储数据的“容器”,它可以让计算机变得有记忆

注意:变量不是数据本身,它们仅仅是一个用来存储数值的容器。

变量的基本使用

  1. 声明变量:要想使用变量,首先需要创建变量
1
let 变量名
  • 声明变量有两部分构成:声明关键字,变量名(标识)
  • let即关键字(let:允许、许可、让、要),所谓关键字是系统提供的专门用来声明(定义)变量的词语
  1. 变量赋值:定义一个变量后,就可以对其进行初始化,在变量名之后跟上一个“=”,然后是数值。
  2. 更新变量:变量赋值后,还可以通过简单地给它一个不同的值来更新它

注意:let不允许多次声明一个变量

声明多个变量:变量赋值后,还可以通过简单地给它一个不同的值来更新它

1
let age = 18, uname = 'pink';

看上去代码长度更短,但是并不推荐。为了更好的可读性,建议一行只声明一个变量。

变量的本质

内存:计算机中存储数据的地方,相当于一个空间

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

变量命名规则和规范

  1. 规则:
  • 不能用关键字

关键字:有特殊含义的字符,JavaScript内置的一些英语词汇。例如:let、var、if、for等

  • 只能用下划线、字母、数字、$组成,且数字不能开头
  • 字母严格区分大小写,如Age和age是不同的变量
  1. 规范:
  • 起名要有意义
  • 遵守小驼峰命名法

第一个单词首字母小写,后面每个单词首字母大写。例:userName

let 和 var 的区别

let 和 var 区别:在较旧的JavaScript,使用关键字 var 来声明变量,而不是 let,var 现在开发中一般不再使用它,只有可能在老版程序中出现。

let 为了解决 var 的一些问题。

var 声明:

  • 可以先使用再声明(不合理)
  • var 声明过的变量可以重复声明(不合理)
  • 比如变量提升、全局变量、没有块级作用域等等

数组

数组的基本使用

1
let 数组名 = [数据1, 数据2, 数据3, ...., 数据n];
1
let names = ['小明', '小刚', '小红', '小丽', '小米'];
  • 数组是按顺序保存,所以每个数据都有自己的编号
  • 计算机中的编号从0开始,所以小明的编号为0,小刚编号为1,以此类推
  • 在数组中,数据的编号也叫索引或下标
  • 数组可以存储仟意类型的数据

数组的取值

通过下标进行取值

常量

常量的基本使用

概念:使用 const 声明的变量称为“常量”。

使用场景:当某个变量永远不会改变的时候,就可以使用 const 来声明,而不是let。

命名规范:和变量一致

注意:常量不允许重新赋值,声明的时候必须赋值(初始化)

小技巧:不需要重新赋值的数据使用const

const 声明的值不能更改,而且const声明变量的时候需要里面进行初始化,但是对于引用数据类型,const声明的变量,里面存的不是值,而是地址。故而可以对里面的值进行修改

数据类型

基本数据类型

number 数字型

JavaScript 中的正数、负数、小数等 统一称为 数字类型。

数字可以有很多操作,比如,乘法*、除法/、加法+、减法-等等,所以经常和算术运算符一起。

数学运算符也叫算术运算符,主要包括加、减、乘、除、取余(求模)。

NaN 代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果

1
console.log('老师' - 2); // NaN

NaN是粘性的,任何对NaN的操作都会返回NaN

1
console.log(NaN + 2); // NaN

string 字符串类型

通过单引号(”)、双引号(““)或反引号(`)包裹的数据都叫字符串,单引号和双引号没有本质上的区别,推荐使用单引号。

1
2
3
4
5
let uname = '小明’ //使用单引号
let gender = "男” //使用双引号
let goods = `小米` //使用反引号
let tel = '13681113456' //看上去是数字,但是引号包裹了就是字符串
let str = '' // 这种情况叫空字符串

注意事项:

  1. 无论单引号或是双引号必须成对使用

  2. 单引号/双引号可以互相嵌套,但是不以自已嵌套自已(口诀:外双内单,或者外单内双)

  3. 必要时可以使用转义符,输出单引号或者双引号

字符串拼接

  1. 通过运算符”+”进行拼接
1
2
let name = prompt("请输入姓名");
console.log("你好," + name);
  1. 通过模板字符串拼接
1
2
let name = prompt("请输入姓名");
console.log(`你好,${name}`);

注意:必须用反斜号包裹字符串

  1. 字符串.trim():用于删除字符串左右两边的空格

boolean 布尔类型

表示肯定或否定时在计算机中对应的布尔类型数据

它有两个固定的值 true 和 false,表示肯定的数据用 true(真),表示否定的数据用 false(假)

undefined 未定义型

未定义是比较特殊的类型,只有一个值 undefined。

只声明变量,不赋值的情况下,变量的默认值为undefined,一般很少直接为一个变量赋值为 undefined。

null 空类型

JavaScript 中的 null 仅仅是一个代表“无”,“空”或“值未知”的特殊值

1
2
let obj = null;
console.log(obj) // null

null 和 undefined 区别:

  • undefined 表示没有赋值
  • null 表示赋值了,但是内容为空

引用数据类型

引用数据类型只有对象类型

检测数据类型

通过typeof 运算符可以返回被检测的数据类型。它支持两种语法格式:

1.作为运算符: typeof x(常用的写法)

2.函数形式:typeof(x)

类型转换

为什么需要类型转换

JavaScript是弱数据类型:Javascript也不知道变量到底属于那种数据类型,只有赋值了才清除。

使用表单、prompt获取过来的数据默认是字符串类型,此时就不能直接简单的进行加法运算。

隐式转换

某些运算符被执行时,系统内部自动将数据类型进行转换,这种转换称为隐式转换。

规则:

  • +号两边只要有一个是字符串,都会把另外一个转成字符串
  • 除了+以外的算术运算符 比如 -* /等都会把数据转成数字类型
  • null 经过数字转换之后会变为0
  • undefined 经过数字转换之后会变为 NaN
  • 空字符串经过数字转换后会变为0

小技巧:

+号作为正号解析可以转换成数字型

任何数据和字符串相加结果都是字符串

显式转换

编写程序时过度依靠系统内部的隐式转换是不严禁的,因为隐式转换规律并不清晰,大多是靠经验总结的规律。

为了避免因隐式转换带来的问题,通常根逻辑需要对数据进行显示转换。

转换成数字型

  1. Number(数据)
  • 转换成数字类型
  • 如果字符串内容里有非数字,转换失败的结果为NaN
  • NaN也是number类型的数据,代表非数字
  1. parseInt(数据):只保留整数
  2. parseFloat(数据):可以保留小数

转换成Boolean型

通过方法Boolean(内容)进行转换

‘’、0、undefined、null、false、NaN 转换为布尔值后都是false,其余则为true

1
2
3
4
5
6
console.log(false && 20); // false
console.log(5 < 3 && 20); // false
console.log(undefined && 20); // undefined
console.log(null && 20); // null
console.log(0 && 20); // 0
console.log(10 && 20); // 20

运算符

赋值运算符

定义:对变量进行赋值的运算符

例如:=、+=、-=、*=、/=、%=

一元运算符

作用:实现更简便的自增或自减操作

自增运算符:++

自减运算符:–

取反运算符:!

  1. 自增运算符的用法:

前置自增(先自增再运算)

1
2
let num = 1;
++num; // 让num的值加1变2

每执行一次,当前变量数值加一

其作用相当于num += 1;

后置自增(先运算再自增)

1
2
let num = 1;
num++; // 让num的值加1变2

每执行一次,当前变量数值加一

其作用相当于num += 1;

比较运算符

  • >:左边是否大于右边
  • <:左边是否小于右边
  • >=:左边是否大于或等于右边
  • <=:左边是否小于或等于右边
  • ==:左右两边值是否相等
  • !=:左右两边值是否不等
  • ===:左右两边是否类型和值都相等
  • !==:左右两边是否不全等

比较结果为boolean类型,即只会得到true或false

开发中判断是否相等,强烈推荐使用 ===

  1. 字符串比较,是比较的字符对应的ASCII码
  • 从左往右依次比较
  • 如果第一位一样在比较第二位,以此类推
  1. NaN不等于任何值,包括它本身

涉及到NaN比较的,都是false

  1. 尽量不要比较小数,因为小数有精度问题
  2. 不同类型之间比较会发生隐式转换

最终把数据隐式转换成number类型再比较

逻辑运算符

符号 名称 特点 口诀
&& 逻辑与 符号两边都为true,结果才是true 有假则假
|| 逻辑或 符号两边有一个true,结果就为true 有真则真
逻辑非 true变false,false变true 真变假,假变真

逻辑中断

  • 短路:只存在于&&和||中,当满足一定条件会让右边代码不执行
符号 短路条件
&& 左边为false就短路
|| 左边为true就短路
  • 原因:通过左边能得到整个式子的结果,因此没必要再判断右边
  • 运算结果:无论&&还是||,运算结果都是最后被执行的表达式值,一般用于变量赋值
1
2
3
4
5
6
7
8
console.log(false && 11); // 逻辑与中断,返回第一个值
console.log(true && 11); // 未中断,返回第二个值
console.log(false || 11); // 未中断,返回第二个值
console.log(true || 11); //逻辑或中断,返回第一个值
console.log(11 || 22); // 返回11
console.log(11 && 22); // 返回22
console.log(0 || 22); // 返回22
console.log(0 && 22); // 返回0

展开运算符

展开运算符(…),将一个数组进行展开

1
2
const arr = [1,2,3,4];
console.log(...arr);

说明:

  1. 不会修改数组
  2. 典型运用场景:求数组最大值(最小值)、合并数组等
1
2
3
4
5
6
7
8
9
10
// 求数组最大最小值
const arr = [1, 5, 3, 8, 2];
console.log(Math.max(...arr)) // 8
console.log(Math.min(...arr)) // 1

// 合并数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [...arr1, ...arr2];
console.log(arr3);

运算符优先级

优先级 运算符 顺序

1 小括号 ()

2 一元运算符 ++、–、!

3 算数运算符 先*、/、%后+、-

4 关系运算符 >、>=、<、<=

5 相等运算符 ==、!=、===、!==

6 逻辑运算符 先&&后

7 赋值运算符 =

8 逗号运算符 ,

  • 一元运算符中的逻辑非优先级非常高
  • 逻辑与的优先级大于逻辑或

语句

表达式和语句

  • 表达式:可以被求值的代码,JavaScript 引擎会将其计算出一个结果
  • 语句:一段可以被执行的代码

区别:

表达式:因为表达式可被求值,所以它可以写在赋值语句的右侧

语句:语句不一定有值,比如alert()、for和break 等语句就不能被用于赋值

分支语句

if 语句

if语句有三种使用:单分支、双分支、多分支

单分支使用语法

1
2
3
if (条件) {
满足条件要执行的代码
}
  • 括号内的条件为true时,进入大括号里执行代码
  • 小括号内的结果若不是布尔类型时,会发生隐式转换转为布尔类型
  • 如果大括号只有一个语句,大括号可以省略,但是不提倡
  • 除了0,其他所有数字都为真
  • 除了空字符串,其他所有字符串都为真

双分支使用语法

1
2
3
4
5
if (条件) {
满足条件要执行的代码
} else {
不满足条件执行的代码
}

多分支使用语法

1
2
3
4
5
6
7
8
9
if (条件1) {
代码1
} else if (条件2){
代码2
} else if (条件3){
代码3
} else {
代码n
}
  • 先判断条件1,若满足条件1就执行代码1,其他不执行
  • 若不满足则向下判断条件2,满足条件2执行代码2,其他不执行
  • 若依然不满足继续往下判断,依次类推
  • 若以上条件都不满足,执行else里的代码n
  • 注:可以写N个条件,但这里演示只写3个

三元运算符

使用场景:其实是比if双分支更简单的写法,可以使用 三元表达式

语法:

1
条件 ? 满足条件执行的代码 : 不满足条件执行的代码

Switch 语句

1
2
3
4
5
6
7
8
9
10
11
switch (数据) {
case1:
代码1
break
case2:
代码2
break
default:
代码n
break
}
  • 找到跟小括号里数据全等的case值,并执行里面对应的代码
  • 若没有全等 === 的则执行default里的代码
  • 例:数据若跟值2全等,则执行代码2

注意:

  1. switch case语句一般用于等值判断,不适合于区间判断
  2. switch case一般需要配合break关键字使用 没有break会造成case穿透

循环语句

while 循环

  1. while 循环基本语法:
1
2
3
while (循环条件) {
要重复执行的代码(循环体)
}
  • 跟if语句很像,都要满足小括号里的条件为true才会进入 循环体 执行代码
  • while大括号里代码执行完毕后不会跳出,而是继续回到小括号里判断条件是否满足,若满足又执行大括号里的代码,然后再回到小括号判断条件,直到括号内条件不满足,即跳出

while 循环三要素:

循环的本质就是以某个变量为起始值,然后不断产生变化量,慢慢靠近终止条件的过程。所以三要素就是变量的起始值,终止条件,变量变化量(用自增或者自减)

for 循环

1
2
3
for (变量起始值; 终止条件; 变量变化值) {
// 循环体
}

退出循环

  1. continue 退出本次循环,一般用于排除或者跳过某个选项的时候,可以使用continue
  2. break 退出整个for循环,一般用于结果已经得到,后续的循环不需要的时候可以使用

for 循环嵌套

1
2
3
4
5
for (外部声明记录循环次数的变量; 循环条件; 变化值) {
for (内部声明记录循环次数的变量; 循环条件; 变化值) {
循环体
}
}

数组

数组的定义

一种可以按顺序保存数据的数据类型

数组的基本使用

  1. 声明语法
1
let 数组名 = [数据1, 数据2, 数据3, ...., 数据n]
1
let arr = new Array(数据1, 数据2, ...., 数据n)
  • 数组是按顺序保存,所以每个数据都有自己的编号,计算机中的编号从0开始
  • 在数组中,数据的编号也叫索引或下标
  1. 取值语法
1
数组名[下标]

操作数组

查询数组数据

使用:数组[下标]

修改数组数据

使用:数组[下标] = 新值

新增数组数据

  1. 数组名.push() 方法可以将一个或多个元素添加到数组的末尾,并返回该数组的新长度
1
2
3
4
let arr = ['小明']
console.log(arr.push('小红')) // 输出2
arr.push('小蓝')
console.log(arr) // 输出['小明', '小红', '小蓝']
  1. 数组名.unshift()方法可以将一个或多个元素添加到数组的头部,并返回该数组的新长度
1
2
3
4
let arr = ['小明']
console.log(arr.unshift('小红')) // 输出2
arr.unshift('小蓝')
console.log(arr) // 输出['小蓝', '小红', '小明']

删除数组数据

  1. 数组名.shift() 方法从数组中删除第一个元素,并返回该元素的值
1
2
3
let arr = ['red', 'green']
arr.shift()
console.log(arr) // 输出[ 'green' ]
  1. 数组名.pop()方法从数组中删除最后一个元素,并返回该元素的值
1
2
3
let arr = ['red', 'green']
arr.shift()
console.log(arr) // 输出[ 'red' ]
  1. 数组名.splice() 方法 删除指定元素
1
2
arr.splice(start, deleteCount)
arr.splice(起始位置,删除几个元素)

start 起始位置:指定修改的开始位置(从0开始计数)

deleteCount:

  • 表示删除的数组元素个数
  • 可选的。如果省略则默认从指定的起始位置删除到最后
1
2
3
let arr = ['red', 'green', 'blue']
arr.splice(1, 1)
console.log(arr) // 输出[ 'red', 'blue' ]

数组中map方法 迭代数组

使用场景:map可以遍历数组处理数据,并返回新的数组

1
2
3
4
5
6
7
const arr = ['red', 'blue', 'green'];
const newArr = arr.map(function (ele, index) {
console.log(ele)// 数组元素
console.log(index)//数组索引号
return ele + '颜色'
})
console.log(newArr) //['red颜色','blue颜色','green颜色']

map也称为映射。映射是个术语,指两个元素的集之间元素相互“对应”的关系。

map重点在于有返回值,forEach没有返回值

数组中join方法 拼接字符串

作用:join()方法用于把数组中的所有元素转换成一个字符串

语法:

1
2
const arr = ['red颜色', 'blue颜色', 'green颜色'];
console.log(arr.join('')); // 打印red颜色blue颜色green颜色

参数:数组元素是通过参数里面指定的分隔符进行分隔的,空字符串(’’),则所有元素之间都没有任何字符。

小括号为空,则以逗号分隔

forEach 遍历数组

forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数

1
2
3
被遍历的数组.forEach(function (当前数组元素, 当前元素索引号) {
// 函数体
}
1
2
3
4
5
6
const arr = ['pink', 'red', 'green'];
arr.forEach(function (item, index) {
console.log(this); // window
console.log(`当前数组元素是:${item}`); // 依次打印数组每一个元素
console.log(`当前数组元素的索引是:${index}`) //依次打印数组每一个元素的索引
});

注意:

  1. forEach主要是遍历数组
  2. 参数当前数组元素是必须要写的, 索引号可选

filter 过滤数组

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素

主要使用场景:筛选数组中符合条件的元素,并返回筛选之后元素的新数组

1
2
3
被遍历的数组.filter(function (currentValue, index) {
return 筛选条件;
});
1
2
3
4
5
6
// 筛选数组中大于30的元素
const score = [10, 50, 3, 40, 33];
const re = score.filter(function (item) {
return item > 30;
});
console.log(re); // [50, 40, 33]

返回值:返回数组,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组

参数:currentValue必须写,index可选

因为返回新数组,所以不会影响原数组

多维数组

1
2
3
4
5
6
const arr = [1, 2, [3, 4]];
console.log(arr[0]);
console.log(arr[1]);
console.log(arr[2]); // [3, 4]
console.log(arr[2][0]); // 3
console.log(arr[2][1]); // 4

函数

函数的定义

将需要重复使用的代码块抽取出来,并用函数进行封装,从而实现代码的复用

函数的基本使用

  1. 函数的声明
1
2
3
function 函数名() {
函数体
}
  1. 函数命名规范
  • 和变量命名基本一致
  • 尽量小驼峰命名
  • 前缀应该为动词
常见动词 含义
can 判断是否可执行某个动作
has 判断是否含有某个值
is 判断是否为某个值
get 获取某个值
set 设置某个值
load 加载某些数据
  1. 函数的调用语法
1
2
// 函数调用,这些函数体内的代码逻辑会被执行
函数名()

注意:声明(定义)的函数必须调用才会真正被执行,使用()调用函数

函数传参

  1. 声明语法
1
2
3
function 函数名(参数列表) {
函数体
}
  1. 函数调用
1
函数名(参数列表);
  • 形参:声明函数时写在函数名右边小括号里的叫形参(形式上的参数)
  • 实参:调用函数时写在函数名右边小括号里的叫实参(实际上的参数)
  • 尽量保证形参和实参个数一致,js允许个数不一致
  1. 函数默认值
1
2
3
4
5
function getSum(x = 0, y = 0) {
console.log(x + y);
}
getSum(); // 结果是0,而不是NaN
getSum(1, 2); // 结果是3

说明:这个默认值只会在缺少实参参数传递时,才会被执行,所以有参数会优先执行传递过来的实参,否者默认为 undefined

函数返回值

当函数需要返回数据出去时,用return关键字

语法:

1
return 数据;
1
2
3
4
5
6
7
8
9
function getArrSum(arr = []) {
let sum = 0;
for(let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
let sum = getArrSum([1,2,3,4,5]);
console.log(sum);

细节:

  • 在函数体中使用return关键字能将内部的执行结果交给函数外部使用
  • return 后面代码不会再被执行,会立即结束当前函数,所以return后面的数据不要换行写
  • return函数可以没有return,这种情况函数默认返回值为undefined
  • 两个相同的函数后面的会覆盖前面的函数
  • 如果形参过多,会自动填上undefined
  • 如果实参过多,那么多余的实参会被忽略

作用域

通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。

全局作用域:全局有效,作用于所有代码执行的环境(整个script标签内部)或者一个独立的js文件。

局部作用域:局部有效,作用于函数内的代码环境,就是局部作用域。因为跟函数有关系,所以也称为函数作用域。

注意:

  • 如果函数内部,变量没有声明,直接赋值,也当全局变量看(函数外已经声明),不推荐。
  • 函数内部的形参可以看做局部变量。

匿名函数

  1. 函数表达式

将匿名函数赋值给一个变量,并且通过变量名称进行调用,称该表达式为函数表达式

语法:

1
2
3
let fn = function() {
函数体
}
1
2
3
4
let fn = function(x, y) {
console.log(x + y);
}
fn(1, 2);

具名函数和匿名函数的区别:

  • 具名函数的调用可以写到任何位置
  • 函数表达式(匿名函数),必须先声明后调用
  1. 立即执行函数

场景介绍:避免全局变量之间的污染

语法:

1
2
3
4
5
6
// 方式一
(function() { console.log(11) })();
// 方式二
(function() {console.log(11)}());

// 不需要调用,立即执行

多个立即执行函数之间用分号隔开

对象

对象的定义

  • 对象:javaScript里的一种数据类型
  • 可以理解为是一种无序的数据集合,注意数组是有序的数据集合

对象的使用

  1. 对象声明语法
1
2
3
4
// 方式一
let 对象名 = {};
// 方式二
let 对象名 = new object();
  1. 对象的属性
  • 数据描述性的信息称为属性,如人的性别、身高、年龄、性别等,一般是名词性的。
1
2
3
4
5
let obj = {
uname: 'pink老师',
age: 18,
geder: '女'
}
  • 属性都是成对出现的,包括属性名和值,它们之间用英文冒号分隔
  • 多个属性之间使用英文逗号分隔
  • 属性就是依附在对象上的变量(外面是变量,对象内是属性)
  • 属性名可以使用””或’’,一般情况下省略,除非名词遇到特殊符号如空格、中划线等
  1. 操作对象的属性
  • 查:
1
2
3
4
// 方式一
对象名.属性名
// 方式二(当属性名中有空格或者中划线等使用)
对象名['属性名']
  • 改:通过对象名.属性名 = 新值的形式修改该属性值
  • 增:通过对象名.新属性名 = 新值的形式增加属性
  • 删:通过 delete 对象名.属性名的形式删除该属性
  1. 对象的方法:数据行为性的信息称为方法,如跑步、唱歌等,一般是动词性的,其本质是函数。
  • 方法是由方法名和函数两部分构成,它们之间使用:分隔
  • 多个属性之间使用英文,分隔
  • 方法是依附在对象中的函数
  • 方法名可以使用”或”,一般情况下省略,除非名称遇到特殊符号如空格、中横线等
  • 声明对象,并添加了若干方法后,可以使用.调用对象中函数,称之为方法调用。
  • 也可以添加形参和实参
  • 动态添加对象的方法格式:
1
2
3
对象名.新方法名 = function() {
方法体;
}

注意:不要忘记给方法名添加小括号

1
2
3
4
5
6
7
let obj = {
uname: '刘德华',
song: function() {
console.log('冰雨');
}
};
obj.song();

遍历对象

1
2
3
4
5
6
7
8
9
let obj = {
uname: 'andy',
age: 18,
sex: '男'
};
for (let k in obj) {
console.log(k); // 打印属性名(字符串类型)
console.log(obj[k]); // 打印属性值
}
  • 一般不用这种方式遍历数组、主要是用来遍历对象
  • for in语法中的k是一个变量,在循环的过程中依次代表对象的属性名
  • 由于k是变量,所以必须使用[]语法解析
  • 一定记住:k是获得对象的属性名,对象名[k]是获得属性值

内置对象

内置对象的定义

JavaScript内部提供的对象,包含各种属性和方法给开发者调用

内置对象-Math

  1. 介绍:Math对象是JavaScript提供的一个“数学”对象
  2. 作用:提供了一系列做数学运算的方法
  3. math对象包含的方法有:
  • radom:生成0-1之间的随机数(包含0不包括1)
  • ceil:向上取整
  • floor:向下取整
  • max: 找最大数
  • min:找最小数
  • pow:幂运算
  • abs:绝对值

生成任意范围随机数

  • Math.radom() 随机数函数,生成0-1之间的随机小数(包含0不包括1)
  • 生成0-10的随机整数
1
math.floor(Math.random() * (10 + 1)); // [0,11)

生成5-10的随机整数

1
Math.floor(Math.random() * (5 + 1)) + 5; // [5,11)

生成N-M之间的随机整数

1
Math.floor(Math.random() * (M - N + 1)) + N; // [N, M + 1);
1
2
3
function getRandom(N, M) {
return Math.floor(Math.random() * (M - N + 1)) + N;
}

Web API

Web API 基本认知

作用和分类

作用:使用Js去操作html和浏览器

分类:DOM(文档对象模型)、BOM(浏览器对象模型)

DOM 的定义

DOM是用来呈现以及与任意HTML或XML文档交互的API,即DOM是浏览器提供的一套专门用来操作网页内容的功能。

DOM的作用:开发网页内容特效和实现用户交互

DOM树

将HTML文档以树状结构直观的表现出来,称之为文档树或DOM树

作用:只管体现标签与标签之间的关系

DOM对象

DOM对象:浏览器根据html标签生成的JS对象

  • 所有的标签属性都可以在该对象中找到
  • 修改该对象的属性值会自动映射到标签上

DOM的核心思想:把网页内容当作对象来处理

document 对象:DOM中提供的一个对象,所以它提供的属性和方法都是用来访问和操作网页内容的。网页所有内容都在document里面。

获取DOM对象

根据CSS 选择器获取DOM对象

  1. 选择匹配的第一个元素
1
document.querySelector('css选择器');

参数:包含一个或多个有效的CSS选择器 字符串。

返回值:CSS选择器匹配的第一个元素,一个HTMLElement对象。如果没有匹配到,则返回null。

  1. 选择匹配的多个元素
1
document.querySelectorAll('css选择器');

参数:包含一个或多个有效的CSS选择器 字符串

返回值:CSS选择器匹配的NodeList 对象集合

  • 有长度有索引号的数组
  • 但是没有pop() push()等数组方法

哪怕只有一个元素,通过querySelectorAll()获取过来的也是一个伪数组,里面只有一个元素而已

1
document.querySelectorAll('ul li');

其他方式获取DOM对象

1
2
3
4
5
6
// 根据id获取一个元素
document.getElementById('nav');
// 根据 标签获取一类元素 获取页面所有div
document.getElementsByTagName('div');
// 根据 类名获取元素 获取页面 所有类名为w的元素
document.getElementsByClassName('w');

操作元素内容

innerText 属性

  • 将文本内容添加/更新到任意标签位置
  • 显示纯文本、不解析标签
1
2
3
4
5
const info = document.querySelector('.info');
// 获取标签内部的文字
// console.log(info.innerText);
// 添加/修改标签内部文字内容
info.innerText = '新内容';

innerHTML 属性

  • 将文本内容添加/更新到任意标签位置
  • 会解析标签,多标签建议使用模板字符串
1
2
3
4
5
const info = document.querySelector('.info');
// 获取标签内部的文字
// console.log(info.innerHTML);
// 添加/修改标签内部文字内容
info.innerHTML = '<strong>新内容</strong>';

操作元素属性

操作元素常见属性

通过JS设置/修改标签元素属性

常见的属性:href、title、src等

1
对象.属性 = 值;
1
2
const img = document.querySelector('img');
img.src = './images/pic1.png';

操作元素样式属性

通过 style 属性操作CSS

1
对象.style.样式属性 = 值;
1
2
3
4
5
const box = document.querySelector('.box');
// 修改元素样式
box.style.width = '200px';
box.style.marginTop = '15px';
box.style.backgroundColor = 'pink';

注意:

  1. 修改样式通过style属性引出
  2. 如果属性有-连接符,需要转换为小驼峰命名法
  3. 赋值的时候,需要的时候不要忘记加css单位

通过类名(ClassName)操作CSS

如果修改的样式比较多,直接通过style属性修改比较繁琐,可以通过借助于css类名的形式。

1
2
// active 是一个css类名
元素.className = 'active';

注意:

  1. 由于class是关键字,所以使用className去代替
  2. className是使用新值换旧值,如果需要添加一个类,需要保留之前的类名

通过classList 操作类控制CSS

为了解决className 容易覆盖以前的类名,可以通过classList方式追加和删除类名

1
2
3
4
5
6
7
8
// 追加一个类
元素.classList.add('类名');
// 删除一个类
元素.classList.remove('类名');
// 切换一个类(有就删除类名,没有就增加类名)
元素.classList.toggle('类名');
// 查看是否包含该类名,有返回true,没有返回false
元素.classList.contains('类名');

操作表单元素属性

表单元素有属性取值和其他标签属性取值无区别,但是获取元素内容则是通过元素.value获取。

1
2
3
const input = document.querySelector('input');
// 通过value属性获取元素内容,而非innerHTML属性获取
console.log(input.value);

表单属性中添加就有效果,移除就没有效果,一律使用布尔值表示 如果为true代表添加了该属性 如果是false代表移除了该属性。比如:disabled、checked、selected属性

自定义属性

  1. 标准属性:标签天生自带的属性 比如class id title等,可以直接使用点语法操作比如:disabled、checked、selected
  2. 自定义属性:
  • 在html5中推出了专门的data-自定义属性
  • 在标签上一律以data-开头
  • 在DOM对象上一律以dataset对象方式获取
1
2
3
4
5
6
7
<body>
<div class='box' data-id='10'>盒子</div>
<script>
const box = document.querSelector('.box);
console.log(box.dataset.id) // 输出1
</script>
</body>

定时器-间歇函数

定义

网页中经常会需要一种功能:每隔一段时间需要自动执行一段代码,不需要我们手动去触发,例如:网页中的倒计时。

基本使用

  1. 开启定时器
1
setInerval(函数,间隔时间);

作用:每隔一段时间调用这个函数

间隔时间单位是毫秒

1
2
3
4
function repeat() {
console.log('前端程序员');
}
setInterval(repeat, 1000);

注意:

  1. 函数名字不需要加括号
  2. 定时器返回的是一个id数字
  1. 关闭定时器
1
2
let 变量名 = setInterval(函数,间隔时间);
clearInterval(变量名);

一般不会刚创建就停止,而是满足一定条件后再停止

事件监听

事件的定义

事件是在编程时系统内发生的动作或者发生的事情,比如用户在网页上单击一个按钮

事件监听的定义

就是让程序检测是否有事件产生,一旦有事件触发,就立即调用一个函数做出响应,也称为绑定事件或者注册事件,比如鼠标经过显示下拉菜单,比如点击可以播放轮播图等等。

1
元素对象.addEventListener('事件类型', 要执行的函数);

事件监听三要素:

事件源:那个dom元素被事件触发了,要获取dom元素

事件类型:用什么方式触发,比如鼠标单击click、鼠标经过mouseover等

事件调用的函数:要做什么事

注意:事件类型要添加引号

事件类型

鼠标事件

  • click:鼠标点击
  • mouseenter:鼠标经过
  • mouseleave:鼠标离开

焦点事件

  • focus:获得焦点
  • blur:失去焦点

键盘事件

  • keydown:键盘按下触发
  • keyup:键盘抬起触发

文本事件

  • input:用户输入事件

提交事件:

  • submit:表单提交触发的事件

变化事件:

  • change:表单内容发生变化时触发的事件

事件监听版本

DOM L0:事件源.on事件 = function() {}

DOM L2:事件源.addEventListener(事件, 事件处理函数)

区别:on方式会被覆盖,addEventListener方式可绑定多次,拥有事件跟多特性,推荐使用

事件对象

获取事件对象

  1. 事件对象的定义:也是个对象,这个对象里有事件触发时的相关信息,例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息
  2. 使用场景
  • 可以判断用户按下哪个键,比如按下回车键可以发布新闻
  • 可以判断鼠标点击了哪个元素,从而做相应的操作
  1. 获取事件对象
  • 在事件绑定的回调函数的第一个参数就是事件对象
  • 一般命名为event、ev、e
1
2
3
4
// 参数e就是事件对象
元素.addEventListener('click', function(e) {
方法体;
});

事件对象常用属性

type:获取当前事件的类型

clientX/clientY:获取光标相对于浏览器可见窗口左上角的位置

offsetX/offsetY:获取光标相对于当前DOM元素左上角的位置

key:用户按下的按键值

环境对象

定义:函数内部特殊的变量this,它代表当前函数运行时所处环境

作用:使代码更简洁

函数的调用方式不同,this指代对象也不同

普通函数中this指向window,其他函数则是谁调用,则指向谁

回调函数

定义:如果将函数A做为参数传递给函数B时,则称函数A为回调函数

常见使用场景:设置定时器中的函数,事件监听中的函数

事件流

事件流与两个阶段说明

事件流指的是事件完整执行过程中的流动路径

说明:假设页面里有个div,当初发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段

捕获阶段时从父到子的过程,而冒泡阶段则是从子到父的过程

实际工作都是使用事件冒泡为主

事件捕获

概念:从DOM的根元素开始去执行对应的事件(从外到里)

事件不糊需要使用对应的代码才能看见效果,代码语法如下:

1
DOMaddEventListener(事件类型, 事件处理函数, 是否使用捕获机制);

说明:

  • addEventListener第三个参数传入true代表是捕获阶段触发(很少使用)
  • 若传入false代表冒泡阶段触发,默认就是false
  • 若是用LO事件监听,则只有冒泡阶段,没有捕获
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
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.father {
width: 300px;
height: 300px;
background-color: pink;
}
.son {
width: 200px;
height: 200px;
background-color: purple;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
const father = document.querySelector('.father');
const son = document.querySelector('.son');
document.addEventListener('click', function() {
alert('爷爷');
}, true);
father.addEventListener('click', function() {
alert('爸爸');
}, true);
son.addEventListener('click', function() {
alert('儿子');
}, true);
</script>
</body>
</html>

事件冒泡

概念:当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发,这一过程被称为事件冒泡。

简单理解:当一个元素被触发事件后,会依次向上调用所有父级元素的同名事件

事件冒泡时默认存在的

L2事件监听第三个参数是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
34
35
36
37
38
39
40
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.father {
width: 300px;
height: 300px;
background-color: pink;
}
.son {
width: 200px;
height: 200px;
background-color: purple;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
const father = document.querySelector('.father');
const son = document.querySelector('.son');
document.addEventListener('click', function() {
alert('爷爷');
});
father.addEventListener('click', function() {
alert('爸爸');
});
son.addEventListener('click', function() {
alert('儿子');
});
</script>
</body>
</html>

补充:鼠标经过事件的区别

  • mouseover和mouseout会有冒泡效果
  • mouseenter和mouseleave没有冒泡效果

阻止冒泡

问题:因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素

需求:若想把事件就限制到当前元素内,就需要阻止事件冒泡

前提:阻止事件冒泡需要拿到事件对象

语法:

1
事件对象.stopPropagation();

注意:此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效

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
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.father {
width: 300px;
height: 300px;
background-color: pink;
}
.son {
width: 200px;
height: 200px;
background-color: purple;
}
</style>
</head>
<body>
<div class="father">
<div class="son"></div>
</div>
<script>
const father = document.querySelector('.father');
const son = document.querySelector('.son');
document.addEventListener('click', function() {
alert('爷爷');
});
father.addEventListener('click', function() {
alert('爸爸');
});
son.addEventListener('click', function(e) {
alert('儿子');
// 阻止事件流动传播
e.stopPropagation();
});
</script>
</body>
</html>

补充:某些情况下需要阻止默认行为的发生,比如 阻止 链接的跳转,表单域跳转

1
e.preventDefault();

解绑事件

  1. on事件方式,直接使用null覆盖偶就可以实现事件的解绑
1
2
3
4
5
6
// 绑定事件
btn.onclick = function() {
alert('点击了');
};
// 解绑事件
btn.onclick = null;
  1. addEventListener方式,必须使用:

removeEventListener(事件类型,事件处理函数,[获取捕获或者冒泡阶段]);

1
2
3
4
5
6
7
function() fn() {
alert('点击了');
}
// 绑定事件
btn.addEventlistener('click', fn);
// 解绑事件
btn.removeEventListener('click', fn);

注意:匿名函数无法解绑

事件委托

优点:减少注册次数,可以提高程序性能

原理:事件委托其实是利用事件冒泡的特点,给父元素注册事件,当触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件。

实现:事件对象.target.tagName 可以获得真正触发事件的元素

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
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul>
<li>第1个孩子</li>
<li>第2个孩子</li>
<li>第3个孩子</li>
<li>第4个孩子</li>
<li>第5个孩子</li>
<p>我不需要变颜色</p>
</ul>
<script>
const ul = document.querySelector('ul');
ul.addEventListener('click', function(e) {
if(e.target.tagName === 'LI') {
e.target.style.color = 'red';
}
})
</script>
</body>
</html>

其他事件

页面加载事件

load事件

概念:加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件

存在理由

  1. 有些时候需要等页面资源全部处理完了做一些事情
  2. 老代码喜欢把script写在head中,这时候直接找DOM元素找不到

事件名:load

监听页面所有资源加载完毕:给window添加load事件

1
2
3
4
// 页面加载事件
window.addEventListener('load', function() {
// 执行操作
})

注意:不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件

DOMContentLoaded事件

当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像等完全加载。

事件名:DOMContentLoaded

监听页面DOM加载完毕:给document 添加 DOMContentLoaded 事件

1
2
3
document.addEventListener('DOMContentLoaded', function() {
// 执行的操作
});

元素滚动事件

概念:滚动条在滚动的时候持续触发的事件

存在理由:很多网页需要检测用户把页面滚动到某个区域后做一些处理,比如固定导航栏,比如返回顶部

事件名:scroll

监听整个页面滚动:给window或document添加scroll事件

1
2
3
4
// 页面滚动事件
window.addEventListener('scroll', function() {
// 执行的操作
});

获取元素

  1. scrollLeft和scrollTop(属性)
  • 获取被卷去的大小
  • 获取元素内容往左,往上滚出去看不到的距离
  • 这两个值是读写的
  1. 尽量在scroll事件里面获取被卷去的距离
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
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body {
height: 1200px;
}
div {
display: none;
margin-top: 300px;
width: 200px;
height: 100px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div>
我里面有很多内容
我里面有很多内容
我里面有很多内容
我里面有很多内容
我里面有很多内容
我里面有很多内容
</div>
<script>
const div = document.querySelector('div');
// div.addEventListener('scroll', function () {
// console.log(div.scrollTop);
// })
window.addEventListener('scroll', function () {
// 获取html标签对象
const html = document.documentElement;
if(html.scrollTop >= 100) {
div.style.display = 'block';
} else {
div.style.display = 'none';
}
})
</script>
</body>
</html>

滚动到指定坐标:scrollTo()方案可把内容滚动到指定的坐标

1
元素.scrollTo(x,y);
1
2
// 让页面滚动到y轴100像素的位置
window.scrollTo(0, 1000);

页面尺寸事件

  1. 会在窗口尺寸改变的时候触发事件:resize
1
2
3
window.addEventlistener('resize', function () {
// 执行的代码
})
  1. 获取宽高:
  • 获取元素的可见部分宽高(不包含边框,margin,滚动条等)
  • clientWidth和clientheight
  1. 检测屏幕宽度:
1
2
3
4
window.addEventlistener('resize', function () {
let w = document.documentElement.clientWidth;
console.log(w);
})

元素的尺寸和位置

  1. 获取宽高
  • 获取元素的自身宽高,包含元素自身设置的宽高、padding、border
  • offsetWidth和offsetHeight
  • 获取出来的是数值,方便计算
  • 注意:获取的是可视宽高,如果盒子是隐藏的,获取的结果是0
  1. 获取位置
  • 获取元素距离自己定位父级元素的左、上距离
  • offsetLeft和offsetTop 注意是只读属性

日期对象

实例化

  1. 概念:在代码中发现了new关键字时,一般将这个操作称为实例化
  2. 创建一个时间对象并获取事件
  • 获取当前事件
1
const date = new Date();
  • 获取指定事件
1
2
const date = new Date('2008-8-8 02:30:00');
console.log(date);

事件对象方法

使用场景:因为事件对象返回的数据并不能直接使用,所以需要转换为实际开发中常用的格式

方法 作用 说明
getFullYear() 获得年份 获取四位年份
getMonth() 获得月份 取值为0~11
getDate() 获取月份中的每一天 不同月份取值也不相同
getDay() 获取星期 取值为0~6(星期天是0)
getHours() 获取小时 取值为0~23
getMinutes() 获取分钟 取值为0~59
getSeconds() 获取秒 取值为0~59

时间戳

  1. 使用场景:如果计算倒计时效果,前面方法无法直接计算,需要借助于时间戳完成
  2. 定义:是指1970年01月01日00时00分00秒起至现在的毫秒数,它是一种特殊的计量时间的方式。
  3. 算法
  • 将来的时间戳 - 现在的时间戳=剩余时间毫秒数
  • 剩余时间毫秒数 转换为 剩余时间的 年月日时分秒 就是 倒计时时间
  • 比如:将来时间戳 2000ms -现在时间戳 1000ms =1000ms,1000ms转换为就是 0小时0分1秒
  1. 获取时间戳
  • 使用getTime()方法
1
2
const date = new Date();
console.log(date.getTime());
  • 使用 +new Date()
1
console.log(+new Date());
  • 使用Date.now():无需实例化,但是只能得到当前时间戳,而前面两种可以返回指定时间的时间戳
1
console.log(Date.now());
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.countdown {
width: 300px;
height: 300px;
text-align: center;
line-height: 1;
color: #fff;
background-color: brown;
/* background-size: 240px; */
/* float: left; */
overflow: hidden;
}

.countdown .next {
font-size: 16px;
margin: 25px 0 14px;
}

.countdown .title {
font-size: 33px;
}

.countdown .tips {
margin-top: 80px;
font-size: 23px;
}

.countdown small {
font-size: 17px;
}

.countdown .clock {
width: 142px;
margin: 18px auto 0;
overflow: hidden;
}

.countdown .clock span,
.countdown .clock i {
display: block;
text-align: center;
line-height: 34px;
font-size: 23px;
float: left;
}

.countdown .clock span {
width: 34px;
height: 34px;
border-radius: 2px;
background-color: #303430;
}

.countdown .clock i {
width: 20px;
font-style: normal;
}
</style>
</head>

<body>
<div class="countdown">
<p class="next">今天是2222年2月22日</p>
<p class="title">下班倒计时</p>
<p class="clock">
<span id="hour">00</span>
<i>:</i>
<span id="minutes">25</span>
<i>:</i>
<span id="second">20</span>
</p>
<p class="tips">18:30:00下课</p>
</div>
<script>
const time = new Date();
const year = time.getFullYear();
const month = time.getMonth() + 1;
const date = time.getDate();
const hours = document.querySelector('#hour');
const minutes = document.querySelector('#minutes');
const seconds = document.querySelector('#second');
const current = document.querySelector('.next');
const tips = document.querySelector('.tips');
tips.innerHTML = `${year}-${month}-${date} 24:00:00下班`;
current.innerHTML = `今天是${year}${month}${date}日`;

function fn(num) {
return num < 10 ? '0' + num : num;
}

setInterval(function () {
const currentTime = Date.now();
const featureTime = +new Date(`${year}-${month}-${date} 24:00:00`);
const remanentTime = (featureTime - currentTime) / 1000;
const h = fn(parseInt(`${remanentTime / 60 / 60 % 24}`));
const m = fn(parseInt(`${remanentTime / 60 % 60}`));
const s = fn(parseInt(`${remanentTime % 60}`));
hours.innerHTML = h;
minutes.innerHTML = m;
seconds.innerHTML = s;
}, 1000);
</script>
</body>

</html>

节点操作

DOM 节点

  1. 定义:DOM树里每个内容都称之为节点
  2. 节点类型:
  • 元素节点:所有标签都是元素节点,html是根节点
  • 属性节点:所有属性都是属性节点,比如a标签中的href属性
  • 文本节点:所有的文本内容,比如标签里面的文字
  • 其他节点

查找节点

父节点查找

  • parentNode 属性
  • 返回最近一级的父节点 找不到返回为null
1
子元素.parentNode;

子节点查找

  1. childNodes:获得所有子节点、包括文本节点(空格、换行)、注释节点等
  2. children属性(重点):
  • 仅获得所有元素节点
  • 返回的是一个伪数组
1
父元素.children;

兄弟关系查找

  1. 下一个兄弟节点:nextElementSibling 属性
  2. 上一个兄弟节点:previousElementSibling 属性

增加节点

创建节点

  • 创造出一个新的网页元素,再添加到网页内,一般先创建节点,然后插入节点
  • 创建元素节点方法:
1
2
// 创建一个新的元素节点
document.createElement('标签名');

追加节点

  • 插入到父元素的最后一个资源
1
2
// 插入到这个父元素的最后
父元素.appendChild(要插入的元素);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<head>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
const ul = document.querySelector('ul');
const li4 = document.createElement('li');
li4.innerHTML = '4';
// 添加到最后一个子元素后面
ul.appendChild(li4);
</script>
</body>
</html>
  • 插入到父元素中某个子元素的前面
1
2
3
4
// 插入到某个子元素的前面
父元素.insetBefore(要插入的元素, 在哪个元素前面);
// 例如在div中的第一个子元素前面插入p标签
div.insertBefore('p', div.children[0]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<head>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>4</li>
</ul>
<script>
const ul = document.querySelector('ul');
const li3 = document.createElement('li');
li3.innerHTML = '3';
// 添加到第三个子元素前面
ul.insertBefore(li3, ul.children[2]);
</script>
</body>
</html>

克隆节点

  1. 特殊情况下,新增节点,按照如下操作:
  • 复制一个原有的节点
  • 把复制的节点放入到指定的元素内部
  1. 克隆节点
1
2
// 克隆一个已有的元素节点
元素.cloneNode(布尔值);

cloneNode会克隆出一个跟原标签一样的元素,括号内传入布尔值:

  • 若为true,则代表克隆时会包含后代节点一起克隆
  • 若为false,则代表克隆时不包含后代节点
  • 默认为false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
</head>
<body>
<ul>
<li>1</li>
</ul>
<script>
const li1 = document.querySelector('ul li:first-child');
const ul = document.querySelector('ul');
// 结构和内容一起克隆
ul.appendChild(li1.cloneNode(true));
// 只复制结构
ul.appendChild(li1.cloneNode());
</script>
</body>
</html>

删除节点

  • 若一个节点在页面中已不需要时,可以删除它
  • 在JavaScript原生DOM操作中,要删除元素必须通过父元素删除
  • 语法:
1
父元素.removeChild(要删除的元素);

注意:

  • 如不存在父子关系则删除不成功
  • 删除节点和隐藏节点(display:none)有区别:隐藏节点还是存在的,但是删除,则从html中删除节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
const ul = document.querySelector('ul');
// 删除第一个子元素
ul.removeChild(ul.children[0]);
</script>
</body>
</html>

M端事件

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

  • 触屏事件touch(也称触摸事件),Android和IOS都有。
  • touch对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。

常见的触屏事件如下:

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

Swiper 插件

该插件用于快速制作轮播图

官网:https://www.swiper.com.cn/

Window 对象

BOM (浏览器对象模型)

  • window对象是一个全局对象,也可以说是JavaScript中的顶级对象
  • 像document、alert()、console.log()这些都是window的属性,基本BOM的属性和方法都是window的
  • 所有通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法
  • window对象下的属性和方法调用的时候可以省略window

定时器-延时函数

  • JavaScript内置的一个用来让代码延迟执行的函数,叫 setTimeout
  • 语法:
1
setTimeout(回调函数, 等待的毫秒数);
  • setTimeout仅仅只执行依次,所以可以理解为就是把一段代码延迟执行,平时省略window
  • 清除延时函数:
1
2
let timer = setTimeout(回调函数, 等待的毫秒数);
clearTimeout(timer);

注意点:

  • 延时器需要等待,所以后面的代码先执行
  • 每一次调用延时器都会产生一个新的延时器

JS执行机制

  • JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
  • 这是因为Javascript这门脚本语言诞生的使命所致 – JavaScript是为处理页面中用户交互,以及操作DOM而诞生的。比如我们对某个DOM元素进行添加和删除操作,不能同时进行。应该先进行添加,之后再删除。
  • 单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是:如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
  • 为了解决这个问题,利用多核CPU的计算能力,HTML5提出Web Worker 标准,允许JavaScript脚本创建多个线程。于是,JS中出现了同步和异步。
  • 同步:前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。比如做饭的同步,做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。
  • 异步:你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事情。比如做饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。

他们的本质区别:这条流水线上各个流程的执行顺序不同。

  • 同步任务:同步任务都在主线程上执行,形成一个执行栈。
  • 异步任务:JS的异步是通过回调函数实现的。一般而言,异步任务有以下三种类型:

1、普通事件,如click、resize 等

2、资源加载,如load、error等

3、定时器,包括 setlnterval、setTimeout 等

异步任务相关添加到任务队列中(任务队列也称为消息队列)。

  • JS具体执行过程:
  1. 先执行执行栈中的同步任务。
  2. 异步任务放入任务队列中。

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

location 对象

  1. location 的数据类型是对象,它拆分并保存了URL地址的各个组成部分
  2. 常用属性和方法:
  • search 属性获取地址中携带的参数,符号?后面的部分
1
2
3
4
// http://location:8080?username=pink&password=123456
//(?后面的值是通过表单提交的形式形成的
// 打印'?username=pink&password=123456'
console.log(location.search);
  • hash 属性获取地址中的哈希值,符号#后面部分
1
2
3
4
// http://location:8080#/friend
// (#后面的值是通过点击<a href="#/friend"></a>标签形成的)
// 打印'#friend'
console.log(location.hash);
  • reload 方法用来刷新当前页面,传入参数true时表示强制刷新
1
2
3
4
5
6
7
8
9
10
<button>点击刷新</button>
<script>
let btn = document.querySelector('button');
btn.addEventListener('click', function () {
// 刷新页面
location.reload();
// 强制刷新,类似 ctrl + f5
location.reload(true);
});
</script>
  1. navigator的数据类型是对象,该对象下记录了浏览器自身的相关信息
  2. 常用属性和方法:
  • 通过userAgent检测浏览器的版本及平台
1
2
3
4
5
6
7
8
9
10
11
12
//检测 userAgent(浏览器信息)
!(function () {
const userAgent = navigator.userAgent
//验证是否为Android或iPhone
const android = userAgent.match(/(Android) ;? [¥s¥/]+([¥d.]+)?/)
const iphone = userAgent.match(/(iPhone¥sOS)¥s([¥d_]+)/)

//如果是Android或iPhone,则跳转至移动站点
if (android || iphone) {
location.href = 'http://m.itcast.cn'
}
})();

history 对象

  1. history 的数据类型是对象,主要管理历史记录,该对象与浏览器地址栏的操作相对应,如前进、后退、历史记录等
  2. 常用属性和方法:
history对象方法 作用
back() 后退功能
forward() 前进功能
go(参数) 前进后退功能 参数如果是1 前进一个页面 如果是-1 后退一个页面

本地存储

本地存储介绍

随着互联网的快速发展,基于网页的应用越来越普遍,同时也变的越来越复杂,为了满足各种各样的需求,会经常性在本地存储大量的数据,HTML5规范提出了相关解决方案。

1、数据存储在用户浏览器中

2、设置、读取方便、甚至页面刷新不丢失数据

3、容量较大,sessionStorage和localStorage约5M左右

本地存储分类

localStorage

  1. 作用:可以将数据永久存储在本地(用户的电脑),除非手动删除,否则关闭页面也会存在
  2. 特性:
  • 可以多窗口(页面)共享(同一浏览器可以共享)
  • 以键值对的形式存储使用
  1. 语法:
  • 存储数据
1
localStorage.setItem(key, value);
  • 获取数据
1
localStorage.getItem(key);
  • 删除数据
1
localStorageremoveItem(key);

sessionStorage

特性:

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

存储复杂数据类型

语法:JSON.stringigy(复杂数据类型)

1
2
3
4
5
const goods = {
name: '小米10',
price: 1999
};
localStorage.setItem('goods', JSON.stringify(goods));

将复杂数据转换成JSON字符串,存储本地存储中

语法:JSON.parse(JSON字符串)

1
2
const obj = JSON.parse(localStorage.getItem('goods'));
console.log(obj);

将JSON字符串转换成对象,取出时候使用

正则表达式

介绍

概念:正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。通常用来查找、替换那些符合正则表达式的文本,许多语言都支持正则表达式。

作用

  • 表单验证(匹配)
  • 过滤敏感词(替换)
  • 字符串中提取我们想要的部分(提取)

语法

  1. 定义正则表达式语法:
1
const 变量名 = /表达式/;

其中/ /是正则表达式字面量

1
const reg = /前端/;

2.判断是否有符合规则的字符串

test()方法,用来查看正则表达式与指定的字符串是否匹配

  • 语法:
1
reg.test(被检测的字符串);
1
2
3
4
5
6
// 要检测的字符串
const str='IT培训,前端开发培训,IT培训课程,web前端培训,Java培训,人工智能培训'
// 1.定义正则表达式,检测规则
const reg =/前端/
// 2.检测方法
console.log(reg.test(str)) // true

如果正则表达式与指定的字符串匹配,返回true,否则返回false

  1. 检索(查找)符合规则的字符串:

exec() 方法在一个指定字符串中执行一个搜索匹配

  • 语法:
1
reg.exec(被检测字符串)
1
2
3
4
5
6
// 要检测的字符串
const str='IT培训,前端开发培训,IT培训课程,web前端培训,Java培训,人工智能培训'
// 1.定义正则表达式,检测规则
const reg =/前端/
// 2.检测方法
console.log(reg.exec(str)) // true

如果匹配成功,exec()方法返回一个数组,否则返回null

元字符

  • 普通字符:大多数的字符仅能够描述它们本身,这些字符称作普通字符,例如所有的字母和数字。也就是说普通字符只能够匹配字符串中与它们相同的字符。
  • 元字符(特殊字符):是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能。

比如,规定用户只能输入英文26个英文字母,普通字符的话 abcdefghijklm …..

但是换成元字符写法:[a-z]

参考文档:

mdn:https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Guide/Regular_Expressions

正则测试工具:http://tool.oschina.net/regex

边界符

正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符

边界符 说明
^ 表示匹配行首的文本(以谁开始)
$ 表示匹配行尾的文本(以谁结束)

如果^和$在一起,表示必须是精确匹配

1
2
3
4
5
6
7
8
9
10
11
console.log(/哈/.test('哈'))// true
console.log(/二哈/.test('二哈'))// true
console.log(/二哈/.test('很二哈哈'))// true

//^ 开头
console.log(/^二哈/.test('很二哈哈'))//false
console.log(/^二哈/.test('二哈很傻'))// true
// $ 结尾
console.log(/^二哈$/.test('二哈很傻'))//false
console.log(/^二哈$/.test('二哈二哈'))//false
console.log(/^二哈$/.test('二哈')) // true

量词

量词用来 设定某个模式出现的次数

量词 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

注意:逗号左右两侧千万不能出现空格

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次或者更多次
console.log(/^哈*$/.test(''))// true
console.log(/^哈*$/.test('哈'))//true
console.log(/^哈*$/.test('哈哈哈'))// true
console.log('----------------------------------')
//+ 表示重复1次或者更多次
console.log(/^哈+$/.test(''))//false
console.log(/^哈+$/.test('哈'))// true
console.log(/^哈+$/.test('哈哈哈'))//true
console.log('----------------------------------')
//?表示重复0次或者1次
console.log(/^哈?$/.test(''))//true
console.log(/^哈?$/.test('哈'))// true
console.log(/^哈?$/.test('哈哈哈'))//fasle
console.log('----------------------------------')
// {n} 表示=n的意思
console.log(/^哈{2}$/.test('哈'))//false
console.log(/^哈{2}$/.test('哈哈'))// true
console.log(/^哈{2}$/.test('哈哈哈'))//false
console.log('----------------------------------')
// {n,} 是>= n 的意思
console.log(/^哈{2,}$/.test('')) // false
console.log(/^哈{2,}$/.test('哈'))//false
console.log(/^哈{2,}$/.test('哈哈')) // true
console.log(/^哈{2,}$/.test('哈哈哈'))// true
console.log('----------------------------------')
// {n,m} 是次数 >=n <=m 的意思,注意,逗号左右千万不要有空格
console.log(/^哈{2,4}$/.test('')) // false
console.log(/^哈{2,4}$/.test('哈'))//false
console.log(/^哈{2,4}$/.test('哈哈'))//true
console.log(/^哈{2,4}$/.test('哈哈哈'))// true
console.log(/^哈{2,4}$/.test('哈哈哈哈'))//true
console.log(/^哈{2,4}$/.test('哈哈哈哈哈'))//false

字符类

  1. [] 匹配字符集合
  • [abc]:后面的字符串只要包含abc中任意一个字符,都返回true
1
2
3
4
5
6
7
// 只要中括号里面的任意字符出现都返回为true
console.log(/[abc]/.test('andy')) // true
console.log(/[abc]/.test('baby')) // true
console.log(/[abc]/.test('cry')) // true
console.log(/[abc]/.test('die')) // false
console.log(/^[abc]$/.test('ab')) // false
console.log(/^[abc]{2}$/.test('ab')) // true
  1. [] 里面加上 - 连字符
  • 使用连字符 - 表示一个范围
1
console.log(/^[a-z]$/.test('c')) // true

示例:

  • [a-z]表示a到z 26个英文字母都可以
  • [a-zA-Z] 表示大小写都可以
  • [0-9] 表示0~9的数字都可以
1
2
// 腾讯QQ号(从10000开始)
console.log(/^[1~9][0-9]{4,}/.test('10000')) // true
  1. [] 里面加上^ 取反符号

示例:

  • [^a-z]匹配除了小写字母以外的字符
  • 注意要写到中括号里面
  1. 匹配除了换行符以外的任何单个字符
  2. 预定义:指的是 某些常见模式的简写方式
预定类 说明
\d 匹配0-9之间的任一数字,相当于[0-9]
\D 匹配所有0-9之外的字符,相当于[^0-9]
\w 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
\W 除所有字母、数字和下划线以外,相当于[^A-Za-z0-9_]
\s 匹配空格(包括换行符、制表符、空格符等),相当于[\t\r\n\v\f]
\S 匹配非空格的字符,相当于[^\t\r\n\v\f]

修饰符

修饰符约束正则执行的某些细节行为,如是否区分大小写,是否支持多行匹配等

语法:

1
/表达式/修饰符

i是单词ignore的缩写,正则匹配时字母不区分大小写,只匹配第一个语句

g时单词global的缩写,匹配所有满足正则表达式的结果

1
2
console.log(/a/i.test('a')) // true
console.log(/a/i.test('A')) // true

替换语法:

1
字符串.replace(/正则表达式/, '替换的文本');

JavaScript 进阶

作用域

局部作用域

函数作用域

在函数内部声明的变量,只能在函数内部被访问,外部无法直接访问

  1. 函数内部声明的变量,在函数外部无法被访问
  2. 函数的参数也是函数内部的局部变量
  3. 不同函数内部声明的变量无法互相访问
  4. 函数执行完毕后,函数内部的变量实际被清空了

块作用域

在JavaScript中使用{}包裹的代码称为代码块,代码块内部声明的变量外部将(有可能)无法被访问。

1
2
3
4
5
6
for (let t = 1; t <= 6; t++) {
// t 只能在该代码块中被访问
console.log(t); // 正常
}
// 超出了t的作用域
console.log(t) // 报错
  1. let声明的变量会产生块作用域,var不会产生块作用域
  2. const声明的常量也会产生块作用域
  3. 不同代码块之间的变量无法互相访问
  4. 推荐使用 let或 const

全局作用域

<script>标签和.js文件的最外层就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。全局作用域中声明的变量,任何其他作用域都可以被访问。

  1. 为 window 对象动态添加的属性默认也是全局的,不推荐!
  2. 函数中未使用任何关键字声明的变量为全局变量,不推荐 !!!
  3. 尽可能少的声明全局变量,防止全局变量被污染

作用域链

作用域本质上是底层的变量查找机制

  • 在函数被执行时,会优先查找当前函数作用域中查找变量
  • 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
  1. 嵌套关系的作用域串联起来形成了作用域链
  2. 相同作用域链中按着从小到大的规则查找变量
  3. 子作用域能够访问父作用域,父级作用域无法访问子级作用域

JS垃圾回收机制

定义

内存的生命周期

JS环境中分配的内存,一般有如下生命周期:

  • 内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
  • 内存使用:即读写内存,也就是使用变量、函数等
  • 内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存

说明:

  • 全局变量一般不会回收(关闭页面回收)
  • 一般情况下局部变量的值,不用了,会被自动回收掉

内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏

算法说明

栈(操作系统):由操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。

堆(操作系统):一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。

出址圾回收机制回收。复杂数据类型放到堆里面。

  1. 引用计数法

IE采用的引用计数算法,定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象。

算法分析:跟踪记录被引用的次数,如果被引用了一次,那么就记录次数1,多次引用会累加,如果减少一个引用就减1,如果引用次数是0,则释放内存。

缺点:当两个对象相互引用(嵌套引用或循环引用),尽管他们已不再使用,垃圾回收器不会进行回收,导致内存泄漏。

1
2
3
4
5
6
7
8
function fn() {
let o1 = {};
let 02 = {};
o1.a = 02;
o2.a = o1;
return‘引用计数无法回收';
}
fn();
  1. 标记清除法
  • 现代的浏览器已经不再使用引用计数算法了。
  • 现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。

核心:

  • 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
  • 就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。
  • 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

闭包

概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域

闭包 = 内层函数 + 外层函数的变量

1
2
3
4
5
6
7
8
function outer() {
const a = 1;
function f() {
console.log(a);
}
f();
}
outer(); // 1

闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function outer1() {
const a = 1;
function f() {
console.log(a);
}
return f;
}
const fun1 = outer1();
// 外层函数使用内部函数的变量
fun1(); // 1

// 简约写法
function outer2() {
const a = 1;
return function f() {
console.log(a);
}
}
const fun2 = outer2();
// 外层函数使用内部函数的变量
fun2(); // 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
// // 普通形式
// let count = 1;
// function fn() {
// count++;
// console.log(`函数被调用${count}次`);
// }

// fn() // 2
// fn() // 3

// // count是全局函数,很容易被修改
// count = 1000;
// fn() // 1001

// 闭包形式
function fn() {
let count = 1;
return function fun() {
count++;
console.log(`函数被调用${count}次`);
}
}
const result = fn();
result() // 2
result() // 3

// 数据私有化,无法被修改
count = 1000;
result() // 4

闭包会引起内存泄漏的问题,因为根据标记清除法可知通过全局作用域中的result变量找到变量i,所以i不会被回收,本来函数执行完成后就应该被回收,但是闭包是个例外,故而引起内部泄漏的问题。

变量提升

概念:变量提升是JavaScript中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)。

注意:

  1. 变量在未声明即被访问时会报语法错误
  2. 变量在var声明之前即被访问,变量的值为 undefined
  3. let/const声明的变量不存在变量提升
  4. 变量提升出现在当前作用域中
  5. 实际开发中推荐先声明再访问变量
1
2
3
4
5
6
7
8
// 只提升变量声明,不提升赋值
console.log(num1);
var num1 = 10;

// 类似于如下书写顺序
var num2;
console.log(num2);
num2 = 10;

函数进阶

函数提升

函数提升和变量提升比较类似,是指函数在声明之前即可被调用

1
2
3
4
5
6
// 调用函数
foo();
// 声明函数
function foo() {
console.log('声明之前即被调用....');
}
1
2
3
4
5
6
// 调用函数
bar(); // 错误
// 声明函数
var bar = function foo() {
console.log('函数表达式不存在提升现象...');
}
  1. 函数提升能够使函数的声明调用更灵活
  2. 函数表达式不存在提升的现象
  3. 函数提升出现在相同作用域当中
  4. 函数提升只提升声明,不提升赋值

函数参数

动态参数

arguments 是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参

1
2
3
4
5
6
7
8
9
10
11
// 求和函数,计算所有参数的和
function sum() {
let s = 0;
for (let i = 0; i < arguments.length; i++) {
s += arguments[i];
}
console.log(s);
}
// 调用求和函数
sum(5, 10);
sum(1, 2, 4);
  1. arguments 是一个伪数组,只存在于函数中
  2. arguments 的作用是动态获取函数的实参
  3. 可以通过for循环依次得到传递过来的实参

剩余参数

  1. …是语法符号,置于最末函数形参之前,用于获取多余的实参
  2. 借助…获取的剩余实参,是个真数组
1
2
3
4
5
6
function config(baseURL, ...other) {
console.log(baseURL); // 得到 'http://baidu.com'
console.log(other); // 打印 ['get', 'json']
}
// 调用函数
config('http://baidu.com', 'get', 'json');

箭头函数

目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁

使用场景:箭头函数更适用于那些本来需要匿名函数的地方

基本语法

语法1:

1
2
3
4
5
6
7
8
9
10
11
// 普通函数
const fn = function () {
console.log('我是普通函数');
}
fn();

// 箭头函数
const fun = () => {
console.log('我是箭头函数');
}
fun();

语法2:只有一个参数可以省略小括号

1
2
3
4
5
6
7
8
9
10
11
// 普通函数
const fn = function (x) {
return x + x;
}
console.log(fn(1));

// 箭头函数
const fun = x => {
return x + x;
}
console.log(fun(1));

语法3:如果函数只有一行代码,可以写到一行上,并且无需写 return(一定不能写,并且不能加花括号) ,直接返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 普通函数
const fn = function (x, y) {
return x + y;
}
console.log(fn(1, 2));

// 箭头函数
const fun1 = (x, y) => x + y;
console.log(fun1(1, 2));

// 上述代码是下面代码的简写形式
const fun2 = (x, y) => {
sum = x + y
return sum
};
console.log(fun2(1, 2));

语法4:加括号的函数体返回对象字面量表达式

1
2
3
4
5
6
7
8
9
// 普通函数
const fn = function (uname) {
return {uname: uname};
}
console.log(fn('刘德华'));

// 箭头函数
const fun = (uname) => ({uname: uname})
console.log(fun('刘德华'));

箭头函数参数

  1. 普通函数有arguments 动态参数
  2. 箭头函数没有arguments 动态参数,但是有 剩余参数 …args
1
2
3
4
5
6
7
8
9
const fn = (...arr) => {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
const result = fn(1, 2);
console.log(result)

箭头函数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
console.log(this) //window
const obj1 = {
uname: 'obj1',
sayHi: function() {
console.log(this); // obj1
}
}
obj1.sayHi();
const obj2 = {
uname: 'obj2',
sayHi: () => {
console.log(this); // window
}
}
obj2.sayHi();
const obj3 = {
uname: 'obj3',
sayHi: function() {
console.log(this); // obj3
let i = 0;
const fn = () => {
console.log(this); // obj3
}
fn();
}
}
obj3.sayHi();

解构赋值

数组解构

数组解构是将数组的单元值快速批量赋值给一系列变量的简介语法

基本语法:

  • 赋值运算符 = 左侧的[]用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
  • 变量的顺序对应数组单元值的位置依次进行赋值操作
1
2
3
4
5
6
7
8
const arr = [60, 100, 70];
const [min, max, avg] = arr;
// const min = arr[0];
// const max = arr[1];
// const avg = arr[2];
console.log(min);
console.log(max);
console.log(avg);

典型应用:交换两个变量

1
2
3
4
5
let a = 1;
let b = 2;
[a,b] = [b,a];
console.log(a);
console.log(b);

变量少,单元值多的情况:

1
2
3
const [a,b] = [1,2,3];
console.log(a);
console.log(b);

变量多,单元值少的情况:

1
2
3
4
5
const [a,b,c,d] = [1,2,3];
console.log(a);
console.log(b);
console.log(c);
console.log(d); // undefined

利用剩余参数(只能放到最末尾)解决变量少,单元值多的情况:

1
2
3
4
const [a,b,...c] = [1,2,3,4];
console.log(a);
console.log(b);
console.log(c); // [3,4]

防止undefined传入,使用默认值的情况:

1
2
3
4
const [a = 0, b = 0, c = 0] = [1, 2];
console.log(a);
console.log(b);
console.log(c);

按需导入,忽略某些返回值的情况:

1
2
3
4
const [a, , c, d] = [1, 2, 3, 4];
console.log(a);
console.log(c);
console.log(d);

多维数组解构的情况:

1
2
3
4
5
6
7
8
9
10
// const [a, b, c] = [1, 2, [3, 4]];
// console.log(a);
// console.log(b);
// console.log(c); // [3, 4]

const [a, b, [c, d]] = [1, 2, [3, 4]];
console.log(a);
console.log(b);
console.log(c);
console.log(d);

对象解构

对象解构是将对象属性和方法快速批量赋值给一系列变量的简介语法

  1. 基本语法:
  • 赋值运算符=左侧的(用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量)
  • 对象属性的值将被赋值给与属性名相同的变量
  • 注意解构的变量名不要和外面的变量名冲突否则报错
  • 对象中找不到与变量名一致的属性时变量值为 undefined
1
2
3
const {uname, color} = {uname: 'pink', color: 'red'};
console.log(uname);
console.log(color);
  1. 对象解构的变量名,可以重新改名
1
2
3
4
const uname = 'zhangsan';
const {uname: username, color} = {uname: 'pink', color: 'red'};
console.log(username);
console.log(color);
  1. 对象数组的解构
1
2
3
4
5
6
const pig = [{
name: '佩奇',
age: 6
}];
const [{name, age}] = pig;
console.log(name, age);
  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
// const pig = {
// name: '佩奇',
// family: {
// mother: '猪妈妈',
// father: '猪爸爸',
// brother: '乔治'
// },
// age: 6
// };
// const {name, family: {mother, father, brother}, age} = pig;
// console.log(name);
// console.log(mother);
// console.log(father);
// console.log(brother);
// console.log(age);

const pig =[{
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
brother: '乔治'
},
age: 6
}];
const [{name, family: {mother, father, brother}, age}] = pig;
console.log(name);
console.log(mother);
console.log(father);
console.log(brother);
console.log(age);
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
// 1. 这是后台传递过来的数据
const msg = {
"code": 200,
"msg": "获取新闻列表成功",
"data": [
{
"id": 1,
"title": "5G商用自己,三大运用商收入下降",
"count": 58
},
{
"id": 2,
"title": "国际媒体头条速览",
"count": 56
},
{
"id": 3,
"title": "乌克兰和俄罗斯持续冲突",
"count": 1669
},

]
}

// 需求1: 请将以上msg对象 采用对象解构的方式 只选出 data 方面后面使用渲染页面
// const { data } = msg
// console.log(data)
// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// const { data } = msg
// msg 虽然很多属性,但是我们利用解构只要 data值
function render({ data }) {
// const { data } = arr
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)

// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)

深入对象

创建对象三种方式

  1. 利用对象字面量创建对象
1
2
3
const o = {
name: '佩奇';
}
  1. 利用 new Object 创建对象
1
2
const o = new Object({ name: '佩奇' });
console.log(o); // {name: '佩奇'}
  1. 利用构造函数创建对象

构造函数

构造函数:是一种特殊的函数,主要用来初始化对象

使用场景:常规的{.}语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一遍,此时可以通过构造函数来快速创建多个类似的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Pig(name, age, gender) {
this.name = name;
this.age = age;
this.gener = gender;
}
// 创建佩奇对象
const Peppa = new Pig('佩奇', 6, '女');
// 创建乔治对象
const George = new Pig('乔治', 3, '男');
// 创建猪妈妈对象
const Mum = new Pig('猪妈妈', 30, '女');
// 创建猪爸爸对象
const Dad = new Pig('猪爸爸', 32, '男');
console.log(Peppa); // {name:'佩奇',age:6,gener:'女'}
  1. 使用new 关键字调用函数的行为被称为实例化
  2. 实例化构造函数时没有参数时可以省略()
  3. 构造函数内部无需写return,返回值即为新创建的对象
  4. 构造函数内部的return返回的值无效,所以不要写return
  5. new Object ( ) new Date() 也是实例化构造函数
  6. 建议构造函数名为大写字母开头

示例化执行过程:

  1. 创建新空对象
  2. 构造函数this执行新对象
  3. 执行构造函数代码,修改this,添加新的属性
  4. 返回新对象

实例成员

通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员(实例属性和实例方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 构造函数
function Person() {
// 构造函数内部的 this 就是实例对象
//实例对象中动态添加属性
this.name ='小明';
// 实例对象动态添加方法
this.sayHi = function () {
console.log('大家好~');
}
}

//实例化,p1 是实例对象
// p1 实际就是 构造函数内部的 this
const p1 = new Person();
console.log(p1);
console.log(p1.name); // 访问实例属性
p1.sayHi(); //调用实例方法

1.为构造函数传入参数,创建结构相同但值不同的对象

2.构造函数创建的实例对象彼此独立互不影响

静态成员

构造函数的属性和方法被称为静态成员(静态属性和静态方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 构造函数
function Person(name, age) {
// 省略实例成员
}
// 静态属性
Person.eyes = 2;
Person.arms = 2;
// 静态方法
Person.walk = function () {
console.log('^_^人都会走路 ... ');
// this 指向 Person
console.log(this.eyes);
}

1.静态成员只能构造函数来访问

2.静态方法中的this指向构造函数

内置构造函数

基本数据类型拥有属性和方法的原因:JS底层对基本数据类型进行了包装

Object

静态方法

  1. Object.keys 静态方法获取对象中所有属性(键)
1
2
3
4
const o = {name: '佩奇', age: 6};
// 获得对象的所有键,并且返回是一个数组
const arr = Object.keys(o);
console.log(arr); // ['name', 'age']

注意:返回的是一个数组

  1. Object.values 静态方法获取对象中所有属性值
1
2
3
4
const o = {name: '佩奇', age: 6};
// 获得对象的所有键,并且返回是一个数组
const arr = Object.values(o);
console.log(arr); // ['佩奇', 6]

注意:返回的是一个数组

  1. Object.assign 静态方法常用于对象拷贝

作用:经常使用的场景是给对象添加属性

1
2
3
4
const o = {name: '佩奇', age: 6};
// 获得对象的所有键,并且返回是一个数组
Object.assign(o, {gender: '女'});
console.log(o); // {name: '佩奇', age: 6, gender: '女'}

Array

常见实例方法

  1. reduce 返回累计处理的结果,经常用于求和等

基本语法:

1
2
arr.reduce(function(){}, 起始值);
arr.reduce(function(上一次值,当前值){}, 初始值);

执行流程:

  1. 如果没有起始值,则上一次值以数组的第一个数组元素的值
  2. 每一次循环,把返回值给做为 下一次循环的上一次值
  3. 如果有起始值,则 起始值做为上一次值
  1. 实例方法 join 数组元素拼接为字符串,返回字符串(重点)
1
2
3
4
const spec = {size:'40cm*40cm', color:'黑色'};
const arr = Object.values(spec);
const str = arr.join('/');
console.log(str);
  1. 实例方法 find 查找元素,返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回undefined(重点)
  2. 实例方法 every 检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回true,否则返回false(重点)
  3. 实例方法 some 检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回true,否则返回false
  4. 实例方法 concat 合并两个数组,返回生成新数组
  5. 实例方法 sort 对原数组单元值排序
  6. 实例方法 splice 删除或替换原数组单元
  7. 实例方法 reverse 反转数组
  8. 实例方法 findIndex 查找元素的索引值

常见静态方法

  1. 静态方法 Array.from() 将伪数组转换为真数组

String

常见实例方法和属性

  1. 实例属性 length 用来获取字符串的度长(重点)
  2. 实例方法 split(‘分隔符’)用来将字符串拆分成数组(重点)
1
2
3
4
5
6
7
8
9
// 将字符串转换为数组
const str1 = 'red,pink';
const arr1 = str1.split(',');
console.log(arr1);

// 将数组转换为字符串
const arr2 = ['red', 'pink'];
const str2 = arr2.join(',');
console.log(str2);
  1. 实例方法 substring(需要截取的第一个字符的索引[,结束的索引号]) 用于字符串截取(重点)
1
2
3
4
const str = 'hello, world';
// 不包含结尾索引的值
const str1 = str.substring(7, 12);
console.log(str1);
  1. 实例方法 startsWith(检测字符串[,检测位置索引号])检测是否以某字符开头(重点)
1
2
3
4
5
6
var str = "To be, or not to be, that is the question.";

console.log(str.startsWith("To be")); // true
console.log(str.startsWith("not to be")); // false
// 以第10个索引的值开始,包括第10个索引的值
console.log(str.startsWith("not to be", 10)); // true
  1. 实例方法 includes(搜索的字符串[,检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回true 或false(重点)
1
2
3
4
5
6
7
8
var str = 'To be, or not to be, that is the question.';

console.log(str.includes('To be')); // true
console.log(str.includes('question')); // true
console.log(str.includes('nonexistent')); // false
// 以第1个索引的值开始,包括第1个索引的值
console.log(str.includes('To be', 1)); // false
console.log(str.includes('TO BE')); // false
  1. 实例方法 toUpperCase 用于将字母转换成大写
  2. 实例方法 toLowerCase 用于将字母转换成小写
  3. 实例方法 indexOf 检测是否包含某字符
  4. 实例方法 endsWith** **检测是否以某字符结尾
  5. 实例方法 replace 用于替换字符串,支持正则匹配
  6. 实例方法 match 用于查找字符串,支持正则匹配

Number

Number 是内置的构造函数,用于创建数值

常用方法:

  1. toFixed() 设置保留小数位的长度 (参数不写,默认为0)
1
2
3
4
// 数值类型
const price = 12.345;
// 保留两位小数,四舍五入
console.log(price.toFixed(2)); // 12.35
  1. toString() 将数字转换为字符串
1
2
3
const num = 10;
console.log(typeof(num));
console.log(typeof(num.toString()));

编程思想

面向过程思想介绍

面向过程:分析解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用即可。

面向对象思想介绍

面向对象:把事务分解为一个个对象,然后由对象之间分工和合作

在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工

面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。

面向对象的特性:

  • 封装性
  • 继承性
  • 多态性

构造函数

  • 封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。
  • 同样的将变量和函数组合到了一起并能通过this实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
  • 构造函数体现了面向对象的封装特性
  • 构造函数实例创建的对象彼此独立、互不影响
1
2
3
4
5
6
7
8
9
10
11
function Star(uname, age) {
this.uname = uname;
this.age = age;
this.sing = function() {
console.log('我会唱歌');
}
}

const ldh=new Star('刘德华',18);
const zxy=new Star('张学友',19);
console.log(ldh.sing === zxy.sing);

上述代码存在浪费内存的问题,new的两个对象是完全不同的对象,故而内存会在堆中开辟两个内存区域存放相同的函数。为解决该问题,引入了原型

原型

定义

  • 构造函数通过原型分配的函数是所有对象所共享的。
  • JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象
  • 这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
  • 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
  • 构造函数和原型对象中的this都指向 实例化的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 公用的属性写到构造函数里面
function Star(uname, age) {
this.uname = uname;
this.age = age;
}

// 公用的方法写到原型对象上面
Star.prototype.sing = function() {
console.log('我会唱歌');
}

const ldh=new Star('刘德华',18);
const zxy=new Star('张学友',19);
console.log(ldh.sing === zxy.sing);

上述写法可以节约内存

构造函数和原型对象中的this都指向实例化的对象

1
2
3
4
5
6
7
8
let that;
function Person(name) {
this.name = name;
that = this;
}
const o = new Person();
// 构造函数中的this指向实例化的对象
console.log(that === o); // true
1
2
3
4
5
6
7
8
9
10
11
12
let that;
function Person(name) {
this.name = name;
}
Person.prototype.sing = function () {
that = this;
}
const o = new Person();
// 调用该方法后才可执行that = this;代码
o.sing();
// 原型对象中的this指向实例化的对象
console.log(that === o); // true

consructor 属性

每个原型对象里面都有个constructor 属性(constructor:构造函数)。该属性指向该原型对象的构造函数。

1
2
3
4
5
function Star() {

}
const star = new Star();
console.log(Star.prototype.constructor === Star); // true

如果有多个对象的方法,我们可以给原型对象采取对象形式赋值。但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Star() {
this.name = name;
}
Star.prototype = {
// 重新指回构造函数
constructor: Star,
sing: function() {
console.log('唱歌');
},
sayHi: function() {
console.log('Hi');
}
};
console.log(Star.prototype.constructor);

对象原型

对象都会有一个属性__proto__指向构造函数的prototype 原型对象,之所以对象可以使用构造函数 prototype 原型对象的属性和方法,因为对象有__proto__原型的存在。

1
2
3
4
5
6
function Star(name) {
this.name = name;
}
const star = new Star();
// 对象原型指向该构造函数的原型对象
console.log(star.__proto__ === Star.prototype);

注意:

  1. __proto__是JS非标准属性
  2. [[prototype]]和__proto__意义相同
  3. 用来表明当前实例对象指向哪个原型对象prototype
  4. __proto__对象原型里面也有一个construtor属性,指向创建该实例对象的构造函数

原型继承

通过原型来实现对象之间的继承

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
// 对象:人
const Person = {
eyes: 2,
head: 1
};

// 构造函数:女人
function Woman() {

}
// 通过原型对象继承
Woman.prototype = Person;
// 通过constructor 属性指向该构造函数
Woman.prototype.constructor = Woman;
// 给女人添加独有的方法:生孩子
Woman.prototype.baby = function() {
console.log('生孩子');
}
// 实例化对象:女人
const red = new Woman;
// 打印red对象的eyes属性
console.log(red.eyes);

// 构造函数:男人
function Man() {

}
// 通过原型对象继承
Man.prototype = Person;
// 通过constructor 属性指向该构造函数
Man.prototype.constructor = Man;
// 实例化对象:男人
const pink = new Man;
// 打印pink对象的eyes属性
console.log(pink.eyes);
// 结果男人也有生孩子的方法
pink.baby()

以上代码存在问题,由于男人和女人的prototype原型对象都是指向相同的Person对象,故而当一个原型对象发生改变,另一个原型对象也会发生改变(原因:两个原型对象分别指向栈中的不同位置,但是这两个位置都存放的是堆中Person对象的地址)

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
// 对象:人
function Person() {
this.eyes = 2,
this.head = 1
}

// 构造函数:女人
function Woman() {

}
// 通过原型对象继承
Woman.prototype = new Person();
// 通过constructor 属性指向该构造函数
Woman.prototype.constructor = Woman;
// 给女人添加独有的方法:生孩子
Woman.prototype.baby = function() {
console.log('生孩子');
}
// 实例化对象:女人
const red = new Woman;
// 打印red对象的eyes属性
console.log(red.eyes);

// 构造函数:男人
function Man() {

}
// 通过原型对象继承
Man.prototype = new Person();
// 通过constructor 属性指向该构造函数
Man.prototype.constructor = Man;
// 实例化对象:男人
const pink = new Man;
// 打印pink对象的eyes属性
console.log(pink.eyes);
// 男人无法调用女人独有的生孩子方法
// pink.baby()

以上代码通过将Person对象改为构造函数,使实例化的男人和女人的prototype原型对象有相同的结构,不同的对象(原因:通过构造函数实例化的对象,在堆中会重新开辟一块空间来存放该对象,进而男人和女人的prototype原型对象指向的栈中不同的位置,两个位置存放的指向堆中对象的地址也不一样)

原型链

基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系使一种链状的结构,将原型对象的链状关系称为原型链。

原型链即为查找规则:

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)
  3. 如果还没有就查找原型对象的原型(Object的原型对象)
  4. 依此类推一直找到Object为止(null)
  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
  6. 可以使用 instanceof 运算符用干检测构造函数的prototvpe属性是否出现在某个实例对象的原型链上
1
2
3
4
5
6
7
function Person() {}
const man = new Person();
console.log(man instanceof Person);
console.log(man instanceof Object);
console.log(man instanceof Array);
console.log([1,2,3] instanceof Array);
console.log([1,2,3] instanceof Object);

深浅拷贝

单纯给对象赋值,会出现下面示例的问题

1
2
3
4
5
6
7
8
9
10
const obj = {
name: 'pink',
age: 18
};
const o = obj;
console.log(o);
o.age = 20;
console.log(o);
// 会改动原对象中的属性,原因在于赋值是将堆中数据的地址赋给对方
console.log(obj);

深浅拷贝只针对于引用类型

浅拷贝

常用方法:

  1. 拷贝对象:Object.assign() / 展开运算符 {…对象名} 拷贝对象
  2. 拷贝数组:Array.prototype.concat() 或者 […数组名] 拷贝数组
1
2
3
4
5
6
7
8
9
const obj = {
name: 'pink',
age: 18
};
const o = { ...obj };
console.log(o);
o.age = 20;
console.log(o);
console.log(obj);

浅拷贝只拷贝最外面一层,故而只适用于简单的引用类型,会存在下面示例的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const obj = {
name: 'pink',
age: 18,
family: {
baby: '小pink'
}
};
const o = {};
Object.assign(o, obj);
console.log(o);
o.age = 20;
o.family.baby = '老pink';
console.log(o);
// 深层的对象还是只拷贝了地址
console.log(obj);

总结:如果是简单数据类型拷贝值,引用数据类型拷贝的是地址(简单理解:如果是单层对象则没问题,如果是多层则会出问题)

深拷贝

  1. 通过递归实现深拷贝

函数递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数

  • 简单理解:函数内部自己调用自己,这个函数就是递归函数
  • 递归函数的作用和循环效果类似
  • 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须加退出条件 return
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
const obj = {
uname: 'pink',
age: 18,
hobby: ['足球', '篮球'],
family: {
baby: '小pink'
}
};
const o = {};
// 拷贝函数,newobj 新对象。oldObj 旧对象
// 必须先判断是否属于数组,再判断是否属于对象,因为数组也属于对象,如果先判断是否属于对象,那么数组也会采用对象拷贝的方式。
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
// for in 遍历对象,k 表示属性名,对象名[k]中有该属性名,则取该属性值,没有则表示对象名.k
// 判断旧对象中属性是否有数组类型
if (oldObj[k] instanceof Array) {
// 在新对象中创建该属性,并赋值为空数组
newObj[k] = [];
// 递归赋值,for in遍历数组,则k表示数组下标
deepCopy(newObj[k], oldObj[k]);
} else if (oldObj[k] instanceof Object) {
// 在新对象中创建该属性,并赋值为空对象
newObj[k] = {};
// 递归赋值
deepCopy(newObj[k], oldObj[k]);
} else {
// 将旧对象中对应的属性值赋值给新对象
newObj[k] = oldObj[k];
}
}
}
deepCopy(o, obj);
console.log(o);
o.age = 20;
o.hobby[0] = '乒乓球';
o.family.baby = '老pink';
console.log(o);
console.log(obj);

总结:

  1. 深拷贝是拷贝出的新对象不会影响旧对象,要想实现深拷贝,需要用到函数递归
  2. 当遇到非数组和非对象的时候,直接赋值即可,当遇到数组或对象时,再次递归调用该函数拷贝
  3. 先判断数组再判断对象
  1. 通过lodash实现深拷贝

通过_.cloneDeep(对象名) 方法实现深拷贝

中文教程:https://www.lodashjs.com/docs/lodash.cloneDeep

  1. 通过JSON.stringify() 实现深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {
uname: 'pink',
age: 18,
hobby: ['足球', '篮球'],
family: {
baby: '小pink'
}
};
// 通过JSON.stringify() 实现深拷贝
const o = JSON.parse(JSON.stringify(obj));
console.log(o);
o.age = 20;
o.hobby[0] = '乒乓球';
o.family.baby = '老pink';
console.log(o);
console.log(obj);

异常处理

throw 抛异常

异常处理是指预估代码执行过程中可能发生的错误,然后最大程序的避免错误的发生导致整个程序无法继续运行。

1
2
3
4
5
6
7
8
function counter(x, y) {
if (!x || !y) {
// throw '参数不能为空!';
throw new Error('参数不能为空!');
}
return x + y;
}
counter();

总结:

  1. throw 抛出异常信息,程序也会终止执行
  2. throw 后面跟的是错误提示信息
  3. Error 对象配合 throw 使用,能够设置更详细的错误信息

try / catch 捕获异常

通过try / catch** 捕获错误信息(浏览器提供的错误信息) try 试试 catch 拦住 finally 最后**

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
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<p>123</p>
<script>
function fn() {
try {
// 可能出错的代码放到try代码块中
const p = document.querySelector('.p');
p.style.color = 'red';
} catch (err) {
// 出错打印该错误信息
console.log(err.message);
} finally {
// 不管try代码块中代码是否出错,都会执行
alert('弹出对话框');
}
// 不会终结程序
console.log(11);
}
fn();
</script>

</body>
</html>

总结:

1.try … catch用于捕获错误信息

2.将预估可能发生错误的代码写在try代码段中

3.如果try代码段中出现错误后,会执行catch代码段,并截获到错误信息

  1. finally 不管是否有错误,都会执行

debugger

在代码中加入debugger,会在页面刷新时从debugger开始打断点调试

处理this

this指向

  1. 普通函数:谁调用函数,this就指向谁
  2. 箭头函数:箭头函数中的this与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在this!
  • 箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中this的值和外层的this是一样
  • 箭头函数中的this引用的就是最近作用域中的this
  • 向外层作用域中,一层一层查找this,直到有this的定义

总结:构造函数、原型函数、字面量对象中函数、DOM事件函数不适用箭头函数,需要使用上层this的地方适用箭头函数

改变this

  1. call()
1
函数名.call(thisArg, arg1, arg2, ...);

thisArg: 在fun函数运行时指定的this值

arg1, arg2:传递的其他参数

返回值就是函数的返回值,因为call就是调用函数

1
2
3
4
5
6
7
8
const obj = {
name: 'pink'
}
function fun(x, y) {
console.log(this);
console.log(x + y);
}
fun.call(obj, 1, 2);
  1. apply()

使用apply() 方法调用函数,同时指定被调用函数中 this 的值

1
函数名.apply(thisArg, [argsArray]);
  • thisArg:在fun函数运行时指定的this值
  • argsArray:传递的值,必须包含在数组里面
  • 返回值就是函数的返回值,因为它就是调用函数
  • 因此apply 主要跟数组有关系,比如使用Math.max()求数组的最大值
1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
name: 'pink'
}
function fun(x, y) {
console.log(this);
console.log(x + y);
}
fun.apply(obj, [1, 2]);
// 使用场景
console.log(Math.max(1, 2, 3));
console.log(Math.max.apply(Math, [1, 2, 3]));
console.log(Math.max.apply(null, [1, 2, 3, 4]));
  1. bind()

bind() 方法不会调用函数。但是能改变函数内部this指向

1
函数名.bind(thisArg, arg1, arg2, ...);
  • thisArg:在fun函数运行时指定的this值
  • arg1,arg2:传递的其他参数
  • 返回由指定的this值和初始化参数改造的 原函数拷贝(新函数)
  • 因此当我们只是想改变this指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this指向.
1
2
3
4
5
6
7
8
9
const obj = {
name: 'pink'
}
function fun(x, y) {
console.log(this);
console.log(x + y);
}
const fn = fun.bind(obj, 1, 2);
fn();

性能优化

防抖

定义

防抖:单位时间内,频繁触发事件,只执行最后一次

使用场景:搜索框搜索输入、手机号、邮箱验证输入检测

实现方式

  1. 利用Lodash实现防抖

官方文档:https://www.lodashjs.com/docs/lodash.debounce#_debouncefunc-wait0-options

  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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>

<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box');
let i = 0;

function mouseMove() {
box.innerHTML = `${i++}`;
}

// 回调函数中加上小括号,会导致函数立即执行,而不会等事件发生才执行
// 防抖实现:只有当鼠标停止500ms后,才执行mouseMove函数
box.addEventListener('mousemove', debounce(mouseMove, 500));

// 防抖函数
function debounce(fn, t) {
// 1.声明一个定时器变量
let timerId;
// 因为上述调用debounce函数加上了小括号,会导致函数立即执行,但是返回一个函数,就相当于
// box.addEventListener('mousemove', function() { //函数体 });写法
return function () {
// 2.当鼠标每次滑动都先判断是否有定时器,如果有定时器先清除以前的定时器
if(timerId) clearTimeout(timerId);
// 3.如果没有定时器则开启定时器,记得存到变量里面
// 4.在定时器里面调用要执行的函数
timerId = setTimeout(fn, t);
}
}
</script>
</body>

</html>

节流

定义

节流:单位事件内,频繁触发事件,只执行一次

使用场景:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll 等等

实现方式

  1. 使用lodash实现节流

官方文档:https://www.lodashjs.com/docs/lodash.throttle#_throttlefunc-wait0-options

  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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Document</title>
<style>
.box {
width: 500px;
height: 500px;
background-color: #ccc;
color: #fff;
text-align: center;
font-size: 100px;
}
</style>
</head>

<body>
<div class="box"></div>
<script>
const box = document.querySelector('.box');
let i = 0;

function mouseMove() {
box.innerHTML = `${i++}`;
}

// 回调函数中加上小括号,会导致函数立即执行,而不会等事件发生才执行
// 节流实现:每500ms才执行一次mouseMove函数
box.addEventListener('mousemove', throttle(mouseMove, 500));

// 节流函数
function throttle(fn, t) {
// 1.声明一个定时器变量
let timerId = null;
// 2.当鼠标每次滑动都先判断是否有定时器,如果有定时器则不开启新定时器
// 3.如果没有定时器则开启定时器,记得存到变量里面(定时器里面调用执行的函数,定时器里面要把定时器清空)
return function () {
if (!timerId) {
timerId = setTimeout(function () {
fn();
// 定时器内部使用clearTimeout无法清除定时器,故使用timerId = null清除
timerId = null;
}, t);
}
}
}
</script>
</body>

</html>