前言
目前大三,这是诸多面试中的第二个面试,确实小厂中薪资算给的最高的一个,而且这次的面试官给我的不是压迫感而是一位大佬正在耐心指导。聊到我不了解的地方,我就会虚心请教一下面试官可否讲解一下,比如一些之前没接触过的公司业务类知识,面试官就会很用心地结合八股给我科普讲解,这一次面试其实我收获了还是蛮多东西的。
面试了这么多天,觉得一个人的力量还是太过薄弱,如果你和我一样有向前冲的勇气,欢迎掘友们私聊我交流面经(wechat: sAnL1ng)
自我介绍
一上来,依旧是亘古不变的定律,就像校园时期甜甜的恋爱,彼此都从相互了解开始,然后再决定是否选择对方。 这里可以参考我的上一篇文章,按照这个模板来介绍自己,因人而异。
聊聊vue2和vue3中响应式处理数据的不同
面试官听到我自我介绍说到熟悉vue全家桶,于是上来先探探底,看似问vue2和vue3的区别,其实是想看看你对这两者的底层了解多深 ,依旧是面经,结合我学习源码时的总结,一套高输出爆发给到面试官:
-
vue2中采用的是
Object.defineProperty
和原生的getter
和setter
方法,当我们初始化一个数据data的时候,会实例化一个Observe
类,首先它会迭代遍历data
中的每一个属性,并通过Object.defineProperty
给遍历到的每一个属性添加上getter
和setter
方法,当属性被读取的时候就会触发getter
方法进行依赖(Wather)收集,当属性被修改的时候就会触发setter
方法进行依赖(Wather)触发。(但是,光靠这些方法并不能建立完全的响应式数据与依赖之间的关系,这种办法效率低、功能弱。当我们使用Object.defineProperty()
来进行数据劫持,只有当数据被修改
或者读取
操作的时候才会触发组件的渲染,当涉及到组件中数据的增加
和删除
操作时就不能触发组件更新渲染,还需要我们手动在数组的增删
方法内通过重写
的方式,在拦截里面进行手动收集依赖
和触发依赖
进行视图更新) -
vue3中不同的是采用了ES6新增的
Proxy
进行代理操作,他提供了一个创建代理对象的构造函数,与vue2不同点就在于它会对整个对象进行监听和拦截,省去了每次迭代遍历data
属性的性能开销,并且里面有内置的getter
和setter
方法,在被读取、赋值、删除属性等操作的时候触发getter
方法进行依赖收集,在修改属性值的时候触发setter
方法,对收集到的依赖进行触发。vue3里面我们一般通过两种方式来响应式处理一个数据,分别是ref
和reactive
,简单来讲,ref可以响应式处理原始数据类型和引用数据类型
,但是reactive只可以处理对象,原因就在于Proxy只能接受一个对象作为参数。底层原理可以看我之前手写的文章。 -
【手搓ref】
你用过postman吗?说说你了解的请求方法。项目开发过程中分别在什么场景下会用到这些方法?
当是回答到这个问题,我只是简单的回答到postman一般是用来测试接口的工具,我在自己写的项目中会通过这个工具来测试我自己写的后端接口是否有效,这里面经常用到的方法就有:get
、set
、post
、delete
等等。,然后还有就是浏览器一般用的是get
请求之类的,于是面试官接着科普到业务类的结合,后来我才知道这其实就是一种restful
风格,具体展示如下。
在项目开发过程中,前后端首先要进行协调沟通,确定好每个接口的请求方式,比如当我们需要对数据库进行查询操作时,一般用的就是
get
方式,由于get
中没有body,参数只能接在url中,而url的长度是有限制的,所以我们在进行查询数据的操作时不会携带太多参数,如果涉及到增加
和修改
操作时,一般会用到post
请求,比如后端通过post方法返回一个响应时是有响应体body的,我们可以携带大量的参数在body中。然后涉及到删除操作显然就是delete
方式,涉及到图像之类的就是put/patch
方式。
vuex和pinia的区别
这两者都是状态管理的一种工具,为什么需要设计出一种状态管理工具呢?原因就是当我们有多个组件进行通讯的时候,涉及到兄弟组件通讯或嵌套的更加深,我们知道父子子父组件之间通信通过的是$emit
以及pros
方式进行组件通讯的,但是我们依靠这种链式的组件通信当组件过多时效果就很差,于是我们需要借助一个状态管理仓库来管理这些响应式数据,这样就可以更加方便且高效的实现组间通讯。
vuex
给我的印象首先就是代码风格类似于vue2中的选项式API风格
,而且里面有state
数据源、mutation
方法、以及action
方法等等,而且限制要求比较繁琐,比如规定在mutation
中只能存放同步
的代码,在action
中只能存放异步
的代码,这么做的目的就是为了防止当调用了两个包含异步回调的mutation
来改变状态,我们无法知道什么时候回调和哪个先回调?这里主要的缺点就是代码不简洁
、过于繁琐
。于是我们程序员更倾向于使用pinia
来进行状态管理。
pinia
不同于vuex,首先它使用的类似于vue3中的组合式API风格
,当然也可以同vuex一样使用组合式API风格
,不过省去了action
存放不同类型代码的区别,不管是异步还是同步代码都可以放进mutation
,pinia会进行托管代理。更重要的一点是它使用的是函数式编程
,我们在pinia中定义的每一个仓库
其实都是一个函数
,这样做的优点就是所用需要用到这个仓库中数据的组件不会共享
一个数据源,而是分为一个个函数
来管理各自
的数据源,不仅提高了代码的可读性,而且也避免了数据污染的潜在危害可能。
CSS中display:grid,网格默认会分为多少份
当我们设置容器属性
display:grid
,不做其他设置的时候,会初始化容器为网格布局
并且默认分为12等分
当我们设置容器属性
display:table
,不做其他设置的时候,会初始化容器为表格布局
并且默认分为24等分
token一般的业务作用
这里我在项目中用到了JWT鉴权
,于是就顺着跟面试官聊了下JWT
相关的知识点,这个功能主要使用在登入注册后,用户想要浏览其他页面
时进行的一个校验手段。浏览器的存储方式简单来说有cookie
,sessionStorage
、localStorage
,但是将用户的信息以及合法性判断时直接放在这些存储中并不安全
,所有我们还需要用到JWT(Json Web Token)
来生成一个以16进制存储的令牌数据。主要流程如下:
前端登录后,后端校验账号密码的成功后,靠
jwt
来生成一个token
,并将该token
返回给前端
,或者也可以让后端
直接将token
保存在cookie
中,因为cookie是浏览器的内存空间,但是受后端的掌控,通常是后端将JWT(登录令牌)保存在cookies
中,所有被保存在cookie
中的数据,都会在每一次的http请求时自动被携带在请求头
中返回给后端。;我还封装了
axios
,在请求拦截当中为每一次的请求头中添加一个authorization
字段,之后的接口请求,后端获取到请求头中的token
,并进行校验,如果token合法
,则返回数据,否则返回401状态码告诉前端token失效。
cookie和sessionStorge和localStorge
于是聊完Token,面试官很感兴趣的继续问道:请简单说说你对 cookie和sessionStorge和localStorge
的理解。
首先,这三种都是浏览器本次存储的方式,但是它们有一些自己的特点和彼此间的差异:
cookie
-
大小只有4kb
-
涉及到跨域时里面的资源不可共享
-
安全性很差:
-
客户端可修改: 客户端可以自由修改
Cookie
中的数据,不具备数据完整性保障,容易受到篡改攻击,影响数据的安全性。 -
跨站脚本攻击(XSS): 如果网站存在
XSS
漏洞,攻击者可以通过注入恶意脚本来窃取用户的Cookie
数据,进而获取用户的敏感信息。 -
跨站请求伪造(CSRF): 攻击者可以利用
CSRF
攻击来伪造用户的请求,实现恶意操作,例如利用用户的Cookie
发起恶意请求,执行不当操作。
-
-
只存在请求头中
-
每一次发送请求响应时,都会将cookie中的数据全部返回给后端
sessionStorage
- 存在在浏览器内存中,相对于cookie体积更大
- 页面关闭时,数据会消失
localStorage
- 相对于前两者,体积更大,可以存储更多内容
- 关闭页面,数据不会消失,除非用户手动删除
- 相对于cookie,数据存在硬盘中,不会携带给后端
数组的头插尾插和头删尾删
当面试官问到这种类型问题的时候,我们可不能一股脑将数组中的那些方式零碎的讲出来,要有条理性,这样面试官也会认为我们对这类知识的理解和记忆方式更加优秀。
数组的头插
splice
设置第三个参数,可以实现将想要的内容插入数组中的指定位置(包括头部)unshift
在数组的开头插入新的元素concat
通过数组拼接,将想要插入的元素拼接在数组之前
数组的尾插
push
直接在数组尾部插入元素splice
设置第三个参数,可以实现将想要的内容插入数组中的指定位置(包括尾部)concat
通过数组拼接,将想要插入的元素拼接在数组之后
数组的头删
shift
删除数组的第一个元素,并返回被删除的元素。splice
不设置第三个参数,可以实现指定数组中的位置删除元素(包括头部)slice
指定数组中的某个位置删除元素
数组的尾删
pop
直接修改原数组,减少数组的长度,并返回被删除的元素设置length属性
通过缩短数组的长度也可以达到尾删的效果splice
不设置第三个参数,可以实现指定数组中的位置删除元素(包括尾部)
9. filter和find的区别
这两者都是可以用来操作数组的方法,作用就是根据提供的回调函数测试数组中的每个元素,其中它们有以下区别:
Array.prototype.filter()
方法:
- 功能 :对数组中的每个元素执行给定的函数,并创建一个新的数组,包含所有使得函数返回值为
true
的元素。 - 返回值:一个新数组,其中包含了原数组中满足条件的所有元素。
- 使用场景 :当你需要筛选出数组中
满足特定条件
的所有元素
时,通常使用filter
。
示例:
js
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // 输出: [2, 4, 6]
Array.prototype.find()
方法:
- 功能 :对数组中的每个元素执行给定的测试函数,直到找到第一个使测试函数返回值为
true
的元素,然后返回该元素。如果没有找到这样的元素,则返回undefined
。 - 返回值 :找到的第一个满足条件的元素或
undefined
(如果未找到符合条件的元素)。 - 使用场景 :当只需要找到数组中
满足特定条件
的第一个元素
时,通常使用find
。
示例:
js
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
const userById = users.find(user => user.id === 2);
console.log(userById); // 输出: { id: 2, name: 'Bob' }
总结起来,
filter
是为了过滤出所有匹配项并生成一个新的数组,而find
则是为了寻找并返回第一个匹配项(或者是 undefined)。
8. 聊聊你知道哪些git指令
聊到git指令,由于我接触到的业务中用到git是从初始化仓库开始到提交代码到仓库里面,还有更多的是团队管理管理一个项目时,会涉及到的git指令,于是我就聊了初始化仓库开始到提交代码到远程仓库分支这一系列过程涉及到的git指令。
初始化和克隆仓库
git init
:用于在一个目录中创建一个新的Git仓库。git clone [url]
:从远程仓库复制一份完整的项目到本地,同时自动创建一个指向远程仓库的追踪分支。
文件操作
git add [file/folder]
:将指定文件或目录下的所有修改添加到暂存区。git add .
:将当前目录下所有文件的改动(包括新建、修改、删除)添加到暂存区。git status
:显示工作目录中文件的状态,包括未追踪的文件、待暂存的改动以及与远程分支差异等。
提交与历史管理
git commit -m "commit message"
:将暂存区的改动以指定的提交消息提交到本地仓库。git commit --amend
:强制覆盖提交(不建议常规使用),主要用于修改上一次提交的内容或提交信息。git log
:显示详细的提交历史记录。
分支操作
git branch [branch-name]
:创建新的本地分支。git checkout [branch-name]
:切换到指定的分支。git merge [branch-name]
:将指定分支的更改合并到当前所在分支。
远程交互
git fetch [remote-name]
:获取远程仓库的所有分支及提交,但不改变当前工作区或HEAD指针。git pull [remote-name] [branch-name]
:将远程分支的最新提交下载到本地并尝试自动合并。git push [remote-name] [branch-name]
:将本地分支的提交推送到远程仓库对应的分支。
从初始化或者克隆一个仓库到提交代码到本地仓库再到远程仓库的对应分支这一系列操作涉及到的git指令就讲了大概这些,其实这些只是Git命令中的一部分,实际上Git的功能远不止这些,还包括
标签管理
、解决冲突
、子模块操作
、重写历史
等多个方面。
总结
这一次面试官很有耐心,而且能够将业务中复杂的逻辑通俗易懂地解释给我听,收获了很多知识点。有趣的是,当我聊完对其公司想问的问题后,面试官问我还有什么想问的吗?我答:目前没有了,那我们今天的面试就到此结束吧!面试官瞬间有点😵:不是哥们,你在面我?
【Base:上海 薪资:200/天 已拿offer】