2023年金九银十面经,假的今年没有金九银十

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;

Jsjs实现文字超出隐藏其实核心逻辑就是循环添加文字添加完之后判断滚动高度(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 一文彻底弄懂JSON.parse()与JSON.stringify()

公司B

这家公司技术面就是直接发一个需求文档,做出来后也没有回音不知道是不是白嫖党,略过吧,对这家公司印象也很差,不知道大家面试过程中有没有遇到这种公司。

公司C

vue3的时候ref为什么要使用.value属性? 如果是你有什么办法更好的实现它吗?

.value属性,首先我们要知道vue3是基于proxyreflect实现的响应式数据的,proxy能代理的东西是什么?

答案是对象,那么其实就不难猜出为什么ref.value

那么有什么更好实现ref,这里其实想问的是.value访问太麻烦,而且容易搞混,近期对于ref的争议也很大,也有人提出ref sugar即(ref语法糖),也就是去除ref,那么如何去除?

核心就是对setget进行操作

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...

SSR渲染流程是什么样?

  1. 客户端发送URl请求到服务端
  2. 服务端查询数据库
  3. 拿到数据,组合好页面
  4. 服务端返回整个DOM结构给客户端

vuex如何解决页面刷新丢失数据的问题?

  • 通过本地储存解决localStoragesessionStorage存储
  • 通过插件vuex-persistedstate解决

后台系统中权限是实现到什么等级? 具体怎么实现的

目前大多数的是实现到按钮级,实现的方法是通过自定义指令

流程具体如下:

  • 从服务器一个对象,对象里包含了所有按钮的权限
  • 给按钮添加自定义指令,并且为每个按钮传递不同参数
  • 通过自定义指令的bind生命周期设置按钮的display控制是否展示按钮或者直接删除元素

说说对于vite和webpack的理解

webpackvite都是很好的打包工具,对于webpack,在大型项目中,构建比较蛮,因为它需要对文件执行多次扫描和转译,从而衍生出了vitevite是以开发模式极速构建著称,它利用了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<string> = [
  '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<string>, num:number) => {
  let result:Array<Array<string>> = [];
  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<any> = 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什么时候执行?

setupbeforeCreate之前执行,在组合式apisetup生命周期取代了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我们声明listmethods都可以放在一起,同时如果将来需要复用此功能,只需要在将这一块的代码复制,而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中(这里划重点不是在旧节点中)删除多余的子节点
相关推荐
真的很上进2 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
测试老哥3 小时前
外包干了两年,技术退步明显。。。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
ThisIsClark5 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
噢,我明白了5 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__6 小时前
APIs-day2
javascript·css·css3
关你西红柿子6 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv
测试19986 小时前
外包干了2年,技术退步明显....
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
济南小草根6 小时前
把一个Vue项目的页面打包后再另一个项目中使用
前端·javascript·vue.js
小木_.7 小时前
【python 逆向分析某有道翻译】分析有道翻译公开的密文内容,webpack类型,全程扣代码,最后实现接口调用翻译,仅供学习参考
javascript·python·学习·webpack·分享·逆向分析
Aphasia3117 小时前
一次搞懂 JS 对象转换,从此告别类型错误!
javascript·面试