24岁的我选择了裸辞
前言
说实话选择裸辞后
很迷茫,但是又不想在原本的公司上班,目前处于昏昏沉沉
的状态,工作也是混一天是一天
,没有了什么明确的目标,不知道屏幕前的你们有没有这种感觉,目前想着休息一段时间
,可是又不敢休息,钱包不允许。
今年的金九银十
根本没有,环境确实很差。
公司A
公司A
:第一家面试的公司,没过,一家初创公司
,没过也是情理之中,毕竟我面试的状态太差了,很多问题都没回答上来,这时候没什么准备,刚开始刷面试题
,不过说实话有点打击
,作为毕业以后第一次换工作的经历
,刚毕业的时候面一个拿一个offer
,甚至都怀疑这两年工作
是不是白做了,技术一直下坡路
。
笔试题目
第一题. [1,3,5,2,19,30,10] 请将数组排序从小到大
1,3,5,2,19,30,10\] .sort((a,b) =\> a - b)
`加分项:` 题目之外还可以自己写一些排序例如算法,但是`笔者`只答了 `sort`方法排序
> 什么是防抖和节流? 它们有什么区别 怎么实现
`防抖`: 是规定时间内多次触发方法只执行一次
`节流`: 延迟一定时间执行某一方法,如果重新触发时间重置
`区别`: 一个是`立即执行`规定时间内不重复执行,一个是`规定时间内`只执行一次,其实区别可以不用回答,介绍`防抖`和`节流`就包含了`区别`
`实现`: 因为现场是`笔试`所以我这里的答案是简易版的`防抖`和`节流`
`防抖实现`:
```javascript
function debounce(func, time) {
let timer = null;
return function (...args) {
clearInterval(timer);
timer = setTimeout(() => {
func.apply(this, args)
}, time)
}
}
```
`节流实现`:
```javascript
function throttle(func, time) {
let timer = null;
return function (...args) {
if (timer) return;
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, time)
}
}
```
> 超出文本省略css和js的实现方式, 考虑兼容性
`css`:这里我只答出这个,如果css实现方式考虑兼容性应该还要加个`-webkit` 类似开头,但是笔试忘记怎么写了
```css
overflow: hidden;
text-overflow: ellipsis;
```
`Js`:`js实现文字超出隐藏`其实核心逻辑就是`循环添加文字添加完之后判断滚动高度(scrollHeight)是否大于可视高度(offsetHeight)`如果`大于`就把`后面三个字符`替换成`...`
```ini
function textHidden() {
let box = document.getElementById('box');
let str = '测试文字测试文字测试文字测试文字测试文字测试文字测试文字测试文字测试文字测试文字';
for (let i = 0; i < str.length; i++) {
if (box.offsetHeight < box.scrollHeight) {
console.log((box.innerHTML).substring(0, i - 3) + '...');
box.innerHTML = str.substring(0, i - 3) + '...'
} else {
box.innerHTML = box.innerHTML + str[i];
}
}
}
```
> 什么是柯里化函数,你对柯里化了解多少?
`柯里化`: 柯里化是一个高阶函数,它可以`将多个形参的函数`转成`单一的形参的函数`
`代码实现:`其实`柯里化`函数的核心点主要在`args.length和fn.length`的判断,当传入的`参数个小`小于`原始函数的参数个数`,则继续柯里化
```javascript
function curry(fn, ...args) {
return function (...params) {
const _args = [...args, ...params];
if (_args.length >= fn.length) {
return fn(..._args)
} else {
return curry.call(this, fn, ..._args)
}
}
}
```
> Json.parse和Json.stringify的实现
这题没能回答出来,虽然知道大致的核心逻辑,面试的时候比较乱,个人认为这个题目可能是要思路而已,如果真的要立马写代码量还是不小,而且是`笔试`
`具体答案`:[zhuanlan.zhihu.com/p/638767443](https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F638767443 "https://zhuanlan.zhihu.com/p/638767443") `一文彻底弄懂JSON.parse()与JSON.stringify()`
## 公司B
这家公司技术面就是直接发一个需求文档,做出来后也没有回音不知道是不是白嫖党,略过吧,对这家公司印象也很差,不知道大家面试过程中有没有遇到这种公司。
## 公司C
> vue3的时候ref为什么要使用.value属性? 如果是你有什么办法更好的实现它吗?
`.value`属性,首先我们要知道`vue3`是基于`proxy`和`reflect`实现的响应式数据的,`proxy`能代理的东西是什么?
答案是`对象`,那么其实就不难猜出为什么`ref`要`.value`
那么有什么更好实现`ref`,这里其实想问的是`.value`访问`太麻烦,而且容易搞混`,近期对于`ref`的争议也很大,也有人提出`ref sugar即(ref语法糖)`,也就是去除`ref`,那么如何去除?
> 核心就是对`set`和`get`进行操作
```vbnet
get(target, key) {
if (key === 'value') {
return target.value
}
return target.value[key]
},
```
核心内容就是如下,因为`vue`模板编译的时候会`自动取value`,所以要判断`key`为value的情况下,最后`return target.value[key]`则是我们正常在`js`里不通过`value`进行访问的逻辑
```vbnet
function magicRef(initialValue) {
const reactive = ref(initialValue)
const proxy = new Proxy(reactive, {
get(target, key) {
if (key === 'value') {
return target.value
}
return target.value[key]
},
set(target, key, value) {
if (key === 'value') {
target.value = value
} else {
target.value[key] = value
}
return true
},
})
return proxy
}
```
这里引用了别人的文章[juejin.cn/post/722654...](https://juejin.cn/post/7226540105698771003?searchId=20231002075204FF098F426246DB507E79 "https://juejin.cn/post/7226540105698771003?searchId=20231002075204FF098F426246DB507E79")
> SSR渲染流程是什么样?
1. 客户端发送URl请求到服务端
2. 服务端查询数据库
3. 拿到数据,组合好页面
4. 服务端返回整个DOM结构给客户端
> vuex如何解决页面刷新丢失数据的问题?
* 通过本地储存解决`localStorage`或`sessionStorage`存储
* 通过插件vuex-persistedstate解决
> 后台系统中权限是实现到什么等级? 具体怎么实现的
目前大多数的是实现到`按钮级`,实现的方法是通过`自定义指令`
流程具体如下:
* 从服务器一个对象,对象里包含了所有按钮的权限
* 给按钮添加`自定义指令`,并且为每个按钮`传递不同参数`
* 通过`自定义指令的bind生命周期`设置按钮的`display`控制是否展示按钮或者直接删除元素
> 说说对于vite和webpack的理解
`webpack`和`vite`都是很好的打包工具,对于`webpack`,在大型项目中,构建比较蛮,因为它需要对文件执行多次扫描和转译,从而衍生出了`vite`,`vite`是以开发模式极速构建著称,它利用了ES模块的特性,只构建正在编辑的模块,而不是`项目`,真正做到`按需引入`。
但是`webpack`目前的插件生态会相对丰富,对于`vite`插件相对较少,同时对于项目的话个人认为`webpack`适合大型项目,`vite`适合中小型项目
> import 和 require的区别
1. 首先是标准不同import/export是`ES6标准`, require和module.exports是`CommonJS`的标准
2. 执行过程,require是`运行时才执行即同步加载`,import是`编译时执行异步加载`
3. require的性能会相对稍微低于import
> 去除字符串中出现次数最少的字符,不改变原字符串的顺序
```ini
const removeLeasetFrequenChar = (str:string) => {
const charMap:any = {};
for(let i = 0; i < str.length; i++) {
const char = str[i];
if (charMap[char]) {
charMap[char] = charMap[char] + 1;
} else {
charMap[char] = 1;
}
}
// 找出最少次数
let minChar:string = '';
let min:number = Infinity;
for(let i in charMap) {
if(charMap[i] < min) {
minChar = i;
min = charMap[i]
}
}
// 直接把原本字符串处理,替换空
return str.replace(minChar, '');
}
```
> 如何控制请求并发数量?
这里问的应该是`Promise.all`如何控制请求的并发量,核心点如下
* 按照最大并大量对请求进行分组
* 然后利用async await的特性,分组请求
```typescript
// 请求url数组
const urls:Array = [
'https://www.baidu.com/s?wd=1',
'https://www.baidu.com/s?wd=3',
'https://www.baidu.com/s?wd=2',
'https://www.baidu.com/s?wd=2',
'https://www.baidu.com/s?wd=2',
'https://www.baidu.com/s?wd=2',
'https://www.baidu.com/s?wd=2'
]
// 最大并发量
const maxRequest:number = 3;
// 请求
const fetchUrl = (url:string) => {
return new Promise((resolve, reject) => {
fetchUrl(url).then(res => resolve(res)).catch(err => reject(err))
})
}
// 分组的方法
const grouping = (arr:Array, num:number) => {
let result:Array> = [];
for(let i = 0; i < arr.length; i+=num) {
result.push(arr.slice(i, i + num));
}
return result;
}
// 进行分组
const urlGroup = grouping(urls, maxRequest);
// 分组请求
const maxNumRequest = async () => {
try {
for(let urls of urlGroup) {
const promiseArr: Array = urls.map(url => fetchUrl(url));
// 核心点在这里 利用async await 分批请求
const reuslts = await Promise.all(promiseArr);
console.log(reuslts);
}
} catch (error) {
console.log(error);
}
}
```
## 公司D
> 讲一讲vue3的生命周期以及它们的作用
`vue3`的生命周期主要有4个阶段9个钩子分别是:
* 创建 ------在组件创建时执行
* 挂载------DOM被挂载时执行
* 更新------当响应式数据被修改时执行
* 销毁------在元素销毁之前执行
`钩子函数分别有`:
* beforeCreate------ 初始化事件以及生命周期,此时data和dom都为创建无法问题
* created------创建后,此时已经初始化完毕,data已经可以访问,但是dom元素依然不行
* onBeforeMount ------在挂载开始之前调用,此时dom元素已经生成,但是未挂载到页面上,可以进行异步操作
* onMounted------ 组件挂载时调用,此时可以访问dom元素,可以进行异步操作
* onBeforeUpdate ------数据更新前调用,这里可以访问更新前的dom,通常用来移除事件监听
* onUpdated------ 由于数据改变更新dom之后调用,这里可以访问更细后的dom
* onBeforeUnmount------卸载组件之前调用,通常用于清除计时,以及事件监听
* onUnMounted------ 组件卸载之后调用,可以用于清除一些资源占用
另外补充`vue2`生命周期
* beforeCreate: 创建前,初始化事件以及生命周期,此时data和dom都为创建无法问题
* created: 创建后,此时已经初始化完毕,data已经可以访问,但是dom元素依然不行
* beforeMounted: 挂载前,此时dom元素、data、计算属性等已经生成,但还dom未挂载到页面上面,可以进行异步操作
* mounted: 挂载后,此时dom元素已经挂载完毕,可以访问dom,可以进行异步操作
* beforeUpdate: 更新前,此时可以获取到更新前的dom元素,可以对更新前的状态进行保存
* update: 更新后,此时页面已经更新完毕,可以访问更新后的dom元素
* beforeDestoy: 销毁前,组件销毁之前调用,通常用来清除计时器、事件监听、
* destroyed: 销毁后,此时dom元素已经卸载,可以释放组件用过的资源等操作
> setup什么时候执行?
`setup`在`beforeCreate`之前执行,在`组合式api`中`setup`生命周期取代了`beforeCreate和created`两个生命周期。
> 异步请求放在created和mounted哪个合适?create会不会有性能提升?
`异步请求`放在create和moutned都是可以的,放在created更快的说法,性能并没有多少提升
1. created和mounted两者生命周期执行间隔差距其实很小,甚至到几微米
2. 如果说再created请求数据,请求后需要对数据进行操作,那么就得插入到`主线程`中,那么我们就需要打断`x渲染线程`
3. 除此之外有时候请求后我们需要对`dom`进行操作,那么`created`生命周期还没讲`dom`元素挂载上此时操作时不适合的
另外如果是`服务端渲染(ssr)`是需要放到`created`里进行异步请求,因为服务端不支持`mounted`
> 怎么看待CompositionApi和Options Api?
我认为两种api各有各的优缺点,在OptionsAPi,我们对一个options进行配置包括了,props、data、methods、computed等这种方式在结构清晰、易于理解,在小型的项目中比较实用.而在大型项目中CompositionAPi会更加适用
`CompositionApi`来说,它通过函数的形式来组织我们的代码,让我们允许把相关功能组合在一起,提高代码可维护性和重用性。
例如: 当我们实现一个todoList
在`OptionsApi`中我们得去data里声明一个`list`,然后再去`methods`声明一个方法,最后再去声明周期里调用等
而在`CompositionApi`我们声明`list`和`methods`都可以放在一起,同时如果将来需要`复用`此功能,只需要在将这一块的代码`复制`,而`OptionsAPi`则要去`data`里找,再到`methods`里找
> vue2对比vue3做了哪些改进?
* 性能的优化 vue3采用Proxy替代Object.definedPropety实现响应式,并且使用的静态提升技术,提高渲染的性能,但是对于proxy有一个缺点对ie11不支持
* 组合式api可以更好的复用组件逻辑
* TypeScript支持,vue3已经完全支持Typescript
* 新的自定义渲染Api,Vue3对于生命周期组件事件等都需要进行自定义以及控制
> Proxy和Object.defineProperty的区别
1. 性能方面Proxy的性能优于Object.definePropety
2. 拦截对象不同,proxy可以对整个对象进行控制,Object.definePorpety只能对象单个属性控制,这也是为什么Vue2要使用Vue.$set去设置属性的原因
3. Object.definePropety是对原始的对象进行操作,而Proxy会在原始对象上面创建一个代理层,Object.definePropety操作原始对象就可以触发拦截器,而Proxy则要操作代理对象
4. 兼容性的区别Object.definePropety兼容性相对较好,Proxy无法兼容IE11
## 公司E
> css的三大特性,有哪些属性可以被继承
`css三大特性`: 继承性、层叠性、优先性
可以继承属性有: font-size;font-family;font-style;text-align;line-heightcursor等等
> 浏览器从输入到渲染的过程经历哪些过程?
这题面试的时候只答出了`DNS查询`、`构建DOM树`、`构建CSS树`、`组合合成渲染树`有些给忘记了 晕
详细的步骤如下:
* DNS查询服务器IP
* TCP三次握手
* TLS协商
* 发送GET请求HTML文件
* 将HTML内容构建DOM树
* 将CSS内容构建CSSOM树
* 将DOM树和CSSOM树合成渲染树
* 根据渲染树进行页面元素的布局
* 绘制到页面上
> for in循环对象的时候是有序的吗
`无序的`,`for in`循环的时候,不要依靠顺序去进行判断。
> 前端做过哪些优化
1. 一些第三方库通过CDN方式引入
2. 使用iconfont代替图片图标
3. 图片懒加载
4. 代码层面例如防抖节流 频繁切换的场景 使用v-show替换v-if
5. 利用webpack-boundle-analyzer分析打包后文件大小,进行优化
> 前端图片懒加载的原理?
核心原理是通过`判断图片元素是否显示在视图中`,如果显示在视图中即显示图片,`据图步骤如下`:
1. 声明img标签但是src属性为空,我们可以另外设置一个自定义属性data-src用来赋值图片的url
2. 然后就是监听scroll事件,同时sroll事件可以加上`节流`,减少资源浪费
3. 判断当前图片是否有在`可视范围内`,判断方法有多种,例如: offsetTop+offsetHeight \> scrollTop或者getBoundingClientRect判断 top和left小于视窗高度(clientHeight)
4. 如果是的情况下我们获取这个img元素的data-src然后把属性赋值src即可
> 说说网络错误码有哪些? 分别代表什么意思
这里我只答出200、404、500、304
`网络错误码`: 以下面前缀开头分别代表不同的错误
* 1 开头 表示消息
* 2 开头 表示成功
* 3 表示 重定向
* 4 表示 客户端错误
* 5 表示 服务器错误
`1XX段`:表示请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息
常见的有:
* 100(客户端继续发送请求,这是临时响应)
* 101(服务器根据客户请求切换协议)
`2xx段`: 表示请求已成功被服务器接收、理解、并接受
常见的有:
* 200(成功): 请求已成功,并且返回所希望的响应头和数据体
* 201 (已创建): 请求成功并且服务器创建了新的资源
* 202 (已创建): 服务器已接收请求,但是尚未处理完成
* 203(非授权信息): 服务器已成功处理请求,但返回的信息可能来自另一来源
* 204(无内容): 服务器成功处理请求,但无返回内容
* 205(重置内容): 服务器成功处理请求,但没有任何返回内容
* 206(部分内容): 表示服务器成功处理了部分请求,通常在断电续传或者分块下载使用
`3xx段`: 表示完成请求,需要进一步操作。
常见的有:
* 300(多种选择):针对请求,服务器可执行多种操作
* 301(永久移动): 请求的页面已永久移动到新位置
* 302(临时移动): 服务器目前从不同位置的页面响应请求
* 303(查看其它位置): 请求者应对不同的位置单独使用get请求来检索响应
* 304(协商缓存): 服务器通过状态304可以告诉客户端请求资源成功
* 305 (使用代理): 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理
* 307 (临时重定向): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
`4xx段`: 表示服务器无法处理请求,客户端错误
常见的有:
* 400(错误请求): 服务器不理解请求的语法
* 401(未授权): 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
* 403(禁止): 服务器拒绝请求
* 404(未找到): 请求的资源不存在
* 405(方法禁用): 禁用请求中指定的方法
* 406(不接受): 无法使用请求的内容特性响应请求的网页
* 407(需要代理授权): 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理
* 408(请求超时): 服务器等候请求时发生超时
`5xx段`: 表示服务器无法完成明显有效的请求。这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生
常见的有:
* 500(服务器内部错误):服务器遇到错误,无法完成请求
* 501(尚未实施):服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码
* 502(错误网关): 服务器作为网关或代理,从上游服务器收到无效响应
* 503(服务不可用): 服务器目前无法使用(由于超载或停机维护)
* 504(网关超时): 服务器作为网关或代理,但是没有及时从上游服务器收到请求
* 505(HTTP 版本不受支持): 服务器不支持请求中所用的 HTTP 协议版本
> 箭头函数和普通函数的区别?
* 箭头函数没有原型,所以箭头函数没有this
* 箭头函数this继承外层的第一个普通函数
* 不能直接修改箭头函数的this指向
* 箭头没有argements
* 箭头函数只能声明`匿名函数`(箭头函数可以通过表达式让箭头函数具名),普通函数可以是`具名函数`也可以是`匿名`函数
> var和let有什么区别?
* var和let都有变量提升,但是let有一个`暂时性死区`
* var是`函数作用域`let是`块级作用域`
* var可以重复声明,而let不行
## 公司F
> 为什么要使用Ts
* ts可以对代码类型进行检查,避免了js遇到一些错误
* ts可以改进js,同时规范化我们的代码
* ts附带了许多功能,可以协助我们开发
* ts使得代码容易阅读和理解
> 什么是Ts的方法重载?
`方法重载`:是一种特性,允许生命多个`不同的函数签名`,根据传入的`参数`来执行不同的`操作`
```typescript
function sayHello(name: string): string;
function sayHeelo(name: string, age: number): string;
```
上面`两句代码定义了两个函数签名`,如果`传一个name`就`执行第一个操作`,如果传入`name和age`就执行第二个
但是下面我们还需要提供一个`函数`来实现相应的操作
```》vue
function sayHello(name: string, age?: number): string {
if (age !== undefined) {
return `Hello, ${name}! You are ${age} years old.`;
} else {
return `Hello, ${name}!`;
}
}
```
> 谈谈ts的枚举
`枚举`是Ts的一种数据类型,它能允许我们定义一组命名常量,枚举可以帮助我们把需要的集合列举出来,方便使用,也更加的简洁明了。
```arduino
enum Gender {
Male,
Female,
Other
}
```
> ts的内置类型有哪些?
* number
* string
* null
* undefined
* boolean
高级数据类型有:
* Array
* Tuple
* Enum
* Any
> 刚刚你谈到了Tuple它有什么作用? 有什么特点?
`Tuple`又称为元组,它是一个有顺序的数据类型,有以下特点
* 明确知道数组的长度,并且知道元素类型
* 可以知道每个元素的所在的位置
* 长度固定,因为需要对每个元素定义类型
```typescript
type tupleNum = [string, number]
```
> type和interface有什么区别?
* interface可以重复声明,type不行
* tyoe可以用typeof去获取某个一数据类型
* tyoe支持使用in去遍历映射类型
```ini
type names = 'firstName' | 'lastName' | 'AKA'
type nameType = {
[key in names]: string
}
```
* 模块化导出type必须先声明再导出
```typescript
export default interface User {
name: string
age: number
}
type User = {
name: string
age: number
}
export default User
```
> ts的never和vioid有什么区别
* void表示没有任何类型,值可以是null或者undefined
* never表示这个值永远不会存在
> 为什么vue的data必须是一个函数? vue是如何实现的data互相不污染?
在`组件中`,vue其实是通过`构造函数`去`new`一个组件,当`data是一个函数的时候`,它会有自己的作用域,而不会去印象其他的组件,如果说组件的`data`不是一个函数,而是`一个对象`,那么会导致这个`data会被放到原型上`,此时其他实例就可以`访问`
> 说说双向绑定的原理
1. 首先在new Vue的时候会对data里面执行响应化处理,这个过程发生在observer中,通过Object.definePropety对data的属性逐个递归遍历,添加set和get方法
2. 同时对模板进行编译,这个过程在compile中执行,主要操作有两个,获取data里初始化视频,绑定更新函数
3. 定义一个Watcher(观察者),功能主要有:添加订阅者和更新
4. 定义一个Dep用来保存观察者,并且当数据发生更变通知更新

## 公司G
笔试题目,只记得几题,有些没答出来,答案是后续补上的
> 如何实现路由懒加载?
在`router`通过回调的方式引入组件就可以实现异步加载
`原理`:主要是因为`javscipt`运行机制、事件循环以及打包工具的配合
* 当打包工具遇到`import`的时候会把`这些组件`当做单独`js`文件进行打包
* 页面加载的时候 ,只有主要boundle会被下载
* 当用户需要组件的时候,再发起网络请求,请求对应的异步组件
* 然后进行下载和执行
* 最后渲染
> diff算法原理
`diff算法`主要是采用`深度优先`算法,逐层进行比较,具体实现如下:
* 首先会有两个虚拟DOM树,分为新的子节点集合,旧子节点集合
* 同时会有4个索引分别指向, 新子节点的`首尾`,旧的子节点`首尾`4个,这里以:`oldS、oldE、newS、newE`代表4个索引
* 比较的话肯定是 `oldS和oldE`要分别对`newS、newS`进行比较就有四种情况了
`相等情况分为4种`:
1.`oldS(旧节点的头)与newS(新节点头相等)`,那么就`oldS和newS`分别索引+1
2.`oldS(旧节点头部)与newS(新节点的尾相等)`,那么就是`oldS`+1然后 `newE`-1
3.`oldS(旧节点尾部)`....还有两种情况以此类推
* 最后会出现一种情况,就是如果存在不相等的节点的时候
会有两种结果
1. new的子节点集合大于旧的子节点集合 ,添加新的子节点
2. 旧的节点集合大于新的节点集合,那么就会`真实的DOM中(这里划重点不是在旧节点中)`删除多余的子节点