本篇文章是记录来自黑马pink老师2023年课程的学习笔记,不得不说pink老师讲的是真的超级棒!
内容也是非常丰富的,本篇文章我也是基于在原始课程的基础上,添加了一些自己的写法,还有一些小案例内容。所有涉及到的JavaScript知识点有:变量、数组、数据类型、运算符、函数、对象、BOM和DOM、事件、面向对象、以及ES6+新特性。正则表达式等等等。而且本篇内容还不算完结,知识记录了pink老师讲的课的知识点,后面我还会添加上Promise、AJAX、跨域等知识,让整个JavaScript体系完完全全的记录好。
本篇文章也是加了我个人的很多思考,可能会有些问题所在,如果你发现了有不好的地方、或者是存在一些问题,那么可以私信我或者在评论区指出来。如果我看到了,我一定到即使回应并且做出更改∠(°ゝ°)。
废话不多说啦,祝大家学习愉快💪⛽️。
JS介绍
JavaScript认识
简单来说,JavaScript就像是网页的魔法药水。它能够让网页动起来,和用户互动。想象一下,你浏览一个网站,点击一个按钮后,页面会弹出一个小窗口或者改变一些内容,这些都是JavaScript在背后做的手脚。
JavaScript就像是一个小助手,它帮网站做以下这些事情:
- 检查你填写的表单信息是否正确,比如密码是否太短。
- 让网页上的图片动起来,或者做一些酷炫的动画效果。
- 无需重新加载整个页面,就能更新网页上的某些信息,比如微博上的新消息提醒。
- 根据你的操作,显示或隐藏网页上的某些部分。
总之,JavaScript让网页变得更有趣、更智能,能够响应你的操作,而不是呆呆的一成不变。🥳🥳🥳
JavaScript组成
ECMAScript:
- 规定了js基础语法核心知识。比如:变量、分支语句、循环语句、对象等等
Web APIs:
- DOM操作文档,比如对页面元素进行移动、大小、添加删除等操作
- BOM操作浏览器,比如页面弹窗,检测窗口宽度、存储数据到浏览器等等
基本使用
书写位置
理论上写到任何一个位置都可以,但是专业性的应该按照下面的内容写。
JavaScript一些术语:
术语 | 解释 | 举例 |
---|---|---|
关键字 | 在JavaScript中有特殊意义的词汇 | let 、var 、function 、if 、else |
保留字 | 在目前的JavaScript中没意义,但未来可能会具有特殊意义的词汇 | switch 、case 、break |
标识(标识符) | 变量名、函数名的另一种叫法 | 无 |
表达式 | 能产生值的代码,一般配合运算符出现 | 10 + 3 、age >= 18 |
语句 | 一段可执行的代码 | If () 、for() |
内部JS
直接写在<body>
内部的<script>
标签中:
外部JS
代码写在以.js
结尾的文件中:
引入JS方法:
<body>
<script src="js文件.js"></script>
</body>
⚠️中间不要写内容,写内容也不会执行的
<body> <script src="js文件.js"> ❌中间不写内容 </script> </body>
内联JS
写在标签内部:
<body>
<button onclick="alert('这是内联JS')">内联JS</button>
</body>
注意:此处作为了解即可,但是后面vue框架会用这种模式
注释
文件内容是关于编程注释的说明,包括单行注释和块注释的用法。下面是识别出来的信息:
-
单行注释
- 符号:
//
- 作用:
//
右边的这一行代码会被忽略,即不会被程序执行。 - 快捷键:
Ctrl + /
- 符号:
-
块注释
- 符号:
/* */
- 作用:位于
/*
和*/
之间的所有内容都会被忽略,可以跨多行。 - 快捷键:
Shift + Alt + A
- 符号:
这些注释是编程中常用的,用来临时移除代码段或添加说明,而不会影响程序的执行。
// 这是一个JavaScript函数,用于计算两个数的和
function calculateSum(a, b) {
// 计算两个数的和
var sum = a + b;
// 以下代码块是注释,不会被执行
/*
if (sum > 100) {
console.log("Sum is greater than 100");
}
*/
// 返回计算结果
return sum;
}
// 调用函数并打印结果
console.log(calculateSum(50, 70)); // 输出:120
结束符
使用;
来结束语句,但是这个结束符可写可不写,浏览器引擎会自动推断语句的结束位置。
现状:在实际开发中,越来越多的人主张,书写JavaScript代码时省略结束符
约定:为了风格统一,结束符要么每句都写,要么每句都不写(按照团队要求)
输入与输出
输入语法
prompt('内容')
作用:显示一个对话框,对话框中包含一条文字信息,用来提示用户输入文字。
输出语法
documetn.write('内容')
- 作用:向body内输出内容
- 注意:如果输出的内容写的是标签,也会被解析成网页元素
alert('内容')
- 作用:页面弹出警告窗
console.log('内容')
- 作用:控制台输出语法,程序员调试专用!
代码执行顺序
- 按HTML文档流顺序执行JavaScript代码
- alert()和prompt()它们会跳过页面渲染先被执行(目前作为了解,后期讲解详细执行过程)
字面量
在计算机科学中,字面量是在计算机中描述事/物
比如:
- 我们工资是:
1000
(此时1000
就是数字字面量) string
字符串字面量- 还有接下来我们学的
[]
数组字面量、{}
对象字面量量等等
变量
变量是计算机中用来存储数据的“容器”,它可以让计算机变得有记忆。
⚠️注意:变量不是数据本身,它们仅仅是一个用于存储数值的容器。可以理解为是一个个用来装东西的纸箱子。
常量
概念:使用const声明的变量称为“常量”
使用场景:当某个变量永远不会改变的时候,就可以使用const来声明,而不是let。
命名规范:和变量一致
语法:const 常量名 = 值
注意:常量不允许重新赋值,声明的时候必须赋值(初始化)
小技巧:不需要重新赋值的数据使用const
三种常见声明关键字:
- let——现在实际开发变量声明方式
- var——以前的声明变量的方式,会有很多问题
- const——类似于let,但是变量的值无法被修改
变量的基本使用
变量的声明:要想使用变量,首先创建变量
语法:声明关键字 变量名(标识符)
let 变量名
声明关键字:
let 即关键字(let:允许、许可、让、要),所谓关键字是系统提供的专门用来声明(定义)变量的词语
变量的赋值:定义了一个变量后,你就能够初始化它(赋值)。
语法:在变量名之后跟上一个“=”,然后是数值。
age = 28
变量的初始化:在变量声明的时候赋值
语法:声明关键字 变量名(标识符)= 数值
let age = 28
更新变量:变量赋值后,还可以通过简单地给它一个不同的值来更新它,
let age = 28
age = 29
console.log(age)
let age = 28
let age = 29❌这种写法是错误的,let不语序多次声明一个变量
console.log(age)
声明多个变量:
语法:多个变量中间用逗号(,)隔开
let age = 18,name = job
console.log(age,name)
说明:看上去代码长度更短,但并不推荐这样。为了更好的可读性,请一行只声明一个变量。
var、let和const的区别
在较旧的JavaScript,使用关键字var来声明变量,而不是let。
var现在开发中一般不再使用它,只是我们可能再老版程序中看到它。let为了解决var的一些问题。
var 声明:
- 可以先使用在声明 (不合理❌)
- var声明过的变量可以重复声明(不合理❌)
- 比如变量提升、全局变量、没有块级作用域等等
const和let比较:
建议:const优先,尽量使用const
原因:
-
const语义化更好
-
更多变量我们声明的时候就知道他不会被更改,所以就使用const
但是对于引用数据类型就可以更改:因为对于引用数据类型,const声明的变量,里面存的不是值,是地址!
案例——交换变量
需求:
- 有2个变量:num1里面放的是10,num2里面放的是20
- 最后变为num1里面放的是20,num2里面放的是10
目的:
- 练习变量的使用
- 为了后面冒泡排序做准备
分析:
- 核心思路:使用一个临时变量用来做中间存储
步骤:
-
声明一个临时变量temp
-
把num1的值赋值给temp
-
把num2的值赋值给num1
-
把temp的值给num2
不设中间变量的方法是
- num1=num1+num2;
- num2=num1-num2;
- num1=num1-num2;
感兴趣的同学可以验证一下;
变量的本质
内存:计算机中存储数据的地方,相当于一个空间
变量本质:是程序在内存中申请的一块用来存放数据的小空间(开辟的小空间叫做——栈)
变量的命名规范
- 不能是关键字:有特殊含义的字符,JavaScript内置的一些英语词汇。例如:let、var、if、for等
- 只能用下划线、字母、数字、$组成,且数字不能开头。
- 字母严格区分大小写,如Age和age是不同的变量
规范:
-
起名要有意义
-
遵守小驼峰命名法:第一个单词首字母小写,后面每个单词首字母大写。
例:userName
数组
一些关于数组的术语:
- 元素:数组中保存的每个数据都叫数组元素
- 下标:数组中数据的编号
- 长度:数组中数据的个数,通过数组的
length
属性获得
数组的长度 = 索引号 + 1
数组基本使用
声明数组
数组(Array)一一一种将一组数据存储在单个变量名下的优雅方式
语法:let arr = [数据1,数据2,...数组n]
例:
letnames =['小明','小刚','小红',‘小丽',‘小米']
使用数组
语法:数组名[索引]
- 通过下标取数据
- 取出来是什么类型的,就根据这种类型特点来访问
数据单元值类型
数组做为数据的集合,它的单元值可以是任意数据类型
<script>
// 6. 数组单值类型可以是任意数据类型
// a) 数组单元值的类型为字符类型
let list = ['HTML', 'CSS', 'JavaScript']
// b) 数组单元值的类型为数值类型
let scores = [78, 84, 70, 62, 75]
// c) 混合多种类型
let mixin = [true, 1, false, 'hello']
</script>
数组长度属性
重申一次,数组在 JavaScript 中并不是新的数据类型,它属于对象类型。
<script>
// 定义一个数组
let arr = ['html', 'css', 'javascript']
// 数组对应着一个 length 属性,它的含义是获取数组的长度
console.log(arr.length) // 3
</script>
操作数组
数组本质是数据集合,操作数据无非就是增、删、改、查
语法:
使用循环操作给数组批量操作:
let arr = ['小红','小明','小刚'] for(let i = 0; i<arr.length; i++){ arr[i] = arr[i] + '同学' } console.log(arr)
数组做为对象数据类型,不但有 length
属性可以使用,还提供了许多方法:
-
push 动态向数组的尾部添加一个单元,并返回该数组的新长度 (重点)
语法:arr.push('元素1,元素2,...')
-
unshit 动态向数组头部添加一个单元,并返回该数组的新长度 (重点)
语法:arr.unshit('元素1,元素2,...')
-
pop 删除最后一个单元,并返回该元素的值
语法:arr.pop('元素1,元素2,...')
-
shift 删除第一个单元
语法:arr.shift('元素1,元素2,...')
-
splice 动态删除任意单元
语法:arr.splice(起始位置,删除几个元素) 例如:arr.splice(1,1) //是从索引号1的位置开始删,只删除1个 arr.splice(1) //只写一个的话就是从索引号为1的位置往后全部删掉
-
sort 数组内容排序
语法:arr.sort() 升序排列:arr.sort(function(a,b){ return a-b }) 降序排列:arr.sort(function(a,b){ return b-a }) 就是sort()方法中的函数返回正值a-b>0或者b-a>0的情况下就交换,返回负值就不交换
使用以上4个方法时,都是直接在原数组上进行操作,即成功调任何一个方法,原数组都跟着发生相应的改变。并且在添加或删除单元时 length
并不会发生错乱。
<script>
// 定义一个数组
let arr = ['html', 'css', 'javascript']
// 1. push 动态向数组的尾部添加一个单元
arr.push('Nodejs')
console.log(arr)
arr.push('Vue')
// 2. unshit 动态向数组头部添加一个单元
arr.unshift('VS Code')
console.log(arr)
// 3. splice 动态删除任意单元
arr.splice(2, 1) // 从索引值为2的位置开始删除1个单元
console.log(arr)
// 4. pop 删除最后一个单元
arr.pop()
console.log(arr)
// 5. shift 删除第一个单元
arr.shift()
console.log(arr)
</script>
案例——求数组最大值、最小值
⭕这里会涉及到循环语句,可以先跳转到循环语句进行学习,在回来做这个案例💪。
问题:将数组[2,6,1,7,4]中找出最大值和最小值
①:声明一个保存最大元素的变量max
②:默认最大值可以取数组中的第一个元素。
③:遍历这个数组,把里面每个数组元素和max相比较。
④:如果这个数组元素大于max就把这个数组元素存到max里面,否则继续下一轮比较。
③:最后输出这个max
<script>
let arr = [2,6,1,7,4]
let max = arr[0] //最大值
let min = arr[0] //最小值
//遍历数组
for(let i =1; i<arr.length; i++){
//如果max比数组元素出面的值小,我们就要把这个数组元素赋值给max
//if(max<arr[i]) max = arr[i] 也可以写成简写形式,if语句只有一个语句的话就可以省略大括号
if(max<arr[i]){
max = arr[i]
}
//如果min比数组元素大,我们就需要把数组元素给min
if(min > arr[i]){
min = arr[i]
}
}
//输出最大值
console.log(max)
console.log(min)
</script>
案例——数组筛选
问题:将数组[2,0,6,1,77,0,52,0,25,7]中大于等于10的元素选出来,放入新数组
分析:
①:声明一个新的数组用于存放新数据newArr
②:遍历原来的旧数组,找出大于等于10的元素
③:依次追加给新数组newArr
<script>
let arr = [2,0,6,1,77,0,52,0,25,7]
//声明新的空数组
let newArr = []
//遍历旧数组
for (let i = 0; i<arr.length; i++){
if(arr[i] >= 10){
//满足条件,追加新的数组
newArr.push(arr[i])
}
}
//输出新数组
console.log(newArr)
</script>
案例——数组去0
需求:将数组[2,0,6,1,77,0,52,0,25,7]中的0去掉后,形成一个不包含0的新数组
分析:
①:声明一个新的数组用于存放新数据newArr
②:遍历原来的旧数组,找出不等于0的元素
③:依次追加给新数组newArr
<script>
let arr = [2,0,6,1,77,0,52,0,25,7]
//声明一个新数组
let newArr = []
//遍历数组
for(let i = 0; i<arr.length; i++){
if(arr[i] != 0){
newArr.push(arr[i])
}
}
//输出数组
console.log(newArr)
</script>
冒泡排序(了解)
冒泡排序是一种简单的排序算法。
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来,走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
比如数组[2,3,1,4,5] 经过排序成为了 [1,2,3,4,5] 或者 [5,4,3,2,1]
规律:
- 比较相邻的元素。如果第一个元素大于第二个元素,则交换它们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
<script>
let arr = [5,4,3,2,1]
for(let i= 0; i<arr.length - 1; i++){
for(let j = 0; j<arr.length - i - 1;j++){
//开始交换,前提是第一次数大于第二个数才能交换
if(arr[j] > arr[j+1]){
//交换两个变量
let temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
}
console.log(arr)
</script>
数组进阶用法
字符串拼接可以利用map()
和join()
数组方法实现字符串拼接。
map方法
**使用场景:**map 可以遍历数组处理数据,并且返回新的数组。map也称为映射。映射是个术语,指两个元素的集之间元素相互“对应”的关系。
❗️map重点在于有返回值,forEach没有返回值
模版:
<body>
<script>
const arr = ['red', 'blue', 'pink']
// 1. 数组 map方法 处理数据并且 返回一个数组
const newArr = arr.map(function (ele, index) {
// console.log(ele) // 数组元素
// console.log(index) // 索引号
return ele + '颜色'
})
console.log(newArr)
</script>
</body>
join方法
join()
方法用于把数组中的所有元素转换一个字符串
参数:数组元素是通过参数里面指定的分隔符进行风格的,空字符串(``),则所有元素之间都没有任何支字符。
模版:
const arr = ['red', 'blue', 'pink']
// 数组join方法把数组转换为字符串
// 小括号为空则逗号分割
console.log(newArr.join()); // "red颜色,bLue颜色pink颜色"
// 小括号是空字符串,则元素之间没有分隔符
console.log(newArr.join("")); // "red颜色bLue颜色pink颜色"
// 小括号是其他字符,则元素之间以此字符为分隔符
console.log(newArr.join("|")); // "red颜色|bLue颜色|pink颜色"
forEach方法
forEach()
方法用于调用数组的每个元素,并将元素传递给回调函数
❗️只遍历,不返回数组❗️
语法:
被遍历的数组.forEach(function(当前数组元素,当前元素索引号){
//函数体
})
参数当前数组元素是必须要写的, 索引号可选
模版:
<script>
// forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ['red', 'green', 'pink']
const result = arr.forEach(function (item, index) {
console.log(item) // 数组元素 red green pink
console.log(index) // 索引号
})
// console.log(result) 不返回,undenifne
</script>
filter方法
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
主要使用场景: 筛选数组符合条件的元素,并返回筛选之后元素的新数组
JavaScript中的filter
方法是一个数组的方法,用于创建一个新数组,其包含通过测试的所有元素。简单来说,filter
方法会遍历数组中的每个元素,并对每个元素执行一个测试函数,只有当测试函数返回true
时,该元素才会被包含在新的数组中。
以下是filter
方法的基本用法:
array.filter(function(element, index, arr), thisValue)
element
:当前遍历到的元素。index
:当前元素的索引。arr
:调用filter
方法的数组本身。thisValue
:执行回调时使用的上下文(this
的值)。
这里是一个简单的例子:
let numbers = [1, 2, 3, 4, 5];
// 创建一个新数组,只包含原数组中的偶数
let evenNumbers = numbers.filter(function(number) {
return number % 2 === 0;
});
console.log(evenNumbers); // 输出: [2, 4]
在这个例子中,filter
方法遍历numbers
数组,并且只有当number % 2 === 0
(即数字是偶数)时,该数字才会被包含在evenNumbers
数组中。
ES6引入了箭头函数,使得filter
方法的使用更加简洁:
let evenNumbers = numbers.filter(number => number % 2 === 0);
这样,你就可以快速筛选出数组中满足特定条件的元素。
reduce方法
语法:数组名.reduce(function(上一次值,当前值){ },起始值)
没有初始值:
有初始值:
注意:如果有起始值,则把初始值累加到里面
reduce执行过程:
- 如果没有起始值,则上一次值以数组的第一个数组元素的值
- 每一次循环,把返回值给做为下一次循环的上一次值
- 如果有起始值,则起始值做为上一次值
from方法
作用是将伪数组转换成真数组
语法:Array.from()
例子:
数据类型
JS数据类型整体分为两大类:基本数据类型、引用数据类型
基本数据类型
数值类型Number
即我们数学中学习到的数字,可以是整数、小数、正数、负数。
JavaScript 中的正数、负数、小数等统一称为 数字类型。
弱数据类型:只有当我们赋值了,才知道是什么数据类型
JS是弱数据类型,变量到底属于那种类型,只有赋值之后,手我们才能确认
Java是强数据类型,例如 int a=3——必须是整数
算术运算符:数学运算符也叫算术运算符,主要包括加、减、乘、除、取余(求模)。
+
:求和
-
:求差
*
:求积
/
:求商
%
:取模(取余数)
算术运算符执行的优先级顺序:JavaScript中优先级越高越先被执行,,优先级相同时以书从左向右执行。
总结:先乘除、取余后加减,有括号先算括号里面的~~~
NAN代表一个计算错误。它是一个不正确的或者一个未定义的数学操作所得到的结果.
例如: console.log('老师’-2) //NAN
NaN是粘性的。任何对NaN的操作都会返回NaN
例如: console.log(NaN -2) //NA】N
字符串类型String
通过单引号(“)、双引号(”")或反引号(``)包裹的数据都叫字符串,单引号和双引号没有本质上的区别,推荐使用单引号。
⚠️注意:
-
无论单引号或是双引号必须成对使用
-
单引号/双引号可以互相嵌套,但是不以自已嵌套自已(口诀:外双内单,或者外单内双)
-
必要时可以使用转义符
\
,输出单引号或双引号
字符串拼接
使用+
来实现字符串拼接。
例:
模板字符串
使用场景
- 拼接字符串和变量
- 在没有它之前,要拼接变量比较麻烦
document.write('大家好,我叫’+name+',今年’+age+‘岁')//是不是非常的麻烦!!!
语法:
-
``(反引号)在英文输入模式下按键盘的tab键上方那个建(1左边那个键)
-
内容拼接变量时,用
${}
包住变量
布尔类型boolean
表示肯定或否定时在计算机中对应的是布尔类型数据。
它有两个固定的值true和false,表示肯定的数据用true(真),表示否定的数据用false(假)。
转换为Boolean型
显示转换:
记忆:"(空)、0、undefined、null、false、NaN转换为布尔值后都是false,其余则为true
隐式转换:
- 有字符串的加法 “”(空)+1,结果是“1”
- 减法
-
(像大多数数学运算一样)只能用于数字,它会使空字符串""(空)转换为 0 - null 经过数字转换之后会变为 0
- undefined经过数字转换之后会变为 NaN
未定义类型undefined
只声明变量,不赋值的情况下,变量的默认值为undefined,一般很少【直接】为某个变量赋值为undefined。
空类型null
JavaScript中的null仅仅是一个代表“无”、“空”或“值未知”的特殊值
使用场景:把null作为尚未创建的对象
- 大白话:将来有个变量里面存放的是一个对象,但是对象还没创建好,可以先给个null
null 和 undefined区别:
undefined:表示没有赋值
null表示赋值了,但是内容为空
计算有区别:
undefined:
null:
数据类型存储方式
简单类型又叫做基本数据类型或者值类型,复杂类型又叫做引用类型。
值类型:简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型。
string
:字符串类型,用于表示文本数据。number
:数值类型,用于表示数字。boolean
:布尔类型,用于表示逻辑值,真或假。undefined
:未定义类型,变量声明了但未赋值时的默认值。null
:空类型,表示故意赋予变量的空值。
引用类型:复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此叫做引用数据类型。
- 通过
new
关键字创建的对象:例如,数组、函数、日期等。
堆栈空间分配区别
栈(Stack)
- 由操作系统自动分配释放:存放函数的参数值、局部变量的值等。
- 操作方式:类似于数据结构中的栈。
- 存储内容:简单数据类型。
堆(Heap)
- 存储内容:复杂类型(对象)。
- 分配方式:一般由程序员分配释放。
- 垃圾回收:若程序员不释放,由垃圾回收机制回收。
- 存储方式:引用数据类型存放到堆里面。
检测数据类型
通过typeof关键字检测数据类型
typeof运算符可以返回被检测的数据类型。它支持两种语法形式:
- 作为运算符:typeof x(常用的写法)
- 函数形式:typeof (x)
类型转换
我们使用下面这个语句会发现,输出的数据类型竟然是srting
!!!
因为在js默认当中,警告窗
、表单
、单选框
和复选框
等默认输出的都是字符串类型
JavaScript是弱数据类型:JavaScript也不知道变量到底属于那种数据类型,只有赋值了才清楚。此时需要转换变量的数据类型,通俗来说,就是把一种数据类型的变量转换成我们需要的数据类型。
隐式转换
某些运算符被执行时,这种转换称为隐式转换。
规则:
-
+号两边只要有一个是字符串,都会把另外一个转成字符串
-
除了+以外的算术运算符等都会把数据转成数字类型
+转换数据类型:+号作为正号解析可以转换成数字型,任何数据和字符串相加结果都是字符串。
显示转换
编写程序时过度依靠系统内部的隐式转换是不严禁的,因为隐式转换规律并不清晰,大多是靠经验总结的规律。为了避免因隐式转换带来的问题,通常根逻辑需要对数据进行显示转换,
概念:自己写代码告诉系统该转成什么类型。
在JavaScript中,数字类型的转换可以通过以下几种方式实现:
Number 转换
- 使用
Number(数据)
可以将数据转换为数字类型。 - 如果字符串内容包含非数字字符,转换失败时结果为
NaN
(Not a Number),即不是一个数字。 NaN
也是number
类型的数据,代表非数字。
parseInt 转换
- 使用
parseInt(数据)
可以将字符串转换为整数。 - 此方法只保留整数部分。
parseFloat 转换
- 使用
parseFloat(数据)
可以将字符串转换为浮点数。 - 此方法可以保留小数部分。
运算符
赋值运算符
赋值运算符:对变量进行赋值的运算符,用于对变量进行赋值操作。
基本赋值运算符
=
:将等号右边的值赋予给左边的变量。要求左边必须是一个可以存储值的容器。
其他赋值运算符
+=
:将右侧的值加到左侧变量的当前值上,并将结果赋值给左侧变量。-=
:将右侧的值从左侧变量的当前值中减去,并将结果赋值给左侧变量。*=
:将左侧变量的当前值与右侧值相乘,并将结果赋值给左侧变量。/=
:将左侧变量的当前值除以右侧值,并将结果赋值给左侧变量。%=
:求左侧变量的当前值除以右侧值的余数,并将结果赋值给左侧变量。
自增运算符
符号 | 作用 | 说明 |
---|---|---|
++ | 自增 | 变量自身的值加1,例如: x++ |
– | 自减 | 变量自身的值减1,例如: x– |
前置自增是先加1再计算,后置自增先计算后加1。
区别:
- 单独使用的时候是没有区别的。
- 前置自增和后置自增如果参与运算就有区别。
- 前置自增:先自加再使用(记忆口诀:++在前先加)
- 后置自增:先使用再自加(记忆口诀:++在后后加)
后置自增很好理解:注意!(先运算后自增)可以把
+
号忽略掉 1+1=2+1
- 前置自增:先自加再使用(记忆口诀:++在前先加)
比较运算符
使用场景:比较两个数据大小、是否相等,根据比较结果返回一个布尔值(true / false)
运算符 | 作用 |
---|---|
> | 左边是否大于右边 |
< | 左边是否小于右边 |
>= | 左边是否大于或等于右边 |
<= | 左边是否小于或等于右边 |
=== | 左右两边是否类型 和值 都相等(重点) |
== | 左右两边值 是否相等 |
!= | 左右值不相等 |
!== | 左右两边是否不全等 |
逻辑运算符
使用场景:可以把多个布尔值放到一起运算,最终返回一个布尔值
符号 | 名称 | 日常读法 | 特点 | 口诀 |
---|---|---|---|---|
&& | 逻辑与 | 并且 | 符号两边有一个假的结果为假 | 一假则假 |
|| | 逻辑或 | 或者 | 符号两边有一个真的结果为真 | 一真则真 |
! | 逻辑非 | 取反 | true变false false变true | 真变假,假变真 |
A | B | A && B | A || B | !A |
---|---|---|---|---|
false | false | false | false | true |
false | true | false | true | true |
true | false | false | true | false |
true | true | true | true | false |
运算符优先级
在编程中,运算符的优先级决定了它们在表达式中的执行顺序。(请注意,这个优先级列表是按照从高到低的顺序排列的。)
-
小括号
( )
- 优先级最高,用于改变表达式的自然顺序。
-
一元运算符
++
- 用于变量的自增操作。
-
算数运算符
- 乘法
*
、除法/
、取余%
优先于加法+
和减法-
。
- 乘法
-
关系运算符
- 大于
>
、大于等于>=
。
- 大于
-
相等运算符
==
- 用于比较两个值是否相等。
-
逻辑运算符
- 逻辑与
&&
优先于逻辑或||
。
- 逻辑与
-
赋值运算符
=
- 用于变量赋值。
-
逗号运算符
- 用于分隔表达式,从左到右依次执行。
总结:
- 一元运算符里面的逻辑非优先级很高
- 逻辑与比逻辑或优先级高
语句
表达式和语句的取区别:
表达式:表达式是可以被求值的代码,JavaScript引擎会将其计算出一个结果。
语句:语句是一段可以执行的代码。比如:prompt()可以弹出一个输入框,还有if语句、for 循环语句等等
- 表达式:因为表达式可被求值,所以它可以写在赋值语句的右侧。
例:num=3+ 4
- 语句:而语句不一定有值,所以比如alert( )、for和break等语句就不能被用于赋值。
例:alert()弹出对话框、console.log()控制台打印输出
⚠️某些情况,也可以把表达式理解为表达式语句,因为它是在计算结果,但不是必须的成分(例如continue语句)
分支语句
语法:
if(条件表达式) {
// 满足条件要执行的语句
}
小括号内的条件结果是布尔值,为 true 时,进入大括号里执行代码;为false,则不执行大括号里面代码
小括号内的结果若不是布尔类型时,会发生类型转换为布尔值,类似Boolean()
如果大括号只有一个语句,大括号可以省略,但是,俺们不提倡这么做~
if双分支语句
如果有两个条件的时候,可以使用 if else 双分支语句
if (条件表达式){
// 满足条件要执行的语句
} else {
// 不满足条件要执行的语句
}
例如:
<script>
// 1. 用户输入
let uname = prompt('请输入用户名:')
let pwd = prompt('请输入密码:')
// 2. 判断输出
if (uname === 'pink' && pwd === '123456') {
alert('恭喜登录成功')
} else {
alert('用户名或者密码错误')
}
</script>
if 多分支语句
使用场景: 适合于有多个条件的时候
<script>
// 1. 用户输入
let score = +prompt('请输入成绩:')
// 2. 判断输出
if (score >= 90) {
alert('成绩优秀,宝贝,你是我的骄傲')
} else if (score >= 70) {
alert('成绩良好,宝贝,你要加油哦~~')
} else if (score >= 60) {
alert('成绩及格,宝贝,你很危险~')
} else {
alert('成绩不及格,宝贝,我不想和你说话,我只想用鞭子和你说话~')
}
</script>
三元运算符(三元表达式)
使用场景: 一些简单的双分支,可以使用 三元运算符(三元表达式),写起来比 if else双分支 更简单
符号:? 与 : 配合使用
语法:
条件 ? 表达式1 : 表达式2
switch语句(了解)
使用场景: 适合于有多个条件的时候,也属于分支语句,大部分情况下和 if多分支语句 功能相同
语法:
switch (表达式) {
case 值1:
代码1
break
case 值2:
代码2
break
...
default:
代码n
}
注意:
- switch case语句一般用于等值判断, if适合于区间判断
- switchcase一般需要配合break关键字使用 没有break会造成case穿透
- if 多分支语句开发要比switch更重要,使用也更多
例如:
<script>
switch (2) {
case 1:
console.log('您选择的是1')
break // 退出switch
case 2:
console.log('您选择的是2')
break // 退出switch
case 3:
console.log('您选择的是3')
break // 退出switch
default:
console.log('没有符合条件的')
}
</script>
循环语句
- 重复执行 指定的一段代码。
循环三要素:
-
初始值 (经常用变量)
-
终止条件
-
变量的变化量
断点调试
**作用:**学习时可以帮助更好的理解代码运行,工作时可以更快找到bug
浏览器打开调试界面
- 按F12打开开发者工具
- 点到源代码一栏 ( sources )
- 选择代码文件
**断点:**在某句代码上加的标记就叫断点,当程序执行到这句有标记的代码时会暂停下来
while循环
while : 在…. 期间, 所以 while循环 就是在满足条件期间,重复执行某些代码。
语法:
while (条件表达式) {
// 循环体
}
例如:
// while循环: 重复执行代码
// 1. 需求: 利用循环重复打印3次 '月薪过万不是梦,毕业时候见英雄'
let i = 1
while (i <= 3) {
document.write('月薪过万不是梦,毕业时候见英雄~<br>')
i++ // 这里千万不要忘了变量自增否则造成死循环
}
中止循环
break
中止整个循环,一般用于结果已经得到, 后续的循环不需要的时候可以使用(提高效率)
continue
中止本次循环,一般用于排除或者跳过某一个选项的时候
无限循环
1.while(true) 来构造“无限”循环,需要使用break退出循环。(常用)
2.for(;😉 也可以来构造“无限”循环,同样需要使用break退出循环。
for 语句
for
是 JavaScript 提供的另一种循环控制的话句,它和 while
只是语法上存在差异。
语法:
for(起始值; 终止条件; 变化量) {
// 要重复执行的代码
}
for语句的基本使用
- 实现循环的 3 要素
<script>
for(let i = 1; i <= 6; i++) {
document.write(`<h${i}>循环控制,即重复执行<h${i}>`)
}
</script>
-
变化量和死循环,
for
循环和while
一样,如果不合理设置增量和终止条件,便会产生死循环。 -
跳出和终止循环
<script>
// 1. continue
for (let i = 1; i <= 5; i++) {
if (i === 3) {
continue // 结束本次循环,继续下一次循环
}
console.log(i)
}
// 2. break
for (let i = 1; i <= 5; i++) {
if (i === 3) {
break // 退出结束整个循环
}
console.log(i)
}
</script>
结论:
JavaScript
提供了多种语句来实现循环控制,但无论使用哪种语句都离不开循环的3个特征,即起始值、变化量、终止条件,做为初学者应着重体会这3个特征,不必过多纠结三种语句的区别。- 起始值、变化量、终止条件,由开发者根据逻辑需要进行设计,规避死循环的发生。
- 当如果明确了循环的次数的时候推荐使用
for
循环,当不明确循环的次数的时候推荐使用while
循环
注意:
for
的语法结构更简洁,故for
循环的使用频次会更多。
循环嵌套语句
实际上 JavaScript 中任何一种循环语句都支持循环的嵌套,如下代码所示:
for循环是一种常见的编程结构,用于重复执行一段代码直到满足特定条件。for循环的一般形式如下:
for (外部声明记录循环次数的变量; 循环条件; 变化值) {
for (外部声明记录循环次数的变量; 循环条件; 变化值) {
// 循环体
}
}
案例——九九乘法表
样式css:
span {
display: inline-block;
width: 100px;
padding: 5px 10px;
border: 1px solid pink;
margin: 2px;
border-radius: 5px;
box-shadow: 2px 2px 2px rgba(255, 192, 203, .4);
background-color: rgba(255, 192, 203, .1);
text-align: center;
color: hotpink;
}
javascript :
// 外层打印几行
for (let i = 1; i <= 9; i++) {
// 里层打印几个星星
for (let j = 1; j <= i; j++) {
// 只需要吧 ★ 换成 1 x 1 = 1
document.write(`
<div> ${j} x ${i} = ${j * i} </div>
`)
}
document.write('<br>')
}
函数
函数:是被设计为执行特定任务的代码块
函数可以把具有相同或相似逻辑的代码“包裹”起来,通过函数调用执行这些被“包裹”的代码逻辑,这么做的优势是有利于精简代码方便复用。比如我们前面使用的alert()
、prompt()
和console.log()
都是一些js函数,只不过已经封装好了,我们直接使用的。
函数使用
声明函数
声明(定义)一个完整函数包括关键字、函数名、形式参数、函数体、返回值5个部分
语法:
function 函数名(形式参数){
函数体;
返回值;
}
函数命名规范:
- 和变量命名基本一致
- 尽量小驼峰式命名法
- 前缀应该为动词
- 命名建议:常用动词约定
调用函数
声明(定义)的函数必须调用才会真正被执行,使用 ()
调用函数。
语法:
函数名()
⚠️注:函数名的命名规则与变量是一致的,并且尽量保证函数名的语义。
函数的复用代码和循环重复代码有什么不同?
循环代码写完即执行,不能很方便控制执行位置
随时调用,随时执行,可重复调用
函数传参
通过向函数传递参数,可以让函数更加灵活多变,参数可以理解成是一个变量。
声明(定义)一个功能为打招呼的函数
- 传入数据列表
- 声明这个函数需要传入几个数据
- 多个数据用逗号隔开
声明函数:
function hello(name){
console.log('你好'+name)
}
调用函数:
hello('小明')
总结:
- 声明(定义)函数时的形参没有数量限制,当有多个形参时使用
,
分隔 - 调用函数传递的实参要与形参的顺序一致
形参与实参
形参:声明函数时写在函数名右边小括号里的叫形参(形式上的参数)
实参:调用函数时写在函数名右边小括号里的叫实参(实际上的参数)
形参可以理解为是在这个函数内声明的变量(比如 num1 = 10)实参可以理解为是给这个变量赋值,开发中尽量保持形参和实参个数一致。
参数默认值
形参可以看做变量,但是如果一个变量不给值,默认是什么?答案是undefined
,但是如果做用户不输入实参,刚才的案例,则出现undefined+undefined 结果是NAN
。我们可以改进下,用户不输入实参,可以给形参默认值,可以默认为0,这样程序更严谨。
function 函数名(x=0,y=0){
document.write(x+y)
}
函数名()//结果是0,而不是NAN
函数名(1,2)//结果是3
⚠️注意:说明:这个默认值只会在缺少实参参数传递时才会被执行,所以有参数会优先执行传递过来的实参,否则默认为undefined。
函数返回值
函数的本质是封装(包裹),函数体内的逻辑执行完毕后,函数外部如何获得函数内部的执行结果呢?要想获得函数内部逻辑的执行结果,需要通过 return
这个关键字,将内部执行结果传递到函数外部,这个被传递到外部的结果就是返回值。
语法:
function 函数名(){
return 返回值
}
console.log( 函数名())
//或者是👇
let 新的变量名 = 函数名() //使用了一个变量去接受函数返回的值
console.log(新的变量名)
总结:
- 在函数体中使用return 关键字能将内部的执行结果交给函数外部使用
- 函数内部只能出现1 次 return,并且 return 下一行代码不会再被执行,所以return 后面的数据不要换行写
- return会立即结束当前函数
- 函数可以没有return,这种情况默认返回值为 undefined
函数细节
- 两个相同的函数后面的会覆盖前面的函数(函数名相同,后面覆盖前面)
- 在Javascript中实参的个数和形参的个数可以不一致
- 如果形参过多会自动填上undefined(了解即可)
- 如果实参过多那么多余的实参会被忽略(函数内部有一个arguments,里面装着所有的实参)
- 如果形参过多会自动填上undefined(了解即可)
- 函数一旦碰到return就不会在往下执行了,函数的结束用return。
break的结束和return结束有什么区别?
答:return 结束,会返回值,break结束程序,不会返回值
案例——求和函数
function getTotal(x,y){
return x+y
}
let sum = getTotal(1,2)
console.log(sum) //3
作用域
通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问。作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
全局作用域
作用于所有代码执行的环境整个 script 标签内部或者一个独立的 js 文件,处于全局作用域内的变量,称为全局变量。
<script>
标签和 .js
文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
<script>
// 此处是全局
function sayHi() {
// 此处为局部
}
// 此处为全局
</script>
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
<script>
// 全局作用域
// 全局作用域下声明了num变量
const num = 10;
function fn() {
// 函数内部可以使用全局作用域的变量
console.log(num);
}
// 此处全局作用域
</script>
总结:
- 为
window
对象动态添加的属性默认也是全局的,不推荐! - 函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
- 尽可能少的声明全局变量,防止全局变量被污染
JavaScript 中的作用域是程序被执行时的底层机制,了解这一机制有助于规范代码书写习惯,避免因作用域导致的语法错误。
局部作用域
作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域,处于局部作用域内的变量称为局部变量。
特别情况:
- 如果函数内部,变量没有声明,直接赋值,也当全局变量看,但是强烈不推荐
let num = 20 function fn(){ num = 10 } fn() console.log(num) //10
- 函数内部的形参可以看做是局部变量
function fn(x,y){ console.log(x) 11形参可以看做是函数的局部变量 return(x,y) } console.log(x)❌
- 在不同作用域下,可能存在变量命名冲突的情况,到底改执行谁呢?
变量的访问原则:
- 只要是代码,就至少有一个作用域
- 写在函数内部的局部作用域
- 如果函数中还有函数,那么在这个作用域中的就又可以诞生一个作用域
- 访问原则:在能够访问到的情况下,先局部,局部没有再全局【就近原则】
函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
例子:
<script>
// 声明 counter 函数
function counter(x, y) {
// 函数内部声明的变量
const s = x + y
console.log(s) // 18
}
// 设用 counter 函数
counter(10, 8)
// 访问变量 s
console.log(s)// 报错
</script>
总结:
- 函数内部声明的变量,在函数外部无法被访问
- 函数的参数也是函数内部的局部变量
- 不同函数内部声明的变量无法互相访问
- 函数执行完毕后,函数内部的变量实际被清空了
块作用域
在 JavaScript 中使用 {}
包裹的代码称为代码块,代码块内部声明的变量外部将有可能无法被访问。
for (let t = 1; t <= 6; t++) {
// t只能在该代码块中被访问
console.log(t); // 正常
}
// 超出了t的作用域
console.log(t); // 报错
总结:
let
声明的变量会产生块作用域,var
不会产生块作用域const
声明的常量也会产生块作用域- 不同代码块之间的变量无法互相访问
- 推荐使用
let
或const
注:开发中 let
和 const
经常不加区分的使用,如果担心某个值会不小被修改时,则只能使用 const
声明成常量。
作用域链
在解释什么是作用域链前先来看一段代码:
<script>
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// 局部作用域
function g() {
let d = 'yo'
}
}
</script>
函数内部允许创建新的函数,f
函数内部创建的新函数 g
,会产生新的函数作用域,由此可知作用域产生了嵌套的关系。父子关系的作用域关联在一起形成了链状的结构,作用域链的名字也由此而来。
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域。
总结:
- 嵌套关系的作用域串联起来形成了作用域链
- 相同作用域链中按着从小到大的规则查找变量
- 子作用域能够访问父作用域,父级作用域无法访问子级作用域
闭包
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域。
简单理解:闭包=内层函数+外层函数的变量, 内层函数使用外层的函数变量叫做闭包
例子:
浏览器控制台检测闭包:
闭包作用:
- 封闭数据,实现数据私有,外部也可以访问函数内部的变量
- 闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
闭包基本格式:
function outer(){
let a = 1
function f(){
console.log(a)
}
return f
}
// 因为 outer === fn === function () {}
const fun = outer() // 外层函数使用内部函数的变量
fun() // 1
❗️但是闭包会引起内存泄露的问题,闭包引起的内存泄漏主要是因为它们持续持有对外部作用域变量的引用,即使这些变量不再需要,也无法被垃圾回收,从而占用了不必要的内存空间。为了避免内存泄漏,应当确保在不再需要闭包时,释放它们所持有的资源和引用。
匿名函数
函数可以分为具名函数和匿名函数,前面所学的都是具名函数
。
匿名函数:没有名字的函数,无法直接使用,需要配合函数表达式一起。
函数表达式
函数表达式:将匿名函数赋值给一个变量,并且通过变量名称进行调用我们将这个称为函数表达式。
let 表达式名 = funciont(x,y){
函数体
}
表达式名(a,b)
具名函数与匿名函数的区别
- 具名函数的调用可以写到任何位置
- 函数表达式,必须先声明函数表达式,后调用
具名函数调用位置可以随意调整,但是匿名函数不能
报错了❌
立即执行函数
作用:避免全局变量被污染
(function(){函数体})(); //第二个小括号相当于调用函数
或者:(function(){函数体}());
注意事项:
-
无需调用,立即执行,其实本质已经调用了
-
多个立即执行函数之间用分号隔开
函数进阶
变量提升
变量提升是 JavaScript 中比较“奇怪”的现象,它允许在变量声明之前即被访问,
所谓的变量提升就是代码在执行之前,先去检测当前作用域下所有var声明的变量提升到当前作用域的最前面。
❗️只提升声明,不提升赋值。
例1:
例2:
总结:
- 变量在未声明即被访问时会报语法错误
- 变量在声明之前即被访问,变量的值为
undefined
let/const
声明的变量不存在变量提升,推荐使用let
- 变量提升出现在相同作用域当中
- 实际开发中推荐先声明再访问变量
注:关于变量提升的原理分析会涉及较为复杂的词法分析等知识,而开发中使用 let
可以轻松规避变量的提升,因此在此不做过多的探讨,有兴趣可查阅资料。
函数提升
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
例1:
例2:
<script>
// 调用函数
foo()
// 声明函数
function foo() {
console.log('声明之前即被调用...')
}
// 不存在提升现象
bar() // 错误
var bar = function () {
console.log('函数表达式不存在提升现象...')
}
</script>
❗️函数表达式必须先声明和赋值,后调用否则报错
总结:
- 函数提升能够使函数的声明调用更灵活
- 函数表达式不存在提升的现象
- 函数提升出现在相同作用域当中
函数参数
除了前面讲的形参、实参,接下来要讲的默认值、动态参数、剩余参数。学习完这些函数参数,能够提升函数应用的灵活度。
动态参数
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参。
模版:
function 函数名(){
// console.log(arguments)
}
函数名(参数1,参数2, ...)
特点:
arguments
是一个伪数组arguments
的作用是动态获取函数的实参
剩余参数
...
是剩余参数,允许我们将一个不定数量的参数表示为一个数组
模版:
function 函数名(...数组名){
// console.log(数组名)
}
函数名(参数1,参数2, ...)
特点:
...
是语法符号,置于最末函数形参之前,用于获取多余的实参- 借助
...
获取的剩余实参,是个真数组
区别
...
是语法符号,置于最末函数形参之前,用于获取多余的实参
箭头函数🌟
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁。箭头函数更适用于那些本来需要匿名函数的地方。
语法:
const 函数名 = (参数)=>{
//函数体
}
//调用
函数名(参数)
更多简写形式:
如果箭头函数只有一个形参的时候可以省略小括号:
const 函数名 = 参数=>{ 函数体 }
如果只有一行代码的时候,可以省略大括号:
const 函数名 = 参数 => 函数体
如果只有一行代码并且有返回值,可以省略大括号和
return
:const 函数名 = 参数 => 参数
箭头函数可以直接返回一个对象
const 函数名 = (参数) => ({属性名:参数})
特点:
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
()
- 箭头函数函数体只有一行代码时可以省略花括号
{}
,并自动做为返回值被返回
箭头函数参数🌟
❗️箭头函数中没有 arguments
,只能使用 ...
动态获取实参
例子:
//使用箭头函数的剩余参数写一个求和函数
const getSum = (...arr) =>{
let sum = 0
for(let i = 0; i<arr.length; i++){
sum += arr[i]
}
return sum
}
const re = getSum(1,2,3)
console.log(re)
箭头函数this🌟
引入:在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值,非常令人讨厌。
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
经典例子:
⚠️在开发中使用箭头函数前需要考虑函数中this的值,事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数为了简便还是不太推荐使用箭头函数。
解构赋值🌟
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
数组解构🌟
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
语法:let [ ] = 数组
【赋值运算符 =
左侧的 []
用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量】
<script>
// 普通的数组
let arr = [1, 2, 3]
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr
//第二种写法:
//let [a, b, c] = [1, 2, 3]
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
</script>
经典例子:交换两个变量
Js 前面有两哪种情况需要加分号的?
- 立即执行函数
- 数组解构
变量与单元值不匹配
- 变量多,单元值少的情况:
- 可以设置默认参数来防止undefined:
- 可以设置默认参数来防止undefined:
- 变量少,单元值多的情况:
- 可以使用剩余参数解决这给问题:
- 可以使用剩余参数解决这给问题:
- 按需导入,忽略某些返回值:
多维数组解构
支持多维数值结构:
拓展——什么是多维数组:
JavaScript中的多维数组就像是一个嵌套的盒子,每个盒子里可以放更多的盒子。你可以想象它像一个多层的书架,每一层书架上又可以放很多本书。在代码中,你可以通过方括号
[]
来创建数组,并且一个数组的元素可以是另一个数组,这样你就可以创建多层的数据结构。比如,一个二维数组就像是一个表格,你可以通过指定行和列的索引来访问任何一个单元格中的数据。
对象解构🌟
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法。
语法:const { } = 对象
【赋值运算符 =
左侧的 {}
用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量】
⚠️注意:对象属性的值将被赋值给与属性名相同的变量
可能会联想到数组结构为什么没有要求属性名相同?
- 因为数组有有序,对象是无序的,所以数组可以按照顺序,而对象需变量名一样才能解构
(*^▽^*)
解构重命名
对象解构的变量名可以重新改名:旧变量名:新变量名
解构数组对象
解构赋值是一种JavaScript语言特性,允许你将数组或对象中的值快速分配给一组变量。简单来说,它是一种语法糖,让你能够用一行代码来提取数据并将其存储在变量中,而不需要编写传统的循环或索引访问代码。
多级对象解构
使用{ family: { ... } }
来指定我们想要从pig
对象中解构出family
这个子对象。然后,我们使用{ mother, father, sister }
来从family
对象中解构出具体的属性。
对象
对象是 JavaScript 数据类型的一种,之前已经学习了数值类型、字符串类型、布尔类型、undefined。对象数据类型可以被理解成是一种数据集合。它由属性和方法两部分构成。
对象(object):JavaScript里的一种数据类型,可以理解为是一种无序的数据集合,注意数组是有序的数据集合
声明对象
实际开发中,我们多用花括号{}
是对象字面量
语法:
let 对象名 = {}
let 对象名 = new Object()
对象有属性和方法组成:
-
属性:信息或叫特征(名词)。比如手机尺寸、颜色、重量等…
-
方法:功能或叫行为(动词)。比如手机打电话、发短信、玩游戏…
let 对象名 = {
属性名1:属性值1,
属性名2:属性值2,
...,
方法名1:函数1,
方法名2:函数2,
...,
}
特点:
- 属性都是成对出现的,包括属性名和值,它们之间使用英文
:
分隔 - 多个属性之间使用英文
,
分隔 - 属性就是依附在对象上的变量
- 属性名可以使用
""
或''
,一般情况下省略,除非名称遇到特殊符号如空格、中横线等
使用对象
属性调用
对象本质是无序的数据集合,操作数据无非就是增删改查语法:
- **属性访问——**语法:
对象名.属性名
- **属性修改——**语法:
对象.属性 = 值
- **属性增加——**语法:
对象名.新属性=新值
- **属性删除——**语法:
delete 对象名.属性名
方法调用
数据行为性的信息称为方法,如跑步、唱歌等,一般是动词性的,其本质是函数。
语法对象名.方法名(参数1,参数2)
⚠️:千万不要忘记了后面的小括号!!!
遍历对象
对象里面是无序的键值对,没有规律.不像数组里面有规律的下标,所以不能像数组一样使用for循环遍历。
使用到了新的一个语法for in
遍历数组:(不推荐使用)
let arr = ['pink', 'red', 'blue'];
for (let k in arr) {
console.log(k); // 数组的下标索引号
console.log(arr[k]); // arr[k]
}
遍历对象:
let obj = {
uname: 'pink'
}
for(let k in obj) {
console.log(k) // 调用属性名
console.log(obj[k]) // 调用属性值
}
特点:
- 一般不用这种方式遍历数组、主要是用来遍历对象
for in
语法中的k是一个变量,在循环的过程中依次代表对象的属性名- 由于k是变量,所以必须使用
[]
语法解析 - 一定记住:k是获得对象的属性名,对象名[K]是获得属性值
内置对象
回想一下我们曾经使用过的 console.log
,console
其实就是 JavaScript 中内置的对象,该对象中存在一个方法叫 log
,然后调用 log
这个方法,即 console.log()
。
Math对象
Math
是 JavaScript 中内置的对象,称为数学对象,这个对象下即包含了属性,也包含了许多的方法。
Math属性
- Math.PI,获取圆周率
// 圆周率
console.log(Math.PI);
Math方法
-
Math.random,生成 0 到 1 间的随机数
// 0 ~ 1 之间的随机数, 包含 0 不包含 1 Math.random()
-
Math.ceil,数字向上取整
// 舍弃小数部分,整数部分加1 Math.ceil(3.4)
-
Math.floor,数字向下取整
// 舍弃小数部分,整数部分不变 Math.floor(4.68)
-
Math.round,四舍五入取整
// 取整,四舍五入原则 Math.round(5.46539) Math.round(4.849)
-
Math.max,在一组数中找出最大的
// 找出最大值 Math.max(10, 21, 7, 24, 13)
-
Math.min,在一组数中找出最小的
// 找出最小值 Math.min(24, 18, 6, 19, 21)
-
Math.pow,幂方法
// 求某个数的多少次方 Math.pow(4, 2) // 求 4 的 2 次方 Math.pow(2, 3) // 求 2 的 3 次方
-
Math.sqrt,平方根
// 求某数的平方根 Math.sqrt(16)
数学对象提供了比较多的方法,这里不要求强记,通过演示数学对象的使用,加深对对象的理解。
实例化对象
在代码中发现了new关键字时,一般将这个操作称为实例化。
日期对象
日期对象:用来表示时间的对象,可以让我们得到当前系统时间。
ECMAScript 中内置了获取系统时间的对象 Date,使用 Date 时与之前学习的内置对象 console 和 Math 不同,它需要借助 new 关键字才能使用,通过实例化一个事件对象来获取时间。
获得当前时间:
const date = new Date()
指定时间:
const date1 = new Date('2024-1-1 08:30:30')
日期对象方法
因为日期对象返回的数据我们不能直接使用,所以需要转换为实际开发中常用的格式。
方法 | 作用 | 说明 |
---|---|---|
getFullYear() | 获得年份 | 获取四位年份 |
getMonth() | 获得月份 | 取值为0~11 |
getDate() | 获取月份中的每一天 | 不同月份取值也不相同 |
getDay() | 获取星期 | 取值为0 ~ 6(星期天是0) |
getHours() | 获取小时 | 取值为 0 ~ 23 |
getMinutes() | 获取分钟 | 取值为 0 ~ 59 |
getSeconds() | 获取秒 | 取值为0~59 |
例子:
// 创建一个日期对象
const date = new Date();
// 使用里面的方法获取当前日期和时间的相关信息
console.log(date.getFullYear()); // 获取当前的四位年份
console.log(date.getMonth() + 1); // 获取当前月份,由于月份是从0开始计数的,所以需要加1
console.log(date.getDate()); // 获取当前日期,即月份中的第几天
console.log(date.getDay()); // 获取当前是星期几,返回值是0-6,其中0或7代表星期日
案例——页面显示时间
需求:将当前时间以:YYYY-MM-DD HH:mm形式显示在页面【2008-08-08 08:08】
const div = document.querySelector('div')
function getMyDate(){
const date = new Date()
let h =date.getHours()
let m = date.getMinutes()
//检测小于10就在前面加0
h = h<10? '0'+h:h
m = m<10? '0'+m:m
return '今天是:${date.getFullYear()}年#{date.getMounth()+1}月${date.getDate()}号${h}:${m}'
}
div.innerHTML = getMyDate()
setInterval(function(){
div.innerHTML = getMyDate()
},1000)
其实这个案例也是有内置方法的(≧ω≦)
方法名:
toLocaleString()
const div = document.querySelector('div') const date = new Date() div.innerHTML = date.toLocaleString() //2022/4/1 09:41:21 // toLocaleDateString() //2022/4/1 // toLocaleTimeString() //09:41:21
时间戳
时间戳是指1970年01月01日00时00分00秒起至现在的毫秒数,它是一种特殊的计量时间的方式。注:ECMAScript 中时间戳是以毫秒计的。
如果计算倒计时效果,前面方法无法直接计算,需要借助于时间戳完成。
获取时间戳的方法,分别为 getTime
和 Date.now
和 +new Date()
getTime()
方法
⚠️:需要实例化Date.now
方法
⚠️:这种无需实例化,但是只能得到当前的时间戳。Date.now
方法
⚠️:这种无需实例化
例子:
// 1. 实例化
const date = new Date()
// 2. 获取时间戳
console.log(date.getTime())
// +new Date() 获取时间戳的方法
console.log(+new Date())
// 可以返回指定时间
console.log(+new Date('2024-1-1 10:30:00'))
// Date.now()获取时间戳的方法
console.log(Date.now())
构造函数🌟
构造函数是专门用于创建对象的函数,如果一个函数使用 new
关键字调用,那么这个函数就是构造函数。
使用场景:常规的{…}语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一遍,此时可以通过构造函数来快速创建多个类似的对象。
构造函数在技术上是常规函数,不过有两个约定:
- 它们的命名以大写字母开头。
- 它们只能由"new"操作符来执行
语法:
//创建构造函数
function 函数名(参数1,参数2,...){
this.属性1 = 参数1
this.属性2 = 参数2
...
}
//new关键字调用函数
const 变量名 = new 函数名(参数1,参数2,...)
特点:
-
使用
new
关键字调用函数的行为被称为实例化 -
实例化构造函数时没有参数时可以省略
()
-
构造函数无需写
return
,返回值即为新创建的对象 -
构造函数内部的
return
返回的值无效!
注:实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写。
创建对象的三种方式:
- 利用字面量创建对象
- 使用
new Object()
创建对象- 利用构造函数创建对象
实例化执行过程
说明:
- 创建新的空对象
- 构造函数this指向新对象
- 执行构造函数代码,修改this,添加新的属性
- 返回新对象
实例成员
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员(实例属性和实例方法)。
简单来说就是实例成员=实例对象上的属性和方法
特点:
- 构造函数内部
this
实际上就是实例对象,为其动态添加的属性和方法即为实例成员。 - 为构造函数传入参数,动态创建结构相同但值不同的对象。
- 构造函数创建的实例对象彼此独立互不影响。
静态成员
在 JavaScript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员。
特点:
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员只能构造函数来访问
- 静态成员方法中的
this
指向构造函数本身
总结:
实例成员(属性和方法)写在谁身上?
- 实例对象的属性和方法即为实例成员
- 实例对象相互独立,实例成员当前实例对象使用
静态成员(属性和方法)写在谁身上?
- 构造函数的属性和方法被称为静态成员
- 静态成员只能构造函数访问
内置构造函数
在 JavaScript 中最主要的数据类型有 6 种,分别是字符串、数值、布尔、undefined、null 和 对象,常见的对象类型数据包括数组和普通对象。其中字符串、数值、布尔、undefined、null 也被称为简单类型或基础类型,对象也被称为引用类型。
在 JavaScript 内置了一些构造函数,绝大部的数据处理都是基于这些构造函数实现的,JavaScript 基础阶段学习的 Date
就是内置的构造函数。
<script>
// 实例化
let date = new Date();
// date 即为实例对象
console.log(date);
</script>
甚至字符串、数值、布尔、数组、普通对象也都有专门的构造函数,用于创建对应类型的数据。
内置构造函数分为两种,一种是引用类型
,另一种是包装类型
- 引用类型:Object,Array,RegExp,Date等
- 包装类型:String,Number,Boolean等
Object
Object
是内置的构造函数,用于创建普通对象。不过还是推荐使用字面量方式声明对象,而不是Object构造函数。
下面学习三个常用静态方法(静态方法就是只有构造函数Object可以调用的)
- Object.keys 静态方法获取对象中所以属性
语法:Object.keys()
,返回的是一个数组
- Object.values 静态方法获取对象中所以属性值
语法:Object.values()
,返回的是一个数组
- Object.assign 静态方法常用语对象拷贝
语法:Object.assign(接收对象,拷贝对象)
,返回的是一个数组
这个方法经常适用于给对象添加新的属性:
Array
Array
是内置的构造函数,用于创建数组,不过还是建议使用字面量创建,不用Array构造函数创建。
Array基本实例方法:
方法 | 作用 | 说明 |
---|---|---|
forEach | 遍历数组 | 不返回数组,经常用于查找遍历数组元素 |
filter | 过滤数组 | 返回新数组,返回的是筛选满足条件的数组元素 |
map | 选代数组 | 返回新数组,返回的是处理之后的数组元素,想要使用返回的新数组 |
reduce | 累计器 | 返回累计处理的结果,经常用于求和等 |
数组常见方法汇总:
- 实例方法
forEach
用于遍历数组,替代for
循环 (重点🌟)- 实例方法
filter
过滤数组单元值,生成新数组 (重点🌟)- 实例方法
map
迭代原数组,生成新数组 (重点🌟)- 实例方法
join
数组元素拼接为字符串,返回字符串 (重点🌟)- 实例方法
find
查找元素, 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined (重点🌟)- 实例方法
every
检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回 true,否则返回 false (重点🌟)- 实例方法
some
检测数组中的元素是否满足指定条件 如果数组中有元素满足条件返回 true,否则返回 false- 实例方法
concat
合并两个数组,返回生成新数组- 实例方法
sort
对原数组单元值排序- 实例方法
splice
删除或替换原数组单元- 实例方法
reverse
反转数组- 实例方法
findIndex
查找元素的索引值方便理解图:
String
String
是内置的构造函数,用于创建字符串。
例子:
<script>
// 使用构造函数创建字符串
let str = new String('hello world!');
// 字面量创建字符串
let str2 = '你好,世界!';
// 检测是否属于同一个构造函数
console.log(str.constructor === str2.constructor); // true
console.log(str instanceof String); // false
</script>
String汇总:
- 实例属性
length
用来获取字符串的度长(重点🌟)- 实例方法
split('分隔符')
用来将字符串拆分成数组(重点🌟)- 实例方法
substring(需要截取的第一个字符的索引[,结束的索引号])
用于字符串截取(重点🌟)- 实例方法
startsWith(检测字符串[, 检测位置索引号])
检测是否以某字符开头(重点🌟)- 实例方法
includes(搜索的字符串[, 检测位置索引号])
判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false(重点🌟)- 实例方法
toUpperCase
用于将字母转换成大写- 实例方法
toLowerCase
用于将就转换成小写- 实例方法
indexOf
检测是否包含某字符- 实例方法
endsWith
检测是否以某字符结尾- 实例方法
replace
用于替换字符串,支持正则匹配- 实例方法
match
用于查找字符串,支持正则匹配
Number
Number
是内置的构造函数,用于创建数值。
<script>
// 使用构造函数创建数值
let x = new Number('10')
let y = new Number(5)
// 字面量创建数值
let z = 20
</script>
常用方法:toFixed()
设置保留小数位的长度
// 数值类型
const price = 12.345
// 保留两位小数 四舍五入
console.log(price.toFixed(2)) //12.35
DOM
DOM元素
DOM(Document Object Model)文档对象模型,是将整个 HTML 文档的每一个标签元素视为一个对象,这个对象下包含了许多的属性和方法,通过操作这些属性或者调用这些方法实现对 HTML 的动态更新,为实现网页特效以及用户交互提供技术支撑。简言之 DOM 是用来动态修改 HTML 的,其目的是开发网页特效及用户交互。
白话文:DOM是浏览器提供的一套专门用来操作网页内容的功能
DOM树
DOM树:将HTML文档以树状结构直观的表现出来,我们称之为文档树或DOM树,用来描述网页内容关系的名词,文档树直观的体现了标签与标签之间的关系。
DOM节点
节点是文档树的组成部分,每一个节点都是一个 DOM 对象,主要分为元素节点、属性节点、文本节点等。
- 【元素节点】其实就是 HTML 标签,如上图中
head
、div
、body
等都属于元素节点。 - 【属性节点】是指 HTML 标签中的属性,如上图中
a
标签的href
属性、div
标签的class
属性。 - 【文本节点】是指 HTML 标签的文字内容,如
title
标签中的文字。 - 【根节点】特指
html
标签。 - 其它…
节点关系:针对的找亲戚返回的都是对象
- 父节点
- 子节点
- 兄弟节点
查找节点
- 父节点查找:
子元素.parentNode
(返回最近一级的父节点找不到返回为null)
因为没有祖孙节点,所以如果要查找到爷爷级别的元素就只能通过套娃来实现,例如:
- 子节点查找:
父元素.children
(获得所有子节点、包括文本节点(空格、换行)、注释节点等)
特点:
- 仅获取所有元素节点
- 返回的还是一个伪数组
-
兄弟节点查找:
-
上一个兄弟节点:
previousElementsibling
-
下一个兄弟节点:
nextElementSibling
-
创建节点
很多情况下,我们需要在页面中增加元素。
语法:document.createElement('标签')
追加节点
在已有的 DOM 节点中插入新的 DOM 节点时,需要关注两个关键因素:首先要得到新的 DOM 节点,其次在哪个位置插入这个节点。,要想在界面看到,还得插入到某个父元素中。
语法:
- 插入到父元素的最后一个子元素:
父元素.appendChild(要插入的元素)
- 插入到父元素中某个子元素的前面:
父元素.insertBefore(要插入的元素,在哪个元素前面)
克隆节点
特殊情况下,我们新增加节点会复制一个原有的节点,把复制的节点放入到指定的元素内部。
语法:元素.cloneNode(布尔值)
cloneNode会克隆出一个跟原标签一样的元素,括号内传入布尔值
- 若为true,则代表克隆时会包含后代节点一起克隆【深克隆】
- 若为false,则代表克隆时不包含后代节点(默认)【浅克隆】
例子:
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
// 选择页面上的<ul>元素
const ul = document.querySelector('ul');
// 1. 克隆第一个<li>节点元素。cloneNode(true)表示克隆包括其所有子节点
const li1 = ul.children[0].cloneNode(true);
// 2. 将克隆的<li>元素追加到<ul>的末尾
ul.appendChild(li1);
//上面的两步可以直接写成下面这句:
//ul.appendChild(ul.children[0].cloneNode(true))
</script>
</body>
删除节点
若一个节点在页面中已不需要时,可以删除它在JavaScript原生DOM操作中,要删除元素必须通过父元素删除。
语法:父元素.removeChild(要删除的元素)
⚠️注意:
- 如果不存在父子关系则删除不成功
- 删除节点和隐藏节点(display:none)有区别,隐藏节点还是存在的,但是删除则是从html中删除节点。
DOM对象
DOM对象:浏览器根据html标签生成的JS对象
-
所有的标签属性都可以在这个对象上面找到
-
修改这个对象的属性会自动映射到标签身上
document
document
是 JavaScript 内置的专门用于 DOM 的对象,该对象包含了若干的属性和方法,document
是学习 DOM 的核心。
<script>
// document 是内置的对象
// console.log(typeof document);
// 1. 通过 document 获取根节点
console.log(document.documentElement); // 对应 html 标签
// 2. 通过 document 节取 body 节点
console.log(document.body); // 对应 body 标签
// 3. 通过 document.write 方法向网页输出内容
document.write('Hello World!');
</script>
上述列举了 document
对象的部分属性和方法,我们先对 document
有一个整体的认识。
DOM元素获取🌟
querySelector( )
querySelector 根据CSS选择器来获取DOM元素
语法:
document.querySelector('CSS选择器')
参数:包含一个或多个有效的CSS选择器字符串
返回值:CSS选择器匹配的第一个元素,一个HTMLElement对象,如果没有匹配到,则返回null。
querySelector
选择多个元素:语法:
document.querySelectorAll('CSS选择器')
参数:包含一个或多个有效的CSS选择器字符串
返回值:CSS选择器匹配的NodeList对象集合
⚠️:得到的是一个伪数组(哪怕只有一个元素,通过querySelectAll()获取过来的也是一个伪数组,里面只有一个元素而已)
- 有长度有索引号的数组
- 但是没有pop()、push()等数组方法
- 想要得到里面的每一个对象,则需要遍历(for)的方式获得
例1:
例2:
getElementById( )
getElementById 根据id获取一个元素
语法:
document.getElementById('id值') //使用这种就不需要写#id
getElementsByTagName( )
getElementsByTagName 根据标签获取一类元素获取页面所有标签
语法:
document.getElementsByTagName('标签')
getElementsByClassName( )
getElementsByClassName 根据类名获取元素获取页面所有类名
语法:
getElementsByClassName('类名')
DOM元素操作🌟
DOM对象都是根据标签生成的,所以操作标签,本质上就是操作DOM对象,通过修改 DOM 的文本内容,动态改变网页的内容。
操作元素内容
innerText
将文本内容添加/更新到任意标签位置,文本中包含的标签不会被解析。
特点:
- 将文本内容添加/更新到任意标签位置
- 显示纯文本,不解析标签
语法:
<div class="box">这是一个div</div>
<script>
//获取对象
const box = document.querySelector('.box')
//操作对象
box.innerText
</script>
例子:
<script>
// innerText 将文本内容添加/更新到任意标签位置
const intro = document.querySelector('.intro')
// intro.innerText = '嗨~ 我叫李雷!'
// intro.innerText = '<h4>嗨~ 我叫李雷!</h4>'
</script>
innerHTML
将文本内容添加/更新到任意标签位置,文本中包含的标签会被解析。
特点:
- 将文本内容添加/更新到任意标签位置
- 会解析标签,多标签建议使用模板字符
语法:
<div class="box">这是一个div</div>
<script>
//获取对象
const box = document.querySelector('.box')
//操作对象
box.innerHTML
</script>
例子:
<script>
// innerHTML 将文本内容添加/更新到任意标签位置
const intro = document.querySelector('.intro')
intro.innerHTML = '嗨~ 我叫韩梅梅!'
intro.innerHTML = '<h4>嗨~ 我叫韩梅梅!</h4>'
</script>
操作元素属性
JavaScript 是网页中用于操作和控制元素属性的主要脚本语言。
-
常见属性修改:可以通过JS设置/修改标签元素属性,比如通过src更换图片。最常见的属性比如:href、title、Src 等。
语法:对象.属性 = 值
-
通过样式属性修改:还可以通过JS设置/修改标签元素的样式属性,比如通过轮播图小圆点自动更换颜色样式
语法:对象.style = '值' //别忘了跟单位 ❗️如果“属性-连接符”,需要转换为小驼峰命名法
-
通过类名修改样式:如果修改的样式比较多,直接通过style属性修改比较繁琐,我们可以通过借助于css类名的形式。
语法:元素.className = '类名' //给元素添加一个类名 ❗️className是使用新值换旧值,如果需要添加一个类,需要保留之前的类名,不然会覆盖掉
-
通过classList操作样式:为了解决className 容易覆盖以前的类名,我们可以通过classList方式追加和删除类名。
语法: 1. 添加类名:元素.classList.add('类名') 2. 删除类名:元素.classList.remove('类名') 3. 切换类名:元素.classList.toggle('类名') //有就删掉,没有就切换
操作表单属性
表单很多情况,也需要修改属性,比如点击眼睛,可以看到密码,本质是把表单类型转换为文本框,正常的有属性有取值的跟其他的标签属性没有任何区别🤗。
语法:
获取:
DOM对象.属性名
设置:
DOM对象.属性名= 新值
例子:
<body>
<input type="text" value="请输入">
<button disabled>按钮</button>
<input type="checkbox" name="" id="" class="agree">
<script>
// 1. 获取元素
let input = document.querySelector('input')
// 2. 取值或者设置值 得到input里面的值可以用 value
// console.log(input.value)
input.value = '小米手机'
input.type = 'password'
// 2. 启用按钮
let btn = document.querySelector('button')
// disabled 不可用 = false 这样可以让按钮启用
btn.disabled = false
// 3. 勾选复选框
let checkbox = document.querySelector('.agree')
checkbox.checked = false
</script>
</body>
自定义属性
标准属性: 标签天生自带的属性 比如class id title等, 可以直接使用点语法操作比如: disabled、checked、selected
自定义属性:
-
在html5中推出来了专门的
data-自定义属性
-
在标签上一律以
data-
开头 -
在DOM对象上一律以
dataset
对象方式获取
例子:
<div data-id="1"> 自定义属性 </div>
<script>
// 1. 获取元素
let div = document.querySelector('div')
// 2. 获取自定义属性值
console.log(div.dataset.id)
</script>
定时器
间歇函数
网页中经常会需要一种功能:每隔一段时间需要自动执行一段代码,不需要我们手动去触发,例如:网页中的倒计时,要实现这种需求,需要定时器函数,定时器函数有两种,今天我先讲间歇函数
setInterval
是 JavaScript 中内置的函数,它的作用是间隔固定的时间自动重复执行另一个函数,也叫定时器函数。
语法:
setInterval(函数,间隔时间) //间隔时间单位是 毫秒
注意:
- 函数名字不需要加括号
- 定时器返回的是一个id数字
例子:
//例1
setInterVal(function(){
console.log('一秒执行一次')
},1000)
//例2
function fun(){
console.log('一秒执行一次')
}
setInterval(fn,1000) //setIntervaL(函数名,间隔时间)函数名不要加小括号
关闭定时器
语法:
let 变量名 = setInterval(函数,间隔时间)
clearInterval(变量名)
延迟函数
JavaScript内置的一个用来让代码延迟执行的函数。这种功能通常通过setTimeout
函数来实现。setTimeout
允许你设置一个函数在多少毫秒之后执行。
setTimeout仅仅只执行一次,所以可以理解为就是把一段代码延迟执行,平时省略window。
语法:setTimeout(回调函数,等待的毫秒数)
清除延迟函数:
let timeID = setTimeout(回调函数, 延迟时间)
clearTimeout(timerID)
⚠️定时器返回的都是
数值类型
。
事件
事件:事件是编程语言中的术语,它是用来描述程序的行为或状态的,一旦行为或状态发生改变,便立即调用一个函数。
事件监听
事件监听:结合 DOM 使用事件时,需要为 DOM 对象添加事件监听,等待事件发生(触发)时,便立即调用一个函数。
语法:
元素对象.addEventListener('事件类型',要执行的函数)
时间监听三要素:
- 事件源:那个dom元素被事件触发了,要获取dom元素
- 事件类型:用什么方式触发,比如鼠标单击click、鼠标经过mouseover等
- 事件调用的函数:要做什么事
完成事件监听分成3个步骤:
- 获取 DOM 元素
- 通过
addEventListener
方法为 DOM 节点添加事件监听 - 等待事件触发,如用户点击了某个按钮时便会触发
click
事件类型 - 事件触发后,相对应的回调函数会被执行
大白话描述:所谓的事件无非就是找个机会(事件触发)调用一个函数(回调函数)。
事件监听版本
DOM L0:
事件源.on事件=function(){}
DOML1:DOM级别1于1998年10月1日成为W3C推荐标准
DOM L2:
事件源.addEventListener(事件,事件处理函数)
DOML3:DOM3级事件模块在DOM2级事件的基础上重新定义了这些事件,也添加了一些新事件类型。
❗区别:on方式会被覆盖,addEventListener方式可绑定多次,拥有事件更多特性,推荐使用。
事件类型
将众多的事件类型分类可分为:鼠标事件、键盘事件、表单事件、焦点事件等,我们逐一展开学习。
鼠标事件
鼠标事件是指跟鼠标操作相关的事件,如单击、双击、移动等。
语法:
click
:鼠标点击mouseenter
:鼠标经过mouseleave
:鼠标离开
焦点事件
焦点事件是JavaScript中处理用户交互时常用的一类事件,它们在用户与表单元素交互时触发。
语法:
-
focus
:获得焦点 -
blur
:失去焦点
键盘事件
键盘事件是当用户按下或释放键盘上的键时触发的事件。
语法:
keydown
:键盘按下触发keyup
:键盘抬起触发
文本输入事件
文本输入事件主要指的是input
事件,它会在用户输入文本时触发。
语法:input
事件对象
任意事件类型被触发时与事件相关的信息会被以对象的形式记录下来,我们称这个对象为事件对象。
事件对象是什么?
也是个对象,这个对象里有事件触发时的相关信息
例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息
使用场景
可以判断用户按下哪个键,比如按下回车键可以发布新闻
可以判断鼠标点击了哪个元素,从而做相应的操作
获取事件对象
语法:
- 在事件绑定的回调函数的第一个参数就是事件对象,一般命名为
event
、ev
、e
元素.addEventListener('click',function(e)){ }
事件对象部分常用属性:
ev.type
: 当前事件的类型ev.clientX/Y
: 光标相对浏览器窗口的位置ev.offsetX/Y
:光标相于当前 DOM 元素的位置key
:用户按下的键盘值
注:在事件回调函数内部通过 window.event 同样可以获取事件对象。
环境对象
环境对象:指的是函数内部特殊的变量this
,它代表着当前函数运行时所处的环境。
大白话就是this指向的是调用者,“谁点调用,this就是谁”!
结论:
this
本质上是一个变量,数据类型为对象- 函数的调用方式不同
this
变量的值也不同 - 【谁调用
this
就是谁】是判断this
值的粗略规则 - 函数直接调用时实际上
window.sayHi()
所以this
的值为window
回调函数
如果将函数A做为参数传递给函数B 时,我们称函数A为回调函数。简单理解:当一个函数当做参数来传递给另外一个函数的时候,这个函数就是回调函数。
我们回顾一下间歇函数 setInterval
,fn
函数做为参数传给了 setInterval
,这便是回调函数的实际应用了,结合刚刚学习的函数表达式上述代码还有另一种更常见写法。
结论:
- 回调函数本质还是函数,只不过把它当成参数使用
- 使用匿名函数做为回调函数比较常见
案例——全选文本框
需求:用户点击全选,则下面复选框全部选择,取消全选则全部取消
分析:
①:全选复选框点击,可以得到当前按钮的checked
②:把下面所有的小复选框状态checked,改为和全选复选框一致
③:把结果给全选按钮
④:利用cSS复选框选择器input:checked
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
* {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #c0c0c0;
width: 500px;
margin: 100px auto;
text-align: center;
}
th {
background-color: #09c;
font: bold 16px "微软雅黑";
color: #fff;
height: 24px;
}
td {
border: 1px solid #d0d0d0;
color: #404060;
padding: 10px;
}
.allCheck {
width: 80px;
}
</style>
</head>
<body>
<table>
<tr>
<th class="allCheck">
<input type="checkbox" name="" id="checkAll"> <span class="all">全选</span>
</th>
<th>商品</th>
<th>商家</th>
<th>价格</th>
</tr>
<tr>
<td>
<input type="checkbox" name="check" class="ck">
</td>
<td>小米手机</td>
<td>小米</td>
<td>¥1999</td>
</tr>
<tr>
<td>
<input type="checkbox" name="check" class="ck">
</td>
<td>小米净水器</td>
<td>小米</td>
<td>¥4999</td>
</tr>
<tr>
<td>
<input type="checkbox" name="check" class="ck">
</td>
<td>小米电视</td>
<td>小米</td>
<td>¥5999</td>
</tr>
</table>
<script>
// 1. 获取大复选框
const checkAll = document.querySelector('#checkAll')
// 2. 获取所有的小复选框
const cks = document.querySelectorAll('.ck')
// 3. 点击大复选框 注册事件
checkAll.addEventListener('click', function () {
// 得到当前大复选框的选中状态
// console.log(checkAll.checked) // 得到 是 true 或者是 false
// 4. 遍历所有的小复选框 让小复选框的checked = 大复选框的 checked
for (let i = 0; i < cks.length; i++) {
cks[i].checked = this.checked
}
})
// 5. 小复选框控制大复选框
for (let i = 0; i < cks.length; i++) {
// 5.1 给所有的小复选框添加点击事件
cks[i].addEventListener('click', function () {
// 判断选中的小复选框个数 是不是等于 总的小复选框个数
// 一定要写到点击里面,因为每次要获得最新的个数
// console.log(document.querySelectorAll('.ck:checked').length)
// console.log(document.querySelectorAll('.ck:checked').length === cks.length)
checkAll.checked = document.querySelectorAll('.ck:checked').length === cks.length
})
}
</script>
</body>
</html>
事件流
事件流是对事件执行过程的描述,了解事件的执行过程有助于加深对事件的理解,提升开发实践中对事件运用的灵活度。
如上图所示,任意事件被触发时总会经历两个阶段:【捕获阶段】和【冒泡阶段】。
简言之,事件流只会在父子元素具有相同事件类型时才会产生影响。捕获阶段是【从父到子】的传导过程,冒泡阶段是【从子向父】的传导过程。
捕获和冒泡
事件捕获:从DOM的根元素开始去执行对应的事件(从外到里),事件捕获需要写对应代码才能看到效果。
如果事件是在捕获阶段执行,它会先执行父盒子事件再去执行子盒子事件。
事件冒泡:当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。事件冒泡是默认存在的。
简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件。
如果事件是在冒泡阶段执行,它会先执行子盒子事件再去执行父盒子事件。
阻止冒泡
因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素。若想把事件就限制在当前元素内,就需要阻止事件冒泡。
阻止冒泡是指阻断事件的流动,保证事件只在当前元素被执行,而不再去影响到其对应的祖先元素。
前提:阻止事件冒泡需要拿到事件对象。
语法:事件对象.stopPropagatino()
⚠️注意:此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效。
阻止默认行为
我们某些情况下需要阻止默认行为的发生,比如阻止链接的跳转,表单域跳转
语法:e(事件对象).preventDefault()
例子:
<a href="http://www.baidu.com">百度一下</a>
<script>
const a = document.querySelector('a')
a.addEventListener('click', function(e){
e.preventDefault()
})
</script>
解绑事件
on事件方式,直接使用nul覆盖偶就可以实现事件的解绑。
例:
<button>点击</button>
<script>
const btn = document.querySelector('button');
btn.onclick = function() {
alert('点击了');
};
// 事件移除解绑
btn.onclick = null;
</script>
addEventListener方式,必须使用:removeEventListener(事件类型,事件处理函数,[获取捕获或者冒泡阶段])
⚠️注意:匿名函数无法被解绑!
例:
<button>点击</button>
<script>
const btn = document.querySelector('button');
function fn() {
alert('点击了');
}
// 假设btn是已经定义好的按钮元素
btn.addEventListener('click', fn);
btn.removeEventListener('click', fn);
</script>
鼠标经过事件的区别:
mouseover 和 mouseout 会有冒泡效果
mouseenter 和 mouseleave 没有冒泡效果 (推荐)
两种注册事件的区别:
传统on注册(L0)
- 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
- 直接使用nu覆盖偶就可以实现事件的解绑
- 都是冒泡阶段执行的
事件监听注册(L2)
- 语法:addEventListener(事件类型,事件处理函数,是否使用捕获)
- 后面注册的事件不会覆盖前面注册的事件(同一个事件)
- 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
- 必须使用removeEventListener(事件类型,事件处理函数,获取捕获或者冒泡阶段)
- 匿名函数无法被解绑
事件委托
事件委托是利用事件流的特征解决一些现实开发需求的知识技巧,主要的作用是提升程序效率。
优点:减少注册次数可以提高程序性能
原理:事件委托其实是利用事件冒泡的特点,给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件。
实现:事件对象.target.tagName
可以获得真正触发事件的元素
总结:事件委托就是委托给父级,事件写到父级身上
<script>
// 假设页面中有 10000 个 button 元素
const buttons = document.querySelectorAll('table button')
// 假设上述的 10000 个 buttom 元素共同的祖先元素是 table
const parents = document.querySelector('table')
parents.addEventListener('click', function (ev) {
// console.log(ev.target);
// 只有 button 元素才会真正去执行逻辑
if(ev.target.tagName === 'BUTTON') {
// 执行的逻辑
}
})
</script>
其他事件
页面加载事件
加载外部资源(如图片、外联CSS和JavaScript等)加载完毕时触发的事件,有些时候需要等页面资源全部处理完了做一些事情,老代码喜欢把script写在head中,这时候直接找dom元素找不到。
load
事件名:load
监听页面所有资源加载完毕:(给window添加load事件)
语法:
window.addEventListener('load', function() {
// 执行的操作
})
例子:
DOMContentLoaded
当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded
事件被触发,而无需等待样式表、图像等完全加载。
事件名:DOMContentLoaded
(给document添加DoMContentLoaded事件)
语法:
document.addEventListener('DOMContentLoaded', function() {
// 执行的操作
})
页面滚动事件
滚动条在滚动的时候持续触发的事件,很多网页需要检测用户把页面滚动到某个区域后做一些处理,比如固定导航栏,比如返回顶部。
事件名:scroll
语法:
window.addEventListener('scroll', function() {
// 执行的操作
})
获取滚动位置
scrollLeft
和scrollTop
属性:
- 获取被卷去的大小
- 获取元素内容往左、往上滚出去看不到的距离
- 这两个值是可读写的
滚动到指定的坐标
scrollTo()
方法可把内容滚动到指定的坐标语法:
元素.scrollTo(x,y)
开发中,我们经常检测页面滚动的距离,比如页面滚动100像素,就可以显示一个元素,或者固定一个元素。
获取html元素写法:document.documentElement
例子:
// 页面滚动事件
document.dqcumentElement.scrollTop = 800 //`scrollTop`可读写性质,让这个网页从800开始,注意不能带单位
window.addEventListener('scroll', function() {
// document.documentElement是HTML元素获取方式
const n = document.documentElement.scrollTop;
console.log(n);
});
页面尺寸事件
浏览器窗口大小发生变化的时候触发的事件:
事件名:resize
window.addEventListener('resize', function() {
// 执行的操作
})
检测屏幕尺寸:
获取宽高
获取元素的可见部分宽高(不包含边框,margin,滚动条等)
clientWidth
和clientHeight
获取元素的尺寸与位置
前面案例滚动多少距离,都是我们自己算的,最好是页面滚动到某个元素,就可以做某些事。简单说,就是通过js的方式,得到元素在页面中的位置。如果我们可以做页页面滚动到这个位置,就可以做某些操作,省去计算了。
获取元素尺寸:
-
获取元素的自身宽高、包含元素自身设置的宽高、padding、border
-
offsetWidth
和offsetHeight
-
获取出来的是数值,方便计算
⚠️注意:获取的是可视宽高,如果盒子是隐藏的,获取的结果是0
获取元素位置:
- 获取元素距离自己定位父级元素的左、上距离
offsetLeft
和offsetTop
注意是只读属性
offsetWidth和offsetHeight是得到元素什么的宽高?
- 内容+padding+border
offsetTop和offsetLeft得到位置以谁为准?
- 带有定位的父级
- 如果都没有则以文档左上角为准
移动端事件
移动端也有自己独特的地方。比如触屏事件touch(也称触摸事件),Android和IOS都有。
触摸事件
触屏事件touch(也称触摸事件),Android和IOS都有。
常见的触屏事件:
触屏touch事件 | 说明 |
---|---|
touchstart | 手指触摸到一个DOM元素时触发 |
touchmove | 手指在一个DOM元素上滑动时触发 |
touchend | 手指从一个DOM元素上移开时触发 |
例子:
<div></div>
<script>
const div = document.querySelector('div');
// 1. 触摸
div.addEventListener('touchstart', function() {
console.log('开始摸我了');
});
// 2. 离开
div.addEventListener('touchend', function() {
console.log('离开了');
});
// 3. 移动
div.addEventListener('touchmove', function() {
console.log('一直摸,移动');
});
</script>
Swiper插件
Swiper插件是一个非常优秀的轮播图插件,Swiper插件官网:https://wwwswiper.com.cn/
BOM
window对象
BOM (Browser Object Model ) 是浏览器对象模型。window对象是一个全局对象,也可以说是JavaScript中的顶级对象,像document、alert()、console.log()这些都是window的属性。
- 基本BOM的属性和方法都是window的
- 所有通过var定义在全局作用域中的变量、函数都会变成window对象的属性和方法
- window对象下的属性和方法调用的时候可以省略window。
JS执行机制🌟
先来一道经典的面试题:
//问题一:输出的结果是多少? console.log(1111); setTimeout(function() { console.log(2222); }, 1000); console.log(3333);
答案:1–>3–>2
//问题二:输出的结果是多少? console.log(1111); setTimeout(function() { console.log(2222); }, 0); console.log(3333);
答案:1–>3–>2
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。
这是因为Javascript这门脚本语言诞生的使命所致一一JavaScript是为处理页面中用户的交互,以及操作DOM 而诞生的。比如我们对某个DOM元素进行添加和删除操作,不能同时进行。应该先进行添加,之后再删除。
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。这样所导致的问题是:如果JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
为了解决这个问题,利用多核CPU 的计算能力,HTML5 提出Web Worker 标准,允许JavaScript脚本创建多个线程。于是,JS 中出现了同步和异步。
同步
前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的。
- 比如做饭的同步做法:我们要烧水煮饭,等水开了(10分钟之后),再去切菜,炒菜。
异步
你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事情。
- 比如做饭的异步做法,我们在烧水的同时,利用这10分钟,去切菜,炒菜。
他们的本质区别:这条流水线上各个流程的执行顺序不同
同步任务:同步任务都在主线程上执行,形成一个执行栈。
异步任务:JS的异步是通过回调函数实现的,一般而言,异步任务有以下👇三种类型:
- 普通事件,如
click
、resize
等 - 资源加载,如
load
、rror
等 - 定时器,包括
setlnterval
、setTimeout
等
❗️异步任务相关添加到任务队列中(任务队列也称为消息队列)
JS执行机制:
- 先执行执行栈中的同步任务
- 异步任务放入任务队列中
- 一旦执行栈中的所有同步任务执行完毕,系统就会按次序读取任务中的异步任务,于是被读取的是异步任务结束等待状态,进入执行栈,开始执行。(这个过程也叫做事件循环)
⚠️由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,所以这种机制被称为事件循环(event loop)
Location对象
location的数据类型是对象,它拆分并保存了URL地址的各个组成部分。
属性/方法 | 说明 |
---|---|
location.href | 属性,获取完整的 URL 地址,赋值时用于地址的跳转 |
location.search | 属性,获取地址中携带的参数,符号 ?后面部分 |
location.hash | 属性,获取地址中的啥希值,符号 # 后面部分 |
location.reload() | 方法,用来刷新当前页面,传入参数 true 时表示强制刷新 |
Navigator对象
navigator的数据类型是对象,该对象下记录了浏览器自身的相关信息,
常用属性和方法:
- 通过 userAgent 检测浏览器的版本及平台
// 检测 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'
}})();
Histroy对象
history (历史)是对象,主要管理历史记录, 该对象与浏览器地址栏的操作相对应,如前进、后退等。
history对象方法 | 作用 |
---|---|
back() | 可以后退功能 |
forward() | 前进功能 |
go(参数) | 前进后退功能,参数如果是1前进 1个页面,如果是-1后退 1个页面 |
例子:
<body>
<button class="back">←后退</button>
<button class="forward">前进→</button>
<script>
// histroy对象
// 1.前进
const forward = document.querySelector('.forward')
forward.addEventListener('click', function () {
// history.forward()
history.go(1)
})
// 2.后退
const back = document.querySelector('.back')
back.addEventListener('click', function () {
// history.back()
history.go(-1)
})
</script>
</body>
本地存储🌟
本地存储:将数据存储在本地浏览器中
常见的使用场景:https://todomvc.com/examples/vanilla-es6/ 页面刷新数据不丢失
好处:
1、页面刷新或者关闭不丢失数据,实现数据持久化
2、容量较大,sessionStorage和 localStorage 约 5M 左右
localStorage
作用: 数据可以长期保留在本地浏览器中,刷新页面和关闭页面,数据也不会丢失
特性:
- 以键值对的形式存储,并且存储的是字符串, 省略了window
- 可以多窗口(页面)共享(同一浏览器可以共享)
- ❗️本地存储只能存储字符串
语法:
- 存储数据:
localStorage.setltem(key,value)
- 获取数据:
localStorage.getItem('key')
- 删除数据:
localStorage.removeItem('key')
- 改数据:
localStorage.setltem(key,新value)
例子:
浏览器查看本地数据:
存储复杂数据类型
**问题:**本地只能存储字符串,无法存储复杂数据类型.
// 定义一个对象
const obj = {
uname: '美丽老师',
age: 28,
gender: '女'
}
// 存储复杂数据类型,无法直接使用,需要先转换为字符串
localStorage.setItem('obj', JSON.stringify(obj));
// 取出时,需要先转换回对象
console.log(JSON.parse(localStorage.getItem('obj')));
解决:需要将复杂数据类型转换成 JSON字符串,在存储到本地
语法:JSON.stringify(复杂数据类型)
模版:localStorage.setItem('key', JSON.stringify(key))
这个就类似于:
localStorage.setItem('key', JSON.stringify(
const obj = {
uname: '美丽老师',
age: 28,
gender: '女'
}
))
拓展——JSON对象
JSON字符串:
- 首先是1个字符串
- 属性名使用双引号引起来,不能单引号
- 属性值如果是字符串型也必须双引号
获取复杂数据类型
**问题:**因为本地存储里面取出来的是字符串,不是对象,无法直接使用
**解决: **把取出来的字符串转换为对象
语法:JSON.parse(JSON字符串)
模版:console.log(JSON.parse(localStorage.getItem('key')))
如果你觉得复杂可以赋值到一个变量:
案例——信息统计表*
sessionStorage(了解)
sessionStorage用法跟localStorage基本相同,区别在于当页面浏览器被关闭时,存储在sessionStorage
的数据会被清除。
存储:sessionStorage.setItem(key,value)
获取:sessionStorage.getItem(key)
删除:sessionStorage.removeItem(key)
面向对象
编程思想
面相过程
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些**步骤一步一步实现,**使用的时候再一个一个的依次。
面相对象
面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:封装性、继承性、多态性
面相过程编程与面相对象编程对比:
编程范式 优点 缺点 面向过程 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 没有面向对象易维护、易复用、易扩展 面向对象 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 性能比面向过程低
构造函数
封装是面向对象思想中比较重要的一部分,js面向对象可以通过构造函数实现的封装。同样的将变量和函数组合到了一起并能通过this实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的。
特点:
- 构造函数体现了面向对象的封装特性
- 构造函数实例创建的对象彼此独立、互不影响
- js面向对象可以通过构造函数实现的封装
- 存在
浪费内存
的问题(因为两个对象内存地址不一样,即使是同样的属性也会开辟一个新空间)
原型对象
为了解决上面浪费内存的问题,我们希望所有的对象使用同一个函数,这样就比较节省内存,
那么我们要怎样做呢?——那就是今天的主角:原型!!!
构造函数通过原型分配的函数是所有对象所共享的,JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象,这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存,我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
简单来说就是:一个叫做prototype
对象,我们叫做原型对象。作用是共享方法,可以把那些不变的方法,直接定义在prototype对象上。
模版:
<script>
function Person() {
}
// 每个函数都有 prototype 属性
console.log(Person.prototype)
</script>
总结:
- 公共的属性写到构造函数里面
- 公共的方法写到原型对象里面
构造函数的this指向:构造函数里面的this就是实例对象
原型对象的this指向:也是构造函数里面的this就是实例对象
❗️❗️❗️构造函数和原型对象中的this 都指向实例化的对象。
构造器
每个原型对象里面都有个constructor 属性(constructor 构造函数)
作用:该属性指向该原型对象的构造函数, 简单理解,就是“指向我的爸爸,我是有爸爸的孩子”。
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了,此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
对象原型
思考一下,
- 构造函数可以创建实例对象。
- 构造函数还有一个原型对象,一些公共的属性或者方法放到这个原型对象身上。
- 但是为什么实例对象可以访问原型对象里面的属性和方法呢❓
**对象都会有一个属性 __proto__
**指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __proto__
原型的存在。
❗️注意:
__ proto__
是JS非标准属性,有些浏览器可能是不一样的,还有一个[[prototype]]
[[prototype]]
和__proto__
意义相同- 用来表明当前实例对象指向哪个原型对象prototype
__proto__
对象原型里面也有一个 constructor属性,指向创建该实例对象的构造函数
原型继承*
继承是面向对象编程的另一个特征,通过继承进一步提升代码封装的程度,JavaScript 中大多是借助原型对象实现继承。
核心代码:
子类的原型 = new 父类
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链。
原型链其实就是一个查找规则:
- 当访问一个对象的属性(包括方法)时候,首先查找这个对象自身有没有该属性。
- 如果连我有就查找它的原型(也就是
__proto__
指向的prototype对象) - 如果还没有就查找原型对象的原型(Object的原型对象)
- 依此类推一致找到Object为止(null)
__proto__
对象原型的意义就在于为对象成员查找机制提供一个方向,可以使用instanceof
运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上。function Person() { } const p = new Person() console.log(p instanceof Person) //true console.log(p instanceof Object) //true console.log(p instanceof Array) //false console.log([1, 2, 3] instanceof Array) //true console.log(Array instanceof Object) //true 因为万物皆对象
案例——给数组扩展方法
需求:定义一个任何一个数组实例对象都可以使用的求最大值方法。
const arr = [1,2,3]
Array.prototype.max = function(){
// 展开运算符
return Math.max(...this) //原型对象中的this指向实例化的对象
}
console.log(arr.max())
-
定义数组:
const arr = [1, 2, 3]
这行代码定义了一个包含三个元素的数组。 -
扩展原型:
Array.prototype.max = function() {...}
这行代码给所有数组对象添加了一个名为max
的新方法。Array.prototype
是所有数组的原型,这意味着任何数组实例都会继承这个方法。 -
定义
max
方法:这个方法使用了ES6的展开运算符(...
),它将数组中的元素作为独立的参数传递给Math.max
函数。Math.max
是一个内置函数,用于返回一组数字中的最大值。 -
使用
this
关键字:在max
方法中,this
关键字指向调用该方法的数组对象。在这个例子中,this
就是arr
数组。 -
调用
max
方法:console.log(arr.max())
这行代码调用了arr
数组的max
方法,并打印出结果。由于arr
是一个包含1, 2, 3
的数组,Math.max(...this)
将会返回3
。 -
输出结果:控制台将输出
3
,这是数组arr
中的最大值。
JS高级技巧
深浅拷贝
在开发中我们经常需要复制一个对象,但是在复制对象的过程中会出现一些问题,这就涉及到了深浅拷贝的事啦。
浅拷贝和深拷贝都是只针对引用类型。
浅拷贝
浅拷贝:拷贝的是地址【只复制引用,而没有复制真正的值】
常见方法:
- 拷贝对象:
Object.assgin()
或者展开运算符 {...obj} 拷贝对象
- 拷贝数组:
Array.prototype.concat()
或者[...arr]
直接赋值和浅拷贝有什么区别?
- 直接复制的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里面的地址
- 浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响。
如果是简单数据类型拷贝值,引用数据类型拷贝的是地址 (简单理解: 如果是单层对象,没问题,如果有多层就有问题)
const obj = {
uname: 'pink',
age: 18,
family: {
baby: "小pink"
}
};
// 使用Object.assign进行浅拷贝
const o = {};
Object.assign(o, obj);
o.age = 20;
o.family.baby = '老pink';
console.log(o);
console.log(obj);
深拷贝
深拷贝:拷贝的是对象,不是地址。【是复制真正的值,对象的属性一个改变,其他的都不会收到影响】
简单来说就是这样:
常见方法:
- 通过
递归
实现深拷贝 lodash
或者cloneDeep
- 通过
Json.parse(JSON.stringify())
实现
递归实现深拷贝*
拓展——函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数,递归函数的作用和循环效果类似,由于递归很容易发生“栈溢出”错误,所以必须要加退出条件
return
js库lodash里面cloneDeep内部实现了深拷贝*
JSON序列化*
异常处理*
throw
异常处理是指预估代码执行过程中可能发生的错误,然后最大程度的避免错误的发生导致整个程序无法继续运行。
语法:throw new Error()
总结:
- throw 抛出异常信息会中断,程序也会终止执行
- throw 后面跟的是错误提示信息
- Error 对象配合 throw 使用,能够设置更详细的错误信息
<script>
function counter(x, y) {
if(!x || !y) {
// throw '参数不能为空!';
throw new Error('参数不能为空!')
}
return x + y
}
counter()
</script>
try … catch
我们可以通过try
捕获错误信息(浏览器提供的错误信息)
特点:
try...catch
用于捕获错误信息- 将预估可能发生错误的代码写在
try
代码段中 - 如果
try
代码段中出现错误后,会执行catch
代码段,并截获到错误信息 finally
不管是否错误,都会执行
function foo() {
try {
// 查找 DOM 节点
const p = document.querySelector('.p')
p.style.color = 'red'
} catch (error) {
// try 代码段中执行有错误时,会执行 catch 代码段
// 查看错误信息
console.log(error.message)
// 终止代码继续执行
return
}
finally {
//不管你程序对不到,一定会执行的代码
alert('执行')
}
console.log('如果出现错误,我的语句不会执行')
}
foo()
debugger
相当于断点调试,可以在运行的时候直接调转过去
this指向总结
普通函数
普通函数的调用方式决定了 this
的值,即【谁调用 this
的值指向谁】。
普通函数没有明确调用者时 this
值为 window
,严格模式下没有调用者时 this
的值为 undefined
。
箭头函数
箭头函数中的 this
与普通函数完全不同,也不受调用方式的影响,事实上箭头函数中并不存在 this
!
特点:
- 箭头函数会默认帮我们绑定外层this的值,所以在箭头函数中this的值和外层的this是一样的。
- 箭头函数中的this引用的就是最近作用域中的this
- 向外层作用域中,一层一层查找this,直到this的定义
⚠️注意:
-
事件回调函数使用箭头函数时,
this
为全局的window
,因此DOM事件回调函数不推荐使用箭头函数:<script> // DOM 节点 const btn = document.querySelector('.btn') // 箭头函数 此时 this 指向了 window btn.addEventListener('click', () => { console.log(this) }) // 普通函数 此时 this 指向了 DOM 对象 btn.addEventListener('click', function () { console.log(this) }) </script>
-
基于原型的面向对象也不推荐采用箭头函数:
<script> function Person() { } // 原型对像上添加了箭头函数 Person.prototype.walk = () => { console.log('人都要走路...') console.log(this); // window } const p1 = new Person() p1.walk() </script>
this中适用于不适用的情况:
- 适用:需要使用上层this的地方
- 不适用:构造函数、原型函数、dom事件函数等
改变this指向
以上归纳了普通函数和箭头函数中关于 this
默认值的情形,不仅如此 JavaScript 中还允许指定函数中 this
的指向,有 3 个方法可以动态指定普通函数中 this
的指向:
call
使用 call
方法调用函数,同时指定函数中 this
的值。
语法:函数名.call(thisArg,arg1,arg2,...)
thisArg
:在fun函数运行时指定的this值arg1,arg2
:传递的其他参数- 返回值就是函数的返回值,因为它就是调用函数
apply
使用apply方法调用函数,同时指定被调用函数中this的值。
语法:函数名.apply(thisArg, [argsArray])
(要求第二个参数必须是数组)
thisArg
:在fun函数运行时指定的this值argsArray
:传递的值,必须包含在数组里面
⚠️注意:返回值就是函数的返回值,因为它就是调用函数,因此 apply主要跟数组有关系,比如使用 Math.max() 求数组的最大值。
例1:
例2:
call和apply的区别是?
- 都是调用函数,都能改变this指向
- 参数不一样,apply传递的必须是数组
bind
bind
方法并不会调用函数,而是创建一个指定了 this
值的新函数。
语法:函数名.bind(thisArg, arg1, arg2, ...)
thisArg
:在fun函数运行时指定的this值arg1,arg2
:传递的其他参数
⚠️注意:返回由指定的this值和初始化参数改造的原函数拷贝(新函数),因此当我们只是想改变this 指向,并且不想调用这个函数的时候,可以使用bind,比如改变定时器内部的this
指向。
例1:
例2:
总结
call, apply, bind 总结
相同点:
- 都可以改变函数内部的
this
指向。
区别点:
call
和apply
会调用函数,并且改变函数内部this
指向。call
和apply
传递的参数不一样,call
传递参数arg1, arg2...
形式,apply
必须数组形式[arg]
。bind
不会调用函数,可以改变函数内部this
指向。
主要应用场景:
call
调用函数并且可以传递参数。apply
经常跟数组有关系,比如借助于Math
对象实现数组最大值最小值。bind
不调用函数,但是还想改变this
指向,比如改变定时器内部的this
指向。
性能优化
防抖和节流总结:
说明 | 使用场景 | 单位时间内频繁触发事件 | 示例 |
---|---|---|---|
防抖 | 搜索框搜索输入、手机号、邮箱验证输入检测 | 只执行最后一次 | 高频事件:鼠标移动 mousemove 、页面尺寸缩放 resize |
节流 | 滚动条滚动 scroll 等等 | 只执行一次 | 单位时间内限制函数执行的频率 |
(先往下学才能看懂以上👆总结\(^o^)/~
)
防抖
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
举个栗子:王者荣耀回城,只要被打断就需要重新来
使用场景:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
下面直接通过一个例子来更加深刻的理解这个知识点:
源代码:
lodash防抖
语法:_.debounce(fun,时间)
下面进行防抖处理:
手写防抖*
核心思路:防抖的核心就是利用定时器(setTimeout)来实现的
- 声明一个定时器变量
- 当鼠标每次滑动都先判断是否有定时器,如果有定时器先清除以的定时器
- 如果没有定时器则开启定时器,同时存到变量里面
- 在定时器里面调用要执行的函数
function debounce(fn, wait) {
let timeoutId = null; // 保存定时器ID
// 返回一个新的函数
return function() {
const context = this; // 保存函数调用的上下文
const args = arguments; // 保存函数调用的参数
// 清除之前的定时器
if (timeoutId) {
clearTimeout(timeoutId);
}
// 设置一个新的定时器
timeoutId = setTimeout(() => {
fn.apply(context, args); // 执行原函数
}, wait);
};
}
使用示例:
// 假设有一个搜索框,用户输入时触发搜索
const searchInput = document.getElementById('search-input');
// 创建防抖函数,设置延迟时间为300毫秒
const handleSearch = debounce(function(event) {
console.log('搜索内容:', event.target.value);
}, 300);
// 为搜索框绑定事件
searchInput.addEventListener('input', handleSearch);
在这个例子中,debounce
函数接受两个参数:fn
是需要执行的函数,wait
是延迟执行的时间(以毫秒为单位)。每次触发事件时,都会重置定时器,只有在最后一次触发事件后 wait
时间内没有再次触发事件时,fn
函数才会执行。这样就可以避免在快速连续触发事件时频繁调用 fn
函数。
节流
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数
举个栗子:和平精英98k换子弹期间不能射击
使用场景:高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等
lodash节流
语法:_.throttle(fun,时间)
手写节流*
节流的核心就是利用定时器(setTimeout)来实现。
垃圾回收机制
JS环境中分配的内存,一般有如下几个生命周期:
- **内存分配:**当我们声明变量、函数、对象的时候,系统会自动为他们分配内存。
- **内存使用:**即读写内存,也就是使用变量、函数等
- 内存回收:使用完毕后,由垃圾回收器自动回收不再使用的内存
注意:
- 全局变量一般不会回收(只有关闭页面回收才会被回收)
- 一般情况下局部变量的值,不用就会被自动回收掉
内存泄露:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄露。
算法说明
堆栈空间分配区别:
- 栈:由于操作系统自动分配释放函数的参数值、局部变量等,基本数据类型放到栈里面。
- 堆:一般由程序员分配释放,若程序员不释放,由垃圾回收机制回收。复杂数据类型放到堆里面。
下面介绍两种常见的垃圾回收机制算法:引用计数法 和 标记清除法
引用计数法
IE采用的引用计数算法,定义“内存不再使用”,就是看一个对象是否有指向它的引用,没有引用了就回收对象。
算法:
- 跟踪记录被引用的次数
- 如果被引用了一次,那么就记录次数1,多次引用会累加++
- 如果减少一个引用就减1–
- 如果引用次数是0,则释放内存
但它却存在一个致命的问题:嵌套引用(循环引用),如果两个对象相互引用,尽管他们已经不再使用了,垃圾回收器还是不会对他们进行回收,导致内存泄露。
因为他们的引用次数永远不会是0。这样的相互引用如果说很大量的存在就会导致大量的内存泄露。
标记清除法
现代的浏览器已经不再使用引用计数算法了。现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
- 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。
- 就是从根部(在JS中就是全局对象)出发定时扫猫内存中的对象。凡是能从根部到达的对象,都是还需要使用的。
- 那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
正则表达式
正则表达式(RegularExpression)是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。通常用来查找、替换那些符合正则表达式的文本,许多语言都支持正则表达式。
使用场景:
- 例如验证表单:手机号表单要求用户只能输入11位的数字 (匹配)
- 过滤掉页面内容中的一些敏感词(替换)
- 从字符串中获取我们想要的特定部分(提取)等
正则基本使用
-
定义规则
const 规则名称 = /表达式/
- 其中
/ /
是正则表达式字面量 - 正则表达式也是
对象
- 其中
-
使用正则
规则名称.test(被检测的字符串)
test()方法
:用来查看正则表达式与指定的字符串是否匹配- 如果正则表达式与指定的字符串匹配 ,返回
true
,否则false
- 如果正则表达式与指定的字符串匹配 ,返回
exec()方法
:在一个指定字符串中执行一个搜索匹配- 如果匹配成功,exec()方法返回一个数组,否则返回null
元字符
- 普通字符:
- 大多数的字符仅能够描述它们本身,这些字符称作普通字符,例如所有的字母和数字。
- 普通字符只能够匹配字符串中与它们相同的字符。
- 比如,规定用户只能输入英文26个英文字母,普通字符的话 /[abcdefghijklmnopqrstuvwxyz]/
- 元字符(特殊字符)
- 是一些具有特殊含义的字符,可以极大提高了灵活性和强大的匹配功能。
- 比如,规定用户只能输入英文26个英文字母,换成元字符写法: /[a-z]/
说白了就是有点模糊查询的意思
(#^.^#)
元字符分类:
- 边界符(表示位置,开头和皆为,必须用什么开头,用什么结尾)
- 量词(表示重复次数)
- 字符类
边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
❗️如果 ^ 和 $ 在一起,表示必须是精确匹配
量词
量词用来设定某个模式重复次数
量词 | 说明 |
---|---|
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
(n) | 重复n次 |
(n,) | 重复n次或更多次 |
(n,m) | 重复n到m次 |
⚠️注意: 逗号左右两侧千万不要出现空格。
字符类
-
[]
匹配字符集合:后面的字符串只要包含abc中任意一个字符类,都返回true表达式 说明 [abc]
匹配包含的单个字符。也就是只有abc这三个单字符返回true,可以理解为多选1 [a-z]
连字符。来指定字符范围。[a-z]表示 a到z 26个英文字母 [^abc]
取反符。例如[^a-z]匹配除了小写字母以外的字符 -
预定类:指的是某些常见模式的简写方式。
字符类 说明 \d
匹配0-9之间的任一数字,相当于 [0-9]
\D
匹配所有0-9以外的字符,相当于 [^0-9]
\w
匹配任意的字母、数字和下划线,相当于 [A-Za-z0-9_]
\W
除所有字母、数字和下划线以外的字符,相当于 [^A-Za-z0-9_]
\s
匹配空格(包括换行符、制表符、空格符等),相当于 [ \t\n\v\f\r]
\S
匹配非空格的字符,相当于 [^ \t\n\v\f\r]
-
修饰符:约束正则执行的某些细节行为,如是否区分大小写、是否支持多行匹配等
语法:
/表达式/修饰符
i
是单词ignore 的缩写,正则匹配时字母不区分大小写g
是单词global 的缩写,匹配所有满足正则表达式的结果(全局使用)
-
replace
替换方法:可以完成字符的替换语法:
字符串.replace(/正则表达式/, '替换的文本')