前端基础知识
1. 变量和常量
1.1 变量
// 变量
let name = 'Jack'
let age = 20
name = 'lisi'
age = 18
1.2 常量
// 常量
const PI = 3.14
// PI = 3.1415926 // error,常量不可重新赋值
const articleList = []
const user = {
name: 'vue3',
age: 10
}
1.3 const 声明的数组和对象
因为数组和对象在 JS 中属于引用类型,对其做添加、删除等操作,并不改变其内存地址,所以可以对其进行修改
// 数组
const arr = [1, 2, 3]
// 添加
arr.push(4)
// 删除
arr.shift()
console.log(arr)
// 对象
const obj = {
name: 'lisi',
age: 10
}
// 添加属性
obj.birth = 2015
console.log(obj)
// 修改属性
obj.age = 18
console.log(obj)
运行结果:
2. 模版字符串
2.1 普通字符串
用一对单引号或双引号声明,基本上都是用单引号
// 普通字符串
let name = 'Jack'
let msg = "Hello World"
2.2 模版字符串
用一对反引号声明
// 模版字符串
let name = `Jack`
let msg = `Hello World`
2.3 模版字符串的优势
2.3.1 可任意换行
let htmlStr = '<div class="hot-goods-box"><h1>热门商品</h1><p>卖爆了卖爆了卖爆了</p></div>'
let htmlStr = `<div class="hot-goods-box">
<h1>热门商品</h1>
<p>卖爆了卖爆了卖爆了</p>
</div>`
2.3.2 可嵌入表达式
避免了繁琐的 加号 做字符串拼接
嵌入语法:${表达式}
let name = 'lisi'
let age = 9
// let str = 'My name is xxx, I am xxx years old, I am 已成年 or 未成年'
// let str = 'My name is ' + name + ', I am ' + age + ' years old, I am ' + (age >= 18 ? '已成年' : '未成年')
let str = `My name is ${name}, I am ${age} years old, I am ${age >= 18 ? '已成年' : '未成年'}`
console.log(str)
运行结果:
3. 对象
3.1 取值
3.1.1 点取值
const obj = {
name: 'lisi',
age: 18
}
// 点取值
console.log(obj.name)
console.log(obj.age)
3.1.2 中括号取值
const obj = {
name: 'lisi',
age: 18
}
// 中括号取值
console.log(obj['name'])
console.log(obj['age'])
3.1.3 特殊情况
当属性名是变量的时候,只能用中括号取值
let x = 'name'
let y = 'age'
console.log(obj[x])
console.log(obj[y])
console.log(obj.x) // undefined
console.log(obj.y) // undefined
若此时还用 点取值,不会报错,但是会显示 undefined
运行结果:
3.2 简写
3.2.1 属性
当属性名和属性值的名字相同时,可以只写一个,通常会配合变量使用
let min = 1
let max = 99
// 不简写
const obj = {
min: min,
max: max
}
// 简写
const obj = {
min,
max
}
3.2.2 方法
const obj = {
// 不简写
fn: function() {
}
}
// 上面写法和下面写法是等价的
const obj = {
// 简写:连同 : 和 function 一起省略
fn() {
}
}
4. 解构赋值
针对目标:数组 或对象
作用:让数组和对象的取值更加便捷
4.1 代码示例
1. 数组解构
const arr = [11, 22, 33]
// eg1:把 arr 中的三个元素分别赋值给变量 a, b, c
// 数组下标实现:
let a = arr[0]
let b = arr[1]
let c = arr[2]
// 解构实现:
let [a, b, c] = arr
console.log(a, b, c) // 运行结果:11 22 33
// eg2:把 arr 中的后两个元素分别赋值给变量 b, c
let [, b, c] = arr
console.log(b, c) // 运行结果:22 33
// eg3:把 arr 中的第一个元素赋值给变量 a,第三个元素赋值给变量 c
let [a, , c] = arr
console.log(a, c) // 运行结果:11 33
// eg4:把 arr 的第一个元素赋值给变量 a,剩余的全部给变量 rest
let [a, ...rest] = arr
console.log(a, rest) // 运行结果:(2) [22, 33]
// eg5:把下面 arr 中的 3, 4 赋值给变量 a, b
const arr = [2, [3, 4], 5]
let [, [a, b]] = arr
console.log(a, b) // 运行结果:3 4
2. 对象解构
const obj = {
name: '胡图图',
age: 3,
address: '翻斗大街翻斗花园 2 号楼 1001 室'
}
// eg1:把 obj 的 3 个属性分别赋值给变量 name, age, address
// 点赋值方式
let name = obj.name
let age = obj.age
let address = obj.address
// 解构方式
let {age, name, address} = obj
console.log(name, age, address) // 运行结果:胡图图 3 翻斗大街翻斗花园 2 号楼 1001 室
// eg2:把 obj 的 name, age 属性值赋值给变量 name, age
let {name, age} = obj
console.log(age, name) // 运行结果:3 '胡图图'
//eg3:把 obj 的 name 属性值赋值给变量 name,剩余的赋值给变量 rest
let {name, ...rest} = obj
console.log(name, rest)
// eg4:把 obj 的 name 属性值赋值给 uname
let {name: uname} = obj
console.log(uname) // 运行结果:胡图图
// eg5:把下面 obj 中的 code, message, result 的值取出来赋值给变量 code, message, list
const obj = {
data: {
code: 10000,
message: '频道列表获取成功',
result: ['HTML', 'CSS', 'JavaScript', 'Vue', 'SpringBoot']
},
status: 200,
statusText: 'Ok'
}
// 解构拿到 data
const {data} = obj
console.log(data)
// 再解构 data
const {code, message, result: list} = data
console.log(code, message, list)
5. 箭头函数
5.1 非箭头函数
// 有名函数
function fn() {
// some code...
// return 结果
}
// 函数表达式
const fn = function() {
}
// 执行
const result = fn()
5.2 箭头函数语法
const fn = () => {
}
// 两数相加
const add = (x, y) => {
return x + y
}
const result = add(1, 1)
console.log(result) // 运行结果:2
5.3 箭头函数特点
1. 当参数只有一个时,可以省略小括号
// const log = (arg) => { // 等价于下面写法
const log = arg => {
console.log(arg)
}
log(666) // 运行结果:666
log('箭头函数') // 运行结果:箭头函数
2. 当函数体只有一句话时,可以省略大括号,此时箭头函数自带 return 功能
const add = (x, y) => {
return x + y
}
// 上面等价于下面
const add = (x, y) => x + y
console.log(add(1, 2))
3. 当直接返回一个对象时,为了简写,需要给对象加一对小括号
const state = () => {
return {
token: 'xxx',
userInfo: {
name: 'admin',
id: 1
}
}
}
// 上面等价于下面
const state = () => ({
token: 'xxx',
userInfo: {
name: 'admin',
id: 1
}
})
console.log(state())
5.4 应用
可用于普通函数的声明,也多用于回调函数传参
// 延时函数
setTimeout(function() {
console.log(666)
}, 2000)
// 用箭头函数简写如下
setTimeout(() => {
console.log(666)
}, 2000)
6. 数组的重要方法
数组:用来存放相同类型的一组数,管理一组有序数据的集合
6.1 添加 push() 和 unshift()
const arr = [11, 22, 33]
// 添加
const len = arr.push(44) // 尾部添加
console.log(arr, len) // 运行结果:[11, 22, 33, 44] 4
const len = arr.unshift(44) // 头部添加
console.log(arr, len) // 运行结果:[44, 11, 22, 33] 4
6.2 删除 pop() 和 shift()
const arr = [11, 22, 33]
// 删除
const last = arr.pop() // 尾部删除
console.log(arr, last) // 运行结果:[11, 22] 33
const first = arr.shift() // 头部删除
console.log(arr, first) // 运行结果:[22, 33] 11
6.3 任意位置删除或添加 splice(startIndex, delCount, ...addItem)
- startIndex:起始下标(从哪开始操作)
- delCount:删除元素的个数
- addItem:要添加的元素
const arr = [11, 22, 33]
const temp = arr.splice(1, 1) // 从下标为 1 的位置开始删除,共删除 1 个元素
console.log(arr, temp) // 运行结果:[11, 33] [22]
arr.splice(2, 0, 44) // 从下标为 2 的位置,删除 0 个元素,添加元素:44
console.log(arr) // 运行结果:[11, 22, 44, 33]
6.4 包含 includes()
包含返回 true,否则返回 false,通常配合 if 语句做判断
const arr = [11, 22, 33]
// 包含
console.log(arr.includes(33)) // true
console.log(arr.includes(55)) // false
6.5 遍历 forEach()
const arr = [11, 22, 33, 44]
// for 循环遍历
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
// forEach 循环遍历
const result = arr.forEach((item, index, array) => {
// item:每次遍历的元素
// index:当前元素在数组中的下标
// array:遍历数组本身
console.log(item, index, array)
})
console.log(result) // 注意:forEach() 只遍历数组,只是对 for 循环的简化而已,无返回值
6.6 过滤/筛选 filter()
保留满足条件的,去掉不满足条件的
const arr = [11, 22, 33, 44]
arr.filter((item, index, array) => {
// 内部会遍历数组,每遍历一次都会执行回调一次
// 如果返回 true,则当前元素会保留,否则去掉
return 布尔值
})
// 需求:留下 arr 中所有的偶数
const evenArr = arr.filter((item) => {
if (item % 2 === 0) { // 使用全等号(===),比 == 效率高,不会发生隐式类型转换
return true
} else {
return false
}
})
// 简化后:
const evenArr = arr.filter((item) => {
return item % 2 === 0
})
// 进一步简化
const evenArr = arr.filter((item) => item % 2 === 0)
console.log(evenArr) // 运行结果:[22, 44]
// 需求:留下 arr 中大于 30 的数
const filterArr = arr.filter((item) => item > 30)
console.log(filterArr) //运行结果:[33, 44]
6.7 映射 map()
由一个数组得到另一个数组,二者满足两个条件:
- 两个数组长度一致(元素个数一致)
- 新数组中的每个元素可以由原数组中的每个元素一一得到
const arr = [11, 22, 33, 44]
// 语法
arr.map((item, index, array) => {
return 新值
})
// 需求:得到每个元素翻倍的新数组:[22, 44, 66, 88]
const doubleArr = arr.map((item) => {
return item * 2
})
// 简写:
const doubleArr = arr.map((item) => item * 2)
console.log(doubleArr) // 运行结果:[22, 44, 66, 88]
// 需求:得到如下对象数组
// [
// { index: 0, value: 11 },
// { index: 1, value: 22 },
// { index: 2, value: 33 },
// { index: 3, value: 44 }
// ]
const newArr = arr.map((value, index) => {
return {
index,
value
}
})
// 简写:
const newArr = arr.map((value, index) => ({ index, value }))
console.log(newArr)
6.8 检测每一个 every()
const arr = [11, 22, 33, 44]
// 语法
// arr.every((item, index, array) => {
// // 1. 如果返回 true,说明当前元素满足条件,则继续检测下一次,若都满足条件,则最终返回 true
// // 2. 如果返回 false,说明当前元素不满足条件,立即停止检测,最终返回 false
// return 布尔值
// })
// 需求:判断 arr 中元素是否都为奇数
const flag = arr.every((item) => item % 2 ===1)
console.log(flag)
// 需求:检测 arr 中的数是否都大于 10
const flag = arr.every((item) => item > 10)
console.log(flag)
6.9 汇总 reduce()
常用语数组求和,但不限于求和
const arr = [11, 22, 33, 44]
// 语法(有两个参数,第一个是回调函数,第二个是初始值)
const result = arr.reduce((prev, item, index, array) => {
return 结果
}, 初始值)
// 需求:遍历求和
// forEach 方式:
let sum = 0
arr.forEach(item => {
sum += item
})
console.log(sum)
// reduce 方式:
arr.reduce((prev, item, index, array) => {
// prev 第一次的初始值就是 reduce 的第二个参数 0
// 后面再进入回调函数,prev 的值是上一次回调函数的返回值
// 若上一次回调函数无返回值,则 prev 就是 undefined
console.log(prev, item, index, array)
return prev + item
}, 0)
// 简写:
const sum = arr.reduce((prev, item) => prev + item, 0)
console.log(sum)
// 商品列表数组
const goodsList = [
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 3 },
{ id: 3, name: '书籍', num: 2 }
]
// 需求:求总数量
const totalNum = goodsList.reduce((prev, item) => prev + item.num, 0)
console.log(totalNum)
7. 对象的重要方法
Object.keys()
用来获取对象的键的数组,从而可以很灵活的遍历对象(遍历对象的每一对键值对)
const obj = {
id: 100001,
name: '胡图图',
age: 3,
address: '翻斗大街翻斗花园 2 号楼 1001 室'
}
// 使用 for-in 遍历对象
for (let key in obj) {
// console.log(key, typeof key) // typeof 可以判断当前 key 是什么类型
console.log(key, obj[key]) // tip:此处 key 是变量,只能使用 [] 取值,且不能加引号
}
// 使用 Object.keys() + forEach() 遍历对象
Object.keys(obj).forEach((key) => {
console.log(key, obj[key])
})
// Object.keys() 返回的是数组,我们可以对数组进行很多操作之后,再进行遍历对象,更灵活,更强大
// 需求:只遍历 obj 中以字母 a 开头的属性
Object
.keys(obj)
.filter((key) => key.startsWith('a')) // 过滤掉首字母不是 a 的 key
.forEach(key => {
console.log(key, obj[key])
})
8. 扩展运算符 ...
作用:
- 在解构赋值时,用于收集余下所有的
- 复制数组或对象
- 合并数组或对象
8.1 复制数组或对象
// 复制数组或对象,复制,也叫做拷贝/克隆/备份
const arr1 = [11, 22, 33]
const arr2 = arr1 // 这是引用,不是复制,两者会相互影响(因为其指向的是同一个地址)
arr2.push(44) // 修改了 arr2,arr1 也会被修改
console.log(arr1) // 运行结果:[11, 22, 33, 44]
const arr2 = [...arr1] // 把 arr1 复制一份给 arr2,二者值相同,但地址不同,相互独立
arr2.push(44)
console.log(arr1) // [11, 22, 33]
// 复制对象
const obj1 = {
id: 100001,
name: '胡图图',
age: 3,
}
const obj2 = {
...obj1
}
obj2.age = 10
console.log(obj1) // 运行结果:{id: 100001, name: '胡图图', age: 3}
console.log(obj2) // 运行结果:{id: 100001, name: '胡图图', age: 10}
8.2 合并数组或对象
// 合并数组
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr = [...arr1, ...arr2]
console.log(arr) // 运行结果:[1, 2, 3, 4, 5, 6]
// 合并对象
const obj1 = {
name: 'Jack',
height: 178
}
const obj2 = {
height: 180,
age: 18
}
const obj = {
...obj1,
...obj2 // 此处 obj2 中的 height 会覆盖掉 obj1 中的 height
}
console.log(obj) // 运行结果:{name: 'Jack', height: 180, age: 18}
9. 序列化和反序列化
9.1 序列化
序列化:把对象转换为 json 格式字符串
json 格式:是一种严格的对象表示法,体现在 json 格式数据的属性名必须用双引号;若存在字符串类型必须用双引号
// 普通对象
const obj = {
id: 10001,
name: '胡图图',
age: 3
}
// json 格式
const json = {
"id": 10001,
"name": "胡图图",
"age": 3
}
// 序列化语法
// JSON.stringify(对象)
console.log(JSON.stringify(obj))
9.2 反序列化
反序列化:把 json 字符串转换为对象
// json 字符串
const jsonStr = '{"id": 10001, "name": "胡图图", "age": 9}'
// 反序列化
console.log(JSON.parse(jsonStr))
10. Web 存储
10.1 介绍
Web Storage 包含如下两种机制:
1. sessionStorage 该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包含页面重新加载和恢复)
- 仅为会话存储数据,这意味着数据将一直存储到浏览器(或选项卡)关闭
- 数据永远不会被传输到服务器
- 存储限额大于 Cookie(最大 5MB)
2. localStorage 即使浏览器关闭并重新打开也仍然存在
- 存储的数据没有过期日期,只能通过 JavaScript、请求浏览器缓存或本地存储的数据来清除
10.2 用法
以 localStrorage 为例,学习存、取、删
1. 存
// 存
localStorage.setItem(key:string, value:string)
// eg
localStorage.setItem('uname', 'lisi')
// 存对象和数组,需要序列化
2. 取
// 取
// 如果存在 key,则取出相应的数据;否则取值为 null(表示 key 不存在)
localStorage.getItem(key:string)
// eg
console.log(localStorage.getItem('uname'))
// 取出对象和数组,需要反序列化
3. 删
// 删
localStorage.removeItem(key:string)
localStorage.removeItem('uname')
4. 注意
存储对象或数组需要进行序列化和反序列化
const obj = {
id: 10001,
name: '胡图图',
age: 9
}
// 存:序列化
localStorage.setItem('胡图图', JSON.stringify(obj))
// 取:反序列化
console.log(JSON.parse(localStorage.getItem('胡图图')))
11. Promise + Aysnc/Await
11.1 为什么需要 Promise
为了消除回调地狱
// 需求:延迟 2 秒输出 1,完了之后延迟 1 秒输出 2,完了之后延迟 1 秒输出 3
// 语法:两个参数,一个是回调函数,一个是延迟时间
setTimeout(() => {
console.log('setTimeout')
}, 3000)
// 实现
setTimeout(() => {
console.log(1)
}, 2000)
setTimeout(() => {
console.log(2)
}, 1000)
setTimeout(() => {
console.log(3)
}, 1000)
// 发现浏览器打印顺序为:2 3 1,这是因为 js 代码的执行顺序为 异步的
// 同步代码:串行执行,前面的代码先执行,后面的代码后执行
// 异步代码:并行执行,大家同时开始执行,后面的代码不会等待前面代码执行完毕才执行
// 修改成以下代码可以完成需求:
setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
}, 1000)
}, 1000)
}, 2000)
// 上述代码存在的问题:回调套回调(又称回调地狱),代码的可读性差
为了消除回调嵌套,Promise 就应运而生了
11.2 Promise 介绍
Promise 是一个类,用来包装异步操作,根据异步操作的结果,是成功还是失败,进而决定 Promise 是成功还是失败
Promise 支持链式调用,从而消除回调地狱
11.3 Promise 的 3 种状态
Pending: 进行中,此时 Promise 的状态还不确定
Fullfilled: 成功,状态确定了
Rejected: 失败,状态确定了
Promise 的状态
- 只能由 Pending -> Fullfilled,或 Pending -> Rejected
- Promise 的状态一旦确定,就不可改变了
11.4 基本使用
// Promise 基本使用
const p = new Promise((resolve, reject) => {
// 如果异步操作成功了,需要调用 resolve(成功的数据)
// 如果异步操作失败了,需要调用 reject(失败的原因)
// 在这里进行包装异步代码,如:定时器、ajax 请求
setTimeout(() => {
// 调用 resolve(),表示当前 Promise 成功了
// resolve('ok')
reject('error')
}, 2000)
})
p.then((msg) => { // 第一个回调表示 resolve(可传入参数)
console.log('成功了', msg)
}, (err) => { // 第二个回调表示 reject
console.log('失败了', err)
})
11.5 消除上述回调地狱
/**
* 延迟输出的异步函数,用 Promise 进行封装
* duration:延迟的时间
* n:延迟输出的数
*/
function delay(duration, n) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(n)
}, duration)
})
}
// Promise 的链式调用消除回调地狱
delay(2000, 1)
.then((n1) => {
console.log(n1)
return delay(1000, 2)
}).then((n2) => {
console.log(n2)
return delay(1000, 3)
}).then((n3) => {
console.log(n3)
})
上述代码虽然消除了回调地狱,但链式调用过长,也不利于阅读
为了继续优化,Aysnc + Await 就应运而生了
11.6 Aysnc + Await 异步终极解决方案
// Promise 封装延迟输出函数
function delay(duration, n) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(n)
}, duration)
})
}
async function log() {
// 1. 在 Promise 实例前添加 await 关键字,这样 await 的返回值就是 Promise 的 resolve 参数
// 2. await 所在的函数必须被 async 修饰
// 3. await 所在的函数会按照同步的方式进行代码的执行,也就是前面的 await 执行完毕了,才会执行后续的 await
const n1 = await delay(2000, 1)
console.log(n1)
const n2 = await delay(1000, 2)
console.log(n2)
const n3 = await delay(1000, 3)
console.log(n3)
}
log()
12. 模块化
12.1 概述
模块:一个文件就是一个模块,模块=文件
模块化:一种代码的开发思想,是指将一个复杂程序划分为一系列独立、可互操作的模块的过程。每个模块负责特定的功能或任务,并通过定义好的接口与其他模块进行通信。简单来说,就是将代码进行分解、按功能进行管理。
不同模块之间需要通过导入导出的方式进行模块通信,提高代码的可维护性、可重用性、可测试性和可扩展性,让开发者能够更容易地处理大型 JavaScript 项目。
12.2 目录结构准备
1. 新建 12-es-module 目录
2. 命令行进入 12-es-module 目录
两种方式:
1)
2)
3. 执行 npm init,初始化得到 package.json 文件
4. 给 package.json 添加 "type": "module"
5. 根目录下新建 src/index.js 作为代码的入口文件
6. src 目录下新建 utils 目录
12.3 默认导出与导入
新建 utils/min.js
// 定义求两个数最小值的函数并默认导出
export default function min(m, n) {
return m > n ? n : m
}
// tip:默认导出 export default 在一个模块中最多出现 1 次
src/index.js
// 默认导入 min 函数
// 默认导入的变量名可以随便起,但为了语义化,建议和模块功能保持一致
import min from './utils/min.js'
console.log(min(12, 45)) // 打印 12
tip:
12.4 按需导出与导入
新建 utils/math.js
// 定义求和函数并按需导出
export function add(x, y) {
return x + y
}
// 定义作差函数并按需导出
export function sub(x, y) {
return x - y
}
// tip:按需导出 export 在一个模块中可以出现多次
src/index.js
// 按需导入
// tip:按需导入的变量必须用花括号包起来,并且变量名和导出的变量名必须一致
import {add, sub} from './utils/math.js'
console.log(add(33, 18)) // 51
console.log(sub(33, 18)) // 15
运行结果: