JWT流程
- 前端在登录请求中将账号密码发送给后端
- 后端去数据库中检验账号密码,如果存在,则JsonWebToken这个第三方库将用户账号信息生成一个Token,并返回给前端
- 前端获取到该token后进行本地保存,并设置axios请求头,目的是保证在之后的请求过程中该token能传回给后端
- 在后端接口请求中,后端获取到前端传回来的token后,通过JWT检验该token的合法性,检验通过再返回前端需要的数据
深浅拷贝方法
浅拷贝
两个对象指向同一个地址
- Object.assign()
- slice()
- concat()
- 拓展运算符
深拷贝
两个对象指向不同地址
- _.cloneDeep()
- jQuery.extend()
- JSON.stringfy()
- 手写循环递归
函数缓存
函数缓存就是将函数运算过的结果进行缓存,本质上就是用空间换时间,常用于缓存数据计算结果和缓存对象
- 闭包
- 柯里化
- 高阶函数 -->接受其他函数作为参数或返回其他函数的函数
v-show & v-if
控制手段不同:
v-show:为元素添加css-display:none,dom元素还在
v-if:将整个dom元素添加或删除
编译过程不同:
v-show基于简单的css切换
v-if切换有一个局部的编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件
编译条件不同:
v-show由false变为true的时候,触发组件的beforeCreate、create、beforeMount、mounted钩子,由true变为false时触发组件的beforeDestory、destoryed方法
性能消耗方面: v-if有更高的切换消耗,v-show有更高的初始渲染消耗
Promise错误捕获机制
基础概念:Promise 就像快递包裹
想象你网购了一个包裹(Promise),可能有三种状态:
- 运输中(pending) :包裹还没到
- 成功(fulfilled) :包裹完好无损
- 异常(rejected) :包裹丢失或损坏
错误捕获就是处理「快递异常」情况的方案!
核心机制: 基于"冒泡排序"机制,错误会沿着Promise链传递,直到遇到.catch
或try/catch
.未被捕获的拒绝会导致全局unhandlerejection
事件。
具体方法
1. .catch()链式捕获 --统一处理异常
javascript
// 比喻:网购后统一去驿站处理问题
getPackage() // 下单
.then(unbox) // 拆包裹
.then(useItem) // 使用商品
.catch(error => { // 统一处理所有环节的问题
console.log("包裹异常:", error);
// 比如:重新发货、退款等
});
特点: 无论下单、拆快递还是使用商品,都会跳到.catch()
适合场景: 链式调用多个步骤时,统一处理错误
2. async/await + try/catch --同步化处理
javascript
// 比喻:每一步都现场检查包裹
async function handleOrder(){
try{
const package = await getPackage()
const item = await unbox(package)
await useItem(item) // 现场使用
}catch(error){//任何一个步骤出错直接跳转到此
console.log("现场发现问题",error)
}
}
特点: 用同步代码处理异步错误
适合场景: 需要逐步处理,且每一步都可能需要错误处理
- 注意 所有需要通过
reject
传递的错误才能被捕获
3. 全局捕获
javascript
// 比喻:快递公司总部监控所有未处理的异常
// 浏览器环境
window.addEventListener('unhandledrejection', (event) => {
console.log("发现未处理的包裹异常:", event.reason);
event.preventDefault(); // 阻止浏览器默认报错
});
// Node.js 环境
process.on('unhandledRejection', (error) => {
console.log("未处理的异常:", error);
});
特点: 捕获所有未被处理的Promise错误 适合场景:防止程序因未捕获错误直接崩溃
异步回调错误处理方法
异步回调中的错误必须手动处理
setTimeout
、事件监听等非 Promise 异步操作中的错误,必须用 try/catch
包裹后手动调用 reject
。
方法一:在异步操作内部手动捕获
javascript
new Promise((resolve, reject) => {
setTimeout(() => {
try { // 手动添加 try/catch
throw new Error('异步错误!');
} catch (error) {
reject(error); // 手动触发 reject
}
}, 1000);
})
.catch(error => console.log("捕获成功:", error));
方法二:将 setTimeout 包装成 Promise
javascript
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function handleAsyncError() {
try {
await delay(1000);
throw new Error('异步错误!'); // 此时错误在 Promise 链中
} catch (error) {
console.log("捕获成功:", error);
}
}
handleAsyncError();
箭头函数
1. 箭头函数是什么?
箭头函数是 简化版的函数写法 ,用 =>
符号定义。它能解决传统函数的两个痛点:
- 代码冗长:传统函数写起来麻烦
this
指向混乱 :传统函数的this
会变来变去
2.传统写法vs箭头函数
javascript
// ----------------------------
// 传统函数
// ----------------------------
function add(a, b) {
return a + b;
}
// 匿名函数版本
const add = function(a, b) {
return a + b;
};
// ----------------------------
// 箭头函数(完全等效)
// ----------------------------
const add = (a, b) => {
return a + b;
};
// 极简写法(当只有一行 return 时)
const add = (a, b) => a + b; // 自动返回计算结果
3. 特殊规则
规则1:单参数可省略括号
javascript
// 传统写法
const square = function(n) {
return n * n;
};
// 箭头函数简写
const square = n => n * n; //没有括号
规则2:无参数必须有括号
javascript
// 传统写法
const sayHi = function() {
console.log("Hello!");
};
// 箭头函数
const sayHi = () => console.log("Hello!"); //空括号
规则3:返回对象要用括号包裹
javascript
// 错误写法
const createUser = () => { name: "John" }; // ❌ 会被认为是代码块
// 正确写法
const createUser = () => ({ name: "John" }); // ✅ 用括号包裹对象
规则4:!!this不会变!!
这一点是箭头函数与传统函数最本质的区别!
传统函数的 this
会变
javascript
const person = {
name: "Alice",
sayName: function() {
console.log(this.name); // ✅ 正常输出 "Alice"
},
sayNameLater: function() {
setTimeout(function() {
console.log(this.name); // ❌ 这里 this 指向 window(浏览器环境)
}, 1000);
}
};
执行流程:
person.sayNameLater()
调用时,sayNameLater
内部的this
指向person
setTimeout
的回调是一个 传统函数- 当 1 秒后回调执行时,相当于直接调用
function() { ... }
,此时this
指向window
(浏览器环境)
箭头函数的 this
固定
javascript
const person = {
name: "Alice",
sayName: () => {
console.log(this.name); // ❌ person对象字面量没有自己的作用域,箭头函数没有自己的 this,这里指向外层(可能是 window)
},
sayNameLater: function() {
setTimeout(() => {
console.log(this.name); // ✅ 箭头函数继承外层 this,输出 "Alice"
}, 1000);
}
};
执行流程:
person.sayNameLater()
调用时,sayNameLater
内部的this
指向person
setTimeout
的回调是一个 箭头函数- 箭头函数的
this
继承自外层sayNameLater
的this
(即person
) - 因此回调中的
this.name
正确指向person.name
关键结论 :
箭头函数没有自己的this
,它的this
永远指向定义时所在的外层作用域。
4.对比
5. 口诀
- 传统函数看调用 ------ 谁调用,
this
就指向谁 - 箭头函数看定义 ------ 定义时外层是谁,
this
就是谁 - 对象方法慎用箭头 ------ 避免
this
指向全局 - 回调函数优先箭头 ------ 自动锁定外层
this
常用的 Git 命令
按功能分类整理:
1. 配置相关
-
设置用户名和邮箱
bashgit config --global user.name "你的名字" git config --global user.email "你的邮箱"
-
查看配置信息
bashgit config --list
2. 仓库创建与克隆
-
初始化新仓库
bashgit init
-
克隆远程仓库
bashgit clone <远程仓库地址>
3. 基本操作
-
添加文件到暂存区
bashgit add <文件名> # 添加单个文件 git add . # 添加所有修改和新文件
-
提交更改
bashgit commit -m "提交说明"
-
查看仓库状态
bashgit status
-
查看文件修改差异
bashgit diff # 工作区与暂存区的差异 git diff --staged # 暂存区与最新提交的差异
4. 分支管理
-
创建分支
bashgit branch <分支名>
-
切换分支
bashgit checkout <分支名> # 旧版写法 git switch <分支名> # 新版推荐
-
创建并切换分支
bashgit checkout -b <分支名> # 旧版写法 git switch -c <分支名> # 新版推荐
-
合并分支
bashgit merge <分支名> # 将指定分支合并到当前分支
-
删除分支
bashgit branch -d <分支名> # 安全删除(已合并的分支) git branch -D <分支名> # 强制删除(未合并的分支)
-
查看分支列表
bashgit branch # 本地分支 git branch -a # 包括远程分支
-
查看分支合并情况
bashgit log --graph --oneline --all
5. 远程操作
-
添加远程仓库
bashgit remote add origin <远程仓库地址>
-
查看远程仓库
bashgit remote -v
-
拉取远程更新
bashgit fetch origin # 仅拉取不合并 git pull origin <分支名> # 拉取并合并(相当于 fetch + merge)
-
推送本地提交到远程
bashgit push origin <分支名>
-
删除远程分支
bashgit push origin --delete <分支名>
6. 撤销与回退
-
撤销工作区修改
bashgit checkout -- <文件名> # 丢弃未暂存的修改
-
撤销暂存区的文件
bashgit reset HEAD <文件名> # 取消暂存
-
修改最后一次提交
bashgit commit --amend # 修改提交信息或内容
-
回退到指定提交
bashgit reset --hard <commit-id> # 彻底回退到某个版本 git reset --soft HEAD~1 # 回退提交但保留修改
-
恢复文件到某次提交
bashgit restore --source=<commit-id> <文件名>
7. 标签管理
-
创建标签
bashgit tag <标签名> # 轻量标签 git tag -a <标签名> -m "说明" # 附注标签
-
推送标签到远程
bashgit push origin <标签名>
-
删除标签
bashgit tag -d <标签名> # 删除本地标签 git push origin --delete <标签名> # 删除远程标签
8. 查看日志与历史
-
查看提交历史
bashgit log git log --oneline # 简洁模式 git log --graph # 图形化分支历史
-
查看某文件的修改历史
bashgit blame <文件名> # 显示每一行的最后修改者
9. 子模块(Submodule)
-
添加子模块
bashgit submodule add <仓库地址> <路径>
-
更新子模块
bashgit submodule update --init --recursive
常用场景示例
-
首次推送本地项目到远程仓库
bashgit init git add . git commit -m "Initial commit" git remote add origin <远程仓库地址> git push -u origin main
-
同步远程最新代码
bashgit fetch origin # 拉取最新代码 git merge origin/main # 合并到当前分支 # 或直接使用 pull git pull origin main
vue-router
核心定位
Vue Router 是 Vue 生态的官方路由解决方案,实现了 SPA 的核心路由机制。 核心解决两个问题:
- URL 变化时无刷新更新视图
- 保持视图状态与 URL 同步
设计模式
采用『前端路由』模式,通过两种方案实现:
- Hash 模式 :利用
onhashchange
监听 URL 变化,兼容性好 - History 模式 :基于 History API,需要服务端配合支持 HTML5 特性
两者的选择需要根据项目部署环境权衡"
Router VS Route
回答模板:在Vue Route中,
- Router是全局路由管理器,负责控制路由模式(Hash/History)、跳转逻辑(push/replace)和守卫拦截(beforeEach),通过
useRouter()
获取实例。 - Route是单个路由的配置规则,定义路径与组件的映射关系,支持嵌套路由和元信息。通过
useRoute()
获取当前路由信息(如参数、查询字符串)。
怎么判断一个对象是否为空
方法 1:Object.keys()
(最常用)
javascript
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}
// 示例
isEmpty({}); // true
isEmpty({a: 1}); // false
缺点:
- 不检查不可枚举属性
方法 2:JSON.stringify()
(深空检查)
javascript
function isEmpty(obj) {
return JSON.stringify(obj) === '{}';
}
isEmpty({}); // true
isEmpty({a: undefined}); // false(注意:值为 undefined 的属性会被忽略)
适用场景:
- 需要快速检查简单对象的深空状态
注意: - 会忽略
undefined
和函数类型的值 - 性能较差(需序列化整个对象)
方法 3:for...in
循环(最可靠)
javascript
function isEmpty(obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) return false;
}
return true;
}
// 示例
isEmpty(Object.create({a: 1})); // true(不检查原型链)
优势:
- 精确控制检查范围(可选择性包含原型属性)
- 兼容所有 JavaScript 环境
缺点: - 代码稍显冗长
方法 4:Object.getOwnPropertyNames()
(包含不可枚举属性)
javascript
function isEmpty(obj) {
return Object.getOwnPropertyNames(obj).length === 0;
}
// 示例
const obj = Object.create(null);
Object.defineProperty(obj, 'hidden', { enumerable: false });
isEmpty(obj); // false(检测到不可枚举属性)
获取DOM的方法有哪些
一、Vue 专用方法
1. 模板 ref
属性(推荐)
js
<template>
<input ref="inputRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null) // 创建同名ref
onMounted(() => {
inputRef.value.focus() // 获取DOM实例
})
</script>
特点:
- 组合式API标准用法
- 自动处理组件更新和卸载时的引用清理
- 支持 TypeScript 类型提示
二、原生 JavaScript 方法
1. document.getElementById()
js
const el = document.getElementById('my-id') // 返回单个元素
适用场景:
- 简单页面快速获取已知ID元素
- 非响应式静态页面
2. document.querySelector()
js
const el = document.querySelector('.class-name') // 获取首个匹配元素
const list = document.querySelectorAll('div') // 获取所有匹配元素(NodeList)
优势:
- 支持CSS选择器语法
- 可获取复杂结构的元素
3. document.getElementsByClassName()
js
const elements = document.getElementsByClassName('class-name') // HTMLCollection
注意:
- 返回动态集合(随DOM变化自动更新)
- 性能优于
querySelectorAll
但功能有限
4. document.getElementsByTagName()
js
const divs = document.getElementsByTagName('div') // HTMLCollection
Q1:为什么 mounted
钩子中能获取DOM?
- Vue 的生命周期确保 DOM 已挂载完成
- 对于动态内容,可使用
nextTick
:
js
import { nextTick } from 'vue'
nextTick(() => {
// 确保DOM更新后执行
})
Q2:如何获取组件内的DOM?
js
<!-- Child.vue -->
<template>
<div ref="inner"></div>
</template>
<script setup>
const inner = ref(null)
defineExpose({ inner }) // 暴露给父组件
</script>
<!-- Parent.vue -->
<script setup>
const childRef = ref(null)
onMounted(() => {
console.log(childRef.value.inner) // 访问子组件DOM
})
</script>
watch 和computed区别
watch (侦听器)
设计目的:观察变化执行副作用(如异步操作)
无返回值
无缓存(每次触发都执行)
可直接处理异步
可配置immediate
决定是否进行初始化触发
典型场景:数据变化时执行请求,验证等操作
computed (计算属性)
设计目的:派生状态(基于依赖计算新值)
必须返回一个值
有缓存(依赖不变时复用结果)
不能包含异步操作
初始化触发:默认立即计算
典型场景:模板中需要的数据加工
总结
computed 更适用于依赖其它数据进行计算并需要缓存的情况,能够简化模板中的逻辑表达式,使代码更加清晰。
watch 则更适合监控某个数据项的变化并做出响应,特别是那些涉及到异步操作或者需要执行复杂逻辑的情况。