JavaScript
JavaScript = ECMAscript + BOM + DOM
- JavaScript:运行在浏览器上的脚本语言
- ECMAscript:JS的标准
- BOM:用JS控制浏览器
- DOM:用JS控制html、xml文档
运算符
自增和自减
++ 自增运算符 (c++后,c的值会+1,但c++本身为原始的值)
-
++ 使用后会使得原来的变量立刻增加1
-
自增分为前自增(++a)和后自增(a++)
-
无论是++a还是a++都会使==原变量==立刻增加1
-
- 不同的是++a和a++所返回的值不同
a++ 是自增前的值 旧值 ++a 是自增后的值 新值
逻辑运算符
-
! 逻辑非
- ! 可以用来对一个值进行非运算
- 它可以对一个布尔值进行取反操作 true --> false false --> true
- 如果对一个非布尔值进行取反,它会先将其转换为布尔值然后再取反 可以利用这个特点将其他类型转换为布尔值
- 类型转换
- 转换为字符串
- 显式转换 String()
- 隐式转换 +"
- 转换为数值
- 显式转换 Number()
- 隐式转换 +
- 转换为布尔值
- 显式转换 Boolean()
- 隐式转换 !!
- 转换为字符串
-
&& 逻辑与
-
可以对两个值进行与运算
-
当&&左右都为true时,则返回true,否则返回false
-
与运算是短路的与,如果第一个值为false,则不看第二个值的,如果找到false则直接返回,没有false才会返回true
-
对于非布尔值进行与运算,它会转换为布尔值然后运算,但是最终会返回原值
- 如果第一个值为false,则直接返回第一个值
- 如果第一个值为true,则返回第二个值
-
-
|| 逻辑或
-
或运算也是短路的或,如果第一个值为true,则不看第二个值
-
或运算是找true,如果找到true则直接返回,没有true才会返回false
-
对于非布尔值或运算,它会转换为布尔值然后运算,但是最终会返回原值
- 如果第一个值为true,则返回第一个
- 如果第一个值为false,则返回第二个
true && alert(123) // 第一个值为true,alert会执行 false && alert(123) // 第一个值为false,alert不会执行 // true && true -> true result = 1 && 2 // 2 // true && false -> false result = 1 && 0 // 0 // false && false -> false result = 0 && NaN // 0 false || alert(123) // 第一个值为false,alert会执行 true || alert(123) // 第一个值为true,alert不会执行 result = 1 || 2 // 1 result = "hello" || NaN // "hello" result = NaN || 1 // 1 result = NaN || null // null
-
关系运算符 (> >= < <=)
注意:
-
当对非数值进行关系运算时,它会先将前转换为数值然后再比较
-
当关系运算符的两端是两个字符串,它不会将字符串转换为数值,而是逐位的比较字符的Unicode编码,利用这个特点可以对字符串按照字母排序
-
注意比较两个字符串格式的数字时一定要进行类型转换
result = "12" < "2" // true result = +"12" < "2" // false
对象
对象
-
对象是JS中的一种复合数据类型,它相当于一个容器,在对象中可以存储各种不同类型数据
-
对象中可以存储多个各种类型的数据
- 属性:对象中存储的数据
- 属性名
- 通常是一个字符串,所以属性名可以是任何值,没有什么特殊要求
- 也可以使用符号(symbol)作为属性名,来添加属性。使用symbol添加的属性,通常是那些不希望被外界访问的属性
- 使用[]去操作属性时,可以使用变量
- 属性值:可以是任意的数据类型,也可以是一个对象
- 属性名
- 方法:当一个对象的属性指向一个函数,那么我们就称这个函数是该对象的方法,调用函数就称为调用对象的方法(==对象中的函数:方法==)
- 属性:对象中存储的数据
let obj = Object()
obj.name = "小蓝"
obj.age = 18
obj["gender"] = "男"
// 修改属性
obj.name = "James"
// 删除属性
delete obj.name
console.log(obj) //{age: 18, gender: '男'}
let mySymbol = Symbol()
// 使用symbol作为属性名
obj[mySymbol] = "通过symbol添加的属性"
let str = "address"
obj[str] = "china" //obj["address"] = "china" obj.address = "china"
// 使用.的形式添加属性时,不能使用变量
// 函数也可以成为一个对象的属性
obj.sayHello = function(){
alert("hello")
}
obj.sayHello()
obj.f = Object()
obj.f.name = "小芳"
obj.f.age = 28
-
使用typeof检查一个对象时,会返回object
-
in运算符
- 用来检查对象中是否含有某个属性
- 语法 属性名 in obj
- 如果有返回true,没有返回false
对象字面量
创建对象的另一种方式
- 在使用变量存储对象时,很容易因为改变变量指向的对象,提高代码的复杂度。所以通常情况下,声明存储对象的变量时会使用const
let mySymbol = Symbol()
let obj2 = {
name:"孙悟空",
age:18,
["gender"]:"男",
[mySymbol]:"特殊的属性",
hello:{
a:1,
b:true
}
}
const obj = {
name: "孙悟空",
} //const表示变量obj对应的地址不能改变,但是地址指向的对象可以改变
const obj2 = obj
obj2 = {} //报错 不能修改
枚举属性
将对象中的所有的属性全部获取(某些属性如Symbol就不可获取)
let obj = {
name:'孙悟空',
age:18,
gender:"男",
address:"花果山",
[Symbol()]:"测试的属性" // 符号添加的属性是不能枚举
}
for(let propName in obj){
console.log(propName, obj[propName])
}
函数
函数的定义方式
//函数声明
function 函数名([参数]){
语句
}
//函数表达式
const 变量 = function([参数]){
语句
}
//箭头函数
([参数]) => {
语句
}
函数的参数
参数
- 定义形参(局部变量)相当于在函数中声明了对应的变量,但是没有赋值
对象作为参数
-
对象可以作为参数传递,传递实参时,传递并不是变量本身,而是变量中存储的地址
-
函数每次调用,都会重新创建默认值
//修改a对象但不改变obj对象的原因:fn(obj)执行时,把obj的地址传递给函数fn(),形参得到的是对象obj的地址,在执行a={}时,重新给a了一个对象{}的新地址,所以不会改变对象obj的内容
function fn(a){
a = {}
a.name = "猪八戒"
console.log(a) //猪八戒
}
let obj2 = {name:"沙和尚"}
fn(obj)
console.log(obj) //沙和尚
函数的返回值
一般函数的返回值
-
return:返回值就是函数的执行结果,函数调用完毕返回值便会作为结果返回
-
任何值都可以作为返回值使用(包括对象和函数之类)
-
如果return后不跟任何值,则相当于返回undefined
如果不写return,那么函数的返回值依然是undefined
-
箭头函数的返回值
-
箭头函数的返回值可以直接写在箭头后
-
如果直接在箭头后设置对象字面量为返回值时,对象字面量必须使用()括起来
函数的调用
-
函数( )
-
函数.call( ) :函数的实参在第一个参数后一个一个的列出来
-
函数.apply( ):函数的实参需要通过一个数组传递
- call 和 apply可以用来指定函数中的this
- call和apply的第一个参数,将会成为函数的this
function fn(a, b) { console.log("a =", a, "b =", b, this) } fn.call(obj, "hello", true) // this指向obj fn.apply(null, ["hello", true]) // this指向windows
作用域链
局部作用域的变量:外部不能调用内部
但内部要使用某个变量可以去外部查找
Window对象
-
window对象代表的是浏览器窗口,负责存储JS中的内置对象和浏览器的宿主对象
-
使用function声明的函数,都会作为window的方法保存
函数就可以认为是window对象的方法
alert() //等价于window.alert()
window.a = 10 // 向window对象中添加的属性会自动成为全局变量
let c = 33 //let声明的变量不会存储在window对象中,存在于无法访问的地方
console.log(window.c) //undifined
- var
- 在全局中使用var声明的变量,都会作为window对象的属性保存
- var虽然没有块作用域,但有函数作用域
提升
- 使用函数声明创建的函数、使用var定义的变量,会在代码执行前被声明,所以可以在声明前就访问,但是值会显示undefined
- var提升后先被执行声明,后执行的赋值
- let也会提升,只是let定义的变量在定义前不能被访问
立即执行函数IIFE
-
匿名函数,只会调用一次
- 可以在函数外部加括号变为立即执行函数,或者出现在赋值后的位置
-
可以利用IIFE来创建一个一次性的函数作用域,避免变量冲突的问题
(function(){
语句
}()); //注意加分号
(function(){
语句
})(); //括号放在前后都可以
this
普通函数的this
-
this会指向一个对象
-
以函数形式调用时,this指向的是window
-
以方法的形式调用时,this指向的是调用方法的对象
-
通过this可以在方法中引用调用方法的对象
const obj3 = { name: "沙和尚", sayHello: function () { console.log(this.name) }, } obj3.sayHello()
-
箭头函数的this
- 箭头函数的this和它的调用方式无关,由它外层的作用域决定(防止指向来回变)
this总结
- 以函数形式调用,this是window
- 以方法形式调用,this是调用方法的对象
- 构造函数中,this是新建的对象
- 箭头函数没有自己的this,由外层作用域决定(无法通过call apply 和 bind修改)
- 通过call和apply调用的函数,它们的第一个参数就是函数的this
- 通过bind返回的函数,this由bind第一个参数决定(无法修改)
严格模式 use strict
"use strict" // 全局的严格模式
function fn(){
"use strict" // 函数的严格的模式
}
封装函数
// 数组排序函数
const arr = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4]
let result
function sort(array) {
const arr = [...array]
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
// 交换两个元素的位置
let temp = arr[i]
arr[i] = arr[j]
arr[j] = temp
}
}
}
return arr
}
result = sort(arr)
回调函数
回调函数(callback)是一个函数(通常为匿名函数),可以被作为参数传递给另一个函数。
高阶函数
一个函数的参数或返回值是函数。
function filter(arr, cb) {
const newArr = []
for (let i = 0; i < arr.length; i++)
{
if (cb(arr[i])) {
newArr.push(arr[i])
}
}
return newArr
}
// 回调函数一般为匿名函数,可以对另一个函数动态的传递代码
result = filter(personArr, a => a.age >= 18)
result = filter(arr, a => a % 2 === 0)
闭包
- 什么是闭包?可以在一个内层函数中访问到其外层函数的作用域
- 什么时候使用?当需要隐藏一些不希望被别人访问的内容时就可以使用闭包
- 构成闭包的条件:
- 函数的嵌套
- 内部函数要引用外部函数中的变量
- 内部函数要作为返回值返回
- 词法作用域(闭包原理):函数的作用域在创建时就确定,和调用的位置无关
- 闭包的生命周期:
- 闭包在外部函数调用时产生,外部函数每调用一次产生一个全新的闭包(互相独立)
- 在内部函数丢失时销毁(内部函数被垃圾回收了,闭包才会消失)
- 注意:
- 闭包主要用来隐藏一些不希望被外部访问的内容(闭包需要占用一定的内存空间)
- 相较于类来说,闭包比较浪费内存空间(类可以使用原型而闭包不能)
- 需要执行次数较少时,使用闭包
- 需要大量创建实例时,使用类
function outer()
{
let num = 0 // 位于函数作用域中
return () => {
num++
console.log(num)
}
}
//outer函数只调用了一次 newFn直接是outer函数返回的函数
const newFn = outer()
newFn() //调用即是调用outer返回的函数 第一次调用没有找到num的值 沿着作用域向外找 找到num=0 num++执行完后 num返回1
newFn() //再次调用 此时num的值已经为1 不需要向外找
newFn()
递归函数
- 核心思想:将一个大问题拆分成多个小问题,小的问题解决了,大的问题也就解决了
- 构成递归函数的条件:
- 基线条件 —— 递归的终止条件
- 递归条件 —— 如何对问题进行拆分
- 递归的作用和循环是一致的。但递归思路的比较清晰简洁,循环的执行性能比较好
- 开发中,一般的问题都可以通过循环解决,也是尽量去使用循环,少用递归
- 只在一些使用循环解决比较麻烦的场景下,才使用递归
可变参数
-
arguments
-
函数隐藏参数:this、arguments
-
arguments用来存储函数的实参。
- 无论用户是否定义形参,实参都会存储到arguments对象中
- 可以通过该对象直接访问实参
-
arguments是一个伪数组对象:可以通过索引来读取元素,也可以通过for循环遍历变量,但是它不是一个数组对象,不能调用数组的方法
-
箭头函数中arguments失效
function fn(){ console.log(arguments[1]) //传递的是实参 20 } fn(10, 20, 30)
-
-
可变参数(...args 展开运算符)
- 传递的数量、类型不受限制。可以接收任意数量实参,并将他们统一存储到一个数组中返回
- 可变参数的作用和arguments基本是一致,但是也具有一些不同点
- 可变参数的名字可以自己指定
- 可变参数就是一个数组,可以直接使用数组的方法
- 可变参数可以配合其他参数一起使用(写到最后)
bind
函数的方法,可以用来创建一个新的函数
-
bind可以为新函数绑定this
-
bind可以为新函数绑定参数
function fn(a, b, c) {
console.log("fn", this)
console.log(a, b, c)
}
const obj = {}
const newFn = fn.bind(obj,10) // 新函数this指向的对象和第一个参数都被绑定无法更改
newFn()
类
面向对象
对象的分类:
- 内建对象
- 由ES标准所定义的对象(比如 Object Function String Number ....)
- 宿主对象
- 由浏览器提供的对象(BOM、DOM)
- 自定义对象
- 由开发人员自己创建的对象
基本概念
-
==类是对象的模板==,可以将对象中的属性和方法直接定义在类中
-
通过同一个类创建的对象,我们称为同类对象,可以使用instanceof来检查一个对象是否是由某个类创建
-
如果某个对象是由某个类所创建,则我们称该对象是这个类的实例
-
语法:(类名要使用大驼峰命名)
-
class 类名 {}
-
const 类名 = class {}
-
-
通过类创建对象:const 对象名 = new 类名( )
-
类的代码块默认是严格模式
属性
类的实例的属性 && 类的属性
class Person{
name = "孙悟空" // Person的实例属性
age = 18 // 实例属性只能通过实例访问 p1.age
static test = "test静态属性" //静态属性(类属性):static声明的属性
static hh = "静态属性" // 静态属性只能通过类去访问 Person.hh
}
方法
class Person
{
name = "小麦"
sayHello = function(){
console.log("hello,I am" + this.name)
} // 添加方法(实例方法) 实例方法中this就是当前实例
sayGoodbye(){
语句
}
static test(){
console.log("我是静态方法", this)
} // 静态方法(类方法) 通过类来调用 静态方法中this指向的是当前类
}
const p1 = new Person()
p1.sayHello()
Person.test()
构造函数
-
constructor是一个特殊的方法,它会在关键字
new后自动被调用class Person{ // 在类中可以添加一个特殊的方法constructor // 构造函数会在我们调用类创建对象时执行 constructor(name, age, gender){ // 可以在构造函数中,为实例属性进行赋值 // 在构造函数中,this表示当前所创建的对象 this.name = name this.age = age this.gender = gender } } const p1 = new Person("孙悟空", 18, "男")
封装
-
封装主要用来保证数据的安全
-
如何确保数据的安全:
-
私有化数据
- 将需要保护的数据设置为私有,只能在类内部使用
-
提供setter和getter方法来开放对数据的操作
- 属性设置私有,通过getter setter方法操作属性带来的好处
- 可以控制属性的读写权限
- 可以在方法中对属性的值进行验证(方法中使用条件语句进行判断)
- 属性设置私有,通过getter setter方法操作属性带来的好处
-
-
实现封装的方式:
-
属性私有化 加#
-
通过getter和setter方法来操作属性
-
get 属性名( ){ return this.#属性 }
-
set 属性名(参数){ this.#属性 = 参数 }
-
-
class Person{
#name
#age
constructor(name,age){
this.#name = name
this.#age = age
}
sayHello() {
console.log(this.#name)
}
get name(){
return this.#name
}
set name(name){
this.#name= name
}
}
const p1 = new Person("小小",18)
p1.name = "X"
多态
-
只使用一个函数/方法对不同类具中的同一属性进行相同的操作
const dog = new Dog('旺财') const person = new Person("孙悟空") function sayHello(obj){ // if(obj instanceof Person){ console.log("Hello,"+obj.name) // } } sayHello(dog) //Hello,旺财 sayHello(person) //Hello,孙悟空
继承
- OCP 开闭原则:程序应该对修改关闭,对扩展开放
class Animal{
constructor(name){
this.name = name
}
sayHello(){
console.log("动物在叫~")
}
}
class Dog extends Animal{
sayHello(){
console.log("汪汪汪")
}
}
const dog = new Dog("旺财")
console.dir(dog) // 通过console.dir可以看到类中的函数
-
在子类中,可以通过创建同名方法来重写父类的方法
class Cat extends Animal { // 重写构造函数 constructor(name, age){ // 重写构造函数时,构造函数的第一行代码必须为super() super(name) // 调用父类的构造函数 this.age = age } sayHello(){ // 调用一下父类的sayHello super.sayHello() // 在方法中可以使用super来引用父类的方法 console.log("喵喵喵") } }
对象的结构
-
对象自身
- 直接通过对象所添加的属性,位于对象自身中
- 在类中通过 x = y 的形式添加的属性,位于对象自身中
-
原型对象(prototype)
- 在对象中会有一个属性用来存储原型对象,这个属性叫做__proto__
- 原型对象也负责为对象存储属性
- 当我们访问对象中的属性时,会优先访问对象自身的属性
- 对象自身不包含该属性时,才会去原型对象中寻找
- 会添加到原型对象中的情况:
- 在类中通过xxx(){}方式添加的方法,位于原型中
- 主动向原型中添加的属性或方法
sayHello() {
语句
} // 存储在原型对象中
// 存储在自身对象中
sayHello = function(){
语句
}
sayHello = () => {
语句
}
原型对象
-
访问一个原型对象
-
对象. _proto_ (此种方式可对其进行修改,不安全)
-
Object.getPrototypeOf(对象) (只对其进行访问)
-
-
原型对象中的数据
- 对象中的数据(属性、方法等)
- constructor (对象的构造函数)
-
原型链:根据对象的复杂程度不同,原型链的长度也不同(obj.__proto__是原型的源头)
- p对象的原型链:p对象 --> object --> Object原型 --> null
- obj对象的原型链:obj对象 --> 原型 --> null
-
作用域链:用来找变量,找不到报错
原型链:用来找属性,找不到返回undefined
-
所有的同类型对象它们的原型对象都是同一个,同类型对象的原型链是一样的
// 每new一个新的实例,则分配一个新的地址,但是该地址中存放原型的地址是相同的 const p = new Person() const p2 = new Person()
修改原型
/* 原则:
1. 原型尽量不要手动改
2. 要改也不要通过实例对象去改
3. 通过 类.prototype 属性去修改
4. 最好不要直接给prototype去赋值
*/
class Person {
name:"好"
}
// 通过类的prototype属性,来访问、修改实例的原型
Person.prototype.fly = () => {
console.log("我在飞!")
}
// 这样操作会把原来原型中的内容全部覆盖
Person.prototype = {}
instanceof和hasOwn
-
instansof:用来检查一个对象是否是一个类的实例(包括原型链)
- Object是所有对象的原型,所以任何对象和Object进行instanceof运算都会返回true
-
in:用来检查一个属性是否在一个对象自身和原型对象中
-
hasOwn:用来检查一个属性是否在一个对象自身中
Object.hasOwn(对象, 属性名)
旧类
直接通过函数来定义类
new运算符
创建对象时要使用的运算符
- 当使用new去调用一个函数时,这个函数将会作为构造函数调用
- 创建一个普通的JS对象(Object对象 {}), 为了方便,称其为新对象
- 将构造函数的prototype属性设置为新对象的原型
- 使用实参来执行构造函数,并且将新对象设置为函数中的this
如果构造函数返回的是一个非原始值,则该值会作为new运算的返回值返回如果构造函数的返回值是一个原始值或者没有指定返回值,则新的对象将会作为返回值返回(通常不会为构造函数指定返回值)
数组
简介
-
数组也是一种复合数据类型,任何类型的值都可以成为数组中的元素
- 如果一个数组中的元素还是数组,则这个数组我们就称为是二维数组
-
数组中存储的有序的数据叫元素,数组中的每个数据都有一个唯一的索引(不能为负)
-
length:获取数组的长度(数组的最大索引 + 1)
//创建数组
const arr = new Array()
const arr1 = [1, 2, 3, 4, 5] // 数组字面量
//添加元素
数组[索引] = 元素 // 如果读取了一个不存在的元素,不好报错而是返回undefined
arr1[100] = 199 // 使用数组时,应该避免非连续数组,因为它性能不好
// 向数组最后添加元素
数组[数组.length] = 元素
// 修改length
arr.length = 5
遍历数组
-
用普通for循环
-
-
for-of:用于遍历可迭代对象(iterable objects)
- 可迭代对象:好比一个有序的容器,可以逐个访问容器中的元素(数组、字符串、Map、Set等)
- 而普通对象的属性结构是以键值对存在的,且对象本身没有实现迭代器接口,因此不能使用for-of遍历
-
for-in:用于遍历普通对象
-
// for循环正序、倒序遍历
for(let i=0; i<arr.length; i++){
语句
}
for (let i = arr.length - 1; i >= 0; i--) {
语句
}
// for-of(用来遍历可迭代对象)
for(let value of arr){
语句
}
for(let value of "hello"){
console.log(value)
} // h e l l o
let obj = {
name: "guess",
age: 3.5
}
for(let value of obj){
console.log(value);
} // 报错
数组的方法(非破坏性)
-
Array.isArray():用来检查一个对象是否是数组
-
· arr.at():可以根据索引(可取负值代表倒数第几个)获取数组中的指定元素
-
arr.concat(arr1,[a,b]):不会影响原数组,返回一个新的数组
-
indexOf(a, b):获取元素a在从索引b之后第一次出现的索引(包含b索引)
-
lastIndexOf(a, b):和indexof()相同,不过倒序查找,没找到则返回-1
-
join(a):将一个数组中的元素连接为一个字符串。a为指定连接符,默认为逗号
-
slice(a, b):用来截取数组
-
a:截取的起始位置(包括该位置)
-
b:截取的结束位置(不包括该位置)(省略不写则会一直截取到最后)
-
索引可以为负值,代表倒数第几个
-
如果将两个参数全都省略,则可以对数组进行浅拷贝
-
let arr = ["哈", "嘿", "哦", "哼", "嘿"]
let result = arr.indexOf("嘿", 1) // 返回1
let result = arr.indexOf("嘿", 2) // 返回4
let result = arr.lastIndexOf("嘿", 3) // 1 从哼开始到这找,找到第一个嘿
复制(浅拷贝、深拷贝)
-
复制:创建一个数据的副本,使其具有独立的内存空间。二者互不影响
const arr2 = arr // 不是复制 arr2和arr指向同一地址 -
深拷贝:
初原始值不复制,其他全部都复制。-
structuredClone( )
-
使用JSON进行深复制
-
-
浅拷贝:只会复制对象的第一层属性。复制第一层属性也即新对象创建新的第一层(新位置新地址),第一层中的属性还是指向原始地址。(两个洞的盒子,洞口不一样,但内容是一样的)
let originalObj = { a: 1, b: { c: 2 } }; let shallowCopyObj = Object.assign({}, originalObj); // 修改浅拷贝后的对象的嵌套属性 shallowCopyObj.b.c = 3; // 修改浅拷贝后的对象的第一层属性 shallowCopyObj.a = 4; // 输出原始对象 console.log(originalObj); // { a: 1, b: { c: 3 } } // 输出浅拷贝后的对象 console.log(shallowCopyObj); // { a: 4, b: { c: 3 } }
对象的浅复制
- 展开运算符
...- 可以将一个对象的属性展开到另一个对象中或者作为函数的参数传递
- 可以对对象进行浅复制
Object.assign(目标对象, 被复制的对象)
数组的方法(破坏性)
-
push():向数组的末尾添加一个或多个元素,并返回新的长度
-
pop():删除并返回删除的元素
-
unshift():向数组的开头添加一个或多个元素,并返回新的长度
-
shift():删除并返回数组的第一个元素
-
splice(a, b, c):可以删除、插入、替换(替换是在起始值之前插入)返回被删除的元素
a:删除的起始位置
b:删除的数量
c:要插入的元素
-
reverse():反转数组
数组的方法(高阶函数)
-
sort( )- sort用来对数组进行排序,破坏性方法
- sort默认会将数组升序排列(sort默认会按照Unicode编码进行排序,所以如果直接通过sort对数字进行排序,可能会得到一个不正确的结果)
- 参数:
- 可以传递一个回调函数作为参数,通过回调函数来指定排序规则 (a, b) => a - b 升序排列 (a, b) => b - a 降序排列
-
forEach( )- 用来遍历数组
- 它需要一个回调函数作为参数,这个回调函数会被调用多次,数组中有几个元素,回调函数就会调用几次,每次调用,都会将数组中的数据作为参数传递
- 回调函数中有三个参数:
- element 当前的元素
- index 当前元素的索引
- array 被遍历的数组
-
filter()- 将数组中符合条件的元素保存到一个新数组中返回,非破坏性方法
- 需要一个回调函数作为参数,会为每一个元素去调用回调函数,并根据返回值来决定是否将元素添加到新数组中(参数ele,index,arr)
-
map()- 根据当前数组生成一个新数组,非破坏性方法
- 需要一个回调函数作为参数,回调函数的返回值会成为新数组中的元素(参数ele,index,arr)
const map1 = array1.map((x) => x * 2); -
reduce()- 可以用来将一个数组中的所有元素整合为一个值
- 参数:
- 回调函数,通过回调函数来指定合并的规则
- 可选参数,初始值
解构
数组的解构
// 数组的解构赋值
const array = [1, 2, 3]
let a, b, c
;[a, b, c] = array //加分号防止出错
let [d, e, f, g] = ["hello", 1, 45, 18, "world"] //声明同时解构
[d, e = 10, f = 7, g = g ] = [ 1, 2 ] //[1, 2, 7, "world"]
let [n1, n2, ...n3] = [4, 5, 6, 7] // n3 = [6, 7] 解构数组时,可以使用...来设置获取多余的元素
let a1 = 10
let a2 = 20
;[a1, a2] = [a2, a1] // 可以通过解构赋值来快速交换两个变量的值
const arr = [["小蓝", 18, "男"], ["小芳" ,25, "女"]]
let [[name, age, gender], obj] = arr3 //多维数组解构
对象的解构
const obj = { name: "小蓝", age: 18, gender: "男" }
let { name, age, gender } = obj //声明变量同时解构对象
let name, age, gender
;({ name, age, gender } = obj) //先声明后解构 需加括号
let { address } = obj // 没有的属性返回undefined
let {name:a, age:b, gender:c, address:d="china"} = obj //重新命名
// address:d="china" 如果找到address则赋值给d。如果没有,则将china赋值给d
内建对象
内建对象(Built-in Objects)是指在 JavaScript 中预先定义好的对象,无需额外的导入或声明即可直接使用。这些内建对象包括全局对象(如Math、JSON)、原生对象(如Array、String、Date等)、函数对象(如Function)、错误对象(如Error、SyntaxError等)等。
对象的序列化
序列化
- 序列化指将对象转换为一个可以存储的格式。在JS中对象的序列化通常是将一个对象转换为字符串(JSON字符串)
- 序列化的用途:
- 作为数据交换的格式(对象转换为字符串后可以在不同的语言之间进行传递)
- 用来编写配置文字
JSON
- JSON(JavaScript Object Notation) JS对象表示法
- JS对象序列化后会转换为一个字符串,这个字符串我们称其为JSON字符串
- 手动编写JSON字符串:(很多程序的配置文件使用JSON编写)
- JSON字符串有两种类型:
- JSON对象{ }
- JSON数组 [ ]
- JSON字符串的属性名必须使用双引号引起来
- JSON中可以使用的属性值(元素)
- 数字(Number)
- 字符串(String) 必须使用双引号
- 布尔值(Boolean)
- 空值(Null)
- 对象(Object {})
- 数组(Array [])
- JSON字符串如果属性是最后一个,则不要再加逗号
- JSON字符串有两种类型:
- 可以用JSON进行深拷贝
// JSON.stringify() 可以将一个对象转换为JSON字符串
const str = JSON.stringify(obj)
// JSON.parse() 可以将一个JSON格式的字符串转换为JS对象
const obj2 = JSON.parse(str)
Map
-
Map用来存储键值对结构的数据(key-value)
-
Map和Object的主要区别:
- Object中的属性名只能是字符串或符号,如果传递了一个其他类型的属性名, JS解释器会自动将其转换为字符串
- Map中任何类型的值都可以称为数据的key
-
创建:
new Map() -
属性和方法:
- map.size 获取map中键值对的数量
- map.set(key, value) 向map中添加键值对
- map.get(key) 根据key获取值
- map.delete(key) 删除指定数据
- map.has(key) 检查map中是否包含指定键
- map.clear() 删除全部的键值对
// 将map转换为数组 const arr = Array.from(map) const arr = [...map] // 遍历map for (const [key, value] of map) { console.log(key, value) } for (const entry of map) { console.log(entry) } map.forEach((value, key)=>{ console.log(value, key) })
Set
- Set用来创建一个集合(和数组类似,不能存储重复的数据)
- 创建:
new Set()、new Set([...]) - 方法:(和Map相似)
- size 获取数量
- add() 添加元素
- has() 检查元素
- delete() 删除元素
Math
-
一个工具类,不是类,不能
new -
方法:
- Math.floor() 向下取整
- Math.ceil() 向上取整
- Math.round() 四舍五入取整
- Math.trunc() 直接去除小数位
Date
-
在JS中所有的和时间相关的数据都由Date对象来表示
-
创建:
new Date() -
getTime(): 返回当前日期对象的时间戳- 时间戳:自1970年1月1日0时0分0秒到当前时间所经历的毫秒数
-
Date.now():获取当前的时间戳(不需要创建实例)
日期格式化
toLocaleDateString():将日期转换为本地的字符串
toLocaleTimeString():将时间转换为本地的字符串
let time = d.toLocaleString("zh-CN", {
year: "numeric",
month: "long",
day: "2-digit",
weekday: "short",
})
包装类
- 通过包装类可以将一个原始值包装为一个对象
- JS中一共有5个包装类(String、Number、Boolean、BigInt、Symbol)
- 由于原始值会被临时转换为对应的对象,这就意味着对象中的方法都可以直接通过原始值来调用
字符串的方法
- 字符串的本质就是一个字符数组,很多方法与数组类似
/*
length 获取字符串的长度
字符串[索引] 获取指定位置的字符
str.at() (实验方法)
- 根据索引获取字符,可以接受负索引
str.charAt()
- 根据索引获取字符
str.concat()
- 用来连接两个或多个字符串
str.includes()
- 用来检查字符串中是否包含某个内容
有返回true
没有返回false
str.indexOf()
str.lastIndexOf()
- 查询字符串中是否包含某个内容
str.startsWith()
- 检查一个字符串是否以指定内容开头
str.endsWith()
- 检查一个字符串是否以指定内容结尾
str.padStart()
str.padEnd()
- 通过添加指定的内容,使字符串保持某个长度
str.replace()
- 使用一个新字符串替换一个指定内容
str.replaceAll()
- 使用一个新字符串替换所有指定内容
str.slice()
- 对字符串进行切片
str.substring()
- 截取字符串
str.split()
- 用来将一个字符串拆分为一个数组
str.toLowerCase()
- 将字符串转换为小写
str.toUpperCase()
- 将字符串转换为大写
str.trim()
- 去除前后空格
str.trimStart()
- 去除开始空格
str.trimEnd()
- 去除结束空格
*/
- 配合正则表达式使用
/*
split()
- 可以根据正则表达式来对一个字符串进行拆分
search()
- 可以去搜索符合正则表达式的内容第一次在字符串中出现的位置,没有返回-1
replace()
- 根据正则表达式替换字符串中的指定内容
match()
- 根据正则表达式去匹配字符串中符合要求的内容
matchAll()
- 根据正则表达式去匹配字符串中符合要求的内容(必须设置g 全局匹配)
- 它返回的是一个迭代器
*/
let str = "a@b@c@d"
let result = str.split("@") // [a, b, c]
str = "孙悟空abc猪八戒adc沙和尚"
result = str.split(/a[bd]c/) //[孙悟空, 猪八戒, 沙和尚]
str = "dajsdh13715678903jasdlakdkjg13457890657djashdjka13811678908sdadadasd"
result = str.search("abc") // -1
result = str.search(/1[3-9]\d{9}/) // 6
result = str.replace(/1[3-9]\d{9}/g, "哈哈哈")
result = str.match(/1[3-9]\d{9}/g) // 可以直接获得数组,但是无法单独获取其中的片段
result = str.matchAll(/1[3-9](\d{9})/g)
for(let item of result){
console.log(item)
}
正则表达式
-
正则表达式用来定义一个规则,通过这个规则计算机可以检查一个字符串是否符合规则,或者将字符串中符合规则的内容提取出来
-
正则表达式也是JS中的一个对象,使用正则表达式,需要先创建正则表达式的对象
-
new RegExp("a","b"):两个参数a和b,a正则表达式,b匹配模式 -
reg.test():用来检查一个字符串是否符合规则 -
语法:
-
|: 或 -
[]表示或(字符集)[a-z] 任意的小写字母、[A-Z] 任意的大写字母、[a-zA-Z] 任意的字母、[0-9]任意数字
-
[^]: 除了[^x] 除了x
-
.: 除了换行外(/n /r)的任意字符 -
其他字符集:
- \w 任意的单词字符 [A-Za-z0-9_]
- \W 除了单词字符 [^A-Za-z0-9_]
- \d 任意数字 [0-9]
- \D 除了数字 [^0-9]
- \s 空格
- \S 除了空格
- \b 单词边界
- \B 除了单词边界
-
开头和结尾
-
^: 字符串的开头 -
$: 字符串的结尾
-
-
量词:
-
{m} 正好m个
-
{m,} 至少m个
-
{m,n} m-n个
-
一个以上,相当于{1,}
-
任意数量的a
-
? 0-1次 {0,1}
-
-
-
re.exec(): 获取字符串中符合正则表达式的内容
let reg = new RegExp("a", "i") // 构造函数
reg = /a/i // 字面量 检查一个字符串里是否有a
re = /^a$/ // 只匹配字母a,完全匹配,要求字符串必须和正则完全一致
let str = "abcaecafcacc"
let re = /a(([a-z])c)/ig // g代表全局匹配 规则:axc
let result = re.exec(str) // 返回数组,可以将整体以及加括号的全部提取出来
while(result){ // 遍历取出
console.log(result[0], result[1], result[2])
result = re.exec(str)
}
垃圾回收
- 垃圾回收(Garbage collection)
- 如果一个对象没有任何的变量对其进行引用,那么这个对象就是一个垃圾
- 在JS中有自动的垃圾回收机制,这些垃圾对象会被解释器自动回收,我们无需手动处理。唯一能做的事情就是将不再使用的变量设置为null
Web API
Web API(Application Programming Interface)是指由浏览器提供的一组功能和方法,允许开发者与浏览器进行交互并控制网页的行为。通过调用这些 API,开发者可以访问浏览器功能、设备硬件或第三方服务,从而为网页添加丰富的交互体验和功能。 Web API 包括很多不同的类型,其中一些常见的包括:
- DOM API:用于操作文档对象模型(DOM),可以动态地更新网页的内容、结构和样式。
- AJAX API:用于进行异步的HTTP请求,从服务器获取数据而无需刷新整个页面。
- Fetch API:提供了一种现代的方式来发起网络请求,取代了传统的 XMLHttpRequest 对象。
- Canvas API:允许通过 JavaScript 在网页上绘制图形,创建动画等。
- Web Storage API:包括 localStorage 和 sessionStorage,允许在浏览器中存储数据以及保留数据直到用户手动清除或离开网站。
- Geolocation API:用于获取用户设备的地理位置信息。
- Web Audio API:用于在网页中处理和操控音频。 通过使用这些 Web API,开发者可以实现丰富的网页功能,包括响应用户的操作、访问外部数据源、实现动画效果等。这些 API 构成了 Web 开发中重要的一部分,帮助开发者创建更加交互性和吸引人的网页应用。
DOM
DOM的节点
- Document指的是整个网页,Object把页面的所有每部分内容都转为对象,Model用来描述各个对象之间的关系。
- 浏览器提供了document对象,它是一个全局变量,代表整个网页
- 类型节点:
- 文档节点(Document Node):文档节点是DOM树的根节点,用来表示整个文档的入口。在DOM中,文档节点由
Document对象表示,表示整个HTML文档的结构和内容。 - 元素节点(Element Node):元素节点用来表示HTML文档中的各种HTML元素,如
<div>、<p>、<span>等。元素节点是文档节点的子节点,用来构建文档的结构。 - 文本节点(Text Node):文本节点用来表示文档中的文本内容。文本节点可以作为元素节点的子节点,用来展示文本内容。例如,
<p>元素内部的文字就是文本节点。 - 属性节点(Attribute Node):属性节点用来表示HTML元素的属性。在DOM中,并没有单独的属性节点类型,属性通常作为元素节点的一部分存在,用来提供额外的信息或配置。
- 文档节点(Document Node):文档节点是DOM树的根节点,用来表示整个文档的入口。在DOM中,文档节点由
文档节点
document对象
Document是一个接口,定义了与文档对象模型(DOM)相关的属性和方法,用于操作HTML文档的结构。document对象可以看作是Document接口的一个实例,用来代表当前HTML文档的DOM结构。
-
document对象的原型链:
HTMLDocument -> Document -> Node -> EventTarget -> Object.prototype -> null
- 凡是在原型链上存在的对象的属性和方法都可以通过Document去调用
元素节点
Element对象
-
div元素的原型链
HTMLDivElement -> HTMLElement -> Element -> Node -> ...
-
对Element进行操作
-
通过document对element进行操作
- 获取已有的元素节点: document.getElementById() - 根据id获取一个元素节点对象 document.getElementsByClassName() - 根据元素的class属性值获取一组元素节点对象 - 返回的是一个类数组对象 - 实时更新 document.getElementsByTagName() - 根据标签名获取一组元素节点对象 - 实时更新 - document.getElementsByTagName("*") 获取页面中所有的元素 document.getElementsByName() - 根据name属性获取一组元素节点对象 - 实时更新 - 主要用于表单项 document.querySelectorAll()(用类选择器时也会返回一个类数组) - 根据选择器去页面中查询元素 - 会返回一个类数组(不会实时更新) document.querySelector() - 根据选择器去页面中查询第一个符合条件的元素 - 创建一个元素节点 document.createElement() - 根据标签名创建一个元素节点对象 -
通过其他元素获取已有的Element对象
/* element.getElementsByTagName() 获取当前元素下某标签名的元素 element.getElementsByClassName 获取当前元素下某类名的元素 element.childNodes 获取当前元素的子节点(会包含空白的子节点) element.children 获取当前元素的子元素 element.firstChild 获取当前元素的第一个子节点 element.firstElementChild 获取当前元素的第一个子元素 element.lastElementChild 获取当前元素的最后一个子元素 element.nextElementSibling 获取当前元素的下一个兄弟元素 element.previousElementSibling 获取当前元素的前一个兄弟元素 element.parentNode 获取当前元素的父节点 element.tagName 获取当前元素的标签名 */
-
文本节点
/*
修改文本的三个属性
element.textContent 获取或修改元素中的文本内容
- 获取的是标签中的内容,不会考虑css样式(标签中的文字是什么就输出什么)
element.innerText 获取或修改元素中的文本内容
- innerText获取内容时,会考虑css样式(页面中展示什么就输出什么)
- 通过innerText去读取CSS样式,会触发网页的重排(计算CSS样式)
- 当字符串中有标签时,会自动对标签进行转义
element.innerHTML 获取或修改元素中的html代码
- 可以直接向元素中添加html代码
- innerHTML插入内容时,有被xss注入的风险
*/
const box1 = document.getElementById("box1")
box1.innerHTML = "<script src='https://sss/sss.js'><\/script>" //有风险
属性节点
/*
如何操作属性节点
- 方式一:
读取:元素.属性名(注意:class属性需要使用className来读取)
修改:元素.属性名 = 属性值
- 方式二:
读取:元素.getAttribute(属性名)
修改:元素.setAttribute(属性名, 属性值)
删除:元素.removeAttribute(属性名)
*/
// 可以通过属性名查找
const input = document.querySelector("[name=username]")
input.disabled = 0 // 0会被认为是false传入,即disabled属性不开启
input.setAttribute("disabled", true) //无论第二个参数传什么都会转成字符串
DOM的修改
- 添加:
appendChild(): 用于给一个节点添加子节点insertAdjacentElement(): 可以向元素的任意位置添加元素- 两个参数:1.要添加的位置 2.要添加的元素
- beforeend 标签的最后 afterbegin 标签的开始
- beforebegin 在元素的前边插入元素(兄弟元素) afterend 在元素的后边插入元素(兄弟元素)
insertAdjacentHTML: 直接添加html代码,参数和insertAdjacentElement一样
- 替换
replaceWith() - 删除
remove():要删除的对象.remove( ) - 复制
cloneNode(true):添加true之后可以将当前节点和子节点一起复制
事件
- 事件(event)就是用户和页面之间发生的交互行为
- 可以通过为事件绑定响应函数(回调函数),来完成和用户之间的交互
- 在事件的响应函数中,响应函数绑定给谁,this就是谁(箭头函数除外)
- 绑定响应函数的方式:
- 可以直接在元素的属性中设置
- 可以通过为元素的指定属性设置回调函数的形式来绑定事件(一个事件只能绑定一个响应函数)
- 可以通过元素addEventListener()方法来绑定事件
<!-- 方式一 -->
<button id="btn" onmouseenter="alert('hello')">点我一下</button>
<script>
const btn = document.getElementById("btn")
// 方式二
btn.onclick = function(){
alert("hi")
return false // 可以取消超链接的默认跳转行为(作为回调函数) 方式三不可以使用
}
// 方式三
btn.addEventListener("click", () => {
alert('hahaha')
} )
</script>
事件对象
-
事件对象是有浏览器在事件触发时所创建的对象,这个对象中封装了事件相关的各种信息,通过事件对象可以获取到事件的详细信息(比如:鼠标的坐标、键盘的按键..)
const box1 = document.getElementById("box1") box1.addEventListener("mousemove", event => { box1.textContent = event.clientX + "," + event.clientY }) -
在DOM中存在着多种不同类型的事件对象,多种事件对象有一个共同的祖先 Event
- event.target 触发事件的对象 - event.currentTarget 绑定事件的对象(同this) - event.stopPropagation() 停止事件的传导冒泡 - event.preventDefault() 取消默认行为
事件的传播机制
在DOM中,事件的传播可以分为三个阶段:
-
捕获阶段 (祖先元素--->目标元素进行事件的捕获)(默认情况下,事件不会在捕获阶段触发)
-
目标阶段 (触发事件的对象)
-
冒泡阶段 (目标元素--->祖先元素进行事件的冒泡)
事件的捕获
- 当前元素触发事件后,会先从当前元素最大的祖先元素开始向当前元素进行事件的捕获
- 如果希望在捕获阶段触发事件,可以将addEventListener的第三个参数设置为true
事件的冒泡
-
事件的向上传导:当元素上的某个事件被触发后,其祖先元素上的相同事件也会同时被触发(给一个祖先元素绑定了事件,那它的后代元素都绑定了此事件)
-
冒泡的存在大大的简化了代码的编写
-
事件的冒泡和元素的样式无关,只和dom结构相关
事件的委派
- 委派就是将本该绑定给多个元素的事件,统一绑定给document或祖先元素,这样可以降低代码复杂度方便维护(委派给document)
- 希望对当前有的某个事件和未来会出现的某个事件进行事件绑定
文档的加载
-
如果在js代码中对DOM对象进行操作,并且此时js文件在DOM对象前,则会出现无法获取DOM对象的情况
-
解决:
- 将script标签编写到body的最后
- 将代码编写到window.onload的回调函数中
- 将代码编写到document对象的DOMContentLoaded的回调函数中(执行时机更早)
- 将代码编写到外部的js文件中,然后以defer的形式进行引入(执行时机更早,早于DOMContentLoaded)
<script defer src="./script/script.js"></script> <script> window.onload = function () { const btn = document.getElementById("btn") console.log(btn) } window.addEventListener("load", function () { const btn = document.getElementById("btn") alert(btn) }) document.addEventListener("DOMContentLoaded", function () { const btn = document.getElementById("btn") alert(btn) }) </script>
CSS样式读取与修改
修改
-
通过
元素.style.样式名 = 样式值,修改的是行内样式其他无法修改(background-color --> backgroundColor) 不推荐 -
通过class修改:
元素.classList是一个对象,对象中提供了对当前元素的类的各种操作方法/* 通过class修改样式的好处: 1. 可以一次性修改多个样式 2. 对JS和CSS进行解耦 */ /* 元素.classList.add() 向元素中添加一个或多个class 元素.classList.remove() 移除元素中的一个或多个class 元素.classList.toggle() 切换元素中的class(有->没有 没有->有) 元素.classList.replace() 替换class 元素.classList.contains() 检查class */ /* .box1 { width: 200px; height: 200px; background-color: #bfa; } .box2{ background-color: yellow; width: 300px; height: 500px; border: 10px greenyellow solid; } */ box1.classList.add("box2") //加上之后样式从1的换成了2的 box1.classList.remove("box2")
读取
/*
getComputedStyle()
- 它会返回一个对象,这个对象中包含了当前元素所有的生效的样式
- 参数:
1. 要获取样式的对象
2. 要获取的伪元素
- 返回值:
返回的一个对象,对象中存储了当前元素的样式
- 注意:样式对象中返回的样式值,不一定能来拿来直接计算(有些是有单位的、有些auto)
*/
const beforeStyle = getComputedStyle(box1, "::before")
console.log(beforeStyle.color)
/*
以下只读,不能修改,并且都没单位,可以直接使用
元素.clientHeight
元素.clientWidth
- 获取元素内部的宽度和高度(包括内容区和内边距)
元素.offsetHeight
元素.offsetWidth
- 获取元素的可见框的大小(包括内容区、内边距和边框)
元素.scrollHeight
元素.scrollWidth
- 获取元素滚动区域的大小
元素.offsetParent
- 获取元素的定位父元素
- 定位父元素:离当前元素最近的开启了定位的祖先元素,
如果所有的元素都没有开启定位则返回body
元素.offsetTop
元素.offsetLeft
- 获取元素相对于其定位父元素的偏移量
可读且可修改
元素.scrollTop
元素.scrollLeft
- 获取或设置元素滚动条的偏移量
*/
BOM
- BOM对象
- Window —— 代表浏览器窗口(全局对象)
- Navigator —— 浏览器的对象(可以用来识别浏览器)
- Location —— 浏览器的地址栏信息
- History —— 浏览器的历史记录(控制浏览器前进后退)
- Screen —— 屏幕的信息
- BOM对象都是作为window对象的属性保存的,所以可以直接在JS中访问这些对象
Navigator
- 浏览器的对象(可以用来识别浏览器)
- userAgent 返回一个用来描述浏览器信息的字符串
Location
- location 表示的是浏览器地址栏的信息
- 可以直接将location的值修改为一个新的地址,这样会使得网页发生跳转
- location.assign() 跳转到一个新的地址
- location.replace() 跳转到一个新的地址(无法通过回退按钮回退)
- location.reload() 刷新页面,可以传递一个true来强制清缓存刷新
- location.href 获取当前地址 .protocol .host .hostname
History
- history.back():回退按钮
- history.forward():前进按钮
- history.go():可以向前跳转也可以向后跳转
Windows
函数的执行环境
- 函数每次执行时都会产生一个执行环境,执行环境负责存储函数执行时产生的一切数据
- 栈,是一种数据结构,特点 后进先出
- 队列,是一种数据结构,特点 先进先出
- 问题:函数的执行环境要存储到哪里呢?
-
调用栈(call stack)
- 调用栈负责存储函数的执行环境
- 当一个函数被调用时,它的执行环境会作为一个栈帧,插入到调用栈的栈底,函数执行完毕其栈帧会自动从栈中弹出
-
消息队列
- 消息队列负责存储将要执行的函数
- 当我们触发一个事件时,其响应函数并不是直接就添加到调用栈中的,因为调用栈中有可能会存在一些还没有执行完的代码
- 事件触发后,JS引擎是将事件响应函数插入到消息队列中排队,如果消息队列前还有未完成的事件,则需排队等待
-
定时器
-
定时器的本质,就是在指定时间后将函数添加到消息队列中
-
确保函数每次执行都有相同间隔(用setTimeout模拟setInterval)
setTimeout(function fn() { // 在setTimeout的回调函数的最后,在调用一个setTimeout setTimeout(fn, 3000) }, 3000)
-
setTimeout()-
参数:
- 回调函数(要执行的代码)
- 间隔的时间(毫秒)
-
关闭定时器:clearTimeout()
-
-
setInterval()(每间隔一段时间代码就会执行一次)-
setInterval() 每间隔一段时间就将函数添加到消息队列中,但是如果函数执行的速度比较慢,它是无法确保每次执行的间隔都是一样的(间隔时间到,会再次添加函数到消息队列中,如果前面长时间为执行完,可能后面等待的就是多个函数)
-
参数:
- 回调函数(要执行的代码)
- 间隔的时间(毫秒)
-
关闭定时器:clearTimeout()
-
jQuery
异步编程
同步和异步
- 进程和线程
- 进程(Process):进程是程序的一次执行活动,是操作系统进行资源分配和调度的基本单位。
- 线程(Thread):线程是进程中的实际执行单位,一个进程可以包含多个线程。
- 同步
- 通常情况代码都是自上向下一行一行执行的,前边的代码不执行后边的代码也不会执行
- 同步的代码执行会出现阻塞的情况,一行代码执行慢会影响到整个程序的执行
- 解决:
- Java、Python:通过多线程来解决(对计算机性能要求高、代码编写复杂)
- Js:通过异步解决
- 多线程和异步:多线程用分身,异步复用时间
- 异步
- 一段代码的执行不会影响到其他的程序,不会阻塞其他代码的执行
- 无法通过return来设置返回值,需要使用回调函数
- 基于回调函数的异步带来的问题:(调用时机)
- 代码的可读性差,可调试性差
- “回调地狱”
Promise
-
Promise 是 JavaScript 中用于处理异步操作的一种编程模式,它代表一个异步操作的最终完成(或失败)以及其结果。
-
简单理解Promise就是一个用来存储数据的容器,可以帮助解决异步中回调函数产生的问题。
存储数据
-
创建Promise:
const promise = new Promise((resolve, reject) => {})创建Promise时,构造函数中需要一个回调函数作为参数,回调函数会在创建Promise时调用,有 resolve 和 reject 两个参数
-
存储数据:通过resolve 和 reject 这两个函数参数向Promise中存储数据
- resolve:在执行正常时存储数据
- reject:在执行错误时存储数据
/* Promise中数据存放在[[PromiseResult]]属性中(两个[[]]表示私有属性) */ const promise = new Promise((resolve, reject) => { // 通过函数向Promise中添加数据,好处就是可以用来添加异步调用的数据 setTimeout(() => { resolve("哈哈") }, 2000) throw new Error("哈哈,出错了") resolve("resolve返回的数据") reject("reject返回的数据") })
读取数据
-
通过Promise的实例方法then来读取Promise中存储的数据
-
then需要两个回调函数作为参数
- 通过resolve存储的数据,会调用第一个函数返回,可以在第一个函数中编写处理数据的代码
- 通过reject存储的数据或者出现异常时,会调用第二个函数返回,可以在第二个函数中编写处理异常的代码
promise.then((result) => { console.log("resolve存储的数据", result) }, (reason) => { console.log("reject存储的数据", reason) })
流程
-
Promise中维护了两个隐藏属性:
- PromiseResult:用来存储数据
- PromiseState:记录Promise的三种状态,state只能从pedding变化一次,修改以后永远不会在变
- pending (进行中)
- fulfilled(完成) 通过resolve存储数据时
- rejected(拒绝,出错了) 出错了或通过reject存储数据时
-
流程:
- 当Promise创建时,PromiseState初始值为pending
- 当通过resolve存储数据时PromiseState 变为fulfilled,PromiseResult变为存储的数据,then的回调函数放入任务队列
- 当通过reject存储数据或出错时 PromiseState 变为rejected,PromiseResult变为存储的数据或异常对象
- 通过then读取数据时,相当于为Promise设置了回调函数
- 如果PromiseState变为fulfilled,则调用then的第一个回调函数来返回数据
- 如果PromiseState变为rejected,则调用then的第二个回调函数来返回数据
- 当Promise创建时,PromiseState初始值为pending
其他方法
catch()用法和then类似,但是只需要一个回调函数作为参数- catch()中的回调函数只会在Promise被拒绝时才调用
- catch() 相当于 then(null, reason => {})
- catch() 就是一个专门处理Promise异常的方法
finally()- 无论是正常存储数据还是出现异常了,finally总会执行
- finally的回调函数中不会接收到数据
- finally()通常用来编写一些无论成功与否都要执行代码
链式调用
- then、catch (return new Promise())都会返回一个新的Promise,Promise中会存储回调函数的返回值(写入时相当于只第一个需要调用resolve,其他数据用return返回)
- finally的返回值,不会存储到新的Promise中
const promise = new Promise((resolve, reject) => {
resolve("hahahha")
})
promise
.then(result => {
console.log("回调函数", result)
return "锄禾日当午"
})
.then(result => {
console.log("第二个then", result) //此时输出为"锄禾日当午"
return "汗滴禾下土"
})
.then(result => {
console.log(result)
})
sum(123, 456)
.then(result => result + 7)
.then(result => result + 8)
.then(result => console.log(result))
-
对Promise进行链式调用时,后边的方法(then和catch)读取的上一步的执行结果,如果上一步的执行结果不是当前想要的,则跳过当前的方法(跳过的时候,对它后面的一行代码也有影响,对后面的代码来说,本行也不执行)
-
当Promise出现异常时,而整个调用链中没有出现catch,则异常会向外抛出
const promise = new Promise((resolve, reject) => { reject("周一到周五19点,不见不散") }) promise .then(r => console.log("第一个then", r)) .then(r => console.log("第二个then", r))
静态方法
Promise.resolve()创建一个立即完成的PromisePromise.reject()创建一个立即拒绝的Promise(需要用catch读,then读不出来)Promise.all([...])同时返回多个Promise的执行结果- 其中有一个报错,就返回错误(同生共死)
Promise.allSettled([...])同时返回多个Promise的执行结果(无论成功或失败)- {status: 'fulfilled', value: 579}
- {status: 'rejected', reason: '哈哈'}
Promise.race([...])返回执行最快的Promise(不考虑对错)Promise.any([...])返回执行最快的完成的Promise(如果没有完成的则报错)
Promise.all([
sum(123, 456),
sum(5, 6),
// Promise.reject("哈哈"),
sum(33, 44)
]).then(r => {
console.log(r) //[579, 11, 77]
})
Promise.race([
// Promise.reject(1111),
Promise.resolve(1111),
sum(123, 456),
sum(5, 6),
sum(33, 44)
]).then(r => {
console.log(r)
}).catch(r => {
console.log("错误")
})
宏任务和微任务
-
JS是单线程的,它基于事件循环机制(event loop)
- 流程:(JS单线程,只能挨个执行代码,并且按照以下流程)
- 执行调用栈中的代码
- 执行微任务队列中的所有任务
- 执行宏任务队列中的所有任务
- 流程:(JS单线程,只能挨个执行代码,并且按照以下流程)
-
任务队列:先执行微任务再执行宏任务
-
宏任务队列:大部分代码都去宏任务队列中去排队
-
微任务队列 :Promise的回调函数(then、catch、finally)
// queueMicrotask() 用来向微任务队列中添加一个任务 queueMicrotask(() => { //代码 })
-
async和await
async
-
async:
[əˈsɪŋk]可以用来创建一个异步函数,返回值会自动封装到一个Promise中返回function fn1() { return 10 } let result1 = fn1() // result输出10 // function fn2() { // return Promise.resolve(10) // } async function fn2() { return 10 } let result1 = fn2() // result输出一个Promise result.then(r => console.log(r)) //输出10 -
在async声明的异步函数中可以使用await关键字来调用异步函数
await
-
await直到异步代码执行有结果时,才会将结果返回(返回的不是Promise)
-
await只能用在async声明的异步函数或es模块的顶级作用域中
-
通过await调用异步代码时,需要通过try-catch来处理异常
async function fn3() { // sum(123, 456) // .then(r => sum(r, 8)) // .then(r => sum(r, 9)) // .then(r => console.log(r)) let result = await sum(123, 456) result = await sum(result, 8) result = await sum(result, 9) console.log(result) try{ //代码 }catch(e){ console.log("出错了") } } -
通过await去调用异步函数时,它会暂停代码的运行,所以会阻塞异步函数内部处在await代码之后的代码,但不影响
-
使用await调用函数后,当前函数后边的所有代码会在当前函数执行完毕后,放入到微任务队列里
-
如果async声明的函数中没有写await,那么它里边就会依次执行
async function fn() { console.log(1) await console.log(2) console.log(3) } // fn和fn1等价 function fn1() { return new Promise(resolve => { console.log(1) console.log(2) resolve() }).then(r => { console.log(3) }) }
-